File: AutoInsert\AutoInsertService.cs
Web Access
Project: src\src\Razor\src\Razor\src\Microsoft.CodeAnalysis.Razor.Workspaces\Microsoft.CodeAnalysis.Razor.Workspaces.csproj (Microsoft.CodeAnalysis.Razor.Workspaces)
// 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.Collections.Frozen;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.PooledObjects;
 
namespace Microsoft.CodeAnalysis.Razor.AutoInsert;
 
internal class AutoInsertService(IEnumerable<IOnAutoInsertProvider> onAutoInsertProviders) : IAutoInsertService
{
    private readonly ImmutableArray<IOnAutoInsertProvider> _onAutoInsertProviders = onAutoInsertProviders.ToImmutableArray();
 
    public static FrozenSet<string> HtmlAllowedAutoInsertTriggerCharacters { get; }
        = new string[] { "=" }.ToFrozenSet(StringComparer.Ordinal);
    public static FrozenSet<string> CSharpAllowedAutoInsertTriggerCharacters { get; }
        = new string[] { "'", "/", "\n", "\"" }.ToFrozenSet(StringComparer.Ordinal);
 
    private readonly ImmutableArray<string> _triggerCharacters = CalculateTriggerCharacters(onAutoInsertProviders);
 
    private static ImmutableArray<string> CalculateTriggerCharacters(IEnumerable<IOnAutoInsertProvider> onAutoInsertProviders)
    {
        using var builder = new PooledArrayBuilder<string>();
        using var _ = SpecializedPools.GetPooledStringHashSet(out var set);
        foreach (var provider in onAutoInsertProviders)
        {
            var triggerCharacter = provider.TriggerCharacter;
            if (set.Add(triggerCharacter))
            {
                builder.Add(triggerCharacter);
            }
        }
 
        return builder.ToImmutableAndClear();
    }
 
    public ImmutableArray<string> TriggerCharacters => _triggerCharacters;
 
    public bool TryResolveInsertion(
        RazorCodeDocument codeDocument,
        Position position,
        string character,
        bool autoCloseTags,
        [NotNullWhen(true)] out VSInternalDocumentOnAutoInsertResponseItem? insertTextEdit)
    {
        using var applicableProviders = new PooledArrayBuilder<IOnAutoInsertProvider>(capacity: _onAutoInsertProviders.Length);
        foreach (var provider in _onAutoInsertProviders)
        {
            if (provider.TriggerCharacter == character)
            {
                applicableProviders.Add(provider);
            }
        }
 
        if (applicableProviders.Count == 0)
        {
            // There's currently a bug in the LSP platform where other language clients OnAutoInsert trigger characters influence every language clients trigger characters.
            // To combat this we need to preemptively return so we don't try having our providers handle characters that they can't.
            insertTextEdit = null;
            return false;
        }
 
        foreach (var provider in applicableProviders)
        {
            if (provider.TryResolveInsertion(position, codeDocument, autoCloseTags, out insertTextEdit))
            {
                return true;
            }
        }
 
        // No provider could handle the text edit.
        insertTextEdit = null;
        return false;
    }
}