Skip to content

Instantly share code, notes, and snippets.

@jnm2
Created August 1, 2024 03:30
Show Gist options
  • Save jnm2/be5a0ac093819d9581242b3f02ab4f1e to your computer and use it in GitHub Desktop.
Save jnm2/be5a0ac093819d9581242b3f02ab4f1e to your computer and use it in GitHub Desktop.
// Uses https://gist.github.com/jnm2/73d378c5b52547728de1148d72de522a
/// <summary>
/// Leaves the first line alone, but removes common indentation from the remaining lines.
/// </summary>
public static string RemoveIndentation(string syntax)
{
var commonWhitespaceLength = GetCommonWhitespace(syntax).Length;
return string.Create(
GetTextLengthAfterRemoval(syntax, commonWhitespaceLength),
(syntax, commonWhitespaceLength),
static (buffer, state) =>
{
var enumerator = new LineEnumerator(state.syntax);
_ = enumerator.MoveNext();
enumerator.Current.CopyTo(buffer);
buffer = buffer[enumerator.Current.Length..];
while (enumerator.MoveNext())
{
var lengthToRemove = Math.Min(state.commonWhitespaceLength, enumerator.CurrentWithoutLineEnding.Length);
var trimmed = enumerator.Current[lengthToRemove..];
trimmed.CopyTo(buffer);
buffer = buffer[trimmed.Length..];
}
});
static ReadOnlySpan<char> GetIndentationOnFirstNonWhitespaceLine(ReadOnlySpan<char> text)
{
var enumerator = new LineEnumerator(text);
_ = enumerator.MoveNext();
while (enumerator.MoveNext())
{
var line = enumerator.CurrentWithoutLineEnding;
for (var charIndex = 0; charIndex < line.Length; charIndex++)
{
if (line[charIndex] is not ' ' and not '\t')
return line[..charIndex];
}
}
// All lines besides the first are whitespace lines; there are no non-whitespace chars to align.
return default;
}
static ReadOnlySpan<char> GetCommonWhitespace(ReadOnlySpan<char> text)
{
var commonWhitespace = GetIndentationOnFirstNonWhitespaceLine(text);
if (!commonWhitespace.IsEmpty)
{
var enumerator = new LineEnumerator(text);
_ = enumerator.MoveNext();
while (enumerator.MoveNext())
{
var line = enumerator.CurrentWithoutLineEnding;
for (var charIndex = 0; charIndex < Math.Min(line.Length, commonWhitespace.Length); charIndex++)
{
if (line[charIndex] != commonWhitespace[charIndex])
{
commonWhitespace = commonWhitespace[..charIndex];
if (commonWhitespace.IsEmpty)
return commonWhitespace;
break;
}
}
}
}
return commonWhitespace;
}
static int GetTextLengthAfterRemoval(ReadOnlySpan<char> text, int commonWhitespaceLength)
{
var length = text.Length;
var enumerator = new LineEnumerator(text);
_ = enumerator.MoveNext();
while (enumerator.MoveNext())
{
length -= Math.Min(enumerator.CurrentWithoutLineEnding.Length, commonWhitespaceLength);
}
return length;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment