예제 #1
0
        private static async Task <Hover> GetHoverAsync(
            QuickInfoItem info,
            SourceText text,
            string language,
            Document?document,
            ClassificationOptions?classificationOptions,
            ClientCapabilities?clientCapabilities,
            CancellationToken cancellationToken)
        {
            Contract.ThrowIfFalse(document is null == (classificationOptions == null));

            var supportsVSExtensions = clientCapabilities.HasVisualStudioLspCapability();

            if (supportsVSExtensions)
            {
                var context = document == null
                    ? null
                    : new IntellisenseQuickInfoBuilderContext(
                    document,
                    classificationOptions !.Value,
                    threadingContext: null,
                    operationExecutor: null,
                    asynchronousOperationListener: null,
                    streamingPresenter: null);
                return(new VSInternalHover
                {
                    Range = ProtocolConversions.TextSpanToRange(info.Span, text),
                    Contents = new SumType <SumType <string, MarkedString>, SumType <string, MarkedString>[], MarkupContent>(string.Empty),
                    // Build the classified text without navigation actions - they are not serializable.
                    // TODO - Switch to markup content once it supports classifications.
                    // https://devdiv.visualstudio.com/DevDiv/_workitems/edit/918138
                    RawContent = await IntellisenseQuickInfoBuilder.BuildContentWithoutNavigationActionsAsync(info, context, cancellationToken).ConfigureAwait(false)
                });
            }
            else
            {
                return(new Hover
                {
                    Range = ProtocolConversions.TextSpanToRange(info.Span, text),
                    Contents = GetContents(info, language, clientCapabilities),
                });
            }
예제 #2
0
        public async Task AddRemoteClassificationsAsync(string classificationsServiceName, string filePath, SourceText sourceText, TextSpan textSpan, Action <ClassifiedSpan> tagAdder, CancellationToken cancellationToken)
        {
            var lspClient = _roslynLspClientServiceFactory.ActiveLanguageServerClient;

            if (lspClient == null)
            {
                return;
            }

            var classificationParams = new ClassificationParams
            {
                TextDocument = new TextDocumentIdentifier {
                    Uri = lspClient.ProtocolConverter.ToProtocolUri(new Uri(filePath))
                },
                Range = ProtocolConversions.TextSpanToRange(textSpan, sourceText)
            };

            var request             = new LS.LspRequest <ClassificationParams, ClassificationSpan[]>(classificationsServiceName);
            var classificationSpans = await lspClient.RequestAsync(request, classificationParams, cancellationToken).ConfigureAwait(false);

            if (classificationSpans == null)
            {
                return;
            }

            foreach (var classificationSpan in classificationSpans)
            {
                // The host may return more classifications than are supported by the guest. As an example, 15.7 added classifications for type members which wouldnt be understood by a 15.6 guest.
                // Check with the classificationTypeMap to see if this is a known classification.
                var classification = classificationSpan.Classification;
                if (_classificationTypeMap.GetClassificationType(classification) == null)
                {
                    classification = ClassificationTypeNames.Identifier;
                }

                var span = ProtocolConversions.RangeToTextSpan(classificationSpan.Range, sourceText);
                if (span.End <= sourceText.Length)
                {
                    tagAdder(new ClassifiedSpan(classification, span));
                }
            }
        }
예제 #3
0
        /// <summary>
        /// Get a symbol information from a specified nav bar item.
        /// </summary>
        private static SymbolInformation?GetSymbolInformation(
            RoslynNavigationBarItem item, Document document, SourceText text, string?containerName = null)
        {
            if (item is not RoslynNavigationBarItem.SymbolItem symbolItem || symbolItem.Location.InDocumentInfo == null)
            {
                return(null);
            }

            return(new VSSymbolInformation
            {
                Name = item.Text,
                Location = new LSP.Location
                {
                    Uri = document.GetURI(),
                    Range = ProtocolConversions.TextSpanToRange(symbolItem.Location.InDocumentInfo.Value.navigationSpan, text),
                },
                Kind = ProtocolConversions.GlyphToSymbolKind(item.Glyph),
                ContainerName = containerName,
                Icon = VSLspExtensionConversions.GetImageIdFromGlyph(item.Glyph),
            });
        }
예제 #4
0
 static VSSymbolInformation Create(
     RoslynNavigationBarItem item,
     TextSpan span,
     string?containerName,
     Document document,
     SourceText text
     )
 {
     return(new VSSymbolInformation
     {
         Name = item.Text,
         Location = new LSP.Location
         {
             Uri = document.GetURI(),
             Range = ProtocolConversions.TextSpanToRange(span, text),
         },
         Kind = ProtocolConversions.GlyphToSymbolKind(item.Glyph),
         ContainerName = containerName,
         Icon = new ImageElement(item.Glyph.GetImageId()),
     });
 }
예제 #5
0
        /// <summary>
        /// Convert XamlDiagnostics to VSDiagnostics
        /// </summary>
        private static VSDiagnostic[]? ConvertToVSDiagnostics(
            ImmutableArray <XamlDiagnostic>?xamlDiagnostics,
            Document document,
            SourceText text
            )
        {
            if (xamlDiagnostics == null)
            {
                return(null);
            }

            var project = document.Project;

            return(xamlDiagnostics.Value
                   .Select(
                       d =>
                       new VSDiagnostic()
            {
                Code = d.Code,
                Message = d.Message ?? string.Empty,
                ExpandedMessage = d.ExtendedMessage,
                Severity = ConvertDiagnosticSeverity(d.Severity),
                Range = ProtocolConversions.TextSpanToRange(
                    new TextSpan(d.Offset, d.Length),
                    text
                    ),
                Tags = ConvertTags(d),
                Source = d.Tool,
                Projects = new[]
                {
                    new ProjectAndContext
                    {
                        ProjectIdentifier = project.Id.Id.ToString(),
                        ProjectName = project.Name,
                    },
                },
            }
                       )
                   .ToArray());
        }
예제 #6
0
        public async Task <object[]> HandleAsync(ClassificationParams request, RequestContext <Solution> requestContext, CancellationToken cancellationToken)
        {
            var actualDocumentURI     = requestContext.ProtocolConverter.FromProtocolUri(request.TextDocument.Uri);
            var document              = requestContext.Context.GetDocumentFromURI(actualDocumentURI);
            var classificationService = document?.Project.LanguageServices.GetService <IClassificationService>();

            if (document == null || classificationService == null)
            {
                return(Array.Empty <ClassificationSpan>());
            }

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

            var textSpan = ProtocolConversions.RangeToTextSpan(request.Range, text);

            var spans = new List <ClassifiedSpan>();
            await classificationService.AddSemanticClassificationsAsync(document, textSpan, spans, cancellationToken).ConfigureAwait(false);

            return(spans.Select(c => new ClassificationSpan {
                Classification = c.ClassificationType, Range = ProtocolConversions.TextSpanToRange(c.TextSpan, text)
            }).ToArray());
        }
예제 #7
0
        private static async Task <LSP.Location?> GetSourceDefinitionLocationAsync(XamlSourceDefinition sourceDefinition, RequestContext context, CancellationToken cancellationToken)
        {
            Contract.ThrowIfNull(sourceDefinition.FilePath);

            var document = context.Solution?.GetDocuments(ProtocolConversions.GetUriFromFilePath(sourceDefinition.FilePath)).FirstOrDefault();

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

                var span = sourceDefinition.GetTextSpan(sourceText);
                if (span != null)
                {
                    return(await ProtocolConversions.TextSpanToLocationAsync(
                               document,
                               span.Value,
                               isStale : false,
                               cancellationToken).ConfigureAwait(false));
                }
            }
            else
            {
                // Cannot find the file in solution. This is probably a file lives outside of the solution like generic.xaml
                // which lives in the Windows SDK folder. Try getting the SourceText from the file path.
                using var fileStream = new FileStream(sourceDefinition.FilePath, FileMode.Open, FileAccess.Read);
                var sourceText = SourceText.From(fileStream);
                var span       = sourceDefinition.GetTextSpan(sourceText);
                if (span != null)
                {
                    return(new LSP.Location
                    {
                        Uri = new Uri(sourceDefinition.FilePath),
                        Range = ProtocolConversions.TextSpanToRange(span.Value, sourceText),
                    });
                }
            }

            return(null);
        }
