protected async Task <LSP.TextEdit[]?> GetTextEditsAsync( RequestContext context, LSP.FormattingOptions options, CancellationToken cancellationToken, LSP.Range?range = null) { var document = context.Document; if (document == null) { return(null); } var edits = new ArrayBuilder <LSP.TextEdit>(); var formattingService = document.Project.LanguageServices.GetRequiredService <IFormattingInteractionService>(); var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); TextSpan?textSpan = null; if (range != null) { textSpan = ProtocolConversions.RangeToTextSpan(range, text); } // We should use the options passed in by LSP instead of the document's options. var documentOptions = await ProtocolConversions.FormattingOptionsToDocumentOptionsAsync( options, document, cancellationToken).ConfigureAwait(false); var textChanges = await GetFormattingChangesAsync(formattingService, document, textSpan, documentOptions, cancellationToken).ConfigureAwait(false); edits.AddRange(textChanges.Select(change => ProtocolConversions.TextChangeToTextEdit(change, text))); return(edits.ToArrayAndFree()); }
static async Task <LSP.TextEdit> GenerateTextEdit( Document document, CompletionItem item, CompletionService completionService, SourceText?documentText, TextSpan?defaultSpan, LSP.Range?defaultRange, CancellationToken cancellationToken) { Contract.ThrowIfNull(documentText); Contract.ThrowIfNull(defaultSpan); Contract.ThrowIfNull(defaultRange); var completionChange = await completionService.GetChangeAsync( document, item, cancellationToken : cancellationToken).ConfigureAwait(false); var completionChangeSpan = completionChange.TextChange.Span; var textEdit = new LSP.TextEdit() { NewText = completionChange.TextChange.NewText ?? "", Range = completionChangeSpan == defaultSpan.Value ? defaultRange : ProtocolConversions.TextSpanToRange(completionChangeSpan, documentText), }; return(textEdit); }
/// <summary> /// Returns the semantic tokens data for a given document with an optional range. /// </summary> internal static async Task <int[]> ComputeSemanticTokensDataAsync( Document document, Dictionary <string, int> tokenTypesToIndex, LSP.Range?range, ClassificationOptions options, bool includeSyntacticClassifications, CancellationToken cancellationToken) { var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); // By default we calculate the tokens for the full document span, although the user // can pass in a range if they wish. var textSpan = range is null ? root.FullSpan : ProtocolConversions.RangeToTextSpan(range, text); // If the full compilation is not yet available, we'll try getting a partial one. It may contain inaccurate // results but will speed up how quickly we can respond to the client's request. document = document.WithFrozenPartialSemantics(cancellationToken); options = options with { ForceFrozenPartialSemanticsForCrossProcessOperations = true }; var classifiedSpans = await GetClassifiedSpansForDocumentAsync( document, textSpan, options, includeSyntacticClassifications, cancellationToken).ConfigureAwait(false); // Multi-line tokens are not supported by VS (tracked by https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1265495). // Roslyn's classifier however can return multi-line classified spans, so we must break these up into single-line spans. var updatedClassifiedSpans = ConvertMultiLineToSingleLineSpans(text, classifiedSpans); // TO-DO: We should implement support for streaming if LSP adds support for it: // https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1276300 return(ComputeTokens(text.Lines, updatedClassifiedSpans, tokenTypesToIndex)); }
/// <summary> /// Returns the semantic tokens data for a given document with an optional range. /// </summary> internal static async Task <int[]> ComputeSemanticTokensDataAsync( Document document, Dictionary <string, int> tokenTypesToIndex, LSP.Range?range, ClassificationOptions options, bool includeSyntacticClassifications, CancellationToken cancellationToken) { var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); // By default we calculate the tokens for the full document span, although the user // can pass in a range if they wish. var textSpan = range is null ? root.FullSpan : ProtocolConversions.RangeToTextSpan(range, text); var classifiedSpans = await GetClassifiedSpansForDocumentAsync( document, textSpan, options, includeSyntacticClassifications, cancellationToken).ConfigureAwait(false); // Multi-line tokens are not supported by VS (tracked by https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1265495). // Roslyn's classifier however can return multi-line classified spans, so we must break these up into single-line spans. var updatedClassifiedSpans = ConvertMultiLineToSingleLineSpans(text, classifiedSpans); // TO-DO: We should implement support for streaming if LSP adds support for it: // https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1276300 return(ComputeTokens(text.Lines, updatedClassifiedSpans, tokenTypesToIndex)); }
protected static async Task <LSP.TextEdit[]?> GetTextEditsAsync( RequestContext context, LSP.FormattingOptions options, IGlobalOptionService globalOptions, CancellationToken cancellationToken, LSP.Range?range = null) { var document = context.Document; if (document == null) { return(null); } var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var rangeSpan = (range != null) ? ProtocolConversions.RangeToTextSpan(range, text) : new TextSpan(0, root.FullSpan.Length); var formattingSpan = CommonFormattingHelpers.GetFormattingSpan(root, rangeSpan); // We should use the options passed in by LSP instead of the document's options. var formattingOptions = await ProtocolConversions.GetFormattingOptionsAsync(options, document, globalOptions, cancellationToken).ConfigureAwait(false); var services = document.Project.Solution.Services; var textChanges = Formatter.GetFormattedTextChanges(root, SpecializedCollections.SingletonEnumerable(formattingSpan), services, formattingOptions, rules: null, cancellationToken); var edits = new ArrayBuilder <LSP.TextEdit>(); edits.AddRange(textChanges.Select(change => ProtocolConversions.TextChangeToTextEdit(change, text))); return(edits.ToArrayAndFree()); }
private static VSCodeAction GenerateVSCodeAction( CodeActionParams request, SourceText documentText, IUnifiedSuggestedAction suggestedAction, LSP.CodeActionKind codeActionKind, UnifiedSuggestedActionSetPriority setPriority, LSP.Range?applicableRange, int currentSetNumber, ref int currentHighestSetNumber, string currentTitle = "") { if (!string.IsNullOrEmpty(currentTitle)) { // Adding a delimiter for nested code actions, e.g. 'Suppress or Configure issues|Suppress IDEXXXX|in Source' currentTitle += '|'; } var codeAction = suggestedAction.OriginalCodeAction; currentTitle += codeAction.Title; // Nested code actions' unique identifiers consist of: parent code action unique identifier + '|' + title of code action var nestedActions = GenerateNestedVSCodeActions(request, documentText, suggestedAction, codeActionKind, ref currentHighestSetNumber, currentTitle); return(new VSCodeAction { Title = codeAction.Title, Kind = codeActionKind, Diagnostics = request.Context.Diagnostics, Children = nestedActions, Priority = UnifiedSuggestedActionSetPriorityToPriorityLevel(setPriority), Group = $"Roslyn{currentSetNumber}", ApplicableRange = applicableRange, Data = new CodeActionResolveData(currentTitle, request.Range, request.TextDocument) });
protected async Task <LSP.TextEdit[]> GetTextEditsAsync( LSP.FormattingOptions formattingOptions, RequestContext context, CancellationToken cancellationToken, LSP.Range?range = null ) { using var _ = ArrayBuilder <LSP.TextEdit> .GetInstance(out var edits); var document = context.Document; var formattingService = document?.Project.LanguageServices.GetService <IXamlFormattingService>(); if (document != null && formattingService != null) { var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); TextSpan?textSpan = null; if (range != null) { textSpan = ProtocolConversions.RangeToTextSpan(range, text); } var options = new XamlFormattingOptions { InsertSpaces = formattingOptions.InsertSpaces, TabSize = formattingOptions.TabSize, OtherOptions = formattingOptions.OtherOptions }; var textChanges = await formattingService .GetFormattingChangesAsync(document, options, textSpan, cancellationToken) .ConfigureAwait(false); edits.AddRange( textChanges.Select( change => ProtocolConversions.TextChangeToTextEdit(change, text) ) ); } return(edits.ToArray()); }
internal static async Task <int[]> ComputeSemanticTokensDataAsync( Document document, Dictionary <string, int> tokenTypesToIndex, LSP.Range?range, CancellationToken cancellationToken) { var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); // By default we calculate the tokens for the full document span, although the user // can pass in a range if they wish. var textSpan = range == null ? root.FullSpan : ProtocolConversions.RangeToTextSpan(range, text); var classifiedSpans = await Classifier.GetClassifiedSpansAsync(document, textSpan, cancellationToken).ConfigureAwait(false); Contract.ThrowIfNull(classifiedSpans, "classifiedSpans is null"); // TO-DO: We should implement support for streaming once this LSP bug is fixed: // https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1132601 return(ComputeTokens(text.Lines, classifiedSpans.ToArray(), tokenTypesToIndex)); }
static async Task <LSP.CompletionItem> CreateLSPCompletionItemAsync( LSP.CompletionParams request, Document document, CompletionItem item, long?resultId, bool useVSCompletionItem, CompletionTrigger completionTrigger, Dictionary <ImmutableArray <CharacterSetModificationRule>, ImmutableArray <string> > commitCharacterRulesCache, CompletionService completionService, string?clientName, bool returnTextEdits, bool snippetsSupported, StringBuilder stringBuilder, SourceText?documentText, TextSpan?defaultSpan, LSP.Range?defaultRange, CancellationToken cancellationToken) { if (useVSCompletionItem) { var vsCompletionItem = await CreateCompletionItemAsync <LSP.VSCompletionItem>( request, document, item, resultId, completionTrigger, commitCharacterRulesCache, completionService, clientName, returnTextEdits, snippetsSupported, stringBuilder, documentText, defaultSpan, defaultRange, cancellationToken).ConfigureAwait(false); vsCompletionItem.Icon = new ImageElement(item.Tags.GetFirstGlyph().GetImageId()); return(vsCompletionItem); } else { var roslynCompletionItem = await CreateCompletionItemAsync <LSP.CompletionItem>( request, document, item, resultId, completionTrigger, commitCharacterRulesCache, completionService, clientName, returnTextEdits, snippetsSupported, stringBuilder, documentText, defaultSpan, defaultRange, cancellationToken).ConfigureAwait(false); return(roslynCompletionItem); } }
/// <summary> /// Returns the semantic tokens data for a given document with an optional range. /// </summary> internal static async Task <(int[], bool isFinalized)> ComputeSemanticTokensDataAsync( Document document, Dictionary <string, int> tokenTypesToIndex, LSP.Range?range, CancellationToken cancellationToken) { var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); // By default we calculate the tokens for the full document span, although the user // can pass in a range if they wish. var textSpan = range is null ? root.FullSpan : ProtocolConversions.RangeToTextSpan(range, text); // If the full compilation is not yet available, we'll try getting a partial one. It may contain inaccurate // results but will speed up how quickly we can respond to the client's request. var frozenDocument = document.WithFrozenPartialSemantics(cancellationToken); var semanticModel = await frozenDocument.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); var isFinalized = document.Project.TryGetCompilation(out var compilation) && compilation == semanticModel.Compilation; document = frozenDocument; var options = ClassificationOptions.From(document.Project); var classifiedSpans = Classifier.GetClassifiedSpans(document.Project.Solution.Workspace.Services, semanticModel, textSpan, options, cancellationToken); Contract.ThrowIfNull(classifiedSpans, "classifiedSpans is null"); // Multi-line tokens are not supported by VS (tracked by https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1265495). // Roslyn's classifier however can return multi-line classified spans, so we must break these up into single-line spans. var updatedClassifiedSpans = ConvertMultiLineToSingleLineSpans(text, classifiedSpans.ToArray()); // TO-DO: We should implement support for streaming if LSP adds support for it: // https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1276300 return(ComputeTokens(text.Lines, updatedClassifiedSpans, tokenTypesToIndex), isFinalized); }
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); }
static async Task <TCompletionItem> CreateCompletionItemAsync <TCompletionItem>( LSP.CompletionParams request, Document document, CompletionItem item, long?resultId, CompletionTrigger completionTrigger, Dictionary <ImmutableArray <CharacterSetModificationRule>, ImmutableArray <string> > commitCharacterRulesCache, CompletionService completionService, string?clientName, bool returnTextEdits, bool snippetsSupported, StringBuilder stringBuilder, SourceText?documentText, TextSpan?defaultSpan, LSP.Range?defaultRange, CancellationToken cancellationToken) where TCompletionItem : LSP.CompletionItem, new() { // Generate display text stringBuilder.Append(item.DisplayTextPrefix); stringBuilder.Append(item.DisplayText); stringBuilder.Append(item.DisplayTextSuffix); var completeDisplayText = stringBuilder.ToString(); stringBuilder.Clear(); var completionItem = new TCompletionItem { Label = completeDisplayText, SortText = item.SortText, FilterText = item.FilterText, Kind = GetCompletionKind(item.Tags), Data = new CompletionResolveData { TextDocument = request.TextDocument, Position = request.Position, DisplayText = item.DisplayText, CompletionTrigger = completionTrigger, ResultId = resultId, }, Preselect = item.Rules.SelectionBehavior == CompletionItemSelectionBehavior.HardSelection, }; // Complex text edits (e.g. override and partial method completions) are always populated in the // resolve handler, so we leave both TextEdit and InsertText unpopulated in these cases. if (item.IsComplexTextEdit) { // Razor C# is currently the only language client that supports LSP.InsertTextFormat.Snippet. // We can enable it for regular C# once LSP is used for local completion. if (snippetsSupported) { completionItem.InsertTextFormat = LSP.InsertTextFormat.Snippet; } } // If the feature flag is on, always return a TextEdit. else if (returnTextEdits) { var textEdit = await GenerateTextEdit( document, item, completionService, documentText, defaultSpan, defaultRange, cancellationToken).ConfigureAwait(false); completionItem.TextEdit = textEdit; } // If the feature flag is off, return an InsertText. else { completionItem.InsertText = item.Properties.ContainsKey("InsertionText") ? item.Properties["InsertionText"] : completeDisplayText; } var commitCharacters = GetCommitCharacters(item, commitCharacterRulesCache); if (commitCharacters != null) { completionItem.CommitCharacters = commitCharacters; } return(completionItem);
public async Task <LSP.CompletionList?> HandleRequestAsync(LSP.CompletionParams request, RequestContext context, CancellationToken cancellationToken) { var document = context.Document; Contract.ThrowIfNull(document); // C# and VB share the same LSP language server, and thus share the same default trigger characters. // We need to ensure the trigger character is valid in the document's language. For example, the '{' // character, while a trigger character in VB, is not a trigger character in C#. if (request.Context != null && request.Context.TriggerKind == LSP.CompletionTriggerKind.TriggerCharacter && !char.TryParse(request.Context.TriggerCharacter, out var triggerCharacter) && !char.IsLetterOrDigit(triggerCharacter) && !IsValidTriggerCharacterForDocument(document, triggerCharacter)) { return(null); } var completionOptions = GetCompletionOptions(document); var completionService = document.GetRequiredLanguageService <CompletionService>(); var documentText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); var completionListResult = await GetFilteredCompletionListAsync(request, documentText, document, completionOptions, completionService, cancellationToken).ConfigureAwait(false); if (completionListResult == null) { return(null); } var(list, isIncomplete, resultId) = completionListResult.Value; var lspVSClientCapability = context.ClientCapabilities.HasVisualStudioLspCapability() == true; var snippetsSupported = context.ClientCapabilities.TextDocument?.Completion?.CompletionItem?.SnippetSupport ?? false; var commitCharactersRuleCache = new Dictionary <ImmutableArray <CharacterSetModificationRule>, string[]>(CommitCharacterArrayComparer.Instance); // Feature flag to enable the return of TextEdits instead of InsertTexts (will increase payload size). Contract.ThrowIfNull(context.Solution); var returnTextEdits = _globalOptions.GetOption(LspOptions.LspCompletionFeatureFlag); TextSpan?defaultSpan = null; LSP.Range?defaultRange = null; if (returnTextEdits) { // We want to compute the document's text just once. documentText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); // We use the first item in the completion list as our comparison point for span // and range for optimization when generating the TextEdits later on. var completionChange = await completionService.GetChangeAsync( document, list.Items.First(), cancellationToken : cancellationToken).ConfigureAwait(false); // If possible, we want to compute the item's span and range just once. // Individual items can override this range later. defaultSpan = completionChange.TextChange.Span; defaultRange = ProtocolConversions.TextSpanToRange(defaultSpan.Value, documentText); } var supportsCompletionListData = context.ClientCapabilities.HasCompletionListDataCapability(); var completionResolveData = new CompletionResolveData() { ResultId = resultId, }; var stringBuilder = new StringBuilder(); using var _ = ArrayBuilder <LSP.CompletionItem> .GetInstance(out var lspCompletionItems); foreach (var item in list.Items) { var completionItemResolveData = supportsCompletionListData ? null : completionResolveData; var lspCompletionItem = await CreateLSPCompletionItemAsync( request, document, item, completionItemResolveData, lspVSClientCapability, commitCharactersRuleCache, completionService, context.ClientName, returnTextEdits, snippetsSupported, stringBuilder, documentText, defaultSpan, defaultRange, cancellationToken).ConfigureAwait(false); lspCompletionItems.Add(lspCompletionItem); } var completionList = new LSP.VSInternalCompletionList { Items = lspCompletionItems.ToArray(), SuggestionMode = list.SuggestionModeItem != null, IsIncomplete = isIncomplete, }; if (supportsCompletionListData) { completionList.Data = completionResolveData; } if (context.ClientCapabilities.HasCompletionListCommitCharactersCapability()) { PromoteCommonCommitCharactersOntoList(completionList); } var optimizedCompletionList = new LSP.OptimizedVSCompletionList(completionList); return(optimizedCompletionList); // Local functions bool IsValidTriggerCharacterForDocument(Document document, char triggerCharacter) { if (document.Project.Language == LanguageNames.CSharp) { return(_csharpTriggerCharacters.Contains(triggerCharacter)); } else if (document.Project.Language == LanguageNames.VisualBasic) { return(_vbTriggerCharacters.Contains(triggerCharacter)); } // Typescript still calls into this for completion. // Since we don't know what their trigger characters are, just return true. return(true); }
static async Task <TCompletionItem> CreateCompletionItemAsync <TCompletionItem>( LSP.CompletionParams request, Document document, CompletionItem item, long?resultId, CompletionTrigger completionTrigger, Dictionary <ImmutableArray <CharacterSetModificationRule>, ImmutableArray <string> > commitCharacterRulesCache, CompletionService completionService, string?clientName, bool returnTextEdits, bool snippetsSupported, StringBuilder stringBuilder, SourceText?documentText, TextSpan?defaultSpan, LSP.Range?defaultRange, CancellationToken cancellationToken) where TCompletionItem : LSP.CompletionItem, new() { // Generate display text stringBuilder.Append(item.DisplayTextPrefix); stringBuilder.Append(item.DisplayText); stringBuilder.Append(item.DisplayTextSuffix); var completeDisplayText = stringBuilder.ToString(); stringBuilder.Clear(); // The TextEdits for override and partial method completions are provided in the resolve handler. // We do not provide them in this handler. // HACK: We should not be accessing the completion item's properties directly. // See https://github.com/dotnet/roslyn/issues/51396. item.Properties.TryGetValue("Modifiers", out var modifier); var isOverrideOrPartialMethodCompletion = modifier != null && (modifier.Contains("Override") || modifier.Contains("Partial")); var completionItem = new TCompletionItem { Label = completeDisplayText, SortText = item.SortText, FilterText = item.FilterText, Kind = GetCompletionKind(item.Tags), Data = new CompletionResolveData { TextDocument = request.TextDocument, Position = request.Position, DisplayText = item.DisplayText, CompletionTrigger = completionTrigger, ResultId = resultId, }, Preselect = item.Rules.SelectionBehavior == CompletionItemSelectionBehavior.HardSelection, }; // Override and partial method completions are always populated in the resolve handler, so we // leave both TextEdit and InsertText unpopulated in these cases. if (isOverrideOrPartialMethodCompletion) { // Razor C# is currently the only language client that supports LSP.InsertTextFormat.Snippet. // We can enable it for regular C# once LSP is used for local completion. if (snippetsSupported && clientName == ProtocolConstants.RazorCSharp) { completionItem.InsertTextFormat = LSP.InsertTextFormat.Snippet; } } // If the feature flag is on, always return a TextEdit. else if (returnTextEdits) { var textEdit = await GenerateTextEdit( document, item, completionService, documentText, defaultSpan, defaultRange, cancellationToken).ConfigureAwait(false); completionItem.TextEdit = textEdit; } // If the feature flag is off, return an InsertText. else { completionItem.InsertText = item.Properties.ContainsKey("InsertionText") ? item.Properties["InsertionText"] : completeDisplayText; } var commitCharacters = GetCommitCharacters(item, commitCharacterRulesCache); if (commitCharacters != null) { completionItem.CommitCharacters = commitCharacters; } return(completionItem);
// routines for convenience /// <summary> /// Logs a diagnostic message based on the given error code, /// with the given source as the file for which the error occurred. /// </summary> public void Log(ErrorCode code, IEnumerable <string> args, string?source = null, LSP.Range?range = null) => this.Log(new Diagnostic { Severity = DiagnosticSeverity.Error, Code = Errors.Code(code), Source = source, Message = DiagnosticItem.Message(code, args ?? Enumerable.Empty <string>()), Range = range ?? EmptyRange });
/// <summary> /// Generates a Diagnostic message based on the given information code, /// with any message parameters appended on a new line to the message defined by the information code. /// The given source is listed as the file for which the error occurred. /// </summary> public void Log(InformationCode code, IEnumerable <string> args, string?source = null, LSP.Range?range = null, params string[] messageParam) => this.Log(new Diagnostic { Severity = DiagnosticSeverity.Information, Code = null, // do not show a code for infos Source = source, Message = $"{DiagnosticItem.Message(code, args ?? Enumerable.Empty<string>())}{Environment.NewLine}{string.Join(Environment.NewLine, messageParam)}", Range = range ?? EmptyRange });
protected async Task <LSP.TextEdit[]> GetTextEditsAsync(RequestContext context, CancellationToken cancellationToken, LSP.Range?range = null) { var edits = new ArrayBuilder <LSP.TextEdit>(); var document = context.Document; if (document != null) { var formattingService = document.Project.LanguageServices.GetRequiredService <IEditorFormattingService>(); var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); TextSpan?textSpan = null; if (range != null) { textSpan = ProtocolConversions.RangeToTextSpan(range, text); } var textChanges = await GetFormattingChangesAsync(formattingService, document, textSpan, cancellationToken).ConfigureAwait(false); edits.AddRange(textChanges.Select(change => ProtocolConversions.TextChangeToTextEdit(change, text))); } return(edits.ToArrayAndFree()); }
static async Task <LSP.CompletionItem> CreateLSPCompletionItemAsync( LSP.CompletionParams request, Document document, CompletionItem item, CompletionResolveData?completionResolveData, bool supportsVSExtensions, Dictionary <ImmutableArray <CharacterSetModificationRule>, string[]> commitCharacterRulesCache, CompletionService completionService, bool returnTextEdits, bool snippetsSupported, StringBuilder stringBuilder, SourceText?documentText, TextSpan?defaultSpan, LSP.Range?defaultRange, CancellationToken cancellationToken) { // Generate display text stringBuilder.Append(item.DisplayTextPrefix); stringBuilder.Append(item.DisplayText); stringBuilder.Append(item.DisplayTextSuffix); var completeDisplayText = stringBuilder.ToString(); stringBuilder.Clear(); var completionItem = supportsVSExtensions ? new LSP.VSInternalCompletionItem() : new LSP.CompletionItem(); completionItem.Label = completeDisplayText; completionItem.SortText = item.SortText; completionItem.FilterText = item.FilterText; completionItem.Kind = GetCompletionKind(item.Tags); completionItem.Data = completionResolveData; completionItem.Preselect = ShouldItemBePreselected(item); // Complex text edits (e.g. override and partial method completions) are always populated in the // resolve handler, so we leave both TextEdit and InsertText unpopulated in these cases. if (item.IsComplexTextEdit && completionItem is LSP.VSInternalCompletionItem vsItem) { vsItem.VsResolveTextEditOnCommit = true; // Razor C# is currently the only language client that supports LSP.InsertTextFormat.Snippet. // We can enable it for regular C# once LSP is used for local completion. if (snippetsSupported) { completionItem.InsertTextFormat = LSP.InsertTextFormat.Snippet; } } // If the feature flag is on, always return a TextEdit. else if (returnTextEdits) { var textEdit = await GenerateTextEdit( document, item, completionService, documentText, defaultSpan, defaultRange, cancellationToken).ConfigureAwait(false); completionItem.TextEdit = textEdit; } // If the feature flag is off, return an InsertText. else { completionItem.InsertText = SymbolCompletionItem.TryGetInsertionText(item, out var insertionText) ? insertionText : completeDisplayText; } var commitCharacters = GetCommitCharacters(item, commitCharacterRulesCache, supportsVSExtensions); if (commitCharacters != null) { completionItem.CommitCharacters = commitCharacters; } if (completionItem is LSP.VSInternalCompletionItem vsCompletionItem) { vsCompletionItem.Icon = new ImageElement(item.Tags.GetFirstGlyph().GetImageId()); } return(completionItem);