Created
August 1, 2024 03:30
-
-
Save jnm2/be5a0ac093819d9581242b3f02ab4f1e to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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