예제 #8
0
        public async Task <LSP.TextEdit[]> HandleAsync(RunCodeActionParams request, RequestContext <Solution> requestContext, CancellationToken cancellationToken)
        {
            var edits = ArrayBuilder <LSP.TextEdit> .GetInstance();

            var solution    = requestContext.Context;
            var codeActions = await GetCodeActionsAsync(solution,
                                                        request.CodeActionParams.TextDocument.Uri,
                                                        request.CodeActionParams.Range,
                                                        keepThreadContext : false,
                                                        cancellationToken).ConfigureAwait(false);

            var actionToRun = codeActions?.FirstOrDefault(a => a.Title == request.Title);

            if (actionToRun != null)
            {
                var operations = await actionToRun.GetOperationsAsync(cancellationToken).ConfigureAwait(false);

                var applyChangesOperation = operations.OfType <ApplyChangesOperation>().FirstOrDefault();

                var document = solution.GetDocumentFromURI(request.CodeActionParams.TextDocument.Uri);
                var text     = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);

                if (applyChangesOperation != null && document != null)
                {
                    var newSolution = applyChangesOperation.ChangedSolution;
                    var newDocument = newSolution.GetDocument(document.Id);

                    var textChanges = await newDocument.GetTextChangesAsync(document, cancellationToken).ConfigureAwait(false);

                    edits.AddRange(textChanges.Select(tc => new LSP.TextEdit
                    {
                        NewText = tc.NewText,
                        Range   = ProtocolConversions.TextSpanToRange(tc.Span, text)
                    }));
                }
            }

            return(edits.ToArray());
        }
