// 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.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.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 { get { return true; } } public SnippetCompletionProvider(ISnippetInfoService snippetInfoService = null) { _snippetInfoService = snippetInfoService; } internal override bool IsInsertionTrigger(SourceText text, int characterPosition, OptionSet options) { if (!options.GetOption(CSharpCompletionOptions.IncludeSnippets)) { return false; } return CompletionUtilities.IsTriggerCharacter(text, characterPosition, options); } public override async Task ProvideCompletionsAsync(CompletionContext context) { 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; } if (!options.GetOption(CSharpCompletionOptions.IncludeSnippets)) { return; } var snippetCompletionItems = await document.GetUnionItemsFromDocumentAndLinkedDocumentsAsync( UnionCompletionItemComparer.Instance, (d, c) => GetSnippetsForDocumentAsync(d, position, context.DefaultItemSpan, workspace, c), cancellationToken).ConfigureAwait(false); context.AddItems(snippetCompletionItems); } } private async Task> GetSnippetsForDocumentAsync(Document document, int position, TextSpan itemSpan, Workspace workspace, CancellationToken cancellationToken) { var syntaxTree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); var syntaxFacts = document.GetLanguageService(); var semanticFacts = document.GetLanguageService(); if (syntaxFacts.IsInNonUserCode(syntaxTree, position, cancellationToken) || syntaxTree.IsRightOfDotOrArrowOrColonColon(position, cancellationToken) || syntaxFacts.GetContainingTypeDeclaration(await syntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false), position) is EnumDeclarationSyntax) { return SpecializedCollections.EmptyEnumerable(); } var span = new TextSpan(position, 0); var semanticModel = await document.GetSemanticModelForSpanAsync(span, cancellationToken).ConfigureAwait(false); if (semanticFacts.IsPreProcessorDirectiveContext(semanticModel, position, cancellationToken)) { var directive = syntaxTree.GetRoot(cancellationToken).FindTokenOnLeftOfPosition(position, includeDirectives: true).GetAncestor(); 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(); } return await GetSnippetCompletionItemsAsync(workspace, semanticModel, itemSpan, isPreProcessorContext: true, 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, itemSpan, isPreProcessorContext: false, cancellationToken: cancellationToken).ConfigureAwait(false); } return SpecializedCollections.EmptyEnumerable(); } private async Task> GetSnippetCompletionItemsAsync(Workspace workspace, SemanticModel semanticModel, TextSpan itemSpan, bool isPreProcessorContext, CancellationToken cancellationToken) { var service = _snippetInfoService ?? workspace.Services.GetLanguageServices(semanticModel.Language).GetService(); if (service == null) { return SpecializedCollections.EmptyEnumerable(); } var snippets = service.GetSnippetsIfAvailable(); if (isPreProcessorContext) { snippets = snippets.Where(snippet => snippet.Shortcut.StartsWith("#", StringComparison.Ordinal)); } var text = await semanticModel.SyntaxTree.GetTextAsync(cancellationToken).ConfigureAwait(false); return snippets.Select(snippet => CommonCompletionItem.Create( displayText: isPreProcessorContext ? snippet.Shortcut.Substring(1) : snippet.Shortcut, sortText: isPreProcessorContext ? snippet.Shortcut.Substring(1) : snippet.Shortcut, description: (snippet.Title + Environment.NewLine + snippet.Description).ToSymbolDisplayParts(), span: itemSpan, glyph: Glyph.Snippet, shouldFormatOnCommit: service.ShouldFormatSnippet(snippet))).ToList(); } } }