protected static async Task <LSP.VSCompletionItem> CreateCompletionItemAsync( string label, LSP.CompletionItemKind kind, string[] tags, LSP.CompletionParams request, Document document, bool preselect = false, ImmutableArray <char>?commitCharacters = null, LSP.TextEdit?textEdit = null, string?insertText = null, string?sortText = null, string?filterText = null, long resultId = 0 ) { var position = await document .GetPositionFromLinePositionAsync( ProtocolConversions.PositionToLinePosition(request.Position), CancellationToken.None ) .ConfigureAwait(false); var completionTrigger = await ProtocolConversions .LSPToRoslynCompletionTriggerAsync( request.Context, document, position, CancellationToken.None ) .ConfigureAwait(false); var item = new LSP.VSCompletionItem() { TextEdit = textEdit, InsertText = insertText, FilterText = filterText ?? label, Label = label, SortText = sortText ?? label, InsertTextFormat = LSP.InsertTextFormat.Plaintext, Kind = kind, Data = JObject.FromObject(new CompletionResolveData() { ResultId = resultId, }), Preselect = preselect }; if (tags != null) { item.Icon = tags.ToImmutableArray().GetFirstGlyph().GetImageElement(); } if (commitCharacters != null) { item.CommitCharacters = commitCharacters.Value.Select(c => c.ToString()).ToArray(); } return(item); }
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) { return(null); } var lspVSClientCapability = context.ClientCapabilities?.HasVisualStudioLspCapability() == true; 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); return(new LSP.VSCompletionList { Items = list.Items.Select(item => CreateLSPCompletionItem( request, item, resultId, lspVSClientCapability, completionTrigger, commitCharactersRuleCache)).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 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); }