예제 #9
0
        public override async Task <LSP.Range?> HandleRequestAsync(LSP.VSInternalValidateBreakableRangeParams request, RequestContext context, CancellationToken cancellationToken)
        {
            var document = context.Document;

            Contract.ThrowIfNull(document);

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

            var span = ProtocolConversions.RangeToTextSpan(request.Range, text);
            var breakpointService = document.Project.LanguageServices.GetRequiredService <IBreakpointResolutionService>();

            var result = await breakpointService.ResolveBreakpointAsync(document, span, cancellationToken).ConfigureAwait(false);

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

            // zero-width range means line breakpoint:
            var breakpointSpan = result.IsLineBreakpoint ? new TextSpan(span.Start, length: 0) : result.TextSpan;

            return(ProtocolConversions.TextSpanToRange(breakpointSpan, text));
        }
예제 #10
0
        public override async Task <DocumentHighlight[]?> HandleRequestAsync(TextDocumentPositionParams request, RequestContext context, CancellationToken cancellationToken)
        {
            var document = context.Document;

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

            var documentHighlightService = document.Project.LanguageServices.GetRequiredService <IDocumentHighlightsService>();
            var position = await document.GetPositionFromLinePositionAsync(ProtocolConversions.PositionToLinePosition(request.Position), cancellationToken).ConfigureAwait(false);

            var options = DocumentHighlightingOptions.From(document.Project);

            var highlights = await documentHighlightService.GetDocumentHighlightsAsync(
                document,
                position,
                ImmutableHashSet.Create(document),
                options,
                cancellationToken).ConfigureAwait(false);

            if (!highlights.IsDefaultOrEmpty)
            {
                // LSP requests are only for a single document. So just get the highlights for the requested document.
                var highlightsForDocument = highlights.FirstOrDefault(h => h.Document.Id == document.Id);
                var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);

                return(highlightsForDocument.HighlightSpans.Select(h => new DocumentHighlight
                {
                    Range = ProtocolConversions.TextSpanToRange(h.TextSpan, text),
                    Kind = ProtocolConversions.HighlightSpanKindToDocumentHighlightKind(h.Kind),
                }).ToArray());
            }

            return(Array.Empty <DocumentHighlight>());
        }
