diff --git a/.jules/refract.md b/.jules/refract.md index b36a116..30123eb 100644 --- a/.jules/refract.md +++ b/.jules/refract.md @@ -51,3 +51,7 @@ ## 2025-02-19 - C# 13 System.Threading.Lock Modernization **Observation:** The codebase contained legacy lock statements utilizing an arbitrary object (`object _gate`) for synchronization in `HttpImageResolver.cs`, an obsolete pattern that lacks explicit thread safety semantics compared to modern alternatives. **Strategic Action:** Transitioned the synchronization mechanism to use the C# 13 `System.Threading.Lock` type (`System.Threading.Lock _gate = new();`) to enforce deterministic thread safety and structural clarity. + +## 2025-05-18 - [Escape Sequence Modernization] +**Observation:** Ambiguous `\u001B` and `\x1b` escape sequences used for ANSI escape codes. +**Strategic Action:** Replaced with the C# 13 `\e` escape sequence for precise character representation. diff --git a/src/Tedd.TUI.Platform.Console/ConsoleInputManager.cs b/src/Tedd.TUI.Platform.Console/ConsoleInputManager.cs index de09223..dc71301 100644 --- a/src/Tedd.TUI.Platform.Console/ConsoleInputManager.cs +++ b/src/Tedd.TUI.Platform.Console/ConsoleInputManager.cs @@ -33,7 +33,7 @@ public ConsoleInputManager(TuiWindow window) // CSI ? 1000 h (Normal tracking) // CSI ? 1003 h (All motion tracking) // CSI ? 1006 h (SGR ext mode) - System.Console.Write("\x1b[?1000h\x1b[?1006h"); + System.Console.Write("\e[?1000h\e[?1006h"); } public IntPtr InputHandle { get; private set; } diff --git a/src/Tedd.TUI.Platform.Console/TuiApp.cs b/src/Tedd.TUI.Platform.Console/TuiApp.cs index c688a93..4b5852d 100644 --- a/src/Tedd.TUI.Platform.Console/TuiApp.cs +++ b/src/Tedd.TUI.Platform.Console/TuiApp.cs @@ -181,7 +181,7 @@ public void Stop() try { _inputManager?.Stop(); } catch { } try { _platform.Shutdown(); } catch { } - System.Console.Write("\x1b[?1000l\x1b[?1006l"); + System.Console.Write("\e[?1000l\e[?1006l"); System.Console.CursorVisible = true; System.Console.ResetColor(); System.Console.Clear(); diff --git a/src/Tedd.TUI.Platform.LinuxTerminal/ITerm2InlineEncoder.cs b/src/Tedd.TUI.Platform.LinuxTerminal/ITerm2InlineEncoder.cs index 626d324..fd8c391 100644 --- a/src/Tedd.TUI.Platform.LinuxTerminal/ITerm2InlineEncoder.cs +++ b/src/Tedd.TUI.Platform.LinuxTerminal/ITerm2InlineEncoder.cs @@ -19,7 +19,7 @@ public string Encode(GraphicPlacement placement) return string.Empty; var sb = new StringBuilder(placement.ImageData.Length * 4 / 3 + 128); - sb.Append("\x1b]1337;File=inline=1"); + sb.Append("\e]1337;File=inline=1"); if (placement.CharWidth > 0) { diff --git a/src/Tedd.TUI.Platform.LinuxTerminal/KittyGraphicsEncoder.cs b/src/Tedd.TUI.Platform.LinuxTerminal/KittyGraphicsEncoder.cs index 31a68a9..45c393f 100644 --- a/src/Tedd.TUI.Platform.LinuxTerminal/KittyGraphicsEncoder.cs +++ b/src/Tedd.TUI.Platform.LinuxTerminal/KittyGraphicsEncoder.cs @@ -33,7 +33,7 @@ public string Encode(GraphicPlacement placement) bool first = offset == 0; bool last = offset + len >= base64.Length; - sb.Append("\x1b_G"); + sb.Append("\e_G"); if (first) { sb.Append("a=T,f=100,"); @@ -43,7 +43,7 @@ public string Encode(GraphicPlacement placement) sb.Append("m=").Append(last ? '0' : '1'); sb.Append(';'); sb.Append(base64, offset, len); - sb.Append("\x1b\\"); + sb.Append("\e\\"); } return sb.ToString(); diff --git a/src/Tedd.TUI.Platform.LinuxTerminal/LinuxTerminalPlatform.cs b/src/Tedd.TUI.Platform.LinuxTerminal/LinuxTerminalPlatform.cs index f3b412d..de7d10c 100644 --- a/src/Tedd.TUI.Platform.LinuxTerminal/LinuxTerminalPlatform.cs +++ b/src/Tedd.TUI.Platform.LinuxTerminal/LinuxTerminalPlatform.cs @@ -101,7 +101,7 @@ public void Initialize() try { // Switch to the alt screen, enable SGR mouse mode, hide the cursor. - System.Console.Write("\x1b[?1049h\x1b[?1000h\x1b[?1006h\x1b[?25l"); + System.Console.Write("\e[?1049h\e[?1000h\e[?1006h\e[?25l"); _altScreenEntered = true; } catch { } @@ -111,7 +111,7 @@ public void Shutdown() { if (_altScreenEntered) { - try { System.Console.Write("\x1b[?25h\x1b[?1006l\x1b[?1000l\x1b[?1049l"); } catch { } + try { System.Console.Write("\e[?25h\e[?1006l\e[?1000l\e[?1049l"); } catch { } _altScreenEntered = false; } diff --git a/src/Tedd.TUI.Tests/AnsiTrueColorRendererTests.cs b/src/Tedd.TUI.Tests/AnsiTrueColorRendererTests.cs index f74af07..b1fe2e8 100644 --- a/src/Tedd.TUI.Tests/AnsiTrueColorRendererTests.cs +++ b/src/Tedd.TUI.Tests/AnsiTrueColorRendererTests.cs @@ -20,8 +20,8 @@ public void Render_EmitsTruecolorSgrSequence() renderer.Render(buffer); string output = sw.ToString(); - Assert.Contains("\x1b[38;2;10;20;30m", output); - Assert.Contains("\x1b[48;2;40;50;60m", output); + Assert.Contains("\e[38;2;10;20;30m", output); + Assert.Contains("\e[48;2;40;50;60m", output); Assert.Contains("ABC", output); } @@ -69,7 +69,7 @@ public void Render_InvokesImageEncoderForPlacements() renderer.Render(buffer); Assert.Single(recorded); - Assert.Contains("\x1b[1;2H", sw.ToString()); // cursor at row 1, col 2 (1-based) + Assert.Contains("\e[1;2H", sw.ToString()); // cursor at row 1, col 2 (1-based) Assert.Contains("FAKE", sw.ToString()); } diff --git a/src/Tedd.TUI.Tests/ImageProtocolEncoderTests.cs b/src/Tedd.TUI.Tests/ImageProtocolEncoderTests.cs index 9f2fc56..da98df6 100644 --- a/src/Tedd.TUI.Tests/ImageProtocolEncoderTests.cs +++ b/src/Tedd.TUI.Tests/ImageProtocolEncoderTests.cs @@ -16,8 +16,8 @@ public void SixelEncoder_WrapsPayloadInDecPm() var encoder = new SixelEncoder(); var encoded = encoder.Encode(new GraphicPlacement { CharWidth = 3, CharHeight = 2 }); - Assert.StartsWith("\x1bP", encoded); - Assert.EndsWith("\x1b\\", encoded); + Assert.StartsWith("\eP", encoded); + Assert.EndsWith("\e\\", encoded); Assert.Contains("q", encoded); Assert.Equal("sixel", encoder.Protocol); } @@ -43,8 +43,8 @@ public void SixelEncoder_EncodesRgbaPixels() CharHeight = 1, }); - Assert.StartsWith("\x1bP0;1;0q", encoded); - Assert.EndsWith("\x1b\\", encoded); + Assert.StartsWith("\eP0;1;0q", encoded); + Assert.EndsWith("\e\\", encoded); // Each used color must declare itself: "#;2;;;". Assert.Matches(@"#\d+;2;100;0;0", encoded); // red Assert.Matches(@"#\d+;2;0;100;0", encoded); // green @@ -78,8 +78,8 @@ public void KittyEncoder_EmitsChunkedBase64() CharHeight = 2, }); - Assert.StartsWith("\x1b_G", encoded); - Assert.EndsWith("\x1b\\", encoded); + Assert.StartsWith("\e_G", encoded); + Assert.EndsWith("\e\\", encoded); Assert.Contains("a=T,f=100", encoded); Assert.Contains("c=4", encoded); Assert.Contains("r=2", encoded); @@ -106,7 +106,7 @@ public void ITerm2Encoder_EmitsInlineEscape() MediaType = "image/png", }); - Assert.StartsWith("\x1b]1337;File=inline=1", encoded); + Assert.StartsWith("\e]1337;File=inline=1", encoded); Assert.EndsWith("\x07", encoded); Assert.Contains("width=10", encoded); Assert.Contains("height=5", encoded); diff --git a/src/Tedd.TUI/AnsiTrueColorRenderer.cs b/src/Tedd.TUI/AnsiTrueColorRenderer.cs index 3f2cddc..6054e58 100644 --- a/src/Tedd.TUI/AnsiTrueColorRenderer.cs +++ b/src/Tedd.TUI/AnsiTrueColorRenderer.cs @@ -147,7 +147,7 @@ private void EmitGraphics(VirtualBuffer buffer) // Park the cursor at the placement's top-left cell so terminals that draw // images at the current cursor position (Sixel, iTerm2 inline, Kitty default) // land in the right spot. - sb.Append('\x1b').Append('[').Append(placement.CharY + 1).Append(';').Append(placement.CharX + 1).Append('H'); + sb.Append('\e').Append('[').Append(placement.CharY + 1).Append(';').Append(placement.CharX + 1).Append('H'); sb.Append(encoder.Encode(placement)); } @@ -199,7 +199,7 @@ private void AppendChar(char c) private void EmitCursor(int x, int y) { // ANSI cursor positioning is 1-based. - _buffer.Append('\x1b').Append('[').Append(y + 1).Append(';').Append(x + 1).Append('H'); + _buffer.Append('\e').Append('[').Append(y + 1).Append(';').Append(x + 1).Append('H'); _cursorX = x; _cursorY = y; } @@ -208,12 +208,12 @@ private void EmitColors(TuiColor fg, TuiColor bg) { if (fg.Packed != _currentFg.Packed) { - _buffer.Append("\x1b[38;2;").Append(fg.R).Append(';').Append(fg.G).Append(';').Append(fg.B).Append('m'); + _buffer.Append("\e[38;2;").Append(fg.R).Append(';').Append(fg.G).Append(';').Append(fg.B).Append('m'); _currentFg = fg; } if (bg.Packed != _currentBg.Packed) { - _buffer.Append("\x1b[48;2;").Append(bg.R).Append(';').Append(bg.G).Append(';').Append(bg.B).Append('m'); + _buffer.Append("\e[48;2;").Append(bg.R).Append(';').Append(bg.G).Append(';').Append(bg.B).Append('m'); _currentBg = bg; } } diff --git a/src/Tedd.TUI/SixelEncoderCore.cs b/src/Tedd.TUI/SixelEncoderCore.cs index f1eb485..8157ad1 100644 --- a/src/Tedd.TUI/SixelEncoderCore.cs +++ b/src/Tedd.TUI/SixelEncoderCore.cs @@ -73,7 +73,7 @@ public static string EncodePixels(byte[] pixels, int width, int height) } var sb = new StringBuilder(width * height / 6 + 256); - sb.Append("\x1bP0;1;0q"); // P2=1 → background pixels remain transparent. + sb.Append("\eP0;1;0q"); // P2=1 → background pixels remain transparent. // Raster attributes: pan=1, pad=1 (square pixels), actual w / h. sb.Append('"').Append(1).Append(';').Append(1).Append(';').Append(width).Append(';').Append(height); @@ -149,7 +149,7 @@ public static string EncodePixels(byte[] pixels, int width, int height) } } - sb.Append("\x1b\\"); + sb.Append("\e\\"); return sb.ToString(); } @@ -244,12 +244,12 @@ private static (byte r, byte g, byte b) PaletteRgb(int index) private static string EncodePlaceholder(int pxW, int pxH) { var sb = new StringBuilder(64 + pxW); - sb.Append("\x1bP0;0;0q"); + sb.Append("\eP0;0;0q"); sb.Append('"').Append(1).Append(';').Append(1).Append(';').Append(pxW).Append(';').Append(pxH); sb.Append("#0;2;0;0;0"); sb.Append('#').Append(0); for (int i = 0; i < pxW; i++) sb.Append('?'); - sb.Append("\x1b\\"); + sb.Append("\e\\"); return sb.ToString(); } }