public Task <LanguageServer.Protocol.CompletionItem[]> HandleAsync(object input, RequestContext <Solution> requestContext, CancellationToken cancellationToken) { // The VS LSP client supports streaming using IProgress<T> on various requests. // However, this works through liveshare on the LSP client, but not the LSP extension. // see https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1107682 for tracking. var request = ((JObject)input).ToObject <CompletionParams>(s_jsonSerializer); var context = new LSP.RequestContext(requestContext.GetClientCapabilities(), null); return(base.HandleRequestAsync(request, context, cancellationToken)); }
public async Task <InitializeResult> HandleAsync(InitializeParams param, RequestContext <Solution> requestContext, CancellationToken cancellationToken) { var context = new LSP.RequestContext(requestContext.GetClientCapabilities(), null); var initializeResult = await base.HandleRequestAsync(param, context, cancellationToken).ConfigureAwait(false); initializeResult.Capabilities.Experimental = new RoslynExperimentalCapabilities { SyntacticLspProvider = true }; return(initializeResult); }
public async Task <SymbolInformation[]> HandleAsync(DocumentSymbolParams param, RequestContext <Solution> requestContext, CancellationToken cancellationToken) { var clientCapabilities = requestContext.GetClientCapabilities(); if (clientCapabilities.TextDocument?.DocumentSymbol?.HierarchicalDocumentSymbolSupport == true) { // If the value is true, set it to false. Liveshare does not support hierarchical document symbols. clientCapabilities.TextDocument.DocumentSymbol.HierarchicalDocumentSymbolSupport = false; } var context = new LSP.RequestContext(clientCapabilities, null); var response = await base.HandleRequestAsync(param, context, cancellationToken).ConfigureAwait(false); // Since hierarchicalSupport will never be true, it is safe to cast the response to SymbolInformation[] return(response.Cast <SymbolInformation>().ToArray()); }
public override Task <LSP.Location[]> HandleRequestAsync(LSP.TextDocumentPositionParams request, RequestContext context, CancellationToken cancellationToken) => GetDefinitionAsync(request, typeOnly: false, context, cancellationToken);
public override async Task<WorkspaceEdit?> HandleRequestAsync(RenameParams request, RequestContext context, CancellationToken cancellationToken) { var document = context.Document; if (document != null) { var oldSolution = document.Project.Solution; var renameService = document.Project.LanguageServices.GetRequiredService<IEditorInlineRenameService>(); var position = await document.GetPositionFromLinePositionAsync(ProtocolConversions.PositionToLinePosition(request.Position), cancellationToken).ConfigureAwait(false); var renameInfo = await renameService.GetRenameInfoAsync(document, position, cancellationToken).ConfigureAwait(false); if (!renameInfo.CanRename) { return null; } var renameLocationSet = await renameInfo.FindRenameLocationsAsync(oldSolution.Workspace.Options, cancellationToken).ConfigureAwait(false); var renameReplacementInfo = await renameLocationSet.GetReplacementsAsync(request.NewName, oldSolution.Workspace.Options, cancellationToken).ConfigureAwait(false); var renamedSolution = renameReplacementInfo.NewSolution; var solutionChanges = renamedSolution.GetChanges(oldSolution); // Linked files can correspond to multiple roslyn documents each with changes. Merge the changes in the linked files so that all linked documents have the same text. // Then we can just take the text changes from the first document to avoid returning duplicate edits. renamedSolution = await renamedSolution.WithMergedLinkedFileChangesAsync(oldSolution, solutionChanges, cancellationToken: cancellationToken).ConfigureAwait(false); solutionChanges = renamedSolution.GetChanges(oldSolution); var changedDocuments = solutionChanges .GetProjectChanges() .SelectMany(p => p.GetChangedDocuments(onlyGetDocumentsWithTextChanges: true)) .GroupBy(docId => renamedSolution.GetRequiredDocument(docId).FilePath, StringComparer.OrdinalIgnoreCase).Select(group => group.First()); var textDiffService = renamedSolution.Workspace.Services.GetRequiredService<IDocumentTextDifferencingService>(); var documentEdits = await ProtocolConversions.ChangedDocumentsToTextDocumentEditsAsync(changedDocuments, renamedSolution.GetRequiredDocument, oldSolution.GetRequiredDocument, textDiffService, cancellationToken).ConfigureAwait(false); return new WorkspaceEdit { DocumentChanges = documentEdits }; } return null; }
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 <object[]> HandleRequestAsync(DocumentSymbolParams request, RequestContext context, CancellationToken cancellationToken) { var document = context.Document; Contract.ThrowIfNull(document); var navBarService = document.Project.LanguageServices.GetRequiredService <INavigationBarItemService>(); var navBarItems = await navBarService.GetItemsAsync(document, supportsCodeGeneration : false, forceFrozenPartialSemanticsForCrossProcessOperations : false, cancellationToken).ConfigureAwait(false); if (navBarItems.IsEmpty) { return(Array.Empty <object>()); } var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); // TODO - Return more than 2 levels of symbols. // https://github.com/dotnet/roslyn/projects/45#card-20033869 using var _ = ArrayBuilder <object> .GetInstance(out var symbols); if (context.ClientCapabilities?.TextDocument?.DocumentSymbol?.HierarchicalDocumentSymbolSupport == true) { // only top level ones foreach (var item in navBarItems) { symbols.AddIfNotNull(GetDocumentSymbol(item, text, cancellationToken)); } } else { foreach (var item in navBarItems) { symbols.AddIfNotNull(GetSymbolInformation(item, document, text, containerName: null)); foreach (var childItem in item.ChildItems) { symbols.AddIfNotNull(GetSymbolInformation(childItem, document, text, item.Text)); } } } var result = symbols.ToArray(); return(result); }
public abstract Task <ResponseType> HandleRequestAsync(RequestType request, RequestContext context, CancellationToken cancellationToken);
public override async Task <object[]> HandleRequestAsync(DocumentSymbolParams request, RequestContext context, CancellationToken cancellationToken) { var document = context.Document; if (document == null) { return(Array.Empty <SymbolInformation>()); } var symbols = ArrayBuilder <object> .GetInstance(); var navBarService = document.Project.LanguageServices.GetRequiredService <INavigationBarItemService>(); var navBarItems = await navBarService.GetItemsAsync(document, cancellationToken).ConfigureAwait(false); if (navBarItems.Count == 0) { return(symbols.ToArrayAndFree()); } var compilation = await document.Project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); // TODO - Return more than 2 levels of symbols. // https://github.com/dotnet/roslyn/projects/45#card-20033869 if (context.ClientCapabilities?.TextDocument?.DocumentSymbol?.HierarchicalDocumentSymbolSupport == true) { foreach (var item in navBarItems) { // only top level ones symbols.Add(await GetDocumentSymbolAsync(item, compilation, tree, text, cancellationToken).ConfigureAwait(false)); } } else { foreach (var item in navBarItems) { symbols.Add(GetSymbolInformation(item, compilation, tree, document, text, cancellationToken, containerName: null)); foreach (var childItem in item.ChildItems) { symbols.Add(GetSymbolInformation(childItem, compilation, tree, document, text, cancellationToken, item.Text)); } } } var result = symbols.WhereNotNull().ToArray(); symbols.Free(); return(result); }
public override async Task <LSP.Location[]> HandleRequestAsync(LSP.TextDocumentPositionParams request, RequestContext context, CancellationToken cancellationToken) { var locations = ArrayBuilder <LSP.Location> .GetInstance(); var document = context.Document; if (document == null) { return(locations.ToArrayAndFree()); } var findUsagesService = document.Project.LanguageServices.GetRequiredService <IFindUsagesServiceRenameOnceTypeScriptMovesToExternalAccess>(); var position = await document.GetPositionFromLinePositionAsync(ProtocolConversions.PositionToLinePosition(request.Position), cancellationToken).ConfigureAwait(false); var findUsagesContext = new SimpleFindUsagesContext(cancellationToken); await FindImplementationsAsync(findUsagesService, document, position, findUsagesContext).ConfigureAwait(false); foreach (var definition in findUsagesContext.GetDefinitions()) { var text = definition.GetClassifiedText(); foreach (var sourceSpan in definition.SourceSpans) { if (context.ClientCapabilities?.HasVisualStudioLspCapability() == true) { locations.AddIfNotNull(await ProtocolConversions.DocumentSpanToLocationWithTextAsync(sourceSpan, text, cancellationToken).ConfigureAwait(false)); } else { locations.AddIfNotNull(await ProtocolConversions.DocumentSpanToLocationAsync(sourceSpan, cancellationToken).ConfigureAwait(false)); } } } return(locations.ToArrayAndFree()); }
public override async Task <FoldingRange[]?> HandleRequestAsync(FoldingRangeParams request, RequestContext context, CancellationToken cancellationToken) { var document = context.Document; if (document == null) { return(null); } var blockStructureService = document.Project.LanguageServices.GetService <BlockStructureService>(); if (blockStructureService == null) { return(Array.Empty <FoldingRange>()); } var blockStructure = await blockStructureService.GetBlockStructureAsync(document, cancellationToken).ConfigureAwait(false); if (blockStructure == null) { return(Array.Empty <FoldingRange>()); } var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); return(GetFoldingRanges(blockStructure, text)); }
public async Task <LSP.DocumentOnAutoInsertResponseItem?> HandleRequestAsync(LSP.DocumentOnAutoInsertParams autoInsertParams, RequestContext context, CancellationToken cancellationToken) { var document = context.Document; if (document == null) { return(null); } var service = document.GetRequiredLanguageService <IDocumentationCommentSnippetService>(); // The editor calls this handler for C# and VB comment characters, but we only need to process the one for the language that matches the document if (autoInsertParams.Character == "\n" || autoInsertParams.Character == service.DocumentationCommentCharacter) { var documentationCommentResponse = await GetDocumentationCommentResponseAsync(autoInsertParams, document, service, cancellationToken).ConfigureAwait(false); if (documentationCommentResponse != null) { return(documentationCommentResponse); } } // Only support this for razor as LSP doesn't support overtype yet. // https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1165179/ // Once LSP supports overtype we can move all of brace completion to LSP. if (autoInsertParams.Character == "\n" && context.ClientName == document.Services.GetService <DocumentPropertiesService>()?.DiagnosticsLspClientName) { var braceCompletionAfterReturnResponse = await GetBraceCompletionAfterReturnResponseAsync(autoInsertParams, document, cancellationToken).ConfigureAwait(false); if (braceCompletionAfterReturnResponse != null) { return(braceCompletionAfterReturnResponse); } } return(null); }
public override Task <TextEdit[]> HandleRequestAsync(DocumentRangeFormattingParams request, RequestContext context, CancellationToken cancellationToken) => GetTextEditsAsync(context, cancellationToken, range: request.Range);
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, 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.Items.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.Items) { 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 static async Task <RequestContext?> CreateAsync( bool requiresLSPSolution, bool mutatesSolutionState, TextDocumentIdentifier?textDocument, WellKnownLspServerKinds serverKind, ClientCapabilities clientCapabilities, ImmutableArray <string> supportedLanguages, LspServices lspServices, CancellationToken queueCancellationToken, CancellationToken requestCancellationToken) { var lspWorkspaceManager = lspServices.GetRequiredService <LspWorkspaceManager>(); var logger = lspServices.GetRequiredService <ILspLogger>(); var documentChangeTracker = mutatesSolutionState ? (IDocumentChangeTracker)lspWorkspaceManager : new NonMutatingDocumentChangeTracker(); // Retrieve the current LSP tracked text as of this request. // This is safe as all creation of request contexts cannot happen concurrently. var trackedDocuments = lspWorkspaceManager.GetTrackedLspText(); // If the handler doesn't need an LSP solution we do two important things: // 1. We don't bother building the LSP solution for perf reasons // 2. We explicitly don't give the handler a solution or document, even if we could // so they're not accidentally operating on stale solution state. if (!requiresLSPSolution) { return(new RequestContext( solution: null, logger: logger, clientCapabilities: clientCapabilities, serverKind: serverKind, document: null, documentChangeTracker: documentChangeTracker, trackedDocuments: trackedDocuments, supportedLanguages: supportedLanguages, lspServices: lspServices, queueCancellationToken: queueCancellationToken)); } Solution?workspaceSolution; Document?document = null; if (textDocument is not null) { // we were given a request associated with a document. Find the corresponding roslyn document for this. // There are certain cases where we may be asked for a document that does not exist (for example a document is removed) // For example, document pull diagnostics can ask us after removal to clear diagnostics for a document. document = await lspWorkspaceManager.GetLspDocumentAsync(textDocument, requestCancellationToken).ConfigureAwait(false); } workspaceSolution = document?.Project.Solution ?? await lspWorkspaceManager.TryGetHostLspSolutionAsync(requestCancellationToken).ConfigureAwait(false); if (workspaceSolution == null) { logger.TraceError("Could not find appropriate solution for operation"); return(null); } var context = new RequestContext( workspaceSolution, logger, clientCapabilities, serverKind, document, documentChangeTracker, trackedDocuments, supportedLanguages, lspServices, queueCancellationToken); return(context); }