<commit_before>﻿// Copyright (c) Microsoft.  All Rights Reserved.  Licensed under the Apache License, Version 2.0.  See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Completion;
using Microsoft.CodeAnalysis.Completion.Providers;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.ErrorReporting;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.LanguageServices;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Snippets;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.CSharp.Completion.Providers
{
    internal sealed class SnippetCompletionProvider : CommonCompletionProvider
    {
        // If null, the document's language service will be used.
        private readonly ISnippetInfoService _snippetInfoService;

        internal override bool IsSnippetProvider => true;

        public SnippetCompletionProvider(ISnippetInfoService snippetInfoService = null)
        {
            _snippetInfoService = snippetInfoService;
        }

        internal override bool IsInsertionTrigger(SourceText text, int characterPosition, OptionSet options)
        {
            return CompletionUtilities.IsTriggerCharacter(text, characterPosition, options);
        }

        public override async Task ProvideCompletionsAsync(CompletionContext context)
        {
            try
            {
                var document = context.Document;
                var position = context.Position;
                var options = context.Options;
                var cancellationToken = context.CancellationToken;

                using (Logger.LogBlock(FunctionId.Completion_SnippetCompletionProvider_GetItemsWorker_CSharp, cancellationToken))
                {
                    // TODO (https://github.com/dotnet/roslyn/issues/5107): Enable in Interactive.
                    var workspace = document.Project.Solution.Workspace;
                    if (!workspace.CanApplyChange(ApplyChangesKind.ChangeDocument) ||
                         workspace.Kind == WorkspaceKind.Debugger ||
                         workspace.Kind == WorkspaceKind.Interactive)
                    {
                        return;
                    }

                    var snippetCompletionItems = await document.GetUnionItemsFromDocumentAndLinkedDocumentsAsync(
                        UnionCompletionItemComparer.Instance,
                        (d, c) => GetSnippetsForDocumentAsync(d, position, workspace, c),
                        cancellationToken).ConfigureAwait(false);

                    context.AddItems(snippetCompletionItems);
                }
            }
            catch (Exception e) when (FatalError.ReportWithoutCrashUnlessCanceled(e))
            {
                // nop
            }
        }

        private async Task<IEnumerable<CompletionItem>> GetSnippetsForDocumentAsync(
            Document document, int position, Workspace workspace, CancellationToken cancellationToken)
        {
            var syntaxTree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);
            var syntaxFacts = document.GetLanguageService<ISyntaxFactsService>();
            var semanticFacts = document.GetLanguageService<ISemanticFactsService>();

            var leftToken = syntaxTree.GetRoot(cancellationToken).FindTokenOnLeftOfPosition(position, includeDirectives: true);
            var targetToken = leftToken.GetPreviousTokenIfTouchingWord(position);

            if (syntaxFacts.IsInNonUserCode(syntaxTree, position, cancellationToken) ||
                syntaxTree.IsRightOfDotOrArrowOrColonColon(position, targetToken, cancellationToken) ||
                syntaxFacts.GetContainingTypeDeclaration(await syntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false), position) is EnumDeclarationSyntax)
            {
                return SpecializedCollections.EmptyEnumerable<CompletionItem>();
            }

            var span = new TextSpan(position, 0);
            var semanticModel = await document.GetSemanticModelForSpanAsync(span, cancellationToken).ConfigureAwait(false);
            var isPossibleTupleContext = syntaxFacts.IsPossibleTupleContext(syntaxTree, position, cancellationToken);

            if (semanticFacts.IsPreProcessorDirectiveContext(semanticModel, position, cancellationToken))
            {
                var directive = leftToken.GetAncestor<DirectiveTriviaSyntax>();
                if (directive.DirectiveNameToken.IsKind(
                    SyntaxKind.IfKeyword,
                    SyntaxKind.RegionKeyword,
                    SyntaxKind.ElseKeyword,
                    SyntaxKind.ElifKeyword,
                    SyntaxKind.ErrorKeyword,
                    SyntaxKind.LineKeyword,
                    SyntaxKind.PragmaKeyword,
                    SyntaxKind.EndIfKeyword,
                    SyntaxKind.UndefKeyword,
                    SyntaxKind.EndRegionKeyword,
                    SyntaxKind.WarningKeyword))
                {
                    return SpecializedCollections.EmptyEnumerable<CompletionItem>();
                }

                return await GetSnippetCompletionItemsAsync(workspace, semanticModel, isPreProcessorContext: true,
                        isTupleContext: isPossibleTupleContext, cancellationToken: cancellationToken).ConfigureAwait(false);
            }

            if (semanticFacts.IsGlobalStatementContext(semanticModel, position, cancellationToken) ||
                semanticFacts.IsExpressionContext(semanticModel, position, cancellationToken) ||
                semanticFacts.IsStatementContext(semanticModel, position, cancellationToken) ||
                semanticFacts.IsTypeContext(semanticModel, position, cancellationToken) ||
                semanticFacts.IsTypeDeclarationContext(semanticModel, position, cancellationToken) ||
                semanticFacts.IsNamespaceContext(semanticModel, position, cancellationToken) ||
                semanticFacts.IsNamespaceDeclarationNameContext(semanticModel, position, cancellationToken) ||
                semanticFacts.IsMemberDeclarationContext(semanticModel, position, cancellationToken) ||
                semanticFacts.IsLabelContext(semanticModel, position, cancellationToken))
            {
                return await GetSnippetCompletionItemsAsync(workspace, semanticModel, isPreProcessorContext: false,
                    isTupleContext: isPossibleTupleContext, cancellationToken: cancellationToken).ConfigureAwait(false);
            }

            return SpecializedCollections.EmptyEnumerable<CompletionItem>();
        }

        private static readonly CompletionItemRules s_tupleRules = CompletionItemRules.Default.
          WithCommitCharacterRule(CharacterSetModificationRule.Create(CharacterSetModificationKind.Remove, ':'));

        private async Task<IEnumerable<CompletionItem>> GetSnippetCompletionItemsAsync(
            Workspace workspace, SemanticModel semanticModel, bool isPreProcessorContext, bool isTupleContext, CancellationToken cancellationToken)
        {
            var service = _snippetInfoService ?? workspace.Services.GetLanguageServices(semanticModel.Language).GetService<ISnippetInfoService>();
            if (service == null)
            {
                return SpecializedCollections.EmptyEnumerable<CompletionItem>();
            }

            var snippets = service.GetSnippetsIfAvailable();
            if (isPreProcessorContext)
            {
                snippets = snippets.Where(snippet => snippet.Shortcut.StartsWith("#", StringComparison.Ordinal));
            }<commit_msg>Tolerate snippets where Shortcut is null. (#31738)<commit_after>﻿// Copyright (c) Microsoft.  All Rights Reserved.  Licensed under the Apache License, Version 2.0.  See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Completion;
using Microsoft.CodeAnalysis.Completion.Providers;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.ErrorReporting;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.LanguageServices;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Snippets;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.CSharp.Completion.Providers
{
    internal sealed class SnippetCompletionProvider : CommonCompletionProvider
    {
        // If null, the document's language service will be used.
        private readonly ISnippetInfoService _snippetInfoService;

        internal override bool IsSnippetProvider => true;

        public SnippetCompletionProvider(ISnippetInfoService snippetInfoService = null)
        {
            _snippetInfoService = snippetInfoService;
        }

        internal override bool IsInsertionTrigger(SourceText text, int characterPosition, OptionSet options)
        {
            return CompletionUtilities.IsTriggerCharacter(text, characterPosition, options);
        }

        public override async Task ProvideCompletionsAsync(CompletionContext context)
        {
            try
            {
                var document = context.Document;
                var position = context.Position;
                var options = context.Options;
                var cancellationToken = context.CancellationToken;

                using (Logger.LogBlock(FunctionId.Completion_SnippetCompletionProvider_GetItemsWorker_CSharp, cancellationToken))
                {
                    // TODO (https://github.com/dotnet/roslyn/issues/5107): Enable in Interactive.
                    var workspace = document.Project.Solution.Workspace;
                    if (!workspace.CanApplyChange(ApplyChangesKind.ChangeDocument) ||
                         workspace.Kind == WorkspaceKind.Debugger ||
                         workspace.Kind == WorkspaceKind.Interactive)
                    {
                        return;
                    }

                    var snippetCompletionItems = await document.GetUnionItemsFromDocumentAndLinkedDocumentsAsync(
                        UnionCompletionItemComparer.Instance,
                        (d, c) => GetSnippetsForDocumentAsync(d, position, workspace, c),
                        cancellationToken).ConfigureAwait(false);

                    context.AddItems(snippetCompletionItems);
                }
            }
            catch (Exception e) when (FatalError.ReportWithoutCrashUnlessCanceled(e))
            {
                // nop
            }
        }

        private async Task<IEnumerable<CompletionItem>> GetSnippetsForDocumentAsync(
            Document document, int position, Workspace workspace, CancellationToken cancellationToken)
        {
            var syntaxTree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);
            var syntaxFacts = document.GetLanguageService<ISyntaxFactsService>();
            var semanticFacts = document.GetLanguageService<ISemanticFactsService>();

            var leftToken = syntaxTree.GetRoot(cancellationToken).FindTokenOnLeftOfPosition(position, includeDirectives: true);
            var targetToken = leftToken.GetPreviousTokenIfTouchingWord(position);

            if (syntaxFacts.IsInNonUserCode(syntaxTree, position, cancellationToken) ||
                syntaxTree.IsRightOfDotOrArrowOrColonColon(position, targetToken, cancellationToken) ||
                syntaxFacts.GetContainingTypeDeclaration(await syntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false), position) is EnumDeclarationSyntax)
            {
                return SpecializedCollections.EmptyEnumerable<CompletionItem>();
            }

            var span = new TextSpan(position, 0);
            var semanticModel = await document.GetSemanticModelForSpanAsync(span, cancellationToken).ConfigureAwait(false);
            var isPossibleTupleContext = syntaxFacts.IsPossibleTupleContext(syntaxTree, position, cancellationToken);

            if (semanticFacts.IsPreProcessorDirectiveContext(semanticModel, position, cancellationToken))
            {
                var directive = leftToken.GetAncestor<DirectiveTriviaSyntax>();
                if (directive.DirectiveNameToken.IsKind(
                    SyntaxKind.IfKeyword,
                    SyntaxKind.RegionKeyword,
                    SyntaxKind.ElseKeyword,
                    SyntaxKind.ElifKeyword,
                    SyntaxKind.ErrorKeyword,
                    SyntaxKind.LineKeyword,
                    SyntaxKind.PragmaKeyword,
                    SyntaxKind.EndIfKeyword,
                    SyntaxKind.UndefKeyword,
                    SyntaxKind.EndRegionKeyword,
                    SyntaxKind.WarningKeyword))
                {
                    return SpecializedCollections.EmptyEnumerable<CompletionItem>();
                }

                return await GetSnippetCompletionItemsAsync(workspace, semanticModel, isPreProcessorContext: true,
                        isTupleContext: isPossibleTupleContext, cancellationToken: cancellationToken).ConfigureAwait(false);
            }

            if (semanticFacts.IsGlobalStatementContext(semanticModel, position, cancellationToken) ||
                semanticFacts.IsExpressionContext(semanticModel, position, cancellationToken) ||
                semanticFacts.IsStatementContext(semanticModel, position, cancellationToken) ||
                semanticFacts.IsTypeContext(semanticModel, position, cancellationToken) ||
                semanticFacts.IsTypeDeclarationContext(semanticModel, position, cancellationToken) ||
                semanticFacts.IsNamespaceContext(semanticModel, position, cancellationToken) ||
                semanticFacts.IsNamespaceDeclarationNameContext(semanticModel, position, cancellationToken) ||
                semanticFacts.IsMemberDeclarationContext(semanticModel, position, cancellationToken) ||
                semanticFacts.IsLabelContext(semanticModel, position, cancellationToken))
            {
                return await GetSnippetCompletionItemsAsync(workspace, semanticModel, isPreProcessorContext: false,
                    isTupleContext: isPossibleTupleContext, cancellationToken: cancellationToken).ConfigureAwait(false);
            }

            return SpecializedCollections.EmptyEnumerable<CompletionItem>();
        }

        private static readonly CompletionItemRules s_tupleRules = CompletionItemRules.Default.
          WithCommitCharacterRule(CharacterSetModificationRule.Create(CharacterSetModificationKind.Remove, ':'));

        private async Task<IEnumerable<CompletionItem>> GetSnippetCompletionItemsAsync(
            Workspace workspace, SemanticModel semanticModel, bool isPreProcessorContext, bool isTupleContext, CancellationToken cancellationToken)
        {
            var service = _snippetInfoService ?? workspace.Services.GetLanguageServices(semanticModel.Language).GetService<ISnippetInfoService>();
            if (service == null)
            {
                return SpecializedCollections.EmptyEnumerable<CompletionItem>();
            }

            var snippets = service.GetSnippetsIfAvailable();
            if (isPreProcessorContext)
            {
                snippets = snippets.Where(snippet => snippet.Shortcut != null && snippet.Shortcut.StartsWith("#", StringComparison.Ordinal));
            }
{'fraction_non_alphanumeric': 0.0678488335534176, 'fraction_numerical': 0.0011947431302270011, 'mean_word_length': 2.661984803131476, 'pattern_counts': {'":': 0, '<': 27, '<?xml version=': 0, '>': 33, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5406172', 'n_tokens_mistral': 3696, 'n_tokens_neox': 3519, 'n_words': 666}