public override async Task ProvideCompletionsAsync(CompletionContext context) { if (!context.CompletionOptions.ShouldShowNewSnippetExperience()) { return; } var document = context.Document; var cancellationToken = context.CancellationToken; var position = context.Position; var service = document.GetLanguageService <ISnippetService>(); if (service == null) { return; } var(strippedDocument, newPosition) = await GetDocumentWithoutInvokingTextAsync(document, position, cancellationToken).ConfigureAwait(false); var snippets = await service.GetSnippetsAsync(strippedDocument, newPosition, cancellationToken).ConfigureAwait(false); foreach (var snippetData in snippets) { var completionItem = SnippetCompletionItem.Create( displayText: snippetData.DisplayName, displayTextSuffix: "", position: position, snippetIdentifier: snippetData.SnippetIdentifier, glyph: Glyph.Snippet); context.AddItem(completionItem); } }
public override async Task <CompletionChange> GetChangeAsync(Document document, CompletionItem item, char?commitKey = null, CancellationToken cancellationToken = default) { // This retrieves the document without the text used to invoke completion // as well as the new cursor position after that has been removed. var(strippedDocument, position) = await GetDocumentWithoutInvokingTextAsync(document, SnippetCompletionItem.GetInvocationPosition(item), cancellationToken).ConfigureAwait(false); var service = strippedDocument.GetRequiredLanguageService <ISnippetService>(); var snippetIdentifier = SnippetCompletionItem.GetSnippetIdentifier(item); var snippetProvider = service.GetSnippetProvider(snippetIdentifier); // This retrieves the generated Snippet var snippet = await snippetProvider.GetSnippetAsync(strippedDocument, position, cancellationToken).ConfigureAwait(false); var strippedText = await strippedDocument.GetTextAsync(cancellationToken).ConfigureAwait(false); // This introduces the text changes of the snippet into the document with the completion invoking text var allChangesText = strippedText.WithChanges(snippet.TextChanges); // This retrieves ALL text changes from the original document which includes the TextChanges from the snippet // as well as the clean up. var allChangesDocument = document.WithText(allChangesText); var allTextChanges = await allChangesDocument.GetTextChangesAsync(document, cancellationToken).ConfigureAwait(false); var change = Utilities.Collapse(allChangesText, allTextChanges.AsImmutable()); // Converts the snippet to an LSP formatted snippet string. var lspSnippet = await RoslynLSPSnippetConverter.GenerateLSPSnippetAsync(allChangesDocument, snippet.CursorPosition, snippet.Placeholders, change, item.Span.Start, cancellationToken).ConfigureAwait(false); // If the TextChanges retrieved starts after the trigger point of the CompletionItem, // then we need to move the bounds backwards and encapsulate the trigger point. if (change.Span.Start > item.Span.Start) { var textSpan = TextSpan.FromBounds(item.Span.Start, change.Span.End); var snippetText = change.NewText; Contract.ThrowIfNull(snippetText); change = new TextChange(textSpan, snippetText); } var props = ImmutableDictionary <string, string> .Empty .Add(SnippetCompletionItem.LSPSnippetKey, lspSnippet); return(CompletionChange.Create(change, allTextChanges.AsImmutable(), properties: props, snippet.CursorPosition, includesCommitCharacter: true)); }