public void TestExtendTextChangeReplacement() { var testString = "foo bar quux baz"; using var workspace = CreateWorkspaceFromCode(testString); var document = workspace.CurrentSolution.GetRequiredDocument(workspace.Documents.First().Id); var lspSnippetString = RoslynLSPSnippetConverter.GenerateLSPSnippetAsync(document, caretPosition: 12, ImmutableArray <SnippetPlaceholder> .Empty, new TextChange(new TextSpan(4, 4), "bar quux"), triggerLocation: 12, CancellationToken.None).Result; AssertEx.EqualOrDiff("bar quux$0", lspSnippetString); }
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)); }