From 7f0ac8293461b7a4f71f3ee0d4b8fdee64436288 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 17 May 2026 07:19:17 +0000 Subject: [PATCH 1/4] Initial plan From 14666ce36e7a677e4856d3a70c5c829c620802ae Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 17 May 2026 07:25:52 +0000 Subject: [PATCH 2/4] Fix tab reindent after ANSI shift tab Agent-Logs-Url: https://github.com/gui-cs/Editor/sessions/a2616e69-5dd7-4f0c-b59d-2ab6d8bdb8ab Co-authored-by: tig <585482+tig@users.noreply.github.com> --- .../AnsiInputProcessorState.cs | 29 +++++++++++++ src/Terminal.Gui.Editor/Editor.Indentation.cs | 15 ++++++- .../EditorTabTests.cs | 41 +++++++++++++++++++ 3 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 src/Terminal.Gui.Editor/AnsiInputProcessorState.cs diff --git a/src/Terminal.Gui.Editor/AnsiInputProcessorState.cs b/src/Terminal.Gui.Editor/AnsiInputProcessorState.cs new file mode 100644 index 0000000..ebbc336 --- /dev/null +++ b/src/Terminal.Gui.Editor/AnsiInputProcessorState.cs @@ -0,0 +1,29 @@ +using System.Reflection; +using Terminal.Gui.App; +using Terminal.Gui.Drivers; + +namespace Terminal.Gui.Editor; + +internal static class AnsiInputProcessorState +{ + private const string PendingPrintableSuppressionFieldName = "_pendingPrintableSuppression"; + + public static void ClearPendingPrintableSuppression (IApplication? app) + { + if (app?.Driver?.GetInputProcessor () is not AnsiInputProcessor processor) + { + return; + } + + FieldInfo? field = typeof (AnsiInputProcessor).GetField ( + PendingPrintableSuppressionFieldName, + BindingFlags.Instance | BindingFlags.NonPublic); + + if (field?.FieldType != typeof (string)) + { + return; + } + + field.SetValue (processor, string.Empty); + } +} diff --git a/src/Terminal.Gui.Editor/Editor.Indentation.cs b/src/Terminal.Gui.Editor/Editor.Indentation.cs index 926be3a..9c3d82f 100644 --- a/src/Terminal.Gui.Editor/Editor.Indentation.cs +++ b/src/Terminal.Gui.Editor/Editor.Indentation.cs @@ -52,12 +52,21 @@ private bool Unindent () if (ReadOnly) { + AnsiInputProcessorState.ClearPendingPrintableSuppression (App); + return true; } if (HasMultipleCarets) { - return MultiCaretUnindent (); + var handled = MultiCaretUnindent (); + + if (handled) + { + AnsiInputProcessorState.ClearPendingPrintableSuppression (App); + } + + return handled; } List lines = HasSelection && SelectionSpansMultipleLines () @@ -78,6 +87,8 @@ private bool Unindent () if (removals.Count == 0) { + AnsiInputProcessorState.ClearPendingPrintableSuppression (App); + return true; } @@ -102,6 +113,8 @@ private bool Unindent () AdjustOffsetAfterRemovals (selectionEnd, removals)); } + AnsiInputProcessorState.ClearPendingPrintableSuppression (App); + return true; } diff --git a/tests/Terminal.Gui.Editor.IntegrationTests/EditorTabTests.cs b/tests/Terminal.Gui.Editor.IntegrationTests/EditorTabTests.cs index abf1038..02f4393 100644 --- a/tests/Terminal.Gui.Editor.IntegrationTests/EditorTabTests.cs +++ b/tests/Terminal.Gui.Editor.IntegrationTests/EditorTabTests.cs @@ -1,8 +1,10 @@ // Codex - GPT-5 using Terminal.Gui.Editor.IntegrationTests.Testing; +using Terminal.Gui.Drivers; using Terminal.Gui.Input; using Terminal.Gui.Testing; +using Ted; using Xunit; namespace Terminal.Gui.Editor.IntegrationTests; @@ -144,4 +146,43 @@ public async Task Backspace_At_End_Of_Leading_Whitespace_Removes_One_Indentation Assert.Equal ("alpha", fx.Top.Editor.Document!.Text); Assert.Equal (0, fx.Top.Editor.CaretOffset); } + + [Fact] + public async Task Ted_RawAnsi_Tab_After_ShiftTab_Reindents_Line_On_First_Keypress () + { + await using AppFixture fx = new (() => new TedApp ()); + fx.Top.Editor.SetFocus (); + fx.Top.Editor.Document!.Text = "hello world"; + fx.Top.Editor.CaretOffset = 0; + + InjectAnsi (fx, "\t"); + + Assert.Equal (" hello world", fx.Top.Editor.Document.Text); + Assert.Equal (4, fx.Top.Editor.CaretOffset); + + InjectAnsi (fx, "\u001b[Z"); + + Assert.Equal ("hello world", fx.Top.Editor.Document.Text); + Assert.Equal (0, fx.Top.Editor.CaretOffset); + + InjectAnsi (fx, "\t"); + + Assert.Equal (" hello world", fx.Top.Editor.Document.Text); + Assert.Equal (4, fx.Top.Editor.CaretOffset); + } + + private static void InjectAnsi (AppFixture fx, string sequence) + { + if (fx.App.Driver!.GetInputProcessor () is not InputProcessorImpl processor) + { + throw new InvalidOperationException ("ANSI input processor is required for raw ANSI input tests."); + } + + foreach (var ch in sequence) + { + processor.InputQueue.Enqueue (ch); + } + + processor.ProcessQueue (); + } } From 9c781cb5815590cf9fd0060a147768b84bb2b2a2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 17 May 2026 07:35:12 +0000 Subject: [PATCH 3/4] Address tab fix review feedback Agent-Logs-Url: https://github.com/gui-cs/Editor/sessions/a2616e69-5dd7-4f0c-b59d-2ab6d8bdb8ab Co-authored-by: tig <585482+tig@users.noreply.github.com> --- src/Terminal.Gui.Editor/AnsiInputProcessorState.cs | 3 +++ tests/Terminal.Gui.Editor.IntegrationTests/EditorTabTests.cs | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Terminal.Gui.Editor/AnsiInputProcessorState.cs b/src/Terminal.Gui.Editor/AnsiInputProcessorState.cs index ebbc336..cb3598d 100644 --- a/src/Terminal.Gui.Editor/AnsiInputProcessorState.cs +++ b/src/Terminal.Gui.Editor/AnsiInputProcessorState.cs @@ -15,6 +15,9 @@ public static void ClearPendingPrintableSuppression (IApplication? app) return; } + // Terminal.Gui 2.1.1-develop.98 suppresses the next printable fallback key after parsing + // ANSI Shift+Tab (ESC [ Z) because Shift+Tab reports Tab as printable text. Clear that + // one-shot suppression after the editor handles Unindent so the user's next Tab reaches us. FieldInfo? field = typeof (AnsiInputProcessor).GetField ( PendingPrintableSuppressionFieldName, BindingFlags.Instance | BindingFlags.NonPublic); diff --git a/tests/Terminal.Gui.Editor.IntegrationTests/EditorTabTests.cs b/tests/Terminal.Gui.Editor.IntegrationTests/EditorTabTests.cs index 02f4393..1d6dc62 100644 --- a/tests/Terminal.Gui.Editor.IntegrationTests/EditorTabTests.cs +++ b/tests/Terminal.Gui.Editor.IntegrationTests/EditorTabTests.cs @@ -173,7 +173,7 @@ public async Task Ted_RawAnsi_Tab_After_ShiftTab_Reindents_Line_On_First_Keypres private static void InjectAnsi (AppFixture fx, string sequence) { - if (fx.App.Driver!.GetInputProcessor () is not InputProcessorImpl processor) + if (fx.App.Driver!.GetInputProcessor () is not AnsiInputProcessor processor) { throw new InvalidOperationException ("ANSI input processor is required for raw ANSI input tests."); } From 3d726c3f37475eeec6b5c60954f12277dcf95d24 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 17 May 2026 07:37:32 +0000 Subject: [PATCH 4/4] Document ANSI tab workaround fragility Agent-Logs-Url: https://github.com/gui-cs/Editor/sessions/a2616e69-5dd7-4f0c-b59d-2ab6d8bdb8ab Co-authored-by: tig <585482+tig@users.noreply.github.com> --- src/Terminal.Gui.Editor/AnsiInputProcessorState.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Terminal.Gui.Editor/AnsiInputProcessorState.cs b/src/Terminal.Gui.Editor/AnsiInputProcessorState.cs index cb3598d..5b74953 100644 --- a/src/Terminal.Gui.Editor/AnsiInputProcessorState.cs +++ b/src/Terminal.Gui.Editor/AnsiInputProcessorState.cs @@ -16,8 +16,10 @@ public static void ClearPendingPrintableSuppression (IApplication? app) } // Terminal.Gui 2.1.1-develop.98 suppresses the next printable fallback key after parsing - // ANSI Shift+Tab (ESC [ Z) because Shift+Tab reports Tab as printable text. Clear that - // one-shot suppression after the editor handles Unindent so the user's next Tab reaches us. + // ANSI Shift+Tab (ESC [ Z) because Shift+Tab reports Tab as printable text. Until TG exposes + // public input-processor state for this, clear that one-shot suppression after the editor + // handles Unindent so the user's next Tab reaches us. If TG renames this private field, the + // type checks below intentionally no-op; the only consequence is the original Tab suppression. FieldInfo? field = typeof (AnsiInputProcessor).GetField ( PendingPrintableSuppressionFieldName, BindingFlags.Instance | BindingFlags.NonPublic);