Пример #1
0
        protected async Task <LSP.TextEdit[]?> GetTextEditsAsync(
            RequestContext context,
            LSP.FormattingOptions options,
            CancellationToken cancellationToken,
            LSP.Range?range = null)
        {
            var document = context.Document;

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

            var edits = new ArrayBuilder <LSP.TextEdit>();

            var formattingService = document.Project.LanguageServices.GetRequiredService <IFormattingInteractionService>();
            var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);

            TextSpan?textSpan = null;

            if (range != null)
            {
                textSpan = ProtocolConversions.RangeToTextSpan(range, text);
            }

            // We should use the options passed in by LSP instead of the document's options.
            var documentOptions = await ProtocolConversions.FormattingOptionsToDocumentOptionsAsync(
                options, document, cancellationToken).ConfigureAwait(false);

            var textChanges = await GetFormattingChangesAsync(formattingService, document, textSpan, documentOptions, cancellationToken).ConfigureAwait(false);

            edits.AddRange(textChanges.Select(change => ProtocolConversions.TextChangeToTextEdit(change, text)));

            return(edits.ToArrayAndFree());
        }
Пример #2
0
                static async Task <LSP.TextEdit> GenerateTextEdit(
                    Document document,
                    CompletionItem item,
                    CompletionService completionService,
                    SourceText?documentText,
                    TextSpan?defaultSpan,
                    LSP.Range?defaultRange,
                    CancellationToken cancellationToken)
                {
                    Contract.ThrowIfNull(documentText);
                    Contract.ThrowIfNull(defaultSpan);
                    Contract.ThrowIfNull(defaultRange);

                    var completionChange = await completionService.GetChangeAsync(
                        document, item, cancellationToken : cancellationToken).ConfigureAwait(false);

                    var completionChangeSpan = completionChange.TextChange.Span;

                    var textEdit = new LSP.TextEdit()
                    {
                        NewText = completionChange.TextChange.NewText ?? "",
                        Range   = completionChangeSpan == defaultSpan.Value
                            ? defaultRange
                            : ProtocolConversions.TextSpanToRange(completionChangeSpan, documentText),
                    };

                    return(textEdit);
                }