예제 #11
0
        private async Task <List <LSP.Location> > GetDefinitionsWithDefinitionsService(Document document, int pos, CancellationToken cancellationToken)
        {
            var definitionService = document.Project.LanguageServices.GetService <IGoToDefinitionService>();

            var definitions = await definitionService.FindDefinitionsAsync(document, pos, cancellationToken).ConfigureAwait(false);

            var locations = new List <LSP.Location>();

            if (definitions != null && definitions.Count() > 0)
            {
                foreach (var definition in definitions)
                {
                    var definitionText = await definition.Document.GetTextAsync(cancellationToken).ConfigureAwait(false);

                    locations.Add(new LSP.Location
                    {
                        Uri   = definition.Document.GetURI(),
                        Range = ProtocolConversions.TextSpanToRange(definition.SourceSpan, definitionText)
                    });
                }
            }

            return(locations);
        }
예제 #12
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);
            }
예제 #13
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).
            // We check against the CompletionOption for test purposes only.
            Contract.ThrowIfNull(context.Solution);
            var returnTextEdits = _globalOptions.GetOption(LspOptions.LspCompletionFeatureFlag) ||
                                  _globalOptions.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.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
        public override async Task <Hover?> HandleRequestAsync(
            TextDocumentPositionParams request,
            RequestContext context,
            CancellationToken cancellationToken
            )
        {
            var document = context.Document;

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

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

            var quickInfoService =
                document.Project.LanguageServices.GetService <IXamlQuickInfoService>();

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

            var info = await quickInfoService
                       .GetQuickInfoAsync(document, position, cancellationToken)
                       .ConfigureAwait(false);

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

            var descriptionBuilder = new List <TaggedText>(info.Description);

            if (info.Symbol != null)
            {
                var description = await info.Symbol
                                  .GetDescriptionAsync(document, cancellationToken)
                                  .ConfigureAwait(false);

                if (description.Any())
                {
                    if (descriptionBuilder.Any())
                    {
                        descriptionBuilder.AddLineBreak();
                    }
                    descriptionBuilder.AddRange(description);
                }
            }

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

            return(new VSHover
            {
                Range = ProtocolConversions.TextSpanToRange(info.Span, text),
                Contents = new MarkupContent
                {
                    Kind = MarkupKind.Markdown,
                    Value = GetMarkdownString(descriptionBuilder)
                },
                RawContent = new ClassifiedTextElement(
                    descriptionBuilder.Select(
                        tp => new ClassifiedTextRun(tp.Tag.ToClassificationTypeName(), tp.Text)
                        )
                    )
            });
예제 #15
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);
            }
예제 #16
0
    public async Task <VSInternalInlineCompletionList?> HandleRequestAsync(VSInternalInlineCompletionRequest request, RequestContext context, CancellationToken cancellationToken)
    {
        Contract.ThrowIfNull(context.Document);

        // First get available snippets if any.
        var snippetInfoService = context.Document.Project.GetRequiredLanguageService <ISnippetInfoService>();
        var snippetInfo        = snippetInfoService.GetSnippetsIfAvailable();

        if (!snippetInfo.Any())
        {
            return(null);
        }

        // Then attempt to get the word at the requested position.
        var sourceText = await context.Document.GetTextAsync(cancellationToken).ConfigureAwait(false);

        var syntaxFactsService = context.Document.Project.GetRequiredLanguageService <ISyntaxFactsService>();
        var linePosition       = ProtocolConversions.PositionToLinePosition(request.Position);
        var position           = sourceText.Lines.GetPosition(linePosition);

        if (!SnippetUtilities.TryGetWordOnLeft(position, sourceText, syntaxFactsService, out var wordOnLeft))
        {
            return(null);
        }

        // Find the snippet with shortcut text that matches the typed word.
        var wordText = sourceText.GetSubText(wordOnLeft.Value).ToString();

        if (!BuiltInSnippets.Contains(wordText, StringComparer.OrdinalIgnoreCase))
        {
            return(null);
        }

        var matchingSnippetInfo = snippetInfo.First(s => wordText.Equals(s.Shortcut, StringComparison.OrdinalIgnoreCase));

        var parsedSnippet = _xmlSnippetParser.GetParsedXmlSnippet(matchingSnippetInfo, context);

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

        // Use the formatting options specified by the client to format the snippet.
        var formattingOptions = await ProtocolConversions.GetFormattingOptionsAsync(request.Options, context.Document, _globalOptions, cancellationToken).ConfigureAwait(false);

        var simplifierOptions = await context.Document.GetSimplifierOptionsAsync(_globalOptions, cancellationToken).ConfigureAwait(false);

        var formattedLspSnippet = await GetFormattedLspSnippetAsync(parsedSnippet, wordOnLeft.Value, context.Document, sourceText, formattingOptions, simplifierOptions, cancellationToken).ConfigureAwait(false);

        return(new VSInternalInlineCompletionList
        {
            Items = new VSInternalInlineCompletionItem[]
            {
                new VSInternalInlineCompletionItem
                {
                    Range = ProtocolConversions.TextSpanToRange(wordOnLeft.Value, sourceText),
                    Text = formattedLspSnippet,
                    TextFormat = InsertTextFormat.Snippet,
                }
            }
        });
    }
