From 390631c363a8e7861acb63d34d38252088fd877a Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Tue, 17 Sep 2024 15:56:16 -0400 Subject: [PATCH 1/8] feat: combine keyboard enhancements into a nicer API This change combines the keyboard enhancements into a nicer API. Kitty keyboard protocol and XTerm modifyOtherKeys are now combined into a single API. This makes it easier to enable keyboard enhancements. Use `WithKeyboardEnhancements` to enable keyboard enhancements. This function accepts a list of `KeyboardEnhancement` functions that can be used to enable different keyboard features. For now, we only support the `WithReleaseKeys` enhancement which enables support for reporting release key events. --- commands.go | 48 +++++++++++------ examples/print-key/main.go | 17 +++--- kitty.go | 53 ------------------ options.go | 107 +++++++++++++++++-------------------- parse.go | 4 +- screen_test.go | 4 +- tea.go | 89 +++++++++++++++--------------- tty.go | 4 +- xterm.go | 45 ---------------- 9 files changed, 142 insertions(+), 229 deletions(-) diff --git a/commands.go b/commands.go index 0cb6da710c..199ce95dcf 100644 --- a/commands.go +++ b/commands.go @@ -2,6 +2,8 @@ package tea import ( "time" + + "github.com/charmbracelet/x/ansi" ) // Batch performs a bunch of commands concurrently with no ordering guarantees @@ -215,24 +217,36 @@ func WindowSize() Cmd { } } -// setEnhancedKeyboardMsg is a message to enable/disable enhanced keyboard -// features. -type setEnhancedKeyboardMsg bool +type enableKeyboardEnhancementsMsg []KeyboardEnhancement -// EnableEnhancedKeyboard is a command to enable enhanced keyboard features. -// This unambiguously reports more key combinations than traditional terminal -// keyboard sequences. This might also enable reporting of release key events -// depending on the terminal emulator supporting it. -// -// This is equivalent to calling EnablieKittyKeyboard(3) and -// EnableModifyOtherKeys(1). -func EnableEnhancedKeyboard() Msg { - return setEnhancedKeyboardMsg(true) +// EnableKeyboardEnhancements is a command that enables keyboard enhancements +// in the terminal. +func EnableKeyboardEnhancements(enhancements ...KeyboardEnhancement) Cmd { + enhancements = append(enhancements, func(k *keyboardEnhancements) { + k.kittyFlags |= ansi.KittyDisambiguateEscapeCodes + if k.modifyOtherKeys < 1 { + k.modifyOtherKeys = 1 + } + }) + return func() Msg { + return enableKeyboardEnhancementsMsg(enhancements) + } } -// DisableEnhancedKeyboard is a command to disable enhanced keyboard features. -// -// This is equivalent to calling DisableKittyKeyboard() and DisableModifyOtherKeys(). -func DisableEnhancedKeyboard() Msg { - return setEnhancedKeyboardMsg(false) +type disableKeyboardEnhancementsMsg struct{} + +// DisableKeyboardEnhancements is a command that disables keyboard enhancements +// in the terminal. +func DisableKeyboardEnhancements() Msg { + return disableKeyboardEnhancementsMsg{} +} + +// KeyboardEnhancementsMsg is a message that gets sent when the terminal +// supports keyboard enhancements. +type KeyboardEnhancementsMsg keyboardEnhancements + +// SupportsReleaseKeys returns whether the terminal supports key release +// events. +func (k KeyboardEnhancementsMsg) SupportsReleaseKeys() bool { + return k.kittyFlags&ansi.KittyReportEventTypes != 0 } diff --git a/examples/print-key/main.go b/examples/print-key/main.go index 801b5ac1f6..7369e1ba64 100644 --- a/examples/print-key/main.go +++ b/examples/print-key/main.go @@ -14,12 +14,17 @@ func (m model) Init() (tea.Model, tea.Cmd) { func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { - case tea.KeyPressMsg: - switch msg.String() { - case "ctrl+c": - return m, tea.Quit + case tea.KeyboardEnhancementsMsg: + return m, tea.Printf("Keyboard enhancements enabled! ReleaseKeys: %v\n", msg.SupportsReleaseKeys()) + case tea.KeyMsg: + switch msg := msg.(type) { + case tea.KeyPressMsg: + switch msg.String() { + case "ctrl+c": + return m, tea.Quit + } } - return m, tea.Printf("You pressed: %s\n", msg.String()) + return m, tea.Printf("You pressed: %s (%T)\n", msg.String(), msg) } return m, nil } @@ -29,7 +34,7 @@ func (m model) View() string { } func main() { - p := tea.NewProgram(model{}, tea.WithEnhancedKeyboard()) + p := tea.NewProgram(model{}, tea.WithKeyboardEnhancements(tea.WithReleaseKeys)) if _, err := p.Run(); err != nil { log.Printf("Error running program: %v", err) } diff --git a/kitty.go b/kitty.go index 3d128a92f6..3023e636b7 100644 --- a/kitty.go +++ b/kitty.go @@ -7,59 +7,6 @@ import ( "github.com/charmbracelet/x/ansi" ) -// setKittyKeyboardFlagsMsg is a message to set Kitty keyboard progressive -// enhancement protocol flags. -type setKittyKeyboardFlagsMsg int - -// EnableKittyKeyboard is a command to enable Kitty keyboard progressive -// enhancements. -// -// The flags parameter is a bitmask of the following -// -// 1: Disambiguate escape codes -// 2: Report event types -// 4: Report alternate keys -// 8: Report all keys as escape codes -// 16: Report associated text -// -// See https://sw.kovidgoyal.net/kitty/keyboard-protocol/ for more information. -func EnableKittyKeyboard(flags int) Cmd { //nolint:unused - return func() Msg { - return setKittyKeyboardFlagsMsg(flags) - } -} - -// DisableKittyKeyboard is a command to disable Kitty keyboard progressive -// enhancements. -func DisableKittyKeyboard() Msg { //nolint:unused - return setKittyKeyboardFlagsMsg(0) -} - -// kittyKeyboardMsg is a message that queries the current Kitty keyboard -// progressive enhancement flags. -type kittyKeyboardMsg struct{} - -// KittyKeyboard is a command that queries the current Kitty keyboard -// progressive enhancement flags from the terminal. -func KittyKeyboard() Msg { //nolint:unused - return kittyKeyboardMsg{} -} - -// KittyKeyboardMsg is a bitmask message representing Kitty keyboard -// progressive enhancement flags. -// -// The bitmaps represents the following: -// -// 0: Disable all features -// 1: Disambiguate escape codes -// 2: Report event types -// 4: Report alternate keys -// 8: Report all keys as escape codes -// 16: Report associated text -// -// See https://sw.kovidgoyal.net/kitty/keyboard-protocol/ for more information. -type KittyKeyboardMsg int - // Kitty Clipboard Control Sequences var kittyKeyMap = map[int]rune{ ansi.BS: KeyBackspace, diff --git a/options.go b/options.go index c2f9f1e54d..e1aea30d91 100644 --- a/options.go +++ b/options.go @@ -253,72 +253,63 @@ func WithReportFocus() ProgramOption { } } -// WithEnhancedKeyboard enables support for enhanced keyboard features. This -// unambiguously reports more key combinations than traditional terminal -// keyboard sequences. This might also enable reporting of release key events -// depending on the terminal emulator supporting it. -// -// This is a syntactic sugar for WithKittyKeyboard(7) and WithXtermModifyOtherKeys(1). -func WithEnhancedKeyboard() ProgramOption { - return func(p *Program) { - _WithKittyKeyboard(ansi.KittyDisambiguateEscapeCodes | - ansi.KittyReportEventTypes | - ansi.KittyReportAlternateKeys, - )(p) - _WithModifyOtherKeys(1)(p) - } -} +// keyboardEnhancements is a type that represents a set of keyboard +// enhancements. +type keyboardEnhancements struct { + // Kitty progressive keyboard enhancements protocol. This can be used to + // enable different keyboard features. + // + // - 0: disable all features + // - 1: [ansi.DisambiguateEscapeCodes] Disambiguate escape codes such as + // ctrl+i and tab, ctrl+[ and escape, ctrl+space and ctrl+@, etc. + // - 2: [ansi.ReportEventTypes] Report event types such as key presses, + // releases, and repeat events. + // - 4: [ansi.ReportAlternateKeys] Report alternate keys such as shifted + // keys and PC-101 ANSI US keyboard layout. + // - 8: [ansi.ReportAllKeysAsEscapeCodes] Report all key events as escape + // codes. This includes simple printable keys like "a" and other Unicode + // characters. + // - 16: [ansi.ReportAssociatedText] Report associated text with key + // events. This encodes multi-rune key events as escape codes instead of + // individual runes. + // + kittyFlags int -// _WithKittyKeyboard enables support for the Kitty keyboard protocol. This -// protocol enables more key combinations and events than the traditional -// ambiguous terminal keyboard sequences. -// -// Use flags to specify which features you want to enable. -// -// 0: Disable all features -// 1: Disambiguate escape codes -// 2: Report event types -// 4: Report alternate keys -// 8: Report all keys as escape codes -// 16: Report associated text -// -// See https://sw.kovidgoyal.net/kitty/keyboard-protocol/ for more information. -func _WithKittyKeyboard(flags int) ProgramOption { - return func(p *Program) { - p.kittyFlags = flags - p.startupOptions |= withKittyKeyboard - } + // Xterm modifyOtherKeys feature. + // + // - Mode 0 disables modifyOtherKeys. + // - Mode 1 reports ambiguous keys as escape codes. This is similar to + // [ansi.KittyDisambiguateEscapeCodes] but uses XTerm escape codes. + // - Mode 2 reports all key as escape codes including printable keys like "a" and "shift+b". + modifyOtherKeys int } -// _WithModifyOtherKeys enables support for the XTerm modifyOtherKeys feature. -// This feature allows the terminal to report ambiguous keys as escape codes. -// This is useful for terminals that don't support the Kitty keyboard protocol. -// -// The mode can be one of the following: -// -// 0: Disable modifyOtherKeys -// 1: Report ambiguous keys as escape codes -// 2: Report ambiguous keys as escape codes including modified keys like Alt- -// and Meta- +// KeyboardEnhancement is a type that represents a keyboard enhancement. +type KeyboardEnhancement func(k *keyboardEnhancements) + +// WithReleaseKeys enables support for reporting release key events. This is +// useful for terminals that support the Kitty keyboard protocol "Report event +// types" progressive enhancement feature. // -// See https://invisible-island.net/xterm/manpage/xterm.html#VT100-Widget-Resources:modifyOtherKeys -func _WithModifyOtherKeys(mode int) ProgramOption { - return func(p *Program) { - p.modifyOtherKeys = mode - p.startupOptions |= withModifyOtherKeys - } +// Note that not all terminals support this feature. +func WithReleaseKeys(k *keyboardEnhancements) { + k.kittyFlags |= ansi.KittyReportEventTypes } -// _WithWindowsInputMode enables Windows Input Mode (win32-input-mode) which -// allows for more advanced input handling and reporting. This is experimental -// and may not work on all terminals. +// WithKeyboardEnhancements enables support for enhanced keyboard features. This +// unambiguously reports more key combinations than traditional terminal +// keyboard sequences. This might also enable reporting of release key events +// depending on the terminal emulator supporting it. // -// See -// https://github.com/microsoft/terminal/blob/main/doc/specs/%234999%20-%20Improved%20keyboard%20handling%20in%20Conpty.md -// for more information. -func _WithWindowsInputMode() ProgramOption { //nolint:unused +// This is a syntactic sugar for WithKittyKeyboard(1) and WithModifyOtherKeys(1). +func WithKeyboardEnhancements(enhancements ...KeyboardEnhancement) ProgramOption { + ke := keyboardEnhancements{kittyFlags: ansi.KittyDisambiguateEscapeCodes, modifyOtherKeys: 1} + for _, e := range enhancements { + e(&ke) + } return func(p *Program) { - p.startupOptions |= withWindowsInputMode + p.startupOptions |= withKeyboardEnhancements + p.keyboard = ke } } diff --git a/parse.go b/parse.go index 46e9db7e74..06c2581ee2 100644 --- a/parse.go +++ b/parse.go @@ -250,7 +250,7 @@ func parseCsi(b []byte) (int, Msg) { case 'u' | '?'<'<u\x1b[>3u\rsuccess\r\n\x1b[D\x1b[2K\r\x1b[?2004l\x1b[?25h\x1b[>0u", + cmds: []Cmd{DisableKeyboardEnhancements, EnableKeyboardEnhancements(WithReleaseKeys)}, + expected: "\x1b[?25l\x1b[?2004h\x1b[?2027h\x1b[?2027$p\x1b[>4;1m\x1b[>3u\rsuccess\r\n\x1b[D\x1b[2K\r\x1b[?2004l\x1b[?25h\x1b[>4;0m\x1b[>0u", }, } diff --git a/tea.go b/tea.go index bf58d831e2..35a5dadcb1 100644 --- a/tea.go +++ b/tea.go @@ -101,9 +101,7 @@ const ( withoutCatchPanics withoutBracketedPaste withReportFocus - withKittyKeyboard - withModifyOtherKeys - withWindowsInputMode + withKeyboardEnhancements withoutGraphemeClustering ) @@ -204,11 +202,7 @@ type Program struct { // rendererDone is used to stop the renderer. rendererDone chan struct{} - // kittyFlags stores kitty keyboard protocol progressive enhancement flags. - kittyFlags int - - // modifyOtherKeys stores the XTerm modifyOtherKeys mode. - modifyOtherKeys int + keyboard keyboardEnhancements // When a program is suspended, the terminal state is saved and the program // is paused. This saves the terminal colors state so they can be restored @@ -475,34 +469,41 @@ func (p *Program) eventLoop(model Model, cmds chan Cmd) (Model, error) { case cursorColorMsg: p.execute(ansi.RequestCursorColor) - case KittyKeyboardMsg: - // Store the kitty flags whenever they are queried. - p.kittyFlags = int(msg) - - case setKittyKeyboardFlagsMsg: - p.kittyFlags = int(msg) - p.execute(ansi.PushKittyKeyboard(p.kittyFlags)) + case KeyboardEnhancementsMsg: + if msg.kittyFlags != p.keyboard.kittyFlags { + msg.kittyFlags |= p.keyboard.kittyFlags + } + if msg.modifyOtherKeys == 0 { + msg.modifyOtherKeys = p.keyboard.modifyOtherKeys + } - case kittyKeyboardMsg: - p.execute(ansi.RequestKittyKeyboard) + case enableKeyboardEnhancementsMsg: + var ke keyboardEnhancements + for _, e := range msg { + e(&ke) + } - case modifyOtherKeys: - p.execute(ansi.RequestModifyOtherKeys) + p.keyboard.kittyFlags |= ke.kittyFlags + if ke.modifyOtherKeys > p.keyboard.modifyOtherKeys { + p.keyboard.modifyOtherKeys = ke.modifyOtherKeys + } - case setModifyOtherKeysMsg: - p.modifyOtherKeys = int(msg) - p.execute(ansi.ModifyOtherKeys(p.modifyOtherKeys)) + if p.keyboard.modifyOtherKeys > 0 { + p.execute(ansi.ModifyOtherKeys(p.keyboard.modifyOtherKeys)) + } + if p.keyboard.kittyFlags > 0 { + p.execute(ansi.PushKittyKeyboard(p.keyboard.kittyFlags)) + } - case setEnhancedKeyboardMsg: - if bool(msg) { - p.kittyFlags = 3 - p.modifyOtherKeys = 1 - } else { - p.kittyFlags = 0 - p.modifyOtherKeys = 0 + case disableKeyboardEnhancementsMsg: + if p.keyboard.modifyOtherKeys > 0 { + p.execute(ansi.DisableModifyOtherKeys) + p.keyboard.modifyOtherKeys = 0 + } + if p.keyboard.kittyFlags > 0 { + p.execute(ansi.DisableKittyKeyboard) + p.keyboard.kittyFlags = 0 } - p.execute(ansi.ModifyOtherKeys(p.modifyOtherKeys)) - p.execute(ansi.PushKittyKeyboard(p.kittyFlags)) case execMsg: // NB: this blocks. @@ -705,20 +706,20 @@ func (p *Program) Run() (Model, error) { p.modes[ansi.MouseAllMotionMode] = true p.modes[ansi.MouseSgrExtMode] = true } - if p.startupOptions&withModifyOtherKeys != 0 { - p.execute(ansi.ModifyOtherKeys(p.modifyOtherKeys)) - } - if p.startupOptions&withKittyKeyboard != 0 { - p.execute(ansi.PushKittyKeyboard(p.kittyFlags)) - } if p.startupOptions&withReportFocus != 0 { p.execute(ansi.EnableReportFocus) p.modes[ansi.ReportFocusMode] = true } - if p.startupOptions&withWindowsInputMode != 0 { - p.execute(ansi.EnableWin32Input) - p.modes[ansi.Win32InputMode] = true + if p.startupOptions&withKeyboardEnhancements != 0 { + if p.keyboard.modifyOtherKeys > 0 { + p.execute(ansi.ModifyOtherKeys(p.keyboard.modifyOtherKeys)) + p.execute(ansi.RequestModifyOtherKeys) + } + if p.keyboard.kittyFlags > 0 { + p.execute(ansi.PushKittyKeyboard(p.keyboard.kittyFlags)) + p.execute(ansi.RequestKittyKeyboard) + } } // Start the renderer. @@ -913,11 +914,11 @@ func (p *Program) RestoreTerminal() error { if p.modes[ansi.BracketedPasteMode] { p.execute(ansi.EnableBracketedPaste) } - if p.modifyOtherKeys != 0 { - p.execute(ansi.ModifyOtherKeys(p.modifyOtherKeys)) + if p.keyboard.modifyOtherKeys != 0 { + p.execute(ansi.ModifyOtherKeys(p.keyboard.modifyOtherKeys)) } - if p.kittyFlags != 0 { - p.execute(ansi.PushKittyKeyboard(p.kittyFlags)) + if p.keyboard.kittyFlags != 0 { + p.execute(ansi.PushKittyKeyboard(p.keyboard.kittyFlags)) } if p.modes[ansi.ReportFocusMode] { p.execute(ansi.EnableReportFocus) diff --git a/tty.go b/tty.go index 464d7a4eec..97b987321b 100644 --- a/tty.go +++ b/tty.go @@ -71,10 +71,10 @@ func (p *Program) restoreTerminalState() error { p.execute(ansi.DisableMouseAllMotion) p.execute(ansi.DisableMouseSgrExt) } - if p.modifyOtherKeys != 0 { + if p.keyboard.modifyOtherKeys != 0 { p.execute(ansi.DisableModifyOtherKeys) } - if p.kittyFlags != 0 { + if p.keyboard.kittyFlags != 0 { p.execute(ansi.DisableKittyKeyboard) } if p.modes[ansi.ReportFocusMode] { diff --git a/xterm.go b/xterm.go index dc7efe9f69..b6140b5eab 100644 --- a/xterm.go +++ b/xterm.go @@ -4,29 +4,6 @@ import ( "github.com/charmbracelet/x/ansi" ) -// setModifyOtherKeysMsg is a message to set XTerm modifyOtherKeys mode. -type setModifyOtherKeysMsg int - -// EnableXtermModifyOtherKeys is a command to enable XTerm modifyOtherKeys mode. -// -// The mode can be on of the following: -// -// 1: Report ambiguous keys as escape codes -// 2: Report ambiguous keys as escape codes including modified keys like Alt- -// and Meta- -// -// See https://invisible-island.net/xterm/manpage/xterm.html#VT100-Widget-Resources:modifyOtherKeys -func EnableModifyOtherKeys(mode int) Cmd { //nolint:unused - return func() Msg { - return setModifyOtherKeysMsg(mode) - } -} - -// DisableModifyOtherKeys is a command to disable XTerm modifyOtherKeys mode. -func DisableModifyOtherKeys() Msg { //nolint:unused - return setModifyOtherKeysMsg(0) -} - func parseXTermModifyOtherKeys(csi *ansi.CsiSequence) Msg { // XTerm modify other keys starts with ESC [ 27 ; ; ~ mod := KeyMod(csi.Param(1) - 1) @@ -54,28 +31,6 @@ func parseXTermModifyOtherKeys(csi *ansi.CsiSequence) Msg { return k } -// modifyOtherKeys is an internal message that queries the terminal for its -// modifyOtherKeys mode. -type modifyOtherKeys struct{} - -// ModifyOtherKeys is a command that queries the terminal for its -// modifyOtherKeys mode. -func ModifyOtherKeys() Msg { //nolint:unused - return modifyOtherKeys{} -} - -// ModifyOtherKeysMsg is a message that represents XTerm modifyOtherKeys -// report. Querying the terminal for the modifyOtherKeys mode will return a -// ModifyOtherKeysMsg message with the current mode set. -// -// 0: disable -// 1: enable mode 1 -// 2: enable mode 2 -// -// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Functions-using-CSI-_-ordered-by-the-final-character_s_ -// See: https://invisible-island.net/xterm/manpage/xterm.html#VT100-Widget-Resources:modifyOtherKeys -type ModifyOtherKeysMsg int - // TerminalVersionMsg is a message that represents the terminal version. type TerminalVersionMsg string From d519ade7c41845473c3231c43cea54d63552cbf4 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Tue, 17 Sep 2024 16:59:48 -0400 Subject: [PATCH 2/8] Update options.go Co-authored-by: Christian Rocha --- options.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/options.go b/options.go index e1aea30d91..be86d903d6 100644 --- a/options.go +++ b/options.go @@ -264,8 +264,9 @@ type keyboardEnhancements struct { // ctrl+i and tab, ctrl+[ and escape, ctrl+space and ctrl+@, etc. // - 2: [ansi.ReportEventTypes] Report event types such as key presses, // releases, and repeat events. - // - 4: [ansi.ReportAlternateKeys] Report alternate keys such as shifted - // keys and PC-101 ANSI US keyboard layout. + // - 4: [ansi.ReportAlternateKeys] Report keypresses as though they were on a + // PC-101 ANSI US keyboard layout regardless of what they layout actually is. + // Also include information about whether or not is enabled, // - 8: [ansi.ReportAllKeysAsEscapeCodes] Report all key events as escape // codes. This includes simple printable keys like "a" and other Unicode // characters. From ec900963e060f4c079323038f7b5fc24694f903f Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Tue, 17 Sep 2024 17:06:57 -0400 Subject: [PATCH 3/8] chore: go mod tidy --- examples/go.mod | 2 +- examples/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/go.mod b/examples/go.mod index 0e8d888f65..81ceece00a 100644 --- a/examples/go.mod +++ b/examples/go.mod @@ -22,7 +22,7 @@ require ( github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/aymanbagabas/go-udiff v0.2.0 // indirect github.com/aymerick/douceur v0.2.0 // indirect - github.com/charmbracelet/x/ansi v0.3.1 // indirect + github.com/charmbracelet/x/ansi v0.3.2 // indirect github.com/charmbracelet/x/exp/golden v0.0.0-20240815200342-61de596daa2b // indirect github.com/charmbracelet/x/term v0.2.0 // indirect github.com/charmbracelet/x/windows v0.2.0 // indirect diff --git a/examples/go.sum b/examples/go.sum index 39015a0f11..969dd9593e 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -22,8 +22,8 @@ github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao= github.com/charmbracelet/lipgloss v0.13.0 h1:4X3PPeoWEDCMvzDvGmTajSyYPcZM4+y8sCA/SsA3cjw= github.com/charmbracelet/lipgloss v0.13.0/go.mod h1:nw4zy0SBX/F/eAO1cWdcvy6qnkDUxr8Lw7dvFrAIbbY= -github.com/charmbracelet/x/ansi v0.3.1 h1:CRO6lc/6HCx2/D6S/GZ87jDvRvk6GtPyFP+IljkNtqI= -github.com/charmbracelet/x/ansi v0.3.1/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw= +github.com/charmbracelet/x/ansi v0.3.2 h1:wsEwgAN+C9U06l9dCVMX0/L3x7ptvY1qmjMwyfE6USY= +github.com/charmbracelet/x/ansi v0.3.2/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw= github.com/charmbracelet/x/exp/golden v0.0.0-20240815200342-61de596daa2b h1:MnAMdlwSltxJyULnrYbkZpp4k58Co7Tah3ciKhSNo0Q= github.com/charmbracelet/x/exp/golden v0.0.0-20240815200342-61de596daa2b/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= github.com/charmbracelet/x/exp/teatest v0.0.0-20240912153648-d6041061ead9 h1:2q41wBBupsw4GATbF/C3NZ3JtccZ/iYu7cL3doHOnuo= From f369e23daf2340246f9d88afe590013a17d637e7 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Tue, 17 Sep 2024 17:09:01 -0400 Subject: [PATCH 4/8] chore: gofumpt --- options.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/options.go b/options.go index be86d903d6..37ed1995a0 100644 --- a/options.go +++ b/options.go @@ -264,9 +264,9 @@ type keyboardEnhancements struct { // ctrl+i and tab, ctrl+[ and escape, ctrl+space and ctrl+@, etc. // - 2: [ansi.ReportEventTypes] Report event types such as key presses, // releases, and repeat events. - // - 4: [ansi.ReportAlternateKeys] Report keypresses as though they were on a - // PC-101 ANSI US keyboard layout regardless of what they layout actually is. - // Also include information about whether or not is enabled, + // - 4: [ansi.ReportAlternateKeys] Report keypresses as though they were + // on a PC-101 ANSI US keyboard layout regardless of what they layout + // actually is. Also include information about whether or not is enabled, // - 8: [ansi.ReportAllKeysAsEscapeCodes] Report all key events as escape // codes. This includes simple printable keys like "a" and other Unicode // characters. From 0e88b9d93a97a43056f1e39df986b1f97f1e8439 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Tue, 17 Sep 2024 17:36:29 -0400 Subject: [PATCH 5/8] fix: simplify keyboard enhancements disambiguation --- commands.go | 8 +------ keyboard.go | 58 +++++++++++++++++++++++++++++++++++++++++++++++++++ options.go | 60 +++++++---------------------------------------------- 3 files changed, 66 insertions(+), 60 deletions(-) create mode 100644 keyboard.go diff --git a/commands.go b/commands.go index 199ce95dcf..f85707ee0a 100644 --- a/commands.go +++ b/commands.go @@ -222,14 +222,8 @@ type enableKeyboardEnhancementsMsg []KeyboardEnhancement // EnableKeyboardEnhancements is a command that enables keyboard enhancements // in the terminal. func EnableKeyboardEnhancements(enhancements ...KeyboardEnhancement) Cmd { - enhancements = append(enhancements, func(k *keyboardEnhancements) { - k.kittyFlags |= ansi.KittyDisambiguateEscapeCodes - if k.modifyOtherKeys < 1 { - k.modifyOtherKeys = 1 - } - }) return func() Msg { - return enableKeyboardEnhancementsMsg(enhancements) + return enableKeyboardEnhancementsMsg(append(enhancements, withDisambiguousKeys)) } } diff --git a/keyboard.go b/keyboard.go new file mode 100644 index 0000000000..7769b711b3 --- /dev/null +++ b/keyboard.go @@ -0,0 +1,58 @@ +package tea + +import "github.com/charmbracelet/x/ansi" + +// keyboardEnhancements is a type that represents a set of keyboard +// enhancements. +type keyboardEnhancements struct { + // Kitty progressive keyboard enhancements protocol. This can be used to + // enable different keyboard features. + // + // - 0: disable all features + // - 1: [ansi.DisambiguateEscapeCodes] Disambiguate escape codes such as + // ctrl+i and tab, ctrl+[ and escape, ctrl+space and ctrl+@, etc. + // - 2: [ansi.ReportEventTypes] Report event types such as key presses, + // releases, and repeat events. + // - 4: [ansi.ReportAlternateKeys] Report keypresses as though they were + // on a PC-101 ANSI US keyboard layout regardless of what they layout + // actually is. Also include information about whether or not is enabled, + // - 8: [ansi.ReportAllKeysAsEscapeCodes] Report all key events as escape + // codes. This includes simple printable keys like "a" and other Unicode + // characters. + // - 16: [ansi.ReportAssociatedText] Report associated text with key + // events. This encodes multi-rune key events as escape codes instead of + // individual runes. + // + kittyFlags int + + // Xterm modifyOtherKeys feature. + // + // - Mode 0 disables modifyOtherKeys. + // - Mode 1 reports ambiguous keys as escape codes. This is similar to + // [ansi.KittyDisambiguateEscapeCodes] but uses XTerm escape codes. + // - Mode 2 reports all key as escape codes including printable keys like "a" and "shift+b". + modifyOtherKeys int +} + +// KeyboardEnhancement is a type that represents a keyboard enhancement. +type KeyboardEnhancement func(k *keyboardEnhancements) + +// WithReleaseKeys enables support for reporting release key events. This is +// useful for terminals that support the Kitty keyboard protocol "Report event +// types" progressive enhancement feature. +// +// Note that not all terminals support this feature. +func WithReleaseKeys(k *keyboardEnhancements) { + k.kittyFlags |= ansi.KittyReportEventTypes +} + +// withDisambiguousKeys enables support for disambiguating keyboard escape +// codes. This is useful for terminals that support the Kitty keyboard protocol +// "Disambiguate escape codes" progressive enhancement feature or the XTerm +// modifyOtherKeys mode 1 feature to report ambiguous keys as escape codes. +func withDisambiguousKeys(k *keyboardEnhancements) { + k.kittyFlags |= ansi.KittyDisambiguateEscapeCodes + if k.modifyOtherKeys < 1 { + k.modifyOtherKeys = 1 + } +} diff --git a/options.go b/options.go index 37ed1995a0..7e8692f825 100644 --- a/options.go +++ b/options.go @@ -4,8 +4,6 @@ import ( "context" "io" "sync/atomic" - - "github.com/charmbracelet/x/ansi" ) // ProgramOption is used to set options when initializing a Program. Program can @@ -253,59 +251,15 @@ func WithReportFocus() ProgramOption { } } -// keyboardEnhancements is a type that represents a set of keyboard -// enhancements. -type keyboardEnhancements struct { - // Kitty progressive keyboard enhancements protocol. This can be used to - // enable different keyboard features. - // - // - 0: disable all features - // - 1: [ansi.DisambiguateEscapeCodes] Disambiguate escape codes such as - // ctrl+i and tab, ctrl+[ and escape, ctrl+space and ctrl+@, etc. - // - 2: [ansi.ReportEventTypes] Report event types such as key presses, - // releases, and repeat events. - // - 4: [ansi.ReportAlternateKeys] Report keypresses as though they were - // on a PC-101 ANSI US keyboard layout regardless of what they layout - // actually is. Also include information about whether or not is enabled, - // - 8: [ansi.ReportAllKeysAsEscapeCodes] Report all key events as escape - // codes. This includes simple printable keys like "a" and other Unicode - // characters. - // - 16: [ansi.ReportAssociatedText] Report associated text with key - // events. This encodes multi-rune key events as escape codes instead of - // individual runes. - // - kittyFlags int - - // Xterm modifyOtherKeys feature. - // - // - Mode 0 disables modifyOtherKeys. - // - Mode 1 reports ambiguous keys as escape codes. This is similar to - // [ansi.KittyDisambiguateEscapeCodes] but uses XTerm escape codes. - // - Mode 2 reports all key as escape codes including printable keys like "a" and "shift+b". - modifyOtherKeys int -} - -// KeyboardEnhancement is a type that represents a keyboard enhancement. -type KeyboardEnhancement func(k *keyboardEnhancements) - -// WithReleaseKeys enables support for reporting release key events. This is -// useful for terminals that support the Kitty keyboard protocol "Report event -// types" progressive enhancement feature. -// -// Note that not all terminals support this feature. -func WithReleaseKeys(k *keyboardEnhancements) { - k.kittyFlags |= ansi.KittyReportEventTypes -} - -// WithKeyboardEnhancements enables support for enhanced keyboard features. This -// unambiguously reports more key combinations than traditional terminal -// keyboard sequences. This might also enable reporting of release key events -// depending on the terminal emulator supporting it. +// WithKeyboardEnhancements enables support for enhanced keyboard features. You +// can enable different keyboard features by passing one or more +// KeyboardEnhancement functions. // -// This is a syntactic sugar for WithKittyKeyboard(1) and WithModifyOtherKeys(1). +// This is not supported on all terminals. On Windows, these features are +// enabled by default. func WithKeyboardEnhancements(enhancements ...KeyboardEnhancement) ProgramOption { - ke := keyboardEnhancements{kittyFlags: ansi.KittyDisambiguateEscapeCodes, modifyOtherKeys: 1} - for _, e := range enhancements { + var ke keyboardEnhancements + for _, e := range append(enhancements, withDisambiguousKeys) { e(&ke) } return func(p *Program) { From c80b0d21421c6b11d646a58507dbf2272c8a522a Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Tue, 17 Sep 2024 17:45:33 -0400 Subject: [PATCH 6/8] feat: make KeyboardEnhancementsMsg helpers always return true on Windows --- commands.go | 30 ------------------------------ keyboard.go | 48 +++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 47 insertions(+), 31 deletions(-) diff --git a/commands.go b/commands.go index f85707ee0a..bfa3b70455 100644 --- a/commands.go +++ b/commands.go @@ -2,8 +2,6 @@ package tea import ( "time" - - "github.com/charmbracelet/x/ansi" ) // Batch performs a bunch of commands concurrently with no ordering guarantees @@ -216,31 +214,3 @@ func WindowSize() Cmd { return windowSizeMsg{} } } - -type enableKeyboardEnhancementsMsg []KeyboardEnhancement - -// EnableKeyboardEnhancements is a command that enables keyboard enhancements -// in the terminal. -func EnableKeyboardEnhancements(enhancements ...KeyboardEnhancement) Cmd { - return func() Msg { - return enableKeyboardEnhancementsMsg(append(enhancements, withDisambiguousKeys)) - } -} - -type disableKeyboardEnhancementsMsg struct{} - -// DisableKeyboardEnhancements is a command that disables keyboard enhancements -// in the terminal. -func DisableKeyboardEnhancements() Msg { - return disableKeyboardEnhancementsMsg{} -} - -// KeyboardEnhancementsMsg is a message that gets sent when the terminal -// supports keyboard enhancements. -type KeyboardEnhancementsMsg keyboardEnhancements - -// SupportsReleaseKeys returns whether the terminal supports key release -// events. -func (k KeyboardEnhancementsMsg) SupportsReleaseKeys() bool { - return k.kittyFlags&ansi.KittyReportEventTypes != 0 -} diff --git a/keyboard.go b/keyboard.go index 7769b711b3..a36499283a 100644 --- a/keyboard.go +++ b/keyboard.go @@ -1,6 +1,10 @@ package tea -import "github.com/charmbracelet/x/ansi" +import ( + "runtime" + + "github.com/charmbracelet/x/ansi" +) // keyboardEnhancements is a type that represents a set of keyboard // enhancements. @@ -56,3 +60,45 @@ func withDisambiguousKeys(k *keyboardEnhancements) { k.modifyOtherKeys = 1 } } + +type enableKeyboardEnhancementsMsg []KeyboardEnhancement + +// EnableKeyboardEnhancements is a command that enables keyboard enhancements +// in the terminal. +func EnableKeyboardEnhancements(enhancements ...KeyboardEnhancement) Cmd { + return func() Msg { + return enableKeyboardEnhancementsMsg(append(enhancements, withDisambiguousKeys)) + } +} + +type disableKeyboardEnhancementsMsg struct{} + +// DisableKeyboardEnhancements is a command that disables keyboard enhancements +// in the terminal. +func DisableKeyboardEnhancements() Msg { + return disableKeyboardEnhancementsMsg{} +} + +// KeyboardEnhancementsMsg is a message that gets sent when the terminal +// supports keyboard enhancements. +type KeyboardEnhancementsMsg keyboardEnhancements + +// SupportsDisambiguousKeys returns whether the terminal supports reporting +// disambiguous keys as escape codes. +func (k KeyboardEnhancementsMsg) SupportsDisambiguousKeys() bool { + if runtime.GOOS == "windows" { + // We use Windows Console API which supports reporting disambiguous keys. + return true + } + return k.kittyFlags&ansi.KittyDisambiguateEscapeCodes != 0 || k.modifyOtherKeys >= 1 +} + +// SupportsReleaseKeys returns whether the terminal supports key release +// events. +func (k KeyboardEnhancementsMsg) SupportsReleaseKeys() bool { + if runtime.GOOS == "windows" { + // We use Windows Console API which supports key release events. + return true + } + return k.kittyFlags&ansi.KittyReportEventTypes != 0 +} From 3165f9d6b61cccbfb356028bbe09ada82cbcb96c Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Wed, 18 Sep 2024 10:38:55 -0400 Subject: [PATCH 7/8] fix: remove unused win32input mode --- win32input.go | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/win32input.go b/win32input.go index a26bd40955..5dac0f0d30 100644 --- a/win32input.go +++ b/win32input.go @@ -2,30 +2,8 @@ package tea import ( "unicode" - - "github.com/charmbracelet/x/ansi" ) -// EnableWindowsInputMode is a command that enables Windows input mode -// (win32-input-mode). -// -// See -// https://github.com/microsoft/terminal/blob/main/doc/specs/%234999%20-%20Improved%20keyboard%20handling%20in%20Conpty.md -// for more information. -func EnableWindowsInputMode() Msg { //nolint:unused - return enableModeMsg(ansi.Win32InputMode) -} - -// DisableWindowsInputMode is a command that disables Windows input mode -// (win32-input-mode). -// -// See -// https://github.com/microsoft/terminal/blob/main/doc/specs/%234999%20-%20Improved%20keyboard%20handling%20in%20Conpty.md -// for more information. -func DisableWindowsInputMode() Msg { //nolint:unused - return disableModeMsg(ansi.Win32InputMode) -} - func parseWin32InputKeyEvent(vkc uint16, _ uint16, r rune, keyDown bool, cks uint32, repeatCount uint16) Msg { var key Key isCtrl := cks&(_LEFT_CTRL_PRESSED|_RIGHT_CTRL_PRESSED) != 0 From c751d8482ea9c039eabf9bb52a96cdf8eb78dea6 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Wed, 18 Sep 2024 10:49:01 -0400 Subject: [PATCH 8/8] refactor: rename KeyboardEnhancementsMsg methods --- examples/print-key/main.go | 4 ++-- keyboard.go | 18 +++++++++--------- options.go | 2 +- screen_test.go | 2 +- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/examples/print-key/main.go b/examples/print-key/main.go index 7369e1ba64..40c7de22ce 100644 --- a/examples/print-key/main.go +++ b/examples/print-key/main.go @@ -15,7 +15,7 @@ func (m model) Init() (tea.Model, tea.Cmd) { func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.KeyboardEnhancementsMsg: - return m, tea.Printf("Keyboard enhancements enabled! ReleaseKeys: %v\n", msg.SupportsReleaseKeys()) + return m, tea.Printf("Keyboard enhancements enabled! ReleaseKeys: %v\n", msg.SupportsKeyReleases()) case tea.KeyMsg: switch msg := msg.(type) { case tea.KeyPressMsg: @@ -34,7 +34,7 @@ func (m model) View() string { } func main() { - p := tea.NewProgram(model{}, tea.WithKeyboardEnhancements(tea.WithReleaseKeys)) + p := tea.NewProgram(model{}, tea.WithKeyboardEnhancements(tea.WithKeyReleases)) if _, err := p.Run(); err != nil { log.Printf("Error running program: %v", err) } diff --git a/keyboard.go b/keyboard.go index a36499283a..89f9605f40 100644 --- a/keyboard.go +++ b/keyboard.go @@ -41,20 +41,20 @@ type keyboardEnhancements struct { // KeyboardEnhancement is a type that represents a keyboard enhancement. type KeyboardEnhancement func(k *keyboardEnhancements) -// WithReleaseKeys enables support for reporting release key events. This is +// WithKeyReleases enables support for reporting release key events. This is // useful for terminals that support the Kitty keyboard protocol "Report event // types" progressive enhancement feature. // // Note that not all terminals support this feature. -func WithReleaseKeys(k *keyboardEnhancements) { +func WithKeyReleases(k *keyboardEnhancements) { k.kittyFlags |= ansi.KittyReportEventTypes } -// withDisambiguousKeys enables support for disambiguating keyboard escape +// withKeyDisambiguation enables support for disambiguating keyboard escape // codes. This is useful for terminals that support the Kitty keyboard protocol // "Disambiguate escape codes" progressive enhancement feature or the XTerm // modifyOtherKeys mode 1 feature to report ambiguous keys as escape codes. -func withDisambiguousKeys(k *keyboardEnhancements) { +func withKeyDisambiguation(k *keyboardEnhancements) { k.kittyFlags |= ansi.KittyDisambiguateEscapeCodes if k.modifyOtherKeys < 1 { k.modifyOtherKeys = 1 @@ -67,7 +67,7 @@ type enableKeyboardEnhancementsMsg []KeyboardEnhancement // in the terminal. func EnableKeyboardEnhancements(enhancements ...KeyboardEnhancement) Cmd { return func() Msg { - return enableKeyboardEnhancementsMsg(append(enhancements, withDisambiguousKeys)) + return enableKeyboardEnhancementsMsg(append(enhancements, withKeyDisambiguation)) } } @@ -83,9 +83,9 @@ func DisableKeyboardEnhancements() Msg { // supports keyboard enhancements. type KeyboardEnhancementsMsg keyboardEnhancements -// SupportsDisambiguousKeys returns whether the terminal supports reporting +// SupportsKeyDisambiguation returns whether the terminal supports reporting // disambiguous keys as escape codes. -func (k KeyboardEnhancementsMsg) SupportsDisambiguousKeys() bool { +func (k KeyboardEnhancementsMsg) SupportsKeyDisambiguation() bool { if runtime.GOOS == "windows" { // We use Windows Console API which supports reporting disambiguous keys. return true @@ -93,9 +93,9 @@ func (k KeyboardEnhancementsMsg) SupportsDisambiguousKeys() bool { return k.kittyFlags&ansi.KittyDisambiguateEscapeCodes != 0 || k.modifyOtherKeys >= 1 } -// SupportsReleaseKeys returns whether the terminal supports key release +// SupportsKeyReleases returns whether the terminal supports key release // events. -func (k KeyboardEnhancementsMsg) SupportsReleaseKeys() bool { +func (k KeyboardEnhancementsMsg) SupportsKeyReleases() bool { if runtime.GOOS == "windows" { // We use Windows Console API which supports key release events. return true diff --git a/options.go b/options.go index 7e8692f825..d1cb5320f8 100644 --- a/options.go +++ b/options.go @@ -259,7 +259,7 @@ func WithReportFocus() ProgramOption { // enabled by default. func WithKeyboardEnhancements(enhancements ...KeyboardEnhancement) ProgramOption { var ke keyboardEnhancements - for _, e := range append(enhancements, withDisambiguousKeys) { + for _, e := range append(enhancements, withKeyDisambiguation) { e(&ke) } return func(p *Program) { diff --git a/screen_test.go b/screen_test.go index 81c683a3e0..2555264aee 100644 --- a/screen_test.go +++ b/screen_test.go @@ -74,7 +74,7 @@ func TestClearMsg(t *testing.T) { }, { name: "kitty_start", - cmds: []Cmd{DisableKeyboardEnhancements, EnableKeyboardEnhancements(WithReleaseKeys)}, + cmds: []Cmd{DisableKeyboardEnhancements, EnableKeyboardEnhancements(WithKeyReleases)}, expected: "\x1b[?25l\x1b[?2004h\x1b[?2027h\x1b[?2027$p\x1b[>4;1m\x1b[>3u\rsuccess\r\n\x1b[D\x1b[2K\r\x1b[?2004l\x1b[?25h\x1b[>4;0m\x1b[>0u", }, }