Пример #3
0
        /// <summary>
        /// Returns the semantic tokens data for a given document with an optional range.
        /// </summary>
        internal static async Task <int[]> ComputeSemanticTokensDataAsync(
            Document document,
            Dictionary <string, int> tokenTypesToIndex,
            LSP.Range?range,
            ClassificationOptions options,
            bool includeSyntacticClassifications,
            CancellationToken cancellationToken)
        {
            var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);

            var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);

            // By default we calculate the tokens for the full document span, although the user
            // can pass in a range if they wish.
            var textSpan = range is null ? root.FullSpan : ProtocolConversions.RangeToTextSpan(range, text);

            // If the full compilation is not yet available, we'll try getting a partial one. It may contain inaccurate
            // results but will speed up how quickly we can respond to the client's request.
            document = document.WithFrozenPartialSemantics(cancellationToken);
            options  = options with {
                ForceFrozenPartialSemanticsForCrossProcessOperations = true
            };

            var classifiedSpans = await GetClassifiedSpansForDocumentAsync(
                document, textSpan, options, includeSyntacticClassifications, cancellationToken).ConfigureAwait(false);

            // Multi-line tokens are not supported by VS (tracked by https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1265495).
            // Roslyn's classifier however can return multi-line classified spans, so we must break these up into single-line spans.
            var updatedClassifiedSpans = ConvertMultiLineToSingleLineSpans(text, classifiedSpans);

            // TO-DO: We should implement support for streaming if LSP adds support for it:
            // https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1276300
            return(ComputeTokens(text.Lines, updatedClassifiedSpans, tokenTypesToIndex));
        }
        /// <summary>
        /// Returns the semantic tokens data for a given document with an optional range.
        /// </summary>
        internal static async Task <int[]> ComputeSemanticTokensDataAsync(
            Document document,
            Dictionary <string, int> tokenTypesToIndex,
            LSP.Range?range,
            ClassificationOptions options,
            bool includeSyntacticClassifications,
            CancellationToken cancellationToken)
        {
            var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);

            var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);

            // By default we calculate the tokens for the full document span, although the user
            // can pass in a range if they wish.
            var textSpan = range is null ? root.FullSpan : ProtocolConversions.RangeToTextSpan(range, text);

            var classifiedSpans = await GetClassifiedSpansForDocumentAsync(
                document, textSpan, options, includeSyntacticClassifications, cancellationToken).ConfigureAwait(false);

            // Multi-line tokens are not supported by VS (tracked by https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1265495).
            // Roslyn's classifier however can return multi-line classified spans, so we must break these up into single-line spans.
            var updatedClassifiedSpans = ConvertMultiLineToSingleLineSpans(text, classifiedSpans);

            // TO-DO: We should implement support for streaming if LSP adds support for it:
            // https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1276300
            return(ComputeTokens(text.Lines, updatedClassifiedSpans, tokenTypesToIndex));
        }
        protected static async Task <LSP.TextEdit[]?> GetTextEditsAsync(
            RequestContext context,
            LSP.FormattingOptions options,
            IGlobalOptionService globalOptions,
            CancellationToken cancellationToken,
            LSP.Range?range = null)
        {
            var document = context.Document;

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

            var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);

            var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);

            var rangeSpan      = (range != null) ? ProtocolConversions.RangeToTextSpan(range, text) : new TextSpan(0, root.FullSpan.Length);
            var formattingSpan = CommonFormattingHelpers.GetFormattingSpan(root, rangeSpan);

            // We should use the options passed in by LSP instead of the document's options.
            var formattingOptions = await ProtocolConversions.GetFormattingOptionsAsync(options, document, globalOptions, cancellationToken).ConfigureAwait(false);

            var services    = document.Project.Solution.Services;
            var textChanges = Formatter.GetFormattedTextChanges(root, SpecializedCollections.SingletonEnumerable(formattingSpan), services, formattingOptions, rules: null, cancellationToken);

            var edits = new ArrayBuilder <LSP.TextEdit>();

            edits.AddRange(textChanges.Select(change => ProtocolConversions.TextChangeToTextEdit(change, text)));
            return(edits.ToArrayAndFree());
        }
Пример #6
0
        private static VSCodeAction GenerateVSCodeAction(
            CodeActionParams request,
            SourceText documentText,
            IUnifiedSuggestedAction suggestedAction,
            LSP.CodeActionKind codeActionKind,
            UnifiedSuggestedActionSetPriority setPriority,
            LSP.Range?applicableRange,
            int currentSetNumber,
            ref int currentHighestSetNumber,
            string currentTitle = "")
        {
            if (!string.IsNullOrEmpty(currentTitle))
            {
                // Adding a delimiter for nested code actions, e.g. 'Suppress or Configure issues|Suppress IDEXXXX|in Source'
                currentTitle += '|';
            }

            var codeAction = suggestedAction.OriginalCodeAction;

            currentTitle += codeAction.Title;

            // Nested code actions' unique identifiers consist of: parent code action unique identifier + '|' + title of code action
            var nestedActions = GenerateNestedVSCodeActions(request, documentText, suggestedAction, codeActionKind, ref currentHighestSetNumber, currentTitle);

            return(new VSCodeAction
            {
                Title = codeAction.Title,
                Kind = codeActionKind,
                Diagnostics = request.Context.Diagnostics,
                Children = nestedActions,
                Priority = UnifiedSuggestedActionSetPriorityToPriorityLevel(setPriority),
                Group = $"Roslyn{currentSetNumber}",
                ApplicableRange = applicableRange,
                Data = new CodeActionResolveData(currentTitle, request.Range, request.TextDocument)
            });
Пример #7
0
        protected async Task <LSP.TextEdit[]> GetTextEditsAsync(
            LSP.FormattingOptions formattingOptions,
            RequestContext context,
            CancellationToken cancellationToken,
            LSP.Range?range = null
            )
        {
            using var _ = ArrayBuilder <LSP.TextEdit> .GetInstance(out var edits);

            var document          = context.Document;
            var formattingService =
                document?.Project.LanguageServices.GetService <IXamlFormattingService>();

            if (document != null && formattingService != null)
            {
                var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);

                TextSpan?textSpan = null;
                if (range != null)
                {
                    textSpan = ProtocolConversions.RangeToTextSpan(range, text);
                }

                var options = new XamlFormattingOptions
                {
                    InsertSpaces = formattingOptions.InsertSpaces,
                    TabSize      = formattingOptions.TabSize,
                    OtherOptions = formattingOptions.OtherOptions
                };
                var textChanges = await formattingService
                                  .GetFormattingChangesAsync(document, options, textSpan, cancellationToken)
                                  .ConfigureAwait(false);

                edits.AddRange(
                    textChanges.Select(
                        change => ProtocolConversions.TextChangeToTextEdit(change, text)
                        )
                    );
            }

            return(edits.ToArray());
        }
