Ejemplo n.º 1
0
        public async Task <LSP.CompletionList?> HandleRequestAsync(LSP.CompletionParams request, RequestContext context, CancellationToken cancellationToken)
        {
            var document = context.Document;

            Contract.ThrowIfNull(document);
            Contract.ThrowIfNull(context.Solution);

            // 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 completionOptions = GetCompletionOptions(document) with {
                UpdateImportCompletionCacheInBackground = true
            };
            var completionService = document.GetRequiredLanguageService <CompletionService>();
            var documentText      = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);

            var completionListResult = await GetFilteredCompletionListAsync(request, context, documentText, document, completionOptions, completionService, cancellationToken).ConfigureAwait(false);

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

            var(list, isIncomplete, resultId) = completionListResult.Value;

            if (list.IsEmpty)
            {
                return(new LSP.VSInternalCompletionList
                {
                    Items = Array.Empty <LSP.CompletionItem>(),
                    SuggestionMode = list.SuggestionModeItem != null,
                    IsIncomplete = isIncomplete,
                });
            }

            var lspVSClientCapability     = context.ClientCapabilities.HasVisualStudioLspCapability() == true;
            var snippetsSupported         = context.ClientCapabilities.TextDocument?.Completion?.CompletionItem?.SnippetSupport ?? false;
            var itemDefaultsSupported     = context.ClientCapabilities.TextDocument?.Completion?.CompletionListSetting?.ItemDefaults?.Contains(EditRangeSetting) == true;
            var commitCharactersRuleCache = new Dictionary <ImmutableArray <CharacterSetModificationRule>, string[]>(CommitCharacterArrayComparer.Instance);

            // 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.ItemsList.First(), cancellationToken : cancellationToken).ConfigureAwait(false);

            var defaultSpan  = completionChange.TextChange.Span;
            var defaultRange = ProtocolConversions.TextSpanToRange(defaultSpan, documentText);

            var supportsCompletionListData = context.ClientCapabilities.HasCompletionListDataCapability();
            var completionResolveData      = new CompletionResolveData()
            {
                ResultId = resultId,
            };
            var stringBuilder = new StringBuilder();

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

            foreach (var item in list.ItemsList)
            {
                var completionItemResolveData = supportsCompletionListData ? null : completionResolveData;
                var lspCompletionItem         = await CreateLSPCompletionItemAsync(
                    request, document, item, completionItemResolveData, lspVSClientCapability, commitCharactersRuleCache,
                    completionService, snippetsSupported, itemDefaultsSupported, stringBuilder, documentText,
                    defaultSpan, cancellationToken).ConfigureAwait(false);

                lspCompletionItems.Add(lspCompletionItem);
            }

            var completionList = new LSP.VSInternalCompletionList
            {
                Items          = lspCompletionItems.ToArray(),
                SuggestionMode = list.SuggestionModeItem != null,
                IsIncomplete   = isIncomplete,
            };

            if (supportsCompletionListData)
            {
                completionList.Data = completionResolveData;
            }

            if (context.ClientCapabilities.HasCompletionListCommitCharactersCapability())
            {
                PromoteCommonCommitCharactersOntoList(completionList);
            }

            if (itemDefaultsSupported)
            {
                completionList.ItemDefaults = new LSP.CompletionListItemDefaults
                {
                    EditRange = defaultRange,
                };
            }

            var optimizedCompletionList = new LSP.OptimizedVSCompletionList(completionList);

            return(optimizedCompletionList);

            // 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);
            }
Ejemplo n.º 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 completionOptions = await GetCompletionOptionsAsync(document, cancellationToken).ConfigureAwait(false);

            var completionService = document.GetRequiredLanguageService <CompletionService>();
            var documentText      = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);

            var completionListResult = await GetFilteredCompletionListAsync(request, documentText, document, completionOptions, completionService, cancellationToken).ConfigureAwait(false);

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

            var(list, isIncomplete, resultId) = completionListResult.Value;

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

            // 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);

            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 supportsCompletionListData = context.ClientCapabilities.HasCompletionListDataCapability();
            var completionResolveData      = new CompletionResolveData()
            {
                ResultId = resultId,
            };
            var stringBuilder = new StringBuilder();

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

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

                lspCompletionItems.Add(lspCompletionItem);
            }

            var completionList = new LSP.VSCompletionList
            {
                Items          = lspCompletionItems.ToArray(),
                SuggestionMode = list.SuggestionModeItem != null,
                IsIncomplete   = isIncomplete,
            };

            if (supportsCompletionListData)
            {
                completionList.Data = completionResolveData;
            }

            if (context.ClientCapabilities.HasCompletionListCommitCharactersCapability())
            {
                PromoteCommonCommitCharactersOntoList(completionList);
            }

            var optimizedCompletionList = new LSP.OptimizedVSCompletionList(completionList);

            return(optimizedCompletionList);

            // 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);
            }