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));
        }