Пример #8
0
        internal static async Task <int[]> ComputeSemanticTokensDataAsync(
            Document document,
            Dictionary <string, int> tokenTypesToIndex,
            LSP.Range?range,
            CancellationToken cancellationToken)
        {
            var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);

            var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);

            // By default we calculate the tokens for the full document span, although the user
            // can pass in a range if they wish.
            var textSpan = range == null ? root.FullSpan : ProtocolConversions.RangeToTextSpan(range, text);

            var classifiedSpans = await Classifier.GetClassifiedSpansAsync(document, textSpan, cancellationToken).ConfigureAwait(false);

            Contract.ThrowIfNull(classifiedSpans, "classifiedSpans is null");

            // TO-DO: We should implement support for streaming once this LSP bug is fixed:
            // https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1132601
            return(ComputeTokens(text.Lines, classifiedSpans.ToArray(), tokenTypesToIndex));
        }
Пример #9
0
            static async Task <LSP.CompletionItem> CreateLSPCompletionItemAsync(
                LSP.CompletionParams request,
                Document document,
                CompletionItem item,
                long?resultId,
                bool useVSCompletionItem,
                CompletionTrigger completionTrigger,
                Dictionary <ImmutableArray <CharacterSetModificationRule>, ImmutableArray <string> > commitCharacterRulesCache,
                CompletionService completionService,
                string?clientName,
                bool returnTextEdits,
                bool snippetsSupported,
                StringBuilder stringBuilder,
                SourceText?documentText,
                TextSpan?defaultSpan,
                LSP.Range?defaultRange,
                CancellationToken cancellationToken)
            {
                if (useVSCompletionItem)
                {
                    var vsCompletionItem = await CreateCompletionItemAsync <LSP.VSCompletionItem>(
                        request, document, item, resultId, completionTrigger, commitCharacterRulesCache,
                        completionService, clientName, returnTextEdits, snippetsSupported, stringBuilder,
                        documentText, defaultSpan, defaultRange, cancellationToken).ConfigureAwait(false);

                    vsCompletionItem.Icon = new ImageElement(item.Tags.GetFirstGlyph().GetImageId());
                    return(vsCompletionItem);
                }
                else
                {
                    var roslynCompletionItem = await CreateCompletionItemAsync <LSP.CompletionItem>(
                        request, document, item, resultId, completionTrigger, commitCharacterRulesCache,
                        completionService, clientName, returnTextEdits, snippetsSupported, stringBuilder,
                        documentText, defaultSpan, defaultRange, cancellationToken).ConfigureAwait(false);

                    return(roslynCompletionItem);
                }
            }
Пример #10
0
        /// <summary>
        /// Returns the semantic tokens data for a given document with an optional range.
        /// </summary>
        internal static async Task <(int[], bool isFinalized)> ComputeSemanticTokensDataAsync(
            Document document,
            Dictionary <string, int> tokenTypesToIndex,
            LSP.Range?range,
            CancellationToken cancellationToken)
        {
            var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);

            var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);

            // By default we calculate the tokens for the full document span, although the user
            // can pass in a range if they wish.
            var textSpan = range is null ? root.FullSpan : ProtocolConversions.RangeToTextSpan(range, text);

            // If the full compilation is not yet available, we'll try getting a partial one. It may contain inaccurate
            // results but will speed up how quickly we can respond to the client's request.
            var frozenDocument = document.WithFrozenPartialSemantics(cancellationToken);
            var semanticModel  = await frozenDocument.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);

            var isFinalized = document.Project.TryGetCompilation(out var compilation) && compilation == semanticModel.Compilation;

            document = frozenDocument;

            var options         = ClassificationOptions.From(document.Project);
            var classifiedSpans = Classifier.GetClassifiedSpans(document.Project.Solution.Workspace.Services, semanticModel, textSpan, options, cancellationToken);

            Contract.ThrowIfNull(classifiedSpans, "classifiedSpans is null");

            // Multi-line tokens are not supported by VS (tracked by https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1265495).
            // Roslyn's classifier however can return multi-line classified spans, so we must break these up into single-line spans.
            var updatedClassifiedSpans = ConvertMultiLineToSingleLineSpans(text, classifiedSpans.ToArray());

            // TO-DO: We should implement support for streaming if LSP adds support for it:
            // https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1276300
            return(ComputeTokens(text.Lines, updatedClassifiedSpans, tokenTypesToIndex), isFinalized);
        }