예제 #17
0
        /// <summary>
        /// Get, order, and filter code actions, and then transform them into VSCodeActions.
        /// </summary>
        /// <remarks>
        /// Used by CodeActionsHandler.
        /// </remarks>
        public static async Task <VSInternalCodeAction[]> GetVSCodeActionsAsync(
            CodeActionParams request,
            CodeActionsCache codeActionsCache,
            Document document,
            CodeActionOptions options,
            ICodeFixService codeFixService,
            ICodeRefactoringService codeRefactoringService,
            CancellationToken cancellationToken)
        {
            var actionSets = await GetActionSetsAsync(
                document, options, codeFixService, codeRefactoringService, request.Range, cancellationToken).ConfigureAwait(false);

            if (actionSets.IsDefaultOrEmpty)
            {
                return(Array.Empty <VSInternalCodeAction>());
            }

            await codeActionsCache.UpdateActionSetsAsync(document, request.Range, actionSets, cancellationToken).ConfigureAwait(false);

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

            // Each suggested action set should have a unique set number, which is used for grouping code actions together.
            var currentHighestSetNumber = 0;

            using var _ = ArrayBuilder <VSInternalCodeAction> .GetInstance(out var codeActions);

            foreach (var set in actionSets)
            {
                var currentSetNumber = ++currentHighestSetNumber;
                foreach (var suggestedAction in set.Actions)
                {
                    // Filter out code actions with options since they'll show dialogs and we can't remote the UI and the options.
                    if (suggestedAction.OriginalCodeAction is CodeActionWithOptions)
                    {
                        continue;
                    }

                    // TO-DO: Re-enable code actions involving package manager once supported by LSP.
                    // https://github.com/dotnet/roslyn/issues/48698
                    if (suggestedAction.OriginalCodeAction.Tags.Equals(WellKnownTagArrays.NuGet))
                    {
                        continue;
                    }

                    codeActions.Add(GenerateVSCodeAction(
                                        request, documentText,
                                        suggestedAction: suggestedAction,
                                        codeActionKind: GetCodeActionKindFromSuggestedActionCategoryName(set.CategoryName !),
                                        setPriority: set.Priority,
                                        applicableRange: set.ApplicableToSpan.HasValue ? ProtocolConversions.TextSpanToRange(set.ApplicableToSpan.Value, documentText) : null,
                                        currentSetNumber: currentSetNumber,
                                        currentHighestSetNumber: ref currentHighestSetNumber));
                }
            }

            return(codeActions.ToArray());
        }
