diff --git a/lib/parse/ansi.go b/lib/parse/ansi.go index 12ea4c0..19efbcf 100644 --- a/lib/parse/ansi.go +++ b/lib/parse/ansi.go @@ -11,7 +11,11 @@ import ( "git.sr.ht/~rjarry/aerc/lib/log" ) -var AnsiReg = regexp.MustCompile("\x1B\\[[0-?]*[ -/]*[@-~]") +var AnsiReg = regexp.MustCompile( + `\x1B\[[0-?]*[ -/]*[@-~]` + // CSI sequences: ESC [ ... final_byte + `|` + + `\x1B\][^\x07\x1B]*(?:\x07|\x1B\\)`, // OSC sequences: ESC ] ... (BEL | ESC \) +) // StripAnsi strips ansi escape codes from the reader func StripAnsi(r io.Reader) io.Reader { diff --git a/lib/parse/ansi_test.go b/lib/parse/ansi_test.go new file mode 100644 index 0000000..830dd5f --- /dev/null +++ b/lib/parse/ansi_test.go @@ -0,0 +1,68 @@ +package parse + +import ( + "io" + "strings" + "testing" +) + +func TestStripAnsi_CSI(t *testing.T) { + input := "Hello \033[31mred\033[0m world\n" + r := StripAnsi(strings.NewReader(input)) + out, _ := io.ReadAll(r) + if string(out) != "Hello red world\n" { + t.Errorf("got %q", string(out)) + } +} + +func TestStripAnsi_OSC9ImageMarker(t *testing.T) { + input := "Before\n\033]9;image:path=/tmp/img.png;alt=photo\007\nAfter\n" + r := StripAnsi(strings.NewReader(input)) + out, _ := io.ReadAll(r) + expected := "Before\n\nAfter\n" + if string(out) != expected { + t.Errorf("got %q, want %q", string(out), expected) + } +} + +func TestStripAnsi_OSCWithEscBackslash(t *testing.T) { + input := "Text \033]9;image:path=/tmp/x.png\033\\ more\n" + r := StripAnsi(strings.NewReader(input)) + out, _ := io.ReadAll(r) + if strings.Contains(string(out), "\033") { + t.Errorf("OSC not stripped: %q", string(out)) + } +} + +func TestStripAnsi_OSC8Hyperlink(t *testing.T) { + input := "\033]8;;https://example.com\033\\click\033]8;;\033\\\n" + r := StripAnsi(strings.NewReader(input)) + out, _ := io.ReadAll(r) + if strings.Contains(string(out), "\033") { + t.Errorf("OSC 8 not stripped: %q", string(out)) + } + if !strings.Contains(string(out), "click") { + t.Error("visible text lost") + } +} + +func TestStripAnsi_PlainText(t *testing.T) { + input := "No escape sequences here\n" + r := StripAnsi(strings.NewReader(input)) + out, _ := io.ReadAll(r) + if string(out) != input { + t.Errorf("got %q", string(out)) + } +} + +func TestStripAnsi_MixedCSIAndOSC(t *testing.T) { + input := "\033[1mBold\033[0m \033]9;image:path=/tmp/a.png\007 text\n" + r := StripAnsi(strings.NewReader(input)) + out, _ := io.ReadAll(r) + if strings.Contains(string(out), "\033") { + t.Errorf("escape sequences remain: %q", string(out)) + } + if !strings.Contains(string(out), "Bold") || !strings.Contains(string(out), "text") { + t.Error("visible text lost") + } +}