Пример #11
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);
            }
Пример #12
0
            static async Task <TCompletionItem> CreateCompletionItemAsync <TCompletionItem>(
                LSP.CompletionParams request,
                Document document,
                CompletionItem item,
                long?resultId,
                CompletionTrigger completionTrigger,
                Dictionary <ImmutableArray <CharacterSetModificationRule>, ImmutableArray <string> > commitCharacterRulesCache,
                CompletionService completionService,
                string?clientName,
                bool returnTextEdits,
                bool snippetsSupported,
                StringBuilder stringBuilder,
                SourceText?documentText,
                TextSpan?defaultSpan,
                LSP.Range?defaultRange,
                CancellationToken cancellationToken) where TCompletionItem : LSP.CompletionItem, new()
            {
                // Generate display text
                stringBuilder.Append(item.DisplayTextPrefix);
                stringBuilder.Append(item.DisplayText);
                stringBuilder.Append(item.DisplayTextSuffix);
                var completeDisplayText = stringBuilder.ToString();

                stringBuilder.Clear();

                var completionItem = new TCompletionItem
                {
                    Label      = completeDisplayText,
                    SortText   = item.SortText,
                    FilterText = item.FilterText,
                    Kind       = GetCompletionKind(item.Tags),
                    Data       = new CompletionResolveData
                    {
                        TextDocument      = request.TextDocument,
                        Position          = request.Position,
                        DisplayText       = item.DisplayText,
                        CompletionTrigger = completionTrigger,
                        ResultId          = resultId,
                    },
                    Preselect = item.Rules.SelectionBehavior == CompletionItemSelectionBehavior.HardSelection,
                };

                // Complex text edits (e.g. override and partial method completions) are always populated in the
                // resolve handler, so we leave both TextEdit and InsertText unpopulated in these cases.
                if (item.IsComplexTextEdit)
                {
                    // Razor C# is currently the only language client that supports LSP.InsertTextFormat.Snippet.
                    // We can enable it for regular C# once LSP is used for local completion.
                    if (snippetsSupported)
                    {
                        completionItem.InsertTextFormat = LSP.InsertTextFormat.Snippet;
                    }
                }
                // If the feature flag is on, always return a TextEdit.
                else if (returnTextEdits)
                {
                    var textEdit = await GenerateTextEdit(
                        document, item, completionService, documentText, defaultSpan, defaultRange, cancellationToken).ConfigureAwait(false);

                    completionItem.TextEdit = textEdit;
                }
                // If the feature flag is off, return an InsertText.
                else
                {
                    completionItem.InsertText = item.Properties.ContainsKey("InsertionText") ? item.Properties["InsertionText"] : completeDisplayText;
                }

                var commitCharacters = GetCommitCharacters(item, commitCharacterRulesCache);

                if (commitCharacters != null)
                {
                    completionItem.CommitCharacters = commitCharacters;
                }

                return(completionItem);
Пример #13
0
        public async Task <LSP.CompletionList?> HandleRequestAsync(LSP.CompletionParams request, RequestContext context, CancellationToken cancellationToken)
        {
            var document = context.Document;

            Contract.ThrowIfNull(document);

            // 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);
            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).
            Contract.ThrowIfNull(context.Solution);
            var returnTextEdits = _globalOptions.GetOption(LspOptions.LspCompletionFeatureFlag);

            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.VSInternalCompletionList
            {
                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);
            }