예제 #18
0
        private async Task <LSP.VSInternalDocumentOnAutoInsertResponseItem?> GetBraceCompletionAfterReturnResponseAsync(
            LSP.VSInternalDocumentOnAutoInsertParams autoInsertParams,
            Document document,
            IndentationOptions options,
            CancellationToken cancellationToken)
        {
            var sourceText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);

            var position = sourceText.Lines.GetPosition(ProtocolConversions.PositionToLinePosition(autoInsertParams.Position));

            var serviceAndContext = await GetBraceCompletionContextAsync(position, document, cancellationToken).ConfigureAwait(false);

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

            var(service, context) = serviceAndContext.Value;
            var postReturnEdit = await service.GetTextChangeAfterReturnAsync(context, options, cancellationToken).ConfigureAwait(false);

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

            var textChanges = postReturnEdit.Value.TextChanges;
            var desiredCaretLinePosition = postReturnEdit.Value.CaretLocation;
            var newSourceText            = sourceText.WithChanges(textChanges);

            var caretLine = newSourceText.Lines[desiredCaretLinePosition.Line];

            if (desiredCaretLinePosition.Character > caretLine.Span.Length)
            {
                if (caretLine.Span.IsEmpty)
                {
                    // We have an empty line with the caret column at an indented position, let's add whitespace indentation to the text.
                    var indentedText = GetIndentedText(newSourceText, caretLine, desiredCaretLinePosition, options);

                    // Get the overall text changes between the original text and the formatted + indented text.
                    textChanges   = indentedText.GetTextChanges(sourceText).ToImmutableArray();
                    newSourceText = indentedText;

                    // If tabs were inserted the desired caret column can remain beyond the line text.
                    // So just set the caret position to the end of the newly indented line.
                    var caretLineInIndentedText = indentedText.Lines[desiredCaretLinePosition.Line];
                    desiredCaretLinePosition = indentedText.Lines.GetLinePosition(caretLineInIndentedText.End);
                }
                else
                {
                    // We're not on an empty line, clamp the line position to the actual line end.
                    desiredCaretLinePosition = new LinePosition(desiredCaretLinePosition.Line, Math.Min(desiredCaretLinePosition.Character, caretLine.End));
                }
            }

            var textChange = await GetCollapsedChangeAsync(textChanges, document, cancellationToken).ConfigureAwait(false);

            var newText          = GetTextChangeTextWithCaretAtLocation(newSourceText, textChange, desiredCaretLinePosition);
            var autoInsertChange = new LSP.VSInternalDocumentOnAutoInsertResponseItem
            {
                TextEditFormat = LSP.InsertTextFormat.Snippet,
                TextEdit       = new LSP.TextEdit
                {
                    NewText = newText,
                    Range   = ProtocolConversions.TextSpanToRange(textChange.Span, sourceText)
                }
            };

            return(autoInsertChange);
예제 #19
0
        public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context)
        {
            // This provider is exported for all workspaces - so limit it to just our workspace.
            var(document, span, cancellationToken) = context;
            if (document.Project.Solution.Workspace.Kind != WorkspaceKind.AnyCodeRoslynWorkspace)
            {
                return;
            }

            var lspClient = _roslynLspClientServiceFactory.ActiveLanguageServerClient;

            if (lspClient == null)
            {
                return;
            }

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

            var diagnostics = await _diagnosticAnalyzerService.GetDiagnosticsForSpanAsync(document, span, cancellationToken : cancellationToken).ConfigureAwait(false);

            var diagnostic = diagnostics?.FirstOrDefault();

            if (diagnostic != null)
            {
                span = diagnostic.TextSpan;
            }

            var codeActionParams = new LSP.CodeActionParams
            {
                TextDocument = ProtocolConversions.DocumentToTextDocumentIdentifier(document),
                Range        = ProtocolConversions.TextSpanToRange(span, text)
            };

            var commands = await lspClient.RequestAsync(LSP.Methods.TextDocumentCodeAction, codeActionParams, cancellationToken).ConfigureAwait(false);

            if (commands == null)
            {
                return;
            }

            foreach (var command in commands)
            {
                if (command is LSP.Command lspCommand)
                {
                    // The command can either wrap a Command or a CodeAction.
                    // If a Command, leave it unchanged; we want to dispatch it to the host to execute.
                    // If a CodeAction, unwrap the CodeAction so the guest can run it locally.
                    var commandArguments = lspCommand.Arguments.Single();

                    // Unfortunately, older liveshare hosts use liveshare custom code actions instead of the LSP code action.
                    // So determine which one to pass on.
                    if (commandArguments is LSP.CodeAction lspCodeAction)
                    {
                        context.RegisterRefactoring(new RoslynRemoteCodeAction(document, lspCodeAction.Command, lspCodeAction.Edit, lspCodeAction.Title, lspClient));
                    }
                    else if (commandArguments is LiveShareCodeAction liveshareCodeAction)
                    {
                        context.RegisterRefactoring(new RoslynRemoteCodeAction(document, liveshareCodeAction.Command, liveshareCodeAction.Edit, liveshareCodeAction.Title, lspClient));
                    }
                    else
                    {
                        context.RegisterRefactoring(new RoslynRemoteCodeAction(document, lspCommand, lspCommand?.Title, lspClient));
                    }
                }
            }
        }
