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