File: Snippets\SnippetCompletionItemProvider.cs
Web Access
Project: src\src\Razor\src\Razor\src\Microsoft.VisualStudio.LanguageServices.Razor\Microsoft.VisualStudio.LanguageServices.Razor.csproj (Microsoft.VisualStudio.LanguageServices.Razor)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.ComponentModel.Composition;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Microsoft.AspNetCore.Razor.PooledObjects;
using Microsoft.CodeAnalysis.Razor.Completion;
using Microsoft.CodeAnalysis.Razor.Protocol;
 
namespace Microsoft.VisualStudio.Razor.Snippets;
 
[Export(typeof(ISnippetCompletionItemProvider))]
internal sealed class SnippetCompletionItemProvider : ISnippetCompletionItemProvider
{
    [ImportingConstructor]
    public SnippetCompletionItemProvider(SnippetCache snippetCache)
    {
        SnippetCache = snippetCache;
    }
 
    public SnippetCache SnippetCache { get; }
 
    public void AddSnippetCompletions(
        ref PooledArrayBuilder<VSInternalCompletionItem> builder,
        RazorLanguageKind projectedKind,
        VSInternalCompletionInvokeKind invokeKind,
        string? triggerCharacter)
    {
        // Temporary fix: snippets are broken in CSharp. We're investigating
        // but this is very disruptive. This quick fix unblocks things.
        // TODO: Add an option to enable this.
        if (projectedKind != RazorLanguageKind.Html)
        {
            return;
        }
 
        // Don't add snippets for deletion of a character
        if (invokeKind == VSInternalCompletionInvokeKind.Deletion)
        {
            return;
        }
 
        // Don't add snippets if the trigger characters contain whitespace
        if (triggerCharacter is not null && triggerCharacter.Contains(' '))
        {
            return;
        }
 
        var snippets = SnippetCache.GetSnippets(ConvertLanguageKind(projectedKind));
        if (snippets.IsDefaultOrEmpty)
        {
            return;
        }
 
        builder.AddRange(snippets
            .Select(s => new VSInternalCompletionItem()
            {
                Label = s.Shortcut,
                Detail = s.Description,
                InsertTextFormat = InsertTextFormat.Snippet,
                InsertText = s.Shortcut,
                Data = s.CompletionData,
                Kind = CompletionItemKind.Snippet,
                CommitCharacters = []
            }));
    }
 
    public bool TryResolveInsertString(VSInternalCompletionItem completionItem, [NotNullWhen(true)] out string? insertString)
    {
        if (SnippetCompletionData.TryParse(completionItem.Data, out var snippetCompletionData) &&
            SnippetCache.TryResolveSnippetString(snippetCompletionData) is { } snippetInsertText)
        {
            insertString = snippetInsertText;
            return true;
        }
 
        insertString = null;
        return false;
    }
 
    private static SnippetLanguage ConvertLanguageKind(RazorLanguageKind languageKind)
        => languageKind switch
        {
            RazorLanguageKind.CSharp => SnippetLanguage.CSharp,
            RazorLanguageKind.Html => SnippetLanguage.Html,
            RazorLanguageKind.Razor => SnippetLanguage.Razor,
            _ => throw new InvalidOperationException($"Unexpected value {languageKind}")
        };
}