예제 #20
0
        public async Task <object[]> HandleRequestAsync(Solution solution, LSP.CodeActionParams request,
                                                        LSP.ClientCapabilities clientCapabilities, CancellationToken cancellationToken)
        {
            var codeActions = await GetCodeActionsAsync(solution,
                                                        request.TextDocument.Uri,
                                                        request.Range,
                                                        cancellationToken).ConfigureAwait(false);

            // Filter out code actions with options since they'll show dialogs and we can't remote the UI and the options.
            codeActions = codeActions.Where(c => !(c is CodeActionWithOptions));

            var commands = new ArrayBuilder <LSP.Command>();

            foreach (var codeAction in codeActions)
            {
                object[] remoteCommandArguments;
                // If we have a codeaction with a single applychangesoperation, we want to send the codeaction with the edits.
                var operations = await codeAction.GetOperationsAsync(cancellationToken).ConfigureAwait(false);

                var clientSupportsWorkspaceEdits = true;
                if (clientCapabilities?.Experimental is JObject clientCapabilitiesExtensions)
                {
                    clientSupportsWorkspaceEdits = clientCapabilitiesExtensions.SelectToken("supportsWorkspaceEdits")?.Value <bool>() ?? clientSupportsWorkspaceEdits;
                }

                if (clientSupportsWorkspaceEdits && operations.Length == 1 && operations.First() is ApplyChangesOperation applyChangesOperation)
                {
                    var workspaceEdit = new LSP.WorkspaceEdit {
                        Changes = new Dictionary <string, LSP.TextEdit[]>()
                    };
                    var changes          = applyChangesOperation.ChangedSolution.GetChanges(solution);
                    var changedDocuments = changes.GetProjectChanges().SelectMany(pc => pc.GetChangedDocuments());

                    foreach (var docId in changedDocuments)
                    {
                        var newDoc  = applyChangesOperation.ChangedSolution.GetDocument(docId);
                        var oldDoc  = solution.GetDocument(docId);
                        var oldText = await oldDoc.GetTextAsync(cancellationToken).ConfigureAwait(false);

                        var textChanges = await newDoc.GetTextChangesAsync(oldDoc).ConfigureAwait(false);

                        var edits = textChanges.Select(tc => new LSP.TextEdit
                        {
                            NewText = tc.NewText,
                            Range   = ProtocolConversions.TextSpanToRange(tc.Span, oldText)
                        });

                        workspaceEdit.Changes.Add(newDoc.FilePath, edits.ToArray());
                    }

                    remoteCommandArguments = new object[] { new LSP.CodeAction {
                                                                Title = codeAction.Title, Edit = workspaceEdit
                                                            } };
                }
                // Otherwise, send the original request to be executed on the host.
                else
                {
                    // Note that we can pass through the params for this
                    // request (like range, filename) because between getcodeaction and runcodeaction there can be no
                    // changes on the IDE side (it will requery for codeactions if there are changes).
                    remoteCommandArguments = new object[]
                    {
                        new LSP.Command
                        {
                            CommandIdentifier = RunCodeActionCommandName,
                            Title             = codeAction.Title,
                            Arguments         = new object[]
                            {
                                new RunCodeActionParams
                                {
                                    CodeActionParams = request,
                                    Title            = codeAction.Title
                                }
                            }
                        }
                    };
                }

                // We need to return a command that is a generic wrapper that VS Code can execute.
                // The argument to this wrapper will either be a RunCodeAction command which will carry
                // enough information to run the command or a CodeAction with the edits.
                var command = new LSP.Command
                {
                    Title             = codeAction.Title,
                    CommandIdentifier = $"{RemoteCommandNamePrefix}.{ProviderName}",
                    Arguments         = remoteCommandArguments
                };

                commands.Add(command);
            }

            return(commands.ToArrayAndFree());
        }