From 67b875e72d9ee03c932b7b52e292be9fae60a9e6 Mon Sep 17 00:00:00 2001 From: Matt Parker <61717342+MattParkerDev@users.noreply.github.com> Date: Mon, 19 Jan 2026 19:09:28 +1000 Subject: [PATCH] fix missing carriage returns on unix --- .../Features/TerminalBase/SharpIdeTerminal.cs | 17 ++++- .../SharpIdeTerminal_LineEndings.cs | 63 +++++++++++++++++++ 2 files changed, 78 insertions(+), 2 deletions(-) create mode 100644 src/SharpIDE.Godot/Features/TerminalBase/SharpIdeTerminal_LineEndings.cs diff --git a/src/SharpIDE.Godot/Features/TerminalBase/SharpIdeTerminal.cs b/src/SharpIDE.Godot/Features/TerminalBase/SharpIdeTerminal.cs index 495073e..d440f7c 100644 --- a/src/SharpIDE.Godot/Features/TerminalBase/SharpIdeTerminal.cs +++ b/src/SharpIDE.Godot/Features/TerminalBase/SharpIdeTerminal.cs @@ -1,3 +1,4 @@ +using System.Buffers; using GDExtensionBindgen; using Godot; @@ -20,9 +21,21 @@ public partial class SharpIdeTerminal : Control public async Task WriteAsync(byte[] text) { - await this.InvokeAsync(() => _terminal.Write(text)); + var (processedArray, length, wasRented) = ProcessLineEndings(text); + try + { + await this.InvokeAsync(() => _terminal.Write(processedArray.AsSpan(0, length))); + } + finally + { + if (wasRented) + { + ArrayPool.Shared.Return(processedArray); + } + } + _previousArrayEndedInCr = text.Length > 0 && text[^1] == (byte)'\r'; } - + [RequiresGodotUiThread] public void ClearTerminal() { diff --git a/src/SharpIDE.Godot/Features/TerminalBase/SharpIdeTerminal_LineEndings.cs b/src/SharpIDE.Godot/Features/TerminalBase/SharpIdeTerminal_LineEndings.cs new file mode 100644 index 0000000..18c9f4c --- /dev/null +++ b/src/SharpIDE.Godot/Features/TerminalBase/SharpIdeTerminal_LineEndings.cs @@ -0,0 +1,63 @@ +using System.Buffers; + +namespace SharpIDE.Godot.Features.TerminalBase; + +public partial class SharpIdeTerminal +{ + private bool _previousArrayEndedInCr = false; + + // Unfortunately, although the terminal emulator handles escape sequences etc, it does not handle interpreting \n as \r\n - that is handled by the PTY, which we currently don't use + // So we need to replace lone \n with \r\n ourselves + // TODO: Probably run processes with PTY instead, so that this is not needed, and so we can capture user input and Ctrl+C etc + // 🤖 + private (byte[] array, int length, bool wasRented) ProcessLineEndings(byte[] input) + { + if (input.Length == 0) return (input, 0, false); + + // Count how many \n need to be replaced (those not preceded by \r) + var replacementCount = 0; + var previousWasCr = _previousArrayEndedInCr; + + for (var i = 0; i < input.Length; i++) + { + if (input[i] == (byte)'\n') + { + // Check if it's preceded by \r + var precededByCr = (i > 0 && input[i - 1] == (byte)'\r') || (i == 0 && previousWasCr); + if (!precededByCr) + { + replacementCount++; + } + } + + previousWasCr = input[i] == (byte)'\r'; + } + + // If no replacements needed, return original array + if (replacementCount == 0) return (input, input.Length, false); + + // Rent array from pool with space for additional \r characters + var requiredSize = input.Length + replacementCount; + var result = ArrayPool.Shared.Rent(requiredSize); + var writeIndex = 0; + previousWasCr = _previousArrayEndedInCr; + + for (var i = 0; i < input.Length; i++) + { + if (input[i] == (byte)'\n') + { + // Check if it's preceded by \r + var precededByCr = (i > 0 && input[i - 1] == (byte)'\r') || (i == 0 && previousWasCr); + if (!precededByCr) + { + result[writeIndex++] = (byte)'\r'; + } + } + + result[writeIndex++] = input[i]; + previousWasCr = input[i] == (byte)'\r'; + } + + return (result, writeIndex, true); + } +} \ No newline at end of file