From 4626b5a7fae2260976a8befd6617aaa9817d6b99 Mon Sep 17 00:00:00 2001 From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com> Date: Fri, 7 Aug 2026 01:03:30 +0200 Subject: [PATCH] Remove duplicate empty lines in auto formatter --- docs/editor.md | 2 ++ src/YesNt.CodeEditor/Editor.cs | 66 ++++++++++++++++++++++++++++------ 2 files changed, 58 insertions(+), 10 deletions(-) diff --git a/docs/editor.md b/docs/editor.md index 10b9108..fbd6470 100644 --- a/docs/editor.md +++ b/docs/editor.md @@ -51,3 +51,5 @@ If you pass a file path, it is loaded on startup. - `return` / `return ` are regular body statements and stay indented with the block they are in. - `exit` / `throw` / `error` close active non-function blocks for following lines. - Comment lines (`# ...`) are kept unindented. +- Single empty lines are preserved, runs of empty lines are collapsed to one, and leading/trailing blank lines are removed. +- A blank line is inserted before each block opener (`func` / `if` / `while`) that is not nested directly under another opener, and after each block closer (`end_func` / `end_if` / `end_while`), separating sibling blocks. `else:` stays attached to its `if` block. diff --git a/src/YesNt.CodeEditor/Editor.cs b/src/YesNt.CodeEditor/Editor.cs index a8ed4cd..b10c319 100644 --- a/src/YesNt.CodeEditor/Editor.cs +++ b/src/YesNt.CodeEditor/Editor.cs @@ -207,19 +207,16 @@ internal class TextEditor { const int indentationSize = 4; List blockStack = []; + List output = []; + string previousLine = string.Empty; + bool pendingBlank = false; for (int i = 0; i < Lines.Count; i++) { string trimmed = Lines[i].Trim(' '); if (string.IsNullOrWhiteSpace(trimmed)) { - Lines[i] = string.Empty; - continue; - } - - if (trimmed.StartsWith('#')) - { - Lines[i] = trimmed; + pendingBlank = true; continue; } @@ -270,13 +267,47 @@ internal class TextEditor } int lineIndentation = closesFunctionBlock ? Math.Max(0, blockStack.Count - 1) : blockStack.Count; - Lines[i] = new string(' ', lineIndentation * indentationSize) + trimmed; + string indented = new string(' ', lineIndentation * indentationSize) + trimmed; - if (!isTerminatingStatement && ( + bool isBlockOpener = !isTerminatingStatement && ( (trimmed.StartsWith("if ", StringComparison.Ordinal) && trimmed.EndsWith(':')) || (trimmed.StartsWith("while ", StringComparison.Ordinal) && trimmed.EndsWith(':')) || (trimmed.StartsWith("func ", StringComparison.Ordinal) && trimmed.Contains(':')) - || trimmed == "else:")) + || trimmed == "else:"); + bool isBlockCloser = trimmed == "end_if" || trimmed == "end_while" || trimmed == "end_func"; + + bool insertBlank = pendingBlank; + + if (previousLine.Length > 0 && !previousLine.StartsWith('#')) + { + bool previousIsCloser = previousLine == "end_if" || previousLine == "end_while" || previousLine == "end_func"; + bool previousIsOpener = previousLine.StartsWith("if ", StringComparison.Ordinal) && previousLine.EndsWith(':') + || previousLine.StartsWith("while ", StringComparison.Ordinal) && previousLine.EndsWith(':') + || previousLine.StartsWith("func ", StringComparison.Ordinal) && previousLine.Contains(':') + || previousLine == "else:"; + + if (previousIsCloser) + { + if (!isBlockCloser && trimmed != "else:") + { + insertBlank = true; + } + } + else if (isBlockOpener && trimmed != "else:" && !previousIsOpener) + { + insertBlank = true; + } + } + + if (insertBlank && output.Count > 0 && output[^1].Length > 0) + { + output.Add(string.Empty); + } + + output.Add(indented); + pendingBlank = false; + + if (isBlockOpener) { if (trimmed.StartsWith("if ", StringComparison.Ordinal)) { @@ -308,7 +339,22 @@ internal class TextEditor blockStack.RemoveAt(blockStack.Count - 1); } } + + previousLine = trimmed; } + + while (output.Count > 0 && output[0].Length == 0) + { + output.RemoveAt(0); + } + + while (output.Count > 0 && output[^1].Length == 0) + { + output.RemoveAt(output.Count - 1); + } + + Lines.Clear(); + Lines.AddRange(output); } private static string ToLiteral(string input)