예제 #1
0
        protected static async Task <LSP.VSCompletionItem> CreateCompletionItemAsync(
            string label,
            LSP.CompletionItemKind kind,
            string[] tags,
            LSP.CompletionParams request,
            Document document,
            bool preselect = false,
            ImmutableArray <char>?commitCharacters = null,
            LSP.TextEdit?textEdit = null,
            string?insertText     = null,
            string?sortText       = null,
            string?filterText     = null,
            long resultId         = 0
            )
        {
            var position = await document
                           .GetPositionFromLinePositionAsync(
                ProtocolConversions.PositionToLinePosition(request.Position),
                CancellationToken.None
                )
                           .ConfigureAwait(false);

            var completionTrigger = await ProtocolConversions
                                    .LSPToRoslynCompletionTriggerAsync(
                request.Context,
                document,
                position,
                CancellationToken.None
                )
                                    .ConfigureAwait(false);

            var item = new LSP.VSCompletionItem()
            {
                TextEdit         = textEdit,
                InsertText       = insertText,
                FilterText       = filterText ?? label,
                Label            = label,
                SortText         = sortText ?? label,
                InsertTextFormat = LSP.InsertTextFormat.Plaintext,
                Kind             = kind,
                Data             = JObject.FromObject(new CompletionResolveData()
                {
                    ResultId = resultId,
                }),
                Preselect = preselect
            };

            if (tags != null)
            {
                item.Icon = tags.ToImmutableArray().GetFirstGlyph().GetImageElement();
            }

            if (commitCharacters != null)
            {
                item.CommitCharacters = commitCharacters.Value.Select(c => c.ToString()).ToArray();
            }

            return(item);
        }
