File: Formatting\IndentCache.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.
 
#if !NET
using System;
#endif
using Microsoft.AspNetCore.Razor;
 
namespace Microsoft.CodeAnalysis.Razor.Formatting;
 
internal static class IndentCache
{
    // Copied from the compilers IndentCache
    internal const int MaxTabCount = 64;
    internal const int MaxSpaceCount = 128;
 
    internal const int MaxSpaceCountInMixedString = 8;
 
    private static readonly string?[] s_tabStrings = new string[MaxTabCount + 1];
    private static readonly string?[] s_spaceStrings = new string[MaxSpaceCount + 1];
    // Mixed tab+space indentation is sparser than tab-only or space-only lookups, so we keep
    // the first dimension by tab count and lazily allocate each per-tab space row on first use.
    private static readonly string?[][] s_mixedStrings = new string[MaxTabCount + 1][];
 
    public static string GetIndentString(int size, bool insertSpaces, int tabSize)
    {
        ArgHelper.ThrowIfNegative(size);
        ArgHelper.ThrowIfNegativeOrZero(tabSize);
 
        if (size == 0)
        {
            return string.Empty;
        }
 
        if (insertSpaces)
        {
            return GetSingleCharacterString(size, ' ', s_spaceStrings);
        }
 
        var tabCount = size / tabSize;
        var spaceCount = size % tabSize;
 
        if (spaceCount == 0)
        {
            return GetSingleCharacterString(tabCount, '\t', s_tabStrings);
        }
 
        if (tabCount == 0)
        {
            return GetSingleCharacterString(spaceCount, ' ', s_spaceStrings);
        }
 
        return GetMixedString(tabCount, spaceCount);
    }
 
    private static string GetSingleCharacterString(int length, char character, string?[] cache)
    {
        if (length >= cache.Length)
        {
            return new string(character, length);
        }
 
        var indentString = cache[length];
        if (indentString is not null)
        {
            return indentString;
        }
 
        indentString = new string(character, length);
        cache[length] = indentString;
        return indentString;
    }
 
    private static string GetMixedString(int tabCount, int spaceCount)
    {
        if (tabCount > MaxTabCount || spaceCount > MaxSpaceCountInMixedString)
        {
            return CreateMixedString(tabCount, spaceCount);
        }
 
        var tabStrings = s_mixedStrings[tabCount];
        if (tabStrings is null)
        {
            tabStrings = new string?[MaxSpaceCountInMixedString + 1];
            s_mixedStrings[tabCount] = tabStrings;
        }
 
        var indentString = tabStrings[spaceCount];
        if (indentString is not null)
        {
            return indentString;
        }
 
        indentString = CreateMixedString(tabCount, spaceCount);
        tabStrings[spaceCount] = indentString;
        return indentString;
    }
 
    private static string CreateMixedString(int tabCount, int spaceCount)
    {
        return string.Create(length: tabCount + spaceCount, state: tabCount, static (destination, tabCount) =>
        {
            destination[..tabCount].Fill('\t');
            destination[tabCount..].Fill(' ');
        });
    }
}