From 871d2afb904f84443007556a574cec6aaf96929f Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Wed, 6 Mar 2024 14:39:30 -0500 Subject: [PATCH] refactor(term): move ansi subpackages to ansi --- exp/term/ansi/{sys => }/background.go | 16 +- exp/term/ansi/{sys => }/background_test.go | 13 +- exp/term/ansi/{sys => }/clipboard.go | 6 +- exp/term/ansi/{sys => }/clipboard_test.go | 12 +- exp/term/ansi/{style => }/color.go | 2 +- exp/term/ansi/{style => }/color_test.go | 6 +- exp/term/ansi/{ctrl => }/ctrl.go | 6 +- exp/term/ansi/cursor.go | 159 ++++++++++++ exp/term/ansi/cursor/cursor.go | 130 ---------- exp/term/ansi/doc.go | 2 +- exp/term/ansi/{sys => }/hyperlink.go | 4 +- exp/term/ansi/{sys => }/hyperlink_test.go | 10 +- exp/term/ansi/kitty.go | 58 +++++ exp/term/ansi/kitty/kitty.go | 51 ---- exp/term/ansi/{mode/dec.go => mode.go} | 79 +++--- exp/term/ansi/mode/ansi.go | 1 - exp/term/ansi/mode/doc.go | 8 - exp/term/ansi/{screen => }/screen.go | 34 ++- exp/term/ansi/style.go | 280 +++++++++++++++++++++ exp/term/ansi/style/string.go | 9 - exp/term/ansi/style/style.go | 137 ---------- exp/term/ansi/style/style_test.go | 35 --- exp/term/ansi/style_test.go | 38 +++ exp/term/ansi/sys/title_test.go | 25 -- exp/term/ansi/{sys => }/title.go | 8 +- exp/term/ansi/title_test.go | 25 ++ exp/term/ansi/{internal => }/util.go | 13 +- 27 files changed, 683 insertions(+), 484 deletions(-) rename exp/term/ansi/{sys => }/background.go (77%) rename exp/term/ansi/{sys => }/background_test.go (64%) rename exp/term/ansi/{sys => }/clipboard.go (88%) rename exp/term/ansi/{sys => }/clipboard_test.go (72%) rename exp/term/ansi/{style => }/color.go (99%) rename exp/term/ansi/{style => }/color_test.go (93%) rename exp/term/ansi/{ctrl => }/ctrl.go (80%) create mode 100644 exp/term/ansi/cursor.go delete mode 100644 exp/term/ansi/cursor/cursor.go rename exp/term/ansi/{sys => }/hyperlink.go (91%) rename exp/term/ansi/{sys => }/hyperlink_test.go (67%) create mode 100644 exp/term/ansi/kitty.go delete mode 100644 exp/term/ansi/kitty/kitty.go rename exp/term/ansi/{mode/dec.go => mode.go} (53%) delete mode 100644 exp/term/ansi/mode/ansi.go delete mode 100644 exp/term/ansi/mode/doc.go rename exp/term/ansi/{screen => }/screen.go (77%) create mode 100644 exp/term/ansi/style.go delete mode 100644 exp/term/ansi/style/string.go delete mode 100644 exp/term/ansi/style/style.go delete mode 100644 exp/term/ansi/style/style_test.go create mode 100644 exp/term/ansi/style_test.go delete mode 100644 exp/term/ansi/sys/title_test.go rename exp/term/ansi/{sys => }/title.go (85%) create mode 100644 exp/term/ansi/title_test.go rename exp/term/ansi/{internal => }/util.go (56%) diff --git a/exp/term/ansi/sys/background.go b/exp/term/ansi/background.go similarity index 77% rename from exp/term/ansi/sys/background.go rename to exp/term/ansi/background.go index 7c3b794c..f519af08 100644 --- a/exp/term/ansi/sys/background.go +++ b/exp/term/ansi/background.go @@ -1,9 +1,7 @@ -package sys +package ansi import ( "image/color" - - "github.com/charmbracelet/x/exp/term/ansi/internal" ) // SetForegroundColor returns a sequence that sets the default terminal @@ -16,14 +14,14 @@ import ( // // See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Operating-System-Commands func SetForegroundColor(c color.Color) string { - return "\x1b" + "]" + "10;" + internal.ColorToHexString(c) + "\x07" + return "\x1b]10;" + colorToHexString(c) + "\x07" } // RequestForegroundColor is a sequence that requests the current default // terminal foreground color. // // See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Operating-System-Commands -const RequestForegroundColor = "\x1b" + "]" + "10;" + "?" + "\x07" +const RequestForegroundColor = "\x1b]10;?\x07" // SetBackgroundColor returns a sequence that sets the default terminal // background color. @@ -35,14 +33,14 @@ const RequestForegroundColor = "\x1b" + "]" + "10;" + "?" + "\x07" // // See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Operating-System-Commands func SetBackgroundColor(c color.Color) string { - return "\x1b" + "]" + "11;" + internal.ColorToHexString(c) + "\x07" + return "\x1b]11;" + colorToHexString(c) + "\x07" } // RequestBackgroundColor is a sequence that requests the current default // terminal background color. // // See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Operating-System-Commands -const RequestBackgroundColor = "\x1b" + "]" + "11;" + "?" + "\x07" +const RequestBackgroundColor = "\x1b]11;?\x07" // SetCursorColor returns a sequence that sets the terminal cursor color. // @@ -53,11 +51,11 @@ const RequestBackgroundColor = "\x1b" + "]" + "11;" + "?" + "\x07" // // See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Operating-System-Commands func SetCursorColor(c color.Color) string { - return "\x1b" + "]" + "12;" + internal.ColorToHexString(c) + "\x07" + return "\x1b]12;" + colorToHexString(c) + "\x07" } // RequestCursorColor is a sequence that requests the current terminal cursor // color. // // See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Operating-System-Commands -const RequestCursorColor = "\x1b" + "]" + "12;" + "?" + "\x07" +const RequestCursorColor = "\x1b]12;?\x07" diff --git a/exp/term/ansi/sys/background_test.go b/exp/term/ansi/background_test.go similarity index 64% rename from exp/term/ansi/sys/background_test.go rename to exp/term/ansi/background_test.go index 3bf69a54..76870bd2 100644 --- a/exp/term/ansi/sys/background_test.go +++ b/exp/term/ansi/background_test.go @@ -1,23 +1,22 @@ -package sys_test +package ansi_test import ( "testing" - "github.com/charmbracelet/x/exp/term/ansi/style" - "github.com/charmbracelet/x/exp/term/ansi/sys" + "github.com/charmbracelet/x/exp/term/ansi" ) func TestSetForegroundColorNil(t *testing.T) { - s := sys.SetForegroundColor(nil) + s := ansi.SetForegroundColor(nil) if s != "\x1b]10;\x07" { t.Errorf("Unexpected string for SetForegroundColor: got %q", s) } } func TestStringImplementations(t *testing.T) { - foregroundColor := sys.SetForegroundColor(style.BrightMagenta) - backgroundColor := sys.SetBackgroundColor(style.ExtendedColor(255)) - cursorColor := sys.SetCursorColor(style.TrueColor(0xffeeaa)) + foregroundColor := ansi.SetForegroundColor(ansi.BrightMagenta) + backgroundColor := ansi.SetBackgroundColor(ansi.ExtendedColor(255)) + cursorColor := ansi.SetCursorColor(ansi.TrueColor(0xffeeaa)) if foregroundColor != "\x1b]10;#ff00ff\x07" { t.Errorf("Unexpected string for SetForegroundColor: got %q", diff --git a/exp/term/ansi/sys/clipboard.go b/exp/term/ansi/clipboard.go similarity index 88% rename from exp/term/ansi/sys/clipboard.go rename to exp/term/ansi/clipboard.go index 465d2ffa..d1f02a0e 100644 --- a/exp/term/ansi/sys/clipboard.go +++ b/exp/term/ansi/clipboard.go @@ -1,4 +1,4 @@ -package sys +package ansi import "encoding/base64" @@ -21,7 +21,7 @@ func SetClipboard(c byte, d string) string { if d != "" { d = base64.StdEncoding.EncodeToString([]byte(d)) } - return "\x1b" + "]" + "52;" + string(c) + ";" + d + "\x07" + return "\x1b]52;" + string(c) + ";" + d + "\x07" } // ResetClipboard returns a sequence for resetting the clipboard. @@ -37,5 +37,5 @@ func ResetClipboard(c byte) string { // // See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Operating-System-Commands func RequestClipboard(c byte) string { - return "\x1b" + "]" + "52;" + string(c) + ";" + "?" + "\x07" + return "\x1b]52;" + string(c) + ";?\x07" } diff --git a/exp/term/ansi/sys/clipboard_test.go b/exp/term/ansi/clipboard_test.go similarity index 72% rename from exp/term/ansi/sys/clipboard_test.go rename to exp/term/ansi/clipboard_test.go index 912a5c95..531d3206 100644 --- a/exp/term/ansi/sys/clipboard_test.go +++ b/exp/term/ansi/clipboard_test.go @@ -1,9 +1,9 @@ -package sys_test +package ansi_test import ( "testing" - "github.com/charmbracelet/x/exp/term/ansi/sys" + "github.com/charmbracelet/x/exp/term/ansi" ) func TestClipboardNewClipboard(t *testing.T) { @@ -16,10 +16,10 @@ func TestClipboardNewClipboard(t *testing.T) { {'p', "Ansi Test", "\x1b]52;p;QW5zaSBUZXN0\x07"}, {'c', "", "\x1b]52;c;\x07"}, {'p', "?", "\x1b]52;p;Pw==\x07"}, - {sys.SystemClipboard, "test", "\x1b]52;c;dGVzdA==\x07"}, + {ansi.SystemClipboard, "test", "\x1b]52;c;dGVzdA==\x07"}, } for _, tp := range tt { - cb := sys.SetClipboard(tp.name, tp.data) + cb := ansi.SetClipboard(tp.name, tp.data) if cb != tp.expect { t.Errorf("SetClipboard(%q, %q) = %q, want %q", tp.name, tp.data, cb, tp.expect) } @@ -27,14 +27,14 @@ func TestClipboardNewClipboard(t *testing.T) { } func TestClipboardReset(t *testing.T) { - cb := sys.ResetClipboard(sys.PrimaryClipboard) + cb := ansi.ResetClipboard(ansi.PrimaryClipboard) if cb != "\x1b]52;p;\x07" { t.Errorf("Unexpected clipboard reset: %q", cb) } } func TestClipboardRequest(t *testing.T) { - cb := sys.RequestClipboard(sys.PrimaryClipboard) + cb := ansi.RequestClipboard(ansi.PrimaryClipboard) if cb != "\x1b]52;p;?\x07" { t.Errorf("Unexpected clipboard request: %q", cb) } diff --git a/exp/term/ansi/style/color.go b/exp/term/ansi/color.go similarity index 99% rename from exp/term/ansi/style/color.go rename to exp/term/ansi/color.go index eb80f4d0..4cdb35ae 100644 --- a/exp/term/ansi/style/color.go +++ b/exp/term/ansi/color.go @@ -1,4 +1,4 @@ -package style +package ansi import ( "image/color" diff --git a/exp/term/ansi/style/color_test.go b/exp/term/ansi/color_test.go similarity index 93% rename from exp/term/ansi/style/color_test.go rename to exp/term/ansi/color_test.go index 985bcca6..3db5eaf5 100644 --- a/exp/term/ansi/style/color_test.go +++ b/exp/term/ansi/color_test.go @@ -1,10 +1,8 @@ -package style +package ansi import ( "image/color" "testing" - - "github.com/charmbracelet/x/exp/term/ansi/internal" ) func TestRGBAToHex(t *testing.T) { @@ -37,7 +35,7 @@ func TestColorToHexString(t *testing.T) { } for _, c := range cases { - got := internal.ColorToHexString(c.color) + got := colorToHexString(c.color) if got != c.want { t.Errorf("colorToHexString(%v): got %v, want %v", c.color, got, c.want) } diff --git a/exp/term/ansi/ctrl/ctrl.go b/exp/term/ansi/ctrl.go similarity index 80% rename from exp/term/ansi/ctrl/ctrl.go rename to exp/term/ansi/ctrl.go index 12704290..21beb9cd 100644 --- a/exp/term/ansi/ctrl/ctrl.go +++ b/exp/term/ansi/ctrl.go @@ -1,4 +1,4 @@ -package ctrl +package ansi // RequestXTVersion is a control sequence that requests the terminal's XTVERSION. It responds with a DSR sequence identifying the version. // @@ -6,7 +6,7 @@ package ctrl // DCS > | text ST // // See https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-PC-Style-Function-Keys -const RequestXTVersion = "\x1b[" + ">0q" +const RequestXTVersion = "\x1b[>0q" // RequestPrimaryDeviceAttributes is a control sequence that requests the // terminal's primary device attributes (DA1). @@ -14,4 +14,4 @@ const RequestXTVersion = "\x1b[" + ">0q" // CSI c // // See https://vt100.net/docs/vt510-rm/DA1.html -const RequestPrimaryDeviceAttributes = "\x1b[" + "c" +const RequestPrimaryDeviceAttributes = "\x1b[c" diff --git a/exp/term/ansi/cursor.go b/exp/term/ansi/cursor.go new file mode 100644 index 00000000..9f1bab5f --- /dev/null +++ b/exp/term/ansi/cursor.go @@ -0,0 +1,159 @@ +package ansi + +import "strconv" + +// SaveCursor (DECSC) is an escape sequence that saves the current cursor +// position. +// +// See: https://vt100.net/docs/vt510-rm/DECSC.html +const SaveCursor = "\x1b7" + +// RestoreCursor (DECRC) is an escape sequence that restores the cursor +// position. +// +// See: https://vt100.net/docs/vt510-rm/DECRC.html +const RestoreCursor = "\x1b8" + +// CursorUp (CUU) returns a sequence for moving the cursor up n cells. +// +// CSI n A +// +// See: https://vt100.net/docs/vt510-rm/CUU.html +func CursorUp(n int) string { + var s string + if n > 1 { + s = strconv.Itoa(n) + } + return "\x1b[" + s + "A" +} + +// CursorUp1 is a sequence for moving the cursor up one cell. +// +// This is equivalent to CursorUp(1). +const CursorUp1 = "\x1b[A" + +// CursorDown (CUD) returns a sequence for moving the cursor down n cells. +// +// CSI n B +// +// See: https://vt100.net/docs/vt510-rm/CUD.html +func CursorDown(n int) string { + var s string + if n > 1 { + s = strconv.Itoa(n) + } + return "\x1b[" + s + "B" +} + +// CursorDown1 is a sequence for moving the cursor down one cell. +// +// This is equivalent to CursorDown(1). +const CursorDown1 = "\x1b[B" + +// CursorRight (CUF) returns a sequence for moving the cursor right n cells. +// +// CSI n C +// +// See: https://vt100.net/docs/vt510-rm/CUF.html +func CursorRight(n int) string { + var s string + if n > 1 { + s = strconv.Itoa(n) + } + return "\x1b[" + s + "C" +} + +// CursorRight1 is a sequence for moving the cursor right one cell. +// +// This is equivalent to CursorRight(1). +const CursorRight1 = "\x1b[C" + +// CursorLeft (CUB) returns a sequence for moving the cursor left n cells. +// +// CSI n D +// +// See: https://vt100.net/docs/vt510-rm/CUB.html +func CursorLeft(n int) string { + var s string + if n > 1 { + s = strconv.Itoa(n) + } + return "\x1b[" + s + "D" +} + +// CursorLeft1 is a sequence for moving the cursor left one cell. +// +// This is equivalent to CursorLeft(1). +const CursorLeft1 = "\x1b[D" + +// CursorNextLine (CNL) returns a sequence for moving the cursor to the +// beginning of the next line n times. +// +// CSI n E +// +// See: https://vt100.net/docs/vt510-rm/CNL.html +func CursorNextLine(n int) string { + var s string + if n > 1 { + s = strconv.Itoa(n) + } + return "\x1b[" + s + "E" +} + +// CursorPreviousLine (CPL) returns a sequence for moving the cursor to the +// beginning of the previous line n times. +// +// CSI n F +// +// See: https://vt100.net/docs/vt510-rm/CPL.html +func CursorPreviousLine(n int) string { + var s string + if n > 1 { + s = strconv.Itoa(n) + } + return "\x1b[" + s + "F" +} + +// MoveCursor (CUP) returns a sequence for moving the cursor to the given row +// and column. +// +// CSI n ; m H +// +// See: https://vt100.net/docs/vt510-rm/CUP.html +func MoveCursor(row, col int) string { + var r, c string + if row > 1 { + r = strconv.Itoa(row) + } + if col > 1 { + c = strconv.Itoa(col) + } + return "\x1b[" + r + ";" + c + "H" +} + +// MoveCursorZero is a sequence for moving the cursor to the upper left corner +// of the screen. +// This is equivalent to MoveCursor(1, 1). +const MoveCursorZero = "\x1b[1;1H" + +// SaveCursorPosition (SCP or SCOSC) is a sequence for saving the cursor +// position. +// +// CSI s +// +// This acts like Save, except the page number where the cursor is located is +// not saved. +// +// See: https://vt100.net/docs/vt510-rm/SCOSC.html +const SaveCursorPosition = "\x1b[s" + +// RestoreCursorPosition (RCP or SCORC) is a sequence for restoring the cursor +// position. +// +// CSI u +// +// This acts like Restore, except the cursor stays on the same page where the +// cursor was saved. +// +// See: https://vt100.net/docs/vt510-rm/SCORC.html +const RestoreCursorPosition = "\x1b[u" diff --git a/exp/term/ansi/cursor/cursor.go b/exp/term/ansi/cursor/cursor.go deleted file mode 100644 index 7c6a7ec1..00000000 --- a/exp/term/ansi/cursor/cursor.go +++ /dev/null @@ -1,130 +0,0 @@ -package cursor - -import "strconv" - -// Save (DECSC) is an escape sequence that saves the current cursor position. -// -// See: https://vt100.net/docs/vt510-rm/DECSC.html -const Save = "\x1b" + "7" - -// Restore (DECRC) is an escape sequence that restores the cursor position. -// -// See: https://vt100.net/docs/vt510-rm/DECRC.html -const Restore = "\x1b" + "8" - -// Up (CUU) returns a sequence for moving the cursor up n cells. -// -// CSI n A -// -// See: https://vt100.net/docs/vt510-rm/CUU.html -func Up(n int) string { - var s string - if n > 1 { - s = strconv.Itoa(n) - } - return "\x1b" + "[" + s + "A" -} - -// Down (CUD) returns a sequence for moving the cursor down n cells. -// -// CSI n B -// -// See: https://vt100.net/docs/vt510-rm/CUD.html -func Down(n int) string { - var s string - if n > 1 { - s = strconv.Itoa(n) - } - return "\x1b" + "[" + s + "B" -} - -// Right (CUF) returns a sequence for moving the cursor right n cells. -// -// CSI n C -// -// See: https://vt100.net/docs/vt510-rm/CUF.html -func Right(n int) string { - var s string - if n > 1 { - s = strconv.Itoa(n) - } - return "\x1b" + "[" + s + "C" -} - -// Left (CUB) returns a sequence for moving the cursor left n cells. -// -// CSI n D -// -// See: https://vt100.net/docs/vt510-rm/CUB.html -func Left(n int) string { - var s string - if n > 1 { - s = strconv.Itoa(n) - } - return "\x1b" + "[" + s + "D" -} - -// NextLine (CNL) returns a sequence for moving the cursor to the beginning of -// the next line n times. -// -// CSI n E -// -// See: https://vt100.net/docs/vt510-rm/CNL.html -func NextLine(n int) string { - var s string - if n > 1 { - s = strconv.Itoa(n) - } - return "\x1b" + "[" + s + "E" -} - -// PreviousLine (CPL) returns a sequence for moving the cursor to the beginning -// of the previous line n times. -// -// CSI n F -// -// See: https://vt100.net/docs/vt510-rm/CPL.html -func PreviousLine(n int) string { - var s string - if n > 1 { - s = strconv.Itoa(n) - } - return "\x1b" + "[" + s + "F" -} - -// Move (CUP) returns a sequence for moving the cursor to the given row and -// column. -// -// CSI n ; m H -// -// See: https://vt100.net/docs/vt510-rm/CUP.html -func Move(row, col int) string { - var r, c string - if row > 1 { - r = strconv.Itoa(row) - } - if col > 1 { - c = strconv.Itoa(col) - } - return "\x1b" + "[" + r + ";" + c + "H" -} - -// SavePosition (SCP or SCOSC) is a sequence for saving the cursor position. -// -// CSI s -// -// This acts like Save, except the page number where the cursor is located is -// not saved. -// -// See: https://vt100.net/docs/vt510-rm/SCOSC.html -const SavePosition = "\x1b" + "[" + "s" - -// RestorePosition (RCP or SCORC) is a sequence for restoring the cursor position. -// -// CSI u -// -// This acts like Restore, except the cursor stays on the same page where the -// cursor was saved. -// -// See: https://vt100.net/docs/vt510-rm/SCORC.html -const RestorePosition = "\x1b" + "[" + "u" diff --git a/exp/term/ansi/doc.go b/exp/term/ansi/doc.go index efa963ff..e955e9f1 100644 --- a/exp/term/ansi/doc.go +++ b/exp/term/ansi/doc.go @@ -2,6 +2,6 @@ // specs. // // All sequences use 7-bit C1 control codes, which are supported by most -// terminal emulators. Osc sequences are terminated by a BEL for wider +// terminal emulators. OSC sequences are terminated by a BEL for wider // compatibility with terminals. package ansi diff --git a/exp/term/ansi/sys/hyperlink.go b/exp/term/ansi/hyperlink.go similarity index 91% rename from exp/term/ansi/sys/hyperlink.go rename to exp/term/ansi/hyperlink.go index f69502bf..323bfe93 100644 --- a/exp/term/ansi/sys/hyperlink.go +++ b/exp/term/ansi/hyperlink.go @@ -1,4 +1,4 @@ -package sys +package ansi import "strings" @@ -15,7 +15,7 @@ func SetHyperlink(uri string, params ...string) string { if len(params) > 0 { p = strings.Join(params, ":") } - return "\x1b" + "]" + "8;" + p + ";" + uri + "\x07" + return "\x1b]8;" + p + ";" + uri + "\x07" } // ResetHyperlink returns a sequence for resetting the hyperlink. diff --git a/exp/term/ansi/sys/hyperlink_test.go b/exp/term/ansi/hyperlink_test.go similarity index 67% rename from exp/term/ansi/sys/hyperlink_test.go rename to exp/term/ansi/hyperlink_test.go index d82f08f9..eb9a1d46 100644 --- a/exp/term/ansi/sys/hyperlink_test.go +++ b/exp/term/ansi/hyperlink_test.go @@ -1,27 +1,27 @@ -package sys_test +package ansi_test import ( "testing" - "github.com/charmbracelet/x/exp/term/ansi/sys" + "github.com/charmbracelet/x/exp/term/ansi" ) func TestNewHyperlink_NoParams(t *testing.T) { - h := sys.SetHyperlink("https://example.com") + h := ansi.SetHyperlink("https://example.com") if h != "\x1b]8;;https://example.com\x07" { t.Errorf("Unexpected hyperlink: %s", h) } } func TestNewHyperlinkParams(t *testing.T) { - h := sys.SetHyperlink("https://example.com", "color=blue", "size=12") + h := ansi.SetHyperlink("https://example.com", "color=blue", "size=12") if h != "\x1b]8;color=blue:size=12;https://example.com\x07" { t.Errorf("Unexpected hyperlink: %s", h) } } func TestHyperlinkReset(t *testing.T) { - h := sys.SetHyperlink("") + h := ansi.SetHyperlink("") if h != "\x1b]8;;\x07" { t.Errorf("Unexpected hyperlink: %s", h) } diff --git a/exp/term/ansi/kitty.go b/exp/term/ansi/kitty.go new file mode 100644 index 00000000..5bf89814 --- /dev/null +++ b/exp/term/ansi/kitty.go @@ -0,0 +1,58 @@ +package ansi + +import "strconv" + +// Kitty keyboard protocol progressive enhancement flags. +// See: https://sw.kovidgoyal.net/kitty/keyboard-protocol/#progressive-enhancement +const ( + KittyDisambiguateEscapeCodes = 1 << iota + KittyReportEventTypes + KittyReportAlternateKeys + KittyReportAllKeys + KittyReportAssociatedKeys + + KittyAllFlags = KittyDisambiguateEscapeCodes | KittyReportEventTypes | + KittyReportAlternateKeys | KittyReportAllKeys | KittyReportAssociatedKeys +) + +// RequestKittyKeyboard is a sequence to request the terminal Kitty keyboard +// protocol enabled flags. +// +// See: https://sw.kovidgoyal.net/kitty/keyboard-protocol/ +const RequestKittyKeyboard = "\x1b[?u" + +// PushKittyKeyboard returns a sequence to push the given flags to the terminal +// Kitty Keyboard stack. +// +// CSI > flags u +// +// See https://sw.kovidgoyal.net/kitty/keyboard-protocol/#progressive-enhancement +func PushKittyKeyboard(flags int) string { + var f string + if flags > 0 { + f = strconv.Itoa(flags) + } + + return "\x1b[>" + f + "u" +} + +// DisableKittyKeyboard is a sequence to push zero into the terminal Kitty +// Keyboard stack to disable the protocol. +// +// This is equivalent to PushKittyKeyboard(0). +const DisableKittyKeyboard = "\x1b[>0u" + +// PopKittyKeyboard returns a sequence to pop n number of flags from the +// terminal Kitty Keyboard stack. +// +// CSI < flags u +// +// See https://sw.kovidgoyal.net/kitty/keyboard-protocol/#progressive-enhancement +func PopKittyKeyboard(n int) string { + var num string + if n > 0 { + num = strconv.Itoa(n) + } + + return "\x1b[<" + num + "u" +} diff --git a/exp/term/ansi/kitty/kitty.go b/exp/term/ansi/kitty/kitty.go deleted file mode 100644 index 55e8a15f..00000000 --- a/exp/term/ansi/kitty/kitty.go +++ /dev/null @@ -1,51 +0,0 @@ -package kitty - -import "strconv" - -// Kitty keyboard protocol progressive enhancement flags. -// See: https://sw.kovidgoyal.net/kitty/keyboard-protocol/#progressive-enhancement -const ( - DisambiguateEscapeCodes = 1 << iota - ReportEventTypes - ReportAlternateKeys - ReportAllKeys - ReportAssociatedKeys - - AllFlags = DisambiguateEscapeCodes | ReportEventTypes | ReportAlternateKeys | ReportAllKeys | ReportAssociatedKeys -) - -// Request is a sequence to request the terminal Kitty keyboard protocol -// enabled flags. -// -// See: https://sw.kovidgoyal.net/kitty/keyboard-protocol/ -const Request = "\x1b[?u" - -// Push returns a sequence to push the given flags to the terminal Kitty -// Keyboard stack. -// -// CSI > flags u -// -// See https://sw.kovidgoyal.net/kitty/keyboard-protocol/#progressive-enhancement -func Push(flags int) string { - var f string - if flags > 0 { - f = strconv.Itoa(flags) - } - - return "\x1b" + "[" + ">" + f + "u" -} - -// Pop returns a sequence to pop n number of flags from the terminal Kitty -// Keyboard stack. -// -// CSI < flags u -// -// See https://sw.kovidgoyal.net/kitty/keyboard-protocol/#progressive-enhancement -func Pop(n int) string { - var num string - if n > 0 { - num = strconv.Itoa(n) - } - - return "\x1b" + "[" + "<" + num + "u" -} diff --git a/exp/term/ansi/mode/dec.go b/exp/term/ansi/mode.go similarity index 53% rename from exp/term/ansi/mode/dec.go rename to exp/term/ansi/mode.go index 7c3562ab..f1a393d8 100644 --- a/exp/term/ansi/mode/dec.go +++ b/exp/term/ansi/mode.go @@ -1,22 +1,29 @@ -package mode +package ansi + +// This file define uses multiple sequences to set (SM), reset (RM), and request +// (DECRQM) different ANSI and DEC modes. +// +// See: https://vt100.net/docs/vt510-rm/SM.html +// See: https://vt100.net/docs/vt510-rm/RM.html +// See: https://vt100.net/docs/vt510-rm/DECRQM.html // Application Cursor Keys (DECCKM) is a mode that determines whether the // cursor keys send ANSI cursor sequences or application sequences. // // See: https://vt100.net/docs/vt510-rm/DECCKM.html const ( - EnableCursorKeys = "\x1b" + "[" + "?" + "1" + "h" - DisableCursorKeys = "\x1b" + "[" + "?" + "1" + "l" - RequestCursorKeys = "\x1b" + "[" + "?" + "1" + "$" + "p" + EnableCursorKeys = "\x1b[?1h" + DisableCursorKeys = "\x1b[?1l" + RequestCursorKeys = "\x1b[?1$p" ) // Text Cursor Enable Mode (DECTCEM) is a mode that shows/hides the cursor. // // See: https://vt100.net/docs/vt510-rm/DECTCEM.html const ( - ShowCursor = "\x1b" + "[" + "?" + "25" + "h" - HideCursor = "\x1b" + "[" + "?" + "25" + "l" - RequestCursorVisibility = "\x1b" + "[" + "?" + "25" + "$" + "p" + ShowCursor = "\x1b[?25h" + HideCursor = "\x1b[?25l" + RequestCursorVisibility = "\x1b[?25$p" ) // VT Mouse Tracking is a mode that determines whether the mouse reports on @@ -24,9 +31,9 @@ const ( // // See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking const ( - EnableMouseTracking = "\x1b" + "[" + "?" + "1000" + "h" - DisableMouseTracking = "\x1b" + "[" + "?" + "1000" + "l" - RequestMouseTracking = "\x1b" + "[" + "?" + "1000" + "$" + "p" + EnableMouseTracking = "\x1b[?1000h" + DisableMouseTracking = "\x1b[?1000l" + RequestMouseTracking = "\x1b[?1000$p" ) // VT Hilite Mouse Tracking is a mode that determines whether the mouse reports on @@ -34,9 +41,9 @@ const ( // // See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking const ( - EnableHiliteMouseTracking = "\x1b" + "[" + "?" + "1001" + "h" - DisableHiliteMouseTracking = "\x1b" + "[" + "?" + "1001" + "l" - RequestHiliteMouseTracking = "\x1b" + "[" + "?" + "1001" + "$" + "p" + EnableHiliteMouseTracking = "\x1b[?1001h" + DisableHiliteMouseTracking = "\x1b[?1001l" + RequestHiliteMouseTracking = "\x1b[?1001$p" ) // Cell Motion Mouse Tracking is a mode that determines whether the mouse @@ -44,9 +51,9 @@ const ( // // See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking const ( - EnableCellMotionMouseTracking = "\x1b" + "[" + "?" + "1002" + "h" - DisableCellMotionMouseTracking = "\x1b" + "[" + "?" + "1002" + "l" - RequestCellMotionMouseTracking = "\x1b" + "[" + "?" + "1002" + "$" + "p" + EnableCellMotionMouseTracking = "\x1b[?1002h" + DisableCellMotionMouseTracking = "\x1b[?1002l" + RequestCellMotionMouseTracking = "\x1b[?1002$p" ) // All Mouse Tracking is a mode that determines whether the mouse reports on @@ -54,9 +61,9 @@ const ( // // See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking const ( - EnableAllMouseTracking = "\x1b" + "[" + "?" + "1003" + "h" - DisableAllMouseTracking = "\x1b" + "[" + "?" + "1003" + "l" - RequestAllMouseTracking = "\x1b" + "[" + "?" + "1003" + "$" + "p" + EnableAllMouseTracking = "\x1b[?1003h" + DisableAllMouseTracking = "\x1b[?1003l" + RequestAllMouseTracking = "\x1b[?1003$p" ) // SGR Mouse Extension is a mode that determines whether the mouse reports events @@ -64,9 +71,9 @@ const ( // // See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking const ( - EnableSgrMouseExt = "\x1b" + "[" + "?" + "1006" + "h" - DisableSgrMouseExt = "\x1b" + "[" + "?" + "1006" + "l" - RequestSgrMouseExt = "\x1b" + "[" + "?" + "1006" + "$" + "p" + EnableSgrMouseExt = "\x1b[?1006h" + DisableSgrMouseExt = "\x1b[?1006l" + RequestSgrMouseExt = "\x1b[?1006$p" ) // Alternate Screen Buffer is a mode that determines whether the alternate screen @@ -74,9 +81,9 @@ const ( // // See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-The-Alternate-Screen-Buffer const ( - EnableAltScreenBuffer = "\x1b" + "[" + "?" + "1049" + "h" - DisableAltScreenBuffer = "\x1b" + "[" + "?" + "1049" + "l" - RequestAltScreenBuffer = "\x1b" + "[" + "?" + "1049" + "$" + "p" + EnableAltScreenBuffer = "\x1b[?1049h" + DisableAltScreenBuffer = "\x1b[?1049l" + RequestAltScreenBuffer = "\x1b[?1049$p" ) // Bracketed Paste Mode is a mode that determines whether pasted text is @@ -85,9 +92,9 @@ const ( // See: https://cirw.in/blog/bracketed-paste // See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Bracketed-Paste-Mode const ( - EnableBracketedPaste = "\x1b" + "[" + "?" + "2004" + "h" - DisableBracketedPaste = "\x1b" + "[" + "?" + "2004" + "l" - RequestBracketedPaste = "\x1b" + "[" + "?" + "2004" + "$" + "p" + EnableBracketedPaste = "\x1b[?2004h" + DisableBracketedPaste = "\x1b[?2004l" + RequestBracketedPaste = "\x1b[?2004$p" ) // Synchronized Output Mode is a mode that determines whether output is @@ -95,7 +102,17 @@ const ( // // See: https://gist.github.com/christianparpart/d8a62cc1ab659194337d73e399004036 const ( - EnableSyncdOutput = "\x1b" + "[" + "?" + "2026" + "h" - DisableSyncdOutput = "\x1b" + "[" + "?" + "2026" + "l" - RequestSyncdOutput = "\x1b" + "[" + "?" + "2026" + "$" + "p" + EnableSyncdOutput = "\x1b[?2026h" + DisableSyncdOutput = "\x1b[?2026l" + RequestSyncdOutput = "\x1b[?2026$p" +) + +// Win32Input is a mode that determines whether input is processed by the +// Win32 console and Conpty. +// +// See: https://github.com/microsoft/terminal/blob/main/doc/specs/%234999%20-%20Improved%20keyboard%20handling%20in%20Conpty.md +const ( + EnableWin32Input = "\x1b[?9001h" + DisableWin32Input = "\x1b[?9001l" + RequestWin32Input = "\x1b[?9001$p" ) diff --git a/exp/term/ansi/mode/ansi.go b/exp/term/ansi/mode/ansi.go deleted file mode 100644 index 1515e2c2..00000000 --- a/exp/term/ansi/mode/ansi.go +++ /dev/null @@ -1 +0,0 @@ -package mode diff --git a/exp/term/ansi/mode/doc.go b/exp/term/ansi/mode/doc.go deleted file mode 100644 index f39b832b..00000000 --- a/exp/term/ansi/mode/doc.go +++ /dev/null @@ -1,8 +0,0 @@ -// This package uses multiple sequences to set (SM), reset (RM), and request -// (DECRQM) different ANSI and DEC modes. -// -// See: https://vt100.net/docs/vt510-rm/SM.html -// See: https://vt100.net/docs/vt510-rm/RM.html -// See: https://vt100.net/docs/vt510-rm/DECRQM.html - -package mode diff --git a/exp/term/ansi/screen/screen.go b/exp/term/ansi/screen.go similarity index 77% rename from exp/term/ansi/screen/screen.go rename to exp/term/ansi/screen.go index 752b86c9..018dee17 100644 --- a/exp/term/ansi/screen/screen.go +++ b/exp/term/ansi/screen.go @@ -1,4 +1,4 @@ -package screen +package ansi import "strconv" @@ -16,9 +16,17 @@ func EraseDisplay(n int) string { if n < 0 { n = 0 } - return "\x1b" + "[" + strconv.Itoa(n) + "J" + return "\x1b[" + strconv.Itoa(n) + "J" } +// EraseDisplay constants. +// These are the possible values for the EraseDisplay function. +const ( + EraseDisplayRight = "\x1b[0J" + EraseDisplayLeft = "\x1b[1J" + EraseEntierDisplay = "\x1b[2J" +) + // EraseLine (EL) clears the current line or parts of the line. Possible values: // // 0: Clear from cursor to end of line. @@ -34,9 +42,17 @@ func EraseLine(n int) string { if n < 0 { n = 0 } - return "\x1b" + "[" + strconv.Itoa(n) + "K" + return "\x1b[" + strconv.Itoa(n) + "K" } +// EraseLine constants. +// These are the possible values for the EraseLine function. +const ( + EraseLineRight = "\x1b[0K" + EraseLineLeft = "\x1b[1K" + EraseEntireLine = "\x1b[2K" +) + // ScrollUp (SU) scrolls the screen up n lines. New lines are added at the // bottom of the screen. // @@ -48,7 +64,7 @@ func ScrollUp(n int) string { if n > 1 { s = strconv.Itoa(n) } - return "\x1b" + "[" + s + "S" + return "\x1b[" + s + "S" } // ScrollDown (SD) scrolls the screen down n lines. New lines are added at the @@ -62,7 +78,7 @@ func ScrollDown(n int) string { if n > 1 { s = strconv.Itoa(n) } - return "\x1b" + "[" + s + "T" + return "\x1b[" + s + "T" } // InsertLine (IL) inserts n blank lines at the current cursor position. @@ -76,7 +92,7 @@ func InsertLine(n int) string { if n > 1 { s = strconv.Itoa(n) } - return "\x1b" + "[" + s + "L" + return "\x1b[" + s + "L" } // DeleteLine (DL) deletes n lines at the current cursor position. Existing @@ -90,13 +106,13 @@ func DeleteLine(n int) string { if n > 1 { s = strconv.Itoa(n) } - return "\x1b" + "[" + s + "M" + return "\x1b[" + s + "M" } // SetScrollingRegion (DECSTBM) sets the top and bottom margins for the scrolling // region. The default is the entire screen. // -// CSI ;r +// CSI ; r // // See: https://vt100.net/docs/vt510-rm/DECSTBM.html func SetScrollingRegion(t, b int) string { @@ -107,5 +123,5 @@ func SetScrollingRegion(t, b int) string { if b > 1 { bs = strconv.Itoa(b) } - return "\x1b" + "[" + ts + ";" + bs + "r" + return "\x1b[" + ts + ";" + bs + "r" } diff --git a/exp/term/ansi/style.go b/exp/term/ansi/style.go new file mode 100644 index 00000000..621ccad4 --- /dev/null +++ b/exp/term/ansi/style.go @@ -0,0 +1,280 @@ +package ansi + +import ( + "image/color" + "strconv" + "strings" +) + +// ResetStyle is a SGR (Select Graphic Rendition) style sequence that resets +// all attributes. +// See: https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters +const ResetStyle = "\x1b[m" + +// Attr is a SGR (Select Graphic Rendition) style attribute. +type Attr = string + +// Style represents an ANSI SGR (Select Graphic Rendition) style. +type Style []Attr + +// String returns the ANSI SGR (Select Graphic Rendition) style sequence for +// the given style. +func (s Style) String() string { + if len(s) == 0 { + return ResetStyle + } + return "\x1b[" + strings.Join(s, ";") + "m" +} + +// Styled returns a styled string with the given style applied. +func (s Style) Styled(str string) string { + if len(s) == 0 { + return str + } + return s.String() + str + ResetStyle +} + +// Reset appends the reset style attribute to the style. +func (s Style) Reset() Style { + return append(s, resetAttr) +} + +// Bold appends the bold style attribute to the style. +func (s Style) Bold() Style { + return append(s, boldAttr) +} + +// Faint appends the faint style attribute to the style. +func (s Style) Faint() Style { + return append(s, faintAttr) +} + +// Italic appends the italic style attribute to the style. +func (s Style) Italic() Style { + return append(s, italicAttr) +} + +// Underline appends the underline style attribute to the style. +func (s Style) Underline() Style { + return append(s, underlineAttr) +} + +// DoubleUnderline appends the double underline style attribute to the style. +func (s Style) DoubleUnderline() Style { + return append(s, doubleUnderlineAttr) +} + +// CurlyUnderline appends the curly underline style attribute to the style. +func (s Style) CurlyUnderline() Style { + return append(s, curlyUnderlineAttr) +} + +// DottedUnderline appends the dotted underline style attribute to the style. +func (s Style) DottedUnderline() Style { + return append(s, dottedUnderlineAttr) +} + +// DashedUnderline appends the dashed underline style attribute to the style. +func (s Style) DashedUnderline() Style { + return append(s, dashedUnderlineAttr) +} + +// SlowBlink appends the slow blink style attribute to the style. +func (s Style) SlowBlink() Style { + return append(s, slowBlinkAttr) +} + +// RapidBlink appends the rapid blink style attribute to the style. +func (s Style) RapidBlink() Style { + return append(s, rapidBlinkAttr) +} + +// Reverse appends the reverse style attribute to the style. +func (s Style) Reverse() Style { + return append(s, reverseAttr) +} + +// Conceal appends the conceal style attribute to the style. +func (s Style) Conceal() Style { + return append(s, concealAttr) +} + +// Strikethrough appends the strikethrough style attribute to the style. +func (s Style) Strikethrough() Style { + return append(s, strikethroughAttr) +} + +// NoBold appends the no bold style attribute to the style. +func (s Style) NoBold() Style { + return append(s, noBoldAttr) +} + +// NormalIntensity appends the normal intensity style attribute to the style. +func (s Style) NormalIntensity() Style { + return append(s, normalIntensityAttr) +} + +// NoItalic appends the no italic style attribute to the style. +func (s Style) NoItalic() Style { + return append(s, noItalicAttr) +} + +// NoUnderline appends the no underline style attribute to the style. +func (s Style) NoUnderline() Style { + return append(s, noUnderlineAttr) +} + +// NoBlink appends the no blink style attribute to the style. +func (s Style) NoBlink() Style { + return append(s, noBlinkAttr) +} + +// NoReverse appends the no reverse style attribute to the style. +func (s Style) NoReverse() Style { + return append(s, noReverseAttr) +} + +// NoStrikethrough appends the no strikethrough style attribute to the style. +func (s Style) NoStrikethrough() Style { + return append(s, noStrikethroughAttr) +} + +// DefaultForegroundColor appends the default foreground color style attribute to the style. +func (s Style) DefaultForegroundColor() Style { + return append(s, defaultForegroundColorAttr) +} + +// DefaultBackgroundColor appends the default background color style attribute to the style. +func (s Style) DefaultBackgroundColor() Style { + return append(s, defaultBackgroundColorAttr) +} + +// DefaultUnderlineColor appends the default underline color style attribute to the style. +func (s Style) DefaultUnderlineColor() Style { + return append(s, defaultUnderlineColorAttr) +} + +// ForegroundColor appends the foreground color style attribute to the style. +func (s Style) ForegroundColor(c Color) Style { + return append(s, foregroundColorAttr(c)) +} + +// BackgroundColor appends the background color style attribute to the style. +func (s Style) BackgroundColor(c Color) Style { + return append(s, backgroundColorAttr(c)) +} + +// UnderlineColor appends the underline color style attribute to the style. +func (s Style) UnderlineColor(c Color) Style { + return append(s, underlineColorAttr(c)) +} + +// SGR (Select Graphic Rendition) style attributes. +// See: https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters +const ( + resetAttr Attr = "0" + boldAttr Attr = "1" + faintAttr Attr = "2" + italicAttr Attr = "3" + underlineAttr Attr = "4" + doubleUnderlineAttr Attr = "4:2" + curlyUnderlineAttr Attr = "4:3" + dottedUnderlineAttr Attr = "4:4" + dashedUnderlineAttr Attr = "4:5" + slowBlinkAttr Attr = "5" + rapidBlinkAttr Attr = "6" + reverseAttr Attr = "7" + concealAttr Attr = "8" + strikethroughAttr Attr = "9" + noBoldAttr Attr = "21" // Some terminals treat this as double underline. + normalIntensityAttr Attr = "22" + noItalicAttr Attr = "23" + noUnderlineAttr Attr = "24" + noBlinkAttr Attr = "25" + noReverseAttr Attr = "27" + noStrikethroughAttr Attr = "29" + defaultForegroundColorAttr Attr = "39" + defaultBackgroundColorAttr Attr = "49" + defaultUnderlineColorAttr Attr = "59" +) + +// Foreground returns the SGR attribute for the given foreground color. +// See: https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters +func foregroundColorAttr(c Color) Attr { + switch c := c.(type) { + case BasicColor: + // 3-bit or 4-bit ANSI foreground + // "3" or "9" where n is the color number from 0 to 7 + if c < 8 { + return "3" + string('0'+c) + } else if c < 16 { + return "9" + string('0'+c-8) + } + case ExtendedColor: + // 256-color ANSI foreground + // "38;5;" + return "38;5;" + strconv.FormatUint(uint64(c), 10) + case TrueColor, color.Color: + // 24-bit "true color" foreground + // "38;2;;;" + r, g, b, _ := c.RGBA() + return "38;2;" + + strconv.FormatUint(uint64(r), 10) + ";" + + strconv.FormatUint(uint64(g), 10) + ";" + + strconv.FormatUint(uint64(b), 10) + } + return defaultForegroundColorAttr +} + +// backgroundColorAttr returns the SGR attribute for the given background color. +// See: https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters +func backgroundColorAttr(c Color) Attr { + switch c := c.(type) { + case BasicColor: + // 3-bit or 4-bit ANSI foreground + // "4" or "10" where n is the color number from 0 to 7 + if c < 8 { + return "4" + string('0'+c) + } else { + return "10" + string('0'+c-8) + } + case ExtendedColor: + // 256-color ANSI foreground + // "48;5;" + return "48;5;" + strconv.FormatUint(uint64(c), 10) + case TrueColor, color.Color: + // 24-bit "true color" foreground + // "38;2;;;" + r, g, b, _ := c.RGBA() + return "48;2;" + + strconv.FormatUint(uint64(r), 10) + ";" + + strconv.FormatUint(uint64(g), 10) + ";" + + strconv.FormatUint(uint64(b), 10) + } + return defaultBackgroundColorAttr +} + +// underlineColorAttr returns the SGR attribute for the given underline color. +// See: https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters +func underlineColorAttr(c Color) Attr { + switch c := c.(type) { + // NOTE: we can't use 3-bit and 4-bit ANSI color codes with underline + // color, use 256-color instead. + // + // 256-color ANSI underline color + // "58;5;" + case BasicColor: + return "58;5;" + strconv.FormatUint(uint64(c), 10) + case ExtendedColor: + return "58;5;" + strconv.FormatUint(uint64(c), 10) + case TrueColor, color.Color: + // 24-bit "true color" foreground + // "38;2;;;" + r, g, b, _ := c.RGBA() + return "58;2;" + + strconv.FormatUint(uint64(r), 10) + ";" + + strconv.FormatUint(uint64(g), 10) + ";" + + strconv.FormatUint(uint64(b), 10) + } + return defaultUnderlineColorAttr +} diff --git a/exp/term/ansi/style/string.go b/exp/term/ansi/style/string.go deleted file mode 100644 index 6c26d605..00000000 --- a/exp/term/ansi/style/string.go +++ /dev/null @@ -1,9 +0,0 @@ -package style - -// String returns a styled string with the given SGR attributes applied. -func String(s string, attrs ...string) string { - if len(attrs) == 0 { - return s - } - return Sequence(attrs...) + s + ResetSequence -} diff --git a/exp/term/ansi/style/style.go b/exp/term/ansi/style/style.go deleted file mode 100644 index 5c5e93a0..00000000 --- a/exp/term/ansi/style/style.go +++ /dev/null @@ -1,137 +0,0 @@ -package style - -import ( - "image/color" - "strconv" - "strings" -) - -// Attribute is a SGR (Select Graphic Rendition) style attribute. -type Attribute = string - -// ResetSequence is a SGR (Select Graphic Rendition) style sequence that resets -// all attributes. -// See: https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters -const ResetSequence = "\x1b[m" - -// SGR (Select Graphic Rendition) style attributes. -// See: https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters -const ( - Reset Attribute = "0" - Bold Attribute = "1" - Faint Attribute = "2" - Italic Attribute = "3" - Underline Attribute = "4" - DoubleUnderline Attribute = "4:2" - CurlyUnderline Attribute = "4:3" - DottedUnderline Attribute = "4:4" - DashedUnderline Attribute = "4:5" - SlowBlink Attribute = "5" - RapidBlink Attribute = "6" - Reverse Attribute = "7" - Conceal Attribute = "8" - Strikethrough Attribute = "9" - NoBold Attribute = "21" // Some terminals treat this as double underline. - NormalIntensity Attribute = "22" - NoItalic Attribute = "23" - NoUnderline Attribute = "24" - NoBlink Attribute = "25" - NoReverse Attribute = "27" - NoStrikethrough Attribute = "29" - DefaultForegroundColor Attribute = "39" - DefaultBackgroundColor Attribute = "49" - DefaultUnderlineColor Attribute = "59" -) - -// Sequence creates a SGR (Select Graphic Rendition) style sequence with the -// given attributes. -// If no attributes are given, it will return a reset sequence. -// -// See: https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters -func Sequence(attrs ...Attribute) string { - if len(attrs) == 0 { - return ResetSequence - } - return "\x1b" + "[" + strings.Join(attrs, ";") + "m" -} - -// Foreground returns the SGR attribute for the given foreground color. -// See: https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters -func ForegroundColor(c Color) Attribute { - switch c := c.(type) { - case BasicColor: - // 3-bit or 4-bit ANSI foreground - // "3" or "9" where n is the color number from 0 to 7 - if c < 8 { - return "3" + string('0'+c) - } else if c < 16 { - return "9" + string('0'+c-8) - } - case ExtendedColor: - // 256-color ANSI foreground - // "38;5;" - return "38;5;" + strconv.FormatUint(uint64(c), 10) - case TrueColor, color.Color: - // 24-bit "true color" foreground - // "38;2;;;" - r, g, b, _ := c.RGBA() - return "38;2;" + - strconv.FormatUint(uint64(r), 10) + ";" + - strconv.FormatUint(uint64(g), 10) + ";" + - strconv.FormatUint(uint64(b), 10) - } - return DefaultForegroundColor -} - -// BackgroundColor returns the SGR attribute for the given background color. -// See: https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters -func BackgroundColor(c Color) Attribute { - switch c := c.(type) { - case BasicColor: - // 3-bit or 4-bit ANSI foreground - // "4" or "10" where n is the color number from 0 to 7 - if c < 8 { - return "4" + string('0'+c) - } else { - return "10" + string('0'+c-8) - } - case ExtendedColor: - // 256-color ANSI foreground - // "48;5;" - return "48;5;" + strconv.FormatUint(uint64(c), 10) - case TrueColor, color.Color: - // 24-bit "true color" foreground - // "38;2;;;" - r, g, b, _ := c.RGBA() - return "48;2;" + - strconv.FormatUint(uint64(r), 10) + ";" + - strconv.FormatUint(uint64(g), 10) + ";" + - strconv.FormatUint(uint64(b), 10) - } - return DefaultBackgroundColor -} - -// UnderlineColor returns the SGR attribute for the given underline color. -// See: https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters -func UnderlineColor(c Color) Attribute { - switch c := c.(type) { - // NOTE: we can't use 3-bit and 4-bit ANSI color codes with underline - // color, use 256-color instead. - // - // 256-color ANSI underline color - // "58;5;" - case BasicColor: - return "58;5;" + strconv.FormatUint(uint64(c), 10) - case ExtendedColor: - return "58;5;" + strconv.FormatUint(uint64(c), 10) - case TrueColor, color.Color: - // 24-bit "true color" foreground - // "38;2;;;" - r, g, b, _ := c.RGBA() - return "58;2;" + - strconv.FormatUint(uint64(r), 10) + ";" + - strconv.FormatUint(uint64(g), 10) + ";" + - strconv.FormatUint(uint64(b), 10) - } - return DefaultUnderlineColor -} diff --git a/exp/term/ansi/style/style_test.go b/exp/term/ansi/style/style_test.go deleted file mode 100644 index 0cbd9678..00000000 --- a/exp/term/ansi/style/style_test.go +++ /dev/null @@ -1,35 +0,0 @@ -package style_test - -import ( - "testing" - - "github.com/charmbracelet/x/exp/term/ansi/style" -) - -func TestReset(t *testing.T) { - if style.Sequence() != "\x1b[m" { - t.Errorf("Unexpected reset sequence: %s", style.ResetSequence) - } -} - -func TestBold(t *testing.T) { - if style.Sequence(style.Bold) != "\x1b[1m" { - t.Errorf("Unexpected bold sequence: %s", style.Sequence(style.Bold)) - } -} - -func TestDefaultBackground(t *testing.T) { - if style.Sequence(style.DefaultBackgroundColor) != "\x1b[49m" { - t.Errorf("Unexpected default background sequence: %s", style.Sequence(style.DefaultBackgroundColor)) - } -} - -func TestSequence(t *testing.T) { - if style.Sequence( - style.Bold, - style.Underline, - style.ForegroundColor(style.ExtendedColor(255)), - ) != "\x1b[1;4;38;5;255m" { - t.Errorf("Unexpected sequence: %s", style.Sequence(style.Bold, style.Underline, style.ForegroundColor(style.ExtendedColor(255)))) - } -} diff --git a/exp/term/ansi/style_test.go b/exp/term/ansi/style_test.go new file mode 100644 index 00000000..99fb4015 --- /dev/null +++ b/exp/term/ansi/style_test.go @@ -0,0 +1,38 @@ +package ansi_test + +import ( + "testing" + + "github.com/charmbracelet/x/exp/term/ansi" +) + +func TestReset(t *testing.T) { + var s ansi.Style + if s.String() != "\x1b[m" { + t.Errorf("Unexpected reset sequence: %s", ansi.ResetStyle) + } +} + +func TestBold(t *testing.T) { + var s ansi.Style + s = s.Bold() + if s.String() != "\x1b[1m" { + t.Errorf("Unexpected bold sequence: %s", s) + } +} + +func TestDefaultBackground(t *testing.T) { + var s ansi.Style + s = s.DefaultBackgroundColor() + if s.String() != "\x1b[49m" { + t.Errorf("Unexpected default background sequence: %s", s) + } +} + +func TestSequence(t *testing.T) { + var s ansi.Style + s = s.Bold().Underline().ForegroundColor(ansi.ExtendedColor(255)) + if s.String() != "\x1b[1;4;38;5;255m" { + t.Errorf("Unexpected sequence: %s", s) + } +} diff --git a/exp/term/ansi/sys/title_test.go b/exp/term/ansi/sys/title_test.go deleted file mode 100644 index 0edb51d7..00000000 --- a/exp/term/ansi/sys/title_test.go +++ /dev/null @@ -1,25 +0,0 @@ -package sys_test - -import ( - "testing" - - "github.com/charmbracelet/x/exp/term/ansi/sys" -) - -func TestSetIconNameWindowTitle(t *testing.T) { - if sys.SetIconNameWindowTitle("hello") != "\x1b]0;hello\x07" { - t.Errorf("expected: %q, got: %q", "\x1b]0;hello\x07", sys.SetIconNameWindowTitle("hello")) - } -} - -func TestSetIconName(t *testing.T) { - if sys.SetIconName("hello") != "\x1b]1;hello\x07" { - t.Errorf("expected: %q, got: %q", "\x1b]1;hello\x07", sys.SetIconName("hello")) - } -} - -func TestSetWindowTitle(t *testing.T) { - if sys.SetWindowTitle("hello") != "\x1b]2;hello\x07" { - t.Errorf("expected: %q, got: %q", "\x1b]2;hello\x07", sys.SetWindowTitle("hello")) - } -} diff --git a/exp/term/ansi/sys/title.go b/exp/term/ansi/title.go similarity index 85% rename from exp/term/ansi/sys/title.go rename to exp/term/ansi/title.go index 9bdf2de4..8fd8bf98 100644 --- a/exp/term/ansi/sys/title.go +++ b/exp/term/ansi/title.go @@ -1,4 +1,4 @@ -package sys +package ansi // SetIconNameWindowTitle returns a sequence for setting the icon name and // window title. @@ -8,7 +8,7 @@ package sys // // See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Operating-System-Commands func SetIconNameWindowTitle(s string) string { - return "\x1b" + "]" + "0;" + s + "\x07" + return "\x1b]0;" + s + "\x07" } // SetIconName returns a sequence for setting the icon name. @@ -18,7 +18,7 @@ func SetIconNameWindowTitle(s string) string { // // See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Operating-System-Commands func SetIconName(s string) string { - return "\x1b" + "]" + "1;" + s + "\x07" + return "\x1b]1;" + s + "\x07" } // SetWindowTitle returns a sequence for setting the window title. @@ -28,5 +28,5 @@ func SetIconName(s string) string { // // See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Operating-System-Commands func SetWindowTitle(s string) string { - return "\x1b" + "]" + "2;" + s + "\x07" + return "\x1b]2;" + s + "\x07" } diff --git a/exp/term/ansi/title_test.go b/exp/term/ansi/title_test.go new file mode 100644 index 00000000..a710da00 --- /dev/null +++ b/exp/term/ansi/title_test.go @@ -0,0 +1,25 @@ +package ansi_test + +import ( + "testing" + + "github.com/charmbracelet/x/exp/term/ansi" +) + +func TestSetIconNameWindowTitle(t *testing.T) { + if ansi.SetIconNameWindowTitle("hello") != "\x1b]0;hello\x07" { + t.Errorf("expected: %q, got: %q", "\x1b]0;hello\x07", ansi.SetIconNameWindowTitle("hello")) + } +} + +func TestSetIconName(t *testing.T) { + if ansi.SetIconName("hello") != "\x1b]1;hello\x07" { + t.Errorf("expected: %q, got: %q", "\x1b]1;hello\x07", ansi.SetIconName("hello")) + } +} + +func TestSetWindowTitle(t *testing.T) { + if ansi.SetWindowTitle("hello") != "\x1b]2;hello\x07" { + t.Errorf("expected: %q, got: %q", "\x1b]2;hello\x07", ansi.SetWindowTitle("hello")) + } +} diff --git a/exp/term/ansi/internal/util.go b/exp/term/ansi/util.go similarity index 56% rename from exp/term/ansi/internal/util.go rename to exp/term/ansi/util.go index c7161d06..4bb32e21 100644 --- a/exp/term/ansi/internal/util.go +++ b/exp/term/ansi/util.go @@ -1,16 +1,23 @@ -package internal +package ansi import ( "fmt" "image/color" ) -// ColorToHexString returns a hex string representation of a color. -func ColorToHexString(c color.Color) string { +// colorToHexString returns a hex string representation of a color. +func colorToHexString(c color.Color) string { if c == nil { return "" } + shift := func(v uint32) uint32 { + if v > 0xff { + return v >> 8 + } + return v + } r, g, b, _ := c.RGBA() + r, g, b = shift(r), shift(g), shift(b) return fmt.Sprintf("#%02x%02x%02x", r, g, b) }