Пример #14
0
            static async Task <TCompletionItem> CreateCompletionItemAsync <TCompletionItem>(
                LSP.CompletionParams request,
                Document document,
                CompletionItem item,
                long?resultId,
                CompletionTrigger completionTrigger,
                Dictionary <ImmutableArray <CharacterSetModificationRule>, ImmutableArray <string> > commitCharacterRulesCache,
                CompletionService completionService,
                string?clientName,
                bool returnTextEdits,
                bool snippetsSupported,
                StringBuilder stringBuilder,
                SourceText?documentText,
                TextSpan?defaultSpan,
                LSP.Range?defaultRange,
                CancellationToken cancellationToken) where TCompletionItem : LSP.CompletionItem, new()
            {
                // Generate display text
                stringBuilder.Append(item.DisplayTextPrefix);
                stringBuilder.Append(item.DisplayText);
                stringBuilder.Append(item.DisplayTextSuffix);
                var completeDisplayText = stringBuilder.ToString();

                stringBuilder.Clear();

                // The TextEdits for override and partial method completions are provided in the resolve handler.
                // We do not provide them in this handler.
                // HACK: We should not be accessing the completion item's properties directly.
                // See https://github.com/dotnet/roslyn/issues/51396.
                item.Properties.TryGetValue("Modifiers", out var modifier);
                var isOverrideOrPartialMethodCompletion = modifier != null && (modifier.Contains("Override") || modifier.Contains("Partial"));

                var completionItem = new TCompletionItem
                {
                    Label      = completeDisplayText,
                    SortText   = item.SortText,
                    FilterText = item.FilterText,
                    Kind       = GetCompletionKind(item.Tags),
                    Data       = new CompletionResolveData
                    {
                        TextDocument      = request.TextDocument,
                        Position          = request.Position,
                        DisplayText       = item.DisplayText,
                        CompletionTrigger = completionTrigger,
                        ResultId          = resultId,
                    },
                    Preselect = item.Rules.SelectionBehavior == CompletionItemSelectionBehavior.HardSelection,
                };

                // Override and partial method completions are always populated in the resolve handler, so we
                // leave both TextEdit and InsertText unpopulated in these cases.
                if (isOverrideOrPartialMethodCompletion)
                {
                    // Razor C# is currently the only language client that supports LSP.InsertTextFormat.Snippet.
                    // We can enable it for regular C# once LSP is used for local completion.
                    if (snippetsSupported && clientName == ProtocolConstants.RazorCSharp)
                    {
                        completionItem.InsertTextFormat = LSP.InsertTextFormat.Snippet;
                    }
                }
                // If the feature flag is on, always return a TextEdit.
                else if (returnTextEdits)
                {
                    var textEdit = await GenerateTextEdit(
                        document, item, completionService, documentText, defaultSpan, defaultRange, cancellationToken).ConfigureAwait(false);

                    completionItem.TextEdit = textEdit;
                }
                // If the feature flag is off, return an InsertText.
                else
                {
                    completionItem.InsertText = item.Properties.ContainsKey("InsertionText") ? item.Properties["InsertionText"] : completeDisplayText;
                }

                var commitCharacters = GetCommitCharacters(item, commitCharacterRulesCache);

                if (commitCharacters != null)
                {
                    completionItem.CommitCharacters = commitCharacters;
                }

                return(completionItem);
Пример #15
0
        // routines for convenience

        /// <summary>
        /// Logs a diagnostic message based on the given error code,
        /// with the given source as the file for which the error occurred.
        /// </summary>
        public void Log(ErrorCode code, IEnumerable <string> args, string?source = null, LSP.Range?range = null) =>
        this.Log(new Diagnostic
        {
            Severity = DiagnosticSeverity.Error,
            Code     = Errors.Code(code),
            Source   = source,
            Message  = DiagnosticItem.Message(code, args ?? Enumerable.Empty <string>()),
            Range    = range ?? EmptyRange
        });
Пример #16
0
 /// <summary>
 /// Generates a Diagnostic message based on the given information code,
 /// with any message parameters appended on a new line to the message defined by the information code.
 /// The given source is listed as the file for which the error occurred.
 /// </summary>
 public void Log(InformationCode code, IEnumerable <string> args, string?source = null, LSP.Range?range = null, params string[] messageParam) =>
 this.Log(new Diagnostic
 {
     Severity = DiagnosticSeverity.Information,
     Code     = null, // do not show a code for infos
     Source   = source,
     Message  = $"{DiagnosticItem.Message(code, args ?? Enumerable.Empty<string>())}{Environment.NewLine}{string.Join(Environment.NewLine, messageParam)}",
     Range    = range ?? EmptyRange
 });
        protected async Task <LSP.TextEdit[]> GetTextEditsAsync(RequestContext context, CancellationToken cancellationToken, LSP.Range?range = null)
        {
            var edits    = new ArrayBuilder <LSP.TextEdit>();
            var document = context.Document;

            if (document != null)
            {
                var formattingService = document.Project.LanguageServices.GetRequiredService <IEditorFormattingService>();
                var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);

                TextSpan?textSpan = null;
                if (range != null)
                {
                    textSpan = ProtocolConversions.RangeToTextSpan(range, text);
                }

                var textChanges = await GetFormattingChangesAsync(formattingService, document, textSpan, cancellationToken).ConfigureAwait(false);

                edits.AddRange(textChanges.Select(change => ProtocolConversions.TextChangeToTextEdit(change, text)));
            }

            return(edits.ToArrayAndFree());
        }