예제 #2
0
        public async Task <LSP.CompletionList?> HandleRequestAsync(LSP.CompletionParams request, RequestContext context, CancellationToken cancellationToken)
        {
            var document = context.Document;

            if (document == null)
            {
                return(null);
            }

            // C# and VB share the same LSP language server, and thus share the same default trigger characters.
            // We need to ensure the trigger character is valid in the document's language. For example, the '{'
            // character, while a trigger character in VB, is not a trigger character in C#.
            if (request.Context != null &&
                request.Context.TriggerKind == LSP.CompletionTriggerKind.TriggerCharacter &&
                !char.TryParse(request.Context.TriggerCharacter, out var triggerCharacter) &&
                !char.IsLetterOrDigit(triggerCharacter) &&
                !IsValidTriggerCharacterForDocument(document, triggerCharacter))
            {
                return(null);
            }

            var position = await document.GetPositionFromLinePositionAsync(ProtocolConversions.PositionToLinePosition(request.Position), cancellationToken).ConfigureAwait(false);

            var completionOptions = await GetCompletionOptionsAsync(document, cancellationToken).ConfigureAwait(false);

            var completionService = document.Project.LanguageServices.GetRequiredService <CompletionService>();

            // TO-DO: More LSP.CompletionTriggerKind mappings are required to properly map to Roslyn CompletionTriggerKinds.
            // https://dev.azure.com/devdiv/DevDiv/_workitems/edit/1178726
            var completionTrigger = await ProtocolConversions.LSPToRoslynCompletionTriggerAsync(request.Context, document, position, cancellationToken).ConfigureAwait(false);

            var list = await completionService.GetCompletionsAsync(document, position, completionTrigger, options : completionOptions, cancellationToken : cancellationToken).ConfigureAwait(false);

            if (list == null)
            {
                return(null);
            }

            var lspVSClientCapability = context.ClientCapabilities?.HasVisualStudioLspCapability() == true;

            var commitCharactersRuleCache = new Dictionary <ImmutableArray <CharacterSetModificationRule>, ImmutableArray <string> >();

            // Cache the completion list so we can avoid recomputation in the resolve handler
            var resultId = await _completionListCache.UpdateCacheAsync(list, cancellationToken).ConfigureAwait(false);

            return(new LSP.VSCompletionList
            {
                Items = list.Items.Select(item => CreateLSPCompletionItem(
                                              request, item, resultId, lspVSClientCapability, completionTrigger, commitCharactersRuleCache)).ToArray(),
                SuggestionMode = list.SuggestionModeItem != null,
            });

            // Local functions
            bool IsValidTriggerCharacterForDocument(Document document, char triggerCharacter)
            {
                if (document.Project.Language == LanguageNames.CSharp)
                {
                    return(_csharpTriggerCharacters.Contains(triggerCharacter));
                }
                else if (document.Project.Language == LanguageNames.VisualBasic)
                {
                    return(_vbTriggerCharacters.Contains(triggerCharacter));
                }

                // Typescript still calls into this for completion.
                // Since we don't know what their trigger characters are, just return true.
                return(true);
            }
예제 #3
0
        public async Task <LSP.CompletionList?> HandleRequestAsync(LSP.CompletionParams request, RequestContext context, CancellationToken cancellationToken)
        {
            var document = context.Document;

            if (document == null)
            {
                return(null);
            }

            // C# and VB share the same LSP language server, and thus share the same default trigger characters.
            // We need to ensure the trigger character is valid in the document's language. For example, the '{'
            // character, while a trigger character in VB, is not a trigger character in C#.
            if (request.Context != null &&
                request.Context.TriggerKind == LSP.CompletionTriggerKind.TriggerCharacter &&
                !char.TryParse(request.Context.TriggerCharacter, out var triggerCharacter) &&
                !char.IsLetterOrDigit(triggerCharacter) &&
                !IsValidTriggerCharacterForDocument(document, triggerCharacter))
            {
                return(null);
            }

            var position = await document.GetPositionFromLinePositionAsync(ProtocolConversions.PositionToLinePosition(request.Position), cancellationToken).ConfigureAwait(false);

            var completionOptions = await GetCompletionOptionsAsync(document, cancellationToken).ConfigureAwait(false);

            var completionService = document.Project.LanguageServices.GetRequiredService <CompletionService>();

            // TO-DO: More LSP.CompletionTriggerKind mappings are required to properly map to Roslyn CompletionTriggerKinds.
            // https://dev.azure.com/devdiv/DevDiv/_workitems/edit/1178726
            var completionTrigger = await ProtocolConversions.LSPToRoslynCompletionTriggerAsync(request.Context, document, position, cancellationToken).ConfigureAwait(false);

            var list = await completionService.GetCompletionsAsync(document, position, completionTrigger, options : completionOptions, cancellationToken : cancellationToken).ConfigureAwait(false);

            if (list == null || list.Items.IsEmpty)
            {
                return(null);
            }

            var lspVSClientCapability     = context.ClientCapabilities.HasVisualStudioLspCapability() == true;
            var snippetsSupported         = context.ClientCapabilities.TextDocument?.Completion?.CompletionItem?.SnippetSupport ?? false;
            var commitCharactersRuleCache = new Dictionary <ImmutableArray <CharacterSetModificationRule>, ImmutableArray <string> >();

            // Cache the completion list so we can avoid recomputation in the resolve handler
            var resultId = await _completionListCache.UpdateCacheAsync(list, cancellationToken).ConfigureAwait(false);

            // Feature flag to enable the return of TextEdits instead of InsertTexts (will increase payload size).
            // Flag is defined in VisualStudio\Core\Def\PackageRegistration.pkgdef.
            // We also check against the CompletionOption for test purposes only.
            Contract.ThrowIfNull(context.Solution);
            var featureFlagService = context.Solution.Workspace.Services.GetRequiredService <IExperimentationService>();
            var returnTextEdits    = featureFlagService.IsExperimentEnabled(WellKnownExperimentNames.LSPCompletion) ||
                                     completionOptions.GetOption(CompletionOptions.ForceRoslynLSPCompletionExperiment, document.Project.Language);

            SourceText?documentText = null;
            TextSpan?  defaultSpan  = null;

            LSP.Range?defaultRange = null;
            if (returnTextEdits)
            {
                // We want to compute the document's text just once.
                documentText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);

                // We use the first item in the completion list as our comparison point for span
                // and range for optimization when generating the TextEdits later on.
                var completionChange = await completionService.GetChangeAsync(
                    document, list.Items.First(), cancellationToken : cancellationToken).ConfigureAwait(false);

                // If possible, we want to compute the item's span and range just once.
                // Individual items can override this range later.
                defaultSpan  = completionChange.TextChange.Span;
                defaultRange = ProtocolConversions.TextSpanToRange(defaultSpan.Value, documentText);
            }

            var stringBuilder = new StringBuilder();

            using var _ = ArrayBuilder <LSP.CompletionItem> .GetInstance(out var lspCompletionItems);

            foreach (var item in list.Items)
            {
                var lspCompletionItem = await CreateLSPCompletionItemAsync(
                    request, document, item, resultId, lspVSClientCapability, completionTrigger, commitCharactersRuleCache,
                    completionService, context.ClientName, returnTextEdits, snippetsSupported, stringBuilder, documentText,
                    defaultSpan, defaultRange, cancellationToken).ConfigureAwait(false);

                lspCompletionItems.Add(lspCompletionItem);
            }

            return(new LSP.VSCompletionList
            {
                Items = lspCompletionItems.ToArray(),
                SuggestionMode = list.SuggestionModeItem != null,
            });

            // Local functions
            bool IsValidTriggerCharacterForDocument(Document document, char triggerCharacter)
            {
                if (document.Project.Language == LanguageNames.CSharp)
                {
                    return(_csharpTriggerCharacters.Contains(triggerCharacter));
                }
                else if (document.Project.Language == LanguageNames.VisualBasic)
                {
                    return(_vbTriggerCharacters.Contains(triggerCharacter));
                }

                // Typescript still calls into this for completion.
                // Since we don't know what their trigger characters are, just return true.
                return(true);
            }