Пример #18
0
            static async Task <LSP.CompletionItem> CreateLSPCompletionItemAsync(
                LSP.CompletionParams request,
                Document document,
                CompletionItem item,
                CompletionResolveData?completionResolveData,
                bool supportsVSExtensions,
                Dictionary <ImmutableArray <CharacterSetModificationRule>, string[]> commitCharacterRulesCache,
                CompletionService completionService,
                bool returnTextEdits,
                bool snippetsSupported,
                StringBuilder stringBuilder,
                SourceText?documentText,
                TextSpan?defaultSpan,
                LSP.Range?defaultRange,
                CancellationToken cancellationToken)
            {
                // Generate display text
                stringBuilder.Append(item.DisplayTextPrefix);
                stringBuilder.Append(item.DisplayText);
                stringBuilder.Append(item.DisplayTextSuffix);
                var completeDisplayText = stringBuilder.ToString();

                stringBuilder.Clear();

                var completionItem = supportsVSExtensions ? new LSP.VSInternalCompletionItem() : new LSP.CompletionItem();

                completionItem.Label      = completeDisplayText;
                completionItem.SortText   = item.SortText;
                completionItem.FilterText = item.FilterText;
                completionItem.Kind       = GetCompletionKind(item.Tags);
                completionItem.Data       = completionResolveData;
                completionItem.Preselect  = ShouldItemBePreselected(item);

                // Complex text edits (e.g. override and partial method completions) are always populated in the
                // resolve handler, so we leave both TextEdit and InsertText unpopulated in these cases.
                if (item.IsComplexTextEdit && completionItem is LSP.VSInternalCompletionItem vsItem)
                {
                    vsItem.VsResolveTextEditOnCommit = true;
                    // Razor C# is currently the only language client that supports LSP.InsertTextFormat.Snippet.
                    // We can enable it for regular C# once LSP is used for local completion.
                    if (snippetsSupported)
                    {
                        completionItem.InsertTextFormat = LSP.InsertTextFormat.Snippet;
                    }
                }
                // If the feature flag is on, always return a TextEdit.
                else if (returnTextEdits)
                {
                    var textEdit = await GenerateTextEdit(
                        document, item, completionService, documentText, defaultSpan, defaultRange, cancellationToken).ConfigureAwait(false);

                    completionItem.TextEdit = textEdit;
                }
                // If the feature flag is off, return an InsertText.
                else
                {
                    completionItem.InsertText = SymbolCompletionItem.TryGetInsertionText(item, out var insertionText) ? insertionText : completeDisplayText;
                }

                var commitCharacters = GetCommitCharacters(item, commitCharacterRulesCache, supportsVSExtensions);

                if (commitCharacters != null)
                {
                    completionItem.CommitCharacters = commitCharacters;
                }

                if (completionItem is LSP.VSInternalCompletionItem vsCompletionItem)
                {
                    vsCompletionItem.Icon = new ImageElement(item.Tags.GetFirstGlyph().GetImageId());
                }

                return(completionItem);