From 0126cafd1d21f7d93a0f15b6f035c436c117422e Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Tue, 13 Aug 2024 16:57:17 -0400 Subject: [PATCH 01/12] feat: add kitty keyboard options and settings This adds the necessary options to enable/disable the kitty keyboard protocol. Needs: https://github.com/charmbracelet/bubbletea/pull/1080 Fixes: https://github.com/charmbracelet/bubbletea/issues/869 --- go.mod | 2 +- go.sum | 4 ++++ kitty.go | 41 +++++++++++++++++++++++++++++++++++++++++ options.go | 30 ++++++++++++++++++++++++++++++ tea.go | 14 ++++++++++++++ tty.go | 3 +++ 6 files changed, 93 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 34bcfa4f9a..e84a49b908 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/charmbracelet/bubbletea go 1.18 require ( - github.com/charmbracelet/x/ansi v0.1.4 + github.com/charmbracelet/x/ansi v0.1.5-0.20240814155920-d72bfb0444d7 github.com/charmbracelet/x/term v0.1.1 github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 diff --git a/go.sum b/go.sum index afe8f921d0..29768a7219 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,9 @@ github.com/charmbracelet/x/ansi v0.1.4 h1:IEU3D6+dWwPSgZ6HBH+v6oUuZ/nVawMiWj5831KfiLM= github.com/charmbracelet/x/ansi v0.1.4/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw= +github.com/charmbracelet/x/ansi v0.1.5-0.20240813204730-a29dab672bf2 h1:/BRVAHphENSsvuYG+iVP/qgC6Bjjc9zdOmY3pQzg7mE= +github.com/charmbracelet/x/ansi v0.1.5-0.20240813204730-a29dab672bf2/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw= +github.com/charmbracelet/x/ansi v0.1.5-0.20240814155920-d72bfb0444d7 h1:wcBKMnW8Fm0Oiw6b7pTXe+5eCntEuKI6s82J41nZcEY= +github.com/charmbracelet/x/ansi v0.1.5-0.20240814155920-d72bfb0444d7/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw= github.com/charmbracelet/x/input v0.1.0 h1:TEsGSfZYQyOtp+STIjyBq6tpRaorH0qpwZUj8DavAhQ= github.com/charmbracelet/x/input v0.1.0/go.mod h1:ZZwaBxPF7IG8gWWzPUVqHEtWhc1+HXJPNuerJGRGZ28= github.com/charmbracelet/x/term v0.1.1 h1:3cosVAiPOig+EV4X9U+3LDgtwwAoEzJjNdwbXDjF6yI= diff --git a/kitty.go b/kitty.go index ff6279d78d..af63cd8c77 100644 --- a/kitty.go +++ b/kitty.go @@ -7,6 +7,47 @@ 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 +// enhancement protocol. +// +// The flags parameter is a bitmask of 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. +func EnableKittyKeyboard(flags int) Cmd { + return func() Msg { + return setKittyKeyboardFlagsMsg(flags) + } +} + +// DisableKittyKeyboard is a command to disable Kitty keyboard progressive +// enhancement protocol. +func DisableKittyKeyboard() Msg { + return setKittyKeyboardFlagsMsg(0) +} + +// EnableEnhancedKeyboard is a command to enable enhanced keyboard features. +// This unambiguously reports more key combinations than traditional terminal +// keyboard sequences. This will also enable reporting of release key events. +func EnableEnhancedKeyboard() Msg { + return setKittyKeyboardFlagsMsg(3) +} + +// DisableEnhancedKeyboard is a command to disable enhanced keyboard features. +func DisableEnhancedKeyboard() Msg { + return setKittyKeyboardFlagsMsg(0) +} + // KittyKeyboardMsg represents Kitty keyboard progressive enhancement flags message. type KittyKeyboardMsg int diff --git a/options.go b/options.go index a810fe1c7b..cb2ee41424 100644 --- a/options.go +++ b/options.go @@ -234,3 +234,33 @@ func WithFPS(fps int) ProgramOption { p.fps = fps } } + +// WithEnhancedKeyboard enables support for enhanced keyboard features. This +// unambiguously reports more key combinations than traditional terminal +// keyboard sequences. This will also enable reporting of release key events. +// +// This is a syntactic sugar for WithKittyKeyboard(3). +func WithEnhancedKeyboard() ProgramOption { + return WithKittyKeyboard(3) +} + +// 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 + } +} diff --git a/tea.go b/tea.go index d9b95a6d2f..073d363a38 100644 --- a/tea.go +++ b/tea.go @@ -97,6 +97,7 @@ const ( // feature is on by default. withoutCatchPanics withoutBracketedPaste + withKittyKeyboard ) // channelHandlers manages the series of channels returned by various processes. @@ -173,6 +174,9 @@ type Program struct { // fps is the frames per second we should set on the renderer, if // applicable, fps int + + // kittyFlags stores kitty keyboard protocol progressive enhancement flags. + kittyFlags int } // Quit is a special command that tells the Bubble Tea program to exit. @@ -392,6 +396,10 @@ func (p *Program) eventLoop(model Model, cmds chan Cmd) (Model, error) { p.renderer.execute(ansi.DisableBracketedPaste) p.bpActive = false + case setKittyKeyboardFlagsMsg: + p.kittyFlags = int(msg) + p.renderer.execute(ansi.PushKittyKeyboard(p.kittyFlags)) + case execMsg: // NB: this blocks. p.exec(msg.cmd, msg.fn) @@ -556,6 +564,9 @@ func (p *Program) Run() (Model, error) { p.renderer.execute(ansi.EnableMouseAllMotion) p.renderer.execute(ansi.EnableMouseSgrExt) } + if p.startupOptions&withKittyKeyboard != 0 { + p.renderer.execute(ansi.PushKittyKeyboard(p.kittyFlags)) + } // Start the renderer. p.renderer.start() @@ -727,6 +738,9 @@ func (p *Program) RestoreTerminal() error { p.renderer.hideCursor() p.renderer.execute(ansi.EnableBracketedPaste) p.bpActive = true + if p.kittyFlags != 0 { + p.renderer.execute(ansi.PushKittyKeyboard(p.kittyFlags)) + } } // If the output is a terminal, it may have been resized while another diff --git a/tty.go b/tty.go index 9d5612cfbb..52c1e1f4d9 100644 --- a/tty.go +++ b/tty.go @@ -37,6 +37,9 @@ func (p *Program) restoreTerminalState() error { p.bpActive = false p.renderer.showCursor() p.disableMouse() + if p.kittyFlags != 0 { + p.renderer.execute(ansi.DisableKittyKeyboard) + } if p.renderer.altScreen() { p.renderer.exitAltScreen() From bdb32370f6a11f6d3cea67dc11763a7bca9b42a1 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Wed, 14 Aug 2024 12:02:56 -0400 Subject: [PATCH 02/12] fix: kitty: request protocol flags and rename flag methods --- kitty.go | 30 ++++++++++++++++++++---------- tea.go | 7 +++++++ 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/kitty.go b/kitty.go index af63cd8c77..11e764397e 100644 --- a/kitty.go +++ b/kitty.go @@ -36,6 +36,16 @@ func DisableKittyKeyboard() Msg { return setKittyKeyboardFlagsMsg(0) } +// requestKittyKeyboardFlagsMsg is a message to request the current Kitty keyboard +// progressive enhancement protocol flags. +type requestKittyKeyboardFlagsMsg struct{} + +// RequestKittyKeyboardFlags is a command to request the current Kitty keyboard +// progressive enhancement protocol flags from the terminal. +func RequestKittyKeyboardFlags() Msg { + return requestKittyKeyboardFlagsMsg{} +} + // EnableEnhancedKeyboard is a command to enable enhanced keyboard features. // This unambiguously reports more key combinations than traditional terminal // keyboard sequences. This will also enable reporting of release key events. @@ -51,28 +61,28 @@ func DisableEnhancedKeyboard() Msg { // KittyKeyboardMsg represents Kitty keyboard progressive enhancement flags message. type KittyKeyboardMsg int -// IsDisambiguateEscapeCodes returns true if the DisambiguateEscapeCodes flag is set. -func (e KittyKeyboardMsg) IsDisambiguateEscapeCodes() bool { +// HasDisambiguateEscapeCodes returns true if the DisambiguateEscapeCodes flag is set. +func (e KittyKeyboardMsg) HasDisambiguateEscapeCodes() bool { return e&ansi.KittyDisambiguateEscapeCodes != 0 } -// IsReportEventTypes returns true if the ReportEventTypes flag is set. -func (e KittyKeyboardMsg) IsReportEventTypes() bool { +// HasReportEventTypes returns true if the ReportEventTypes flag is set. +func (e KittyKeyboardMsg) HasReportEventTypes() bool { return e&ansi.KittyReportEventTypes != 0 } -// IsReportAlternateKeys returns true if the ReportAlternateKeys flag is set. -func (e KittyKeyboardMsg) IsReportAlternateKeys() bool { +// HasReportAlternateKeys returns true if the ReportAlternateKeys flag is set. +func (e KittyKeyboardMsg) HasReportAlternateKeys() bool { return e&ansi.KittyReportAlternateKeys != 0 } -// IsReportAllKeys returns true if the ReportAllKeys flag is set. -func (e KittyKeyboardMsg) IsReportAllKeys() bool { +// HasReportAllKeys returns true if the ReportAllKeys flag is set. +func (e KittyKeyboardMsg) HasReportAllKeys() bool { return e&ansi.KittyReportAllKeys != 0 } -// IsReportAssociatedKeys returns true if the ReportAssociatedKeys flag is set. -func (e KittyKeyboardMsg) IsReportAssociatedKeys() bool { +// HasReportAssociatedKeys returns true if the ReportAssociatedKeys flag is set. +func (e KittyKeyboardMsg) HasReportAssociatedKeys() bool { return e&ansi.KittyReportAssociatedKeys != 0 } diff --git a/tea.go b/tea.go index 073d363a38..009c7ee429 100644 --- a/tea.go +++ b/tea.go @@ -396,10 +396,17 @@ func (p *Program) eventLoop(model Model, cmds chan Cmd) (Model, error) { p.renderer.execute(ansi.DisableBracketedPaste) p.bpActive = false + case KittyKeyboardMsg: + // Store the kitty flags whenever they are queried. + p.kittyFlags = int(msg) + case setKittyKeyboardFlagsMsg: p.kittyFlags = int(msg) p.renderer.execute(ansi.PushKittyKeyboard(p.kittyFlags)) + case requestKittyKeyboardFlagsMsg: + p.renderer.execute(ansi.RequestKittyKeyboard) + case execMsg: // NB: this blocks. p.exec(msg.cmd, msg.fn) From 03df14ca89936f39489054c744712a80e6e06dfa Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Thu, 15 Aug 2024 14:06:00 -0400 Subject: [PATCH 03/12] feat: kitty: support associated text keyboard enhancement --- kitty.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/kitty.go b/kitty.go index 11e764397e..cceb963158 100644 --- a/kitty.go +++ b/kitty.go @@ -319,13 +319,13 @@ func parseKittyKeyboard(csi *ansi.CsiSequence) Msg { } } } - // TODO: Associated keys are not support yet. - // if params := csi.Subparams(2); len(params) > 0 { - // r := rune(params[0]) - // if unicode.IsPrint(r) { - // key.AltRune = r - // } - // } + if params := csi.Subparams(2); len(params) > 0 { + r := rune(params[0]) + if unicode.IsPrint(r) { + key.altRune = key.Rune() + key.Runes = []rune{r} + } + } if isRelease { return KeyReleaseMsg(key) } From 4b7fef71ae51ebabff5c181911cde7ec591c19ad Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Thu, 15 Aug 2024 14:06:33 -0400 Subject: [PATCH 04/12] chore: add kitty/fixterms test cases --- key_test.go | 78 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/key_test.go b/key_test.go index d46da05379..bea16ed316 100644 --- a/key_test.go +++ b/key_test.go @@ -113,6 +113,84 @@ func buildBaseSeqTests() []seqTest { func TestParseSequence(t *testing.T) { td := buildBaseSeqTests() td = append(td, + // Kitty keyboard / CSI u (fixterms) + seqTest{ + []byte("\x1b[1B"), + []Msg{KeyPressMsg{Type: KeyDown}}, + }, + seqTest{ + []byte("\x1b[1;B"), + []Msg{KeyPressMsg{Type: KeyDown}}, + }, + seqTest{ + []byte("\x1b[1;4B"), + []Msg{KeyPressMsg{Mod: ModShift | ModAlt, Type: KeyDown}}, + }, + seqTest{ + []byte("\x1b[8~"), + []Msg{KeyPressMsg{Type: KeyEnd}}, + }, + seqTest{ + []byte("\x1b[8;~"), + []Msg{KeyPressMsg{Type: KeyEnd}}, + }, + seqTest{ + []byte("\x1b[8;10~"), + []Msg{KeyPressMsg{Mod: ModShift | ModMeta, Type: KeyEnd}}, + }, + seqTest{ + []byte("\x1b[27;4u"), + []Msg{KeyPressMsg{Mod: ModShift | ModAlt, Type: KeyEscape}}, + }, + seqTest{ + []byte("\x1b[127;4u"), + []Msg{KeyPressMsg{Mod: ModShift | ModAlt, Type: KeyBackspace}}, + }, + seqTest{ + []byte("\x1b[57358;4u"), + []Msg{KeyPressMsg{Mod: ModShift | ModAlt, Type: KeyCapsLock}}, + }, + seqTest{ + []byte("\x1b[9;2u"), + []Msg{KeyPressMsg{Mod: ModShift, Type: KeyTab}}, + }, + seqTest{ + []byte("\x1b[195;u"), + []Msg{KeyPressMsg{Runes: []rune{'Ã'}, Type: KeyRunes}}, + }, + seqTest{ + []byte("\x1b[20320;2u"), + []Msg{KeyPressMsg{Runes: []rune{'你'}, Mod: ModShift, Type: KeyRunes}}, + }, + seqTest{ + []byte("\x1b[195;:1u"), + []Msg{KeyPressMsg{Runes: []rune{'Ã'}, Type: KeyRunes}}, + }, + seqTest{ + []byte("\x1b[195;2:3u"), + []Msg{KeyReleaseMsg{Runes: []rune{'Ã'}, Mod: ModShift}}, + }, + seqTest{ + []byte("\x1b[195;2:2u"), + []Msg{KeyPressMsg{Runes: []rune{'Ã'}, IsRepeat: true, Mod: ModShift}}, + }, + seqTest{ + []byte("\x1b[195;2:1u"), + []Msg{KeyPressMsg{Runes: []rune{'Ã'}, Mod: ModShift}}, + }, + seqTest{ + []byte("\x1b[195;2:3u"), + []Msg{KeyReleaseMsg{Runes: []rune{'Ã'}, Mod: ModShift}}, + }, + seqTest{ + []byte("\x1b[97;2;65u"), + []Msg{KeyPressMsg{Runes: []rune{'A'}, Mod: ModShift, altRune: 'a'}}, + }, + seqTest{ + []byte("\x1b[97;;229u"), + []Msg{KeyPressMsg{Runes: []rune{'å'}, altRune: 'a'}}, + }, + // focus/blur seqTest{ []byte{'\x1b', '[', 'I'}, From a37d7315314c99c53551561f88f019b9351d5bba Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Thu, 15 Aug 2024 14:43:40 -0400 Subject: [PATCH 05/12] chore: add kitty keyboard command test case --- screen_test.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/screen_test.go b/screen_test.go index 728cd9779f..a62a10044f 100644 --- a/screen_test.go +++ b/screen_test.go @@ -56,6 +56,11 @@ func TestClearMsg(t *testing.T) { cmds: []Cmd{DisableBracketedPaste, EnableBracketedPaste}, expected: "\x1b[?25l\x1b[?2004h\x1b[?2004l\x1b[?2004h\rsuccess\r\n\x1b[D\x1b[2K\r\x1b[?2004l\x1b[?25h\x1b[?1002l\x1b[?1003l\x1b[?1006l", }, + { + name: "kitty_start", + cmds: []Cmd{DisableKittyKeyboard, EnableKittyKeyboard(3)}, + expected: "\x1b[?25l\x1b[?2004h\x1b[>u\x1b[>3u\rsuccess\r\n\x1b[D\x1b[2K\r\x1b[?2004l\x1b[?25h\x1b[?1002l\x1b[?1003l\x1b[?1006l\x1b[>0u", + }, } for _, test := range tests { From ec5b3626a454315750b94ecc29b34ad07d65956e Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Thu, 15 Aug 2024 14:44:01 -0400 Subject: [PATCH 06/12] refactor: rename query kitty keyboard command --- kitty.go | 19 +++++++++---------- tea.go | 2 +- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/kitty.go b/kitty.go index cceb963158..aa1c4affda 100644 --- a/kitty.go +++ b/kitty.go @@ -12,11 +12,10 @@ import ( type setKittyKeyboardFlagsMsg int // EnableKittyKeyboard is a command to enable Kitty keyboard progressive -// enhancement protocol. +// enhancements. // // The flags parameter is a bitmask of the following // -// 0: Disable all features // 1: Disambiguate escape codes // 2: Report event types // 4: Report alternate keys @@ -31,19 +30,19 @@ func EnableKittyKeyboard(flags int) Cmd { } // DisableKittyKeyboard is a command to disable Kitty keyboard progressive -// enhancement protocol. +// enhancements. func DisableKittyKeyboard() Msg { return setKittyKeyboardFlagsMsg(0) } -// requestKittyKeyboardFlagsMsg is a message to request the current Kitty keyboard -// progressive enhancement protocol flags. -type requestKittyKeyboardFlagsMsg struct{} +// kittyKeyboardMsg is a message that queries the current Kitty keyboard +// progressive enhancement flags. +type kittyKeyboardMsg struct{} -// RequestKittyKeyboardFlags is a command to request the current Kitty keyboard -// progressive enhancement protocol flags from the terminal. -func RequestKittyKeyboardFlags() Msg { - return requestKittyKeyboardFlagsMsg{} +// KittyKeyboard is a command that queries the current Kitty keyboard +// progressive enhancement flags from the terminal. +func KittyKeyboard() Msg { + return kittyKeyboardMsg{} } // EnableEnhancedKeyboard is a command to enable enhanced keyboard features. diff --git a/tea.go b/tea.go index 009c7ee429..29e1befdd4 100644 --- a/tea.go +++ b/tea.go @@ -404,7 +404,7 @@ func (p *Program) eventLoop(model Model, cmds chan Cmd) (Model, error) { p.kittyFlags = int(msg) p.renderer.execute(ansi.PushKittyKeyboard(p.kittyFlags)) - case requestKittyKeyboardFlagsMsg: + case kittyKeyboardMsg: p.renderer.execute(ansi.RequestKittyKeyboard) case execMsg: From 4d2072bfecd6cc3c97c4895210ffa58b6e942659 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Wed, 14 Aug 2024 12:02:56 -0400 Subject: [PATCH 07/12] fix: kitty: request protocol flags and rename flag methods --- commands.go | 22 ++++++++++++++++++++++ examples/go.mod | 2 +- examples/go.sum | 4 ++-- go.mod | 4 ++-- go.sum | 8 ++------ kitty.go | 12 ------------ options.go | 29 ++++++++++++++++++++++++++--- tea.go | 25 +++++++++++++++++++++++++ tty.go | 3 +++ xterm.go | 23 +++++++++++++++++++++++ 10 files changed, 106 insertions(+), 26 deletions(-) diff --git a/commands.go b/commands.go index bfa3b70455..0cb6da710c 100644 --- a/commands.go +++ b/commands.go @@ -214,3 +214,25 @@ func WindowSize() Cmd { return windowSizeMsg{} } } + +// setEnhancedKeyboardMsg is a message to enable/disable enhanced keyboard +// features. +type setEnhancedKeyboardMsg bool + +// 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) +} + +// DisableEnhancedKeyboard is a command to disable enhanced keyboard features. +// +// This is equivalent to calling DisableKittyKeyboard() and DisableModifyOtherKeys(). +func DisableEnhancedKeyboard() Msg { + return setEnhancedKeyboardMsg(false) +} diff --git a/examples/go.mod b/examples/go.mod index 40598a5479..0ccbcf8ccb 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.1.4 // indirect + github.com/charmbracelet/x/ansi v0.1.5-0.20240814160751-e2dc8b53b604 // indirect github.com/charmbracelet/x/exp/golden v0.0.0-20240715153702-9ba8adf781c4 // indirect github.com/charmbracelet/x/input v0.1.0 // indirect github.com/charmbracelet/x/term v0.1.1 // indirect diff --git a/examples/go.sum b/examples/go.sum index 75ebdfe9b3..aa013f4db5 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -20,8 +20,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.12.1 h1:/gmzszl+pedQpjCOH+wFkZr/N90Snz40J/NR7A0zQcs= github.com/charmbracelet/lipgloss v0.12.1/go.mod h1:V2CiwIuhx9S1S1ZlADfOj9HmxeMAORuz5izHb0zGbB8= -github.com/charmbracelet/x/ansi v0.1.4 h1:IEU3D6+dWwPSgZ6HBH+v6oUuZ/nVawMiWj5831KfiLM= -github.com/charmbracelet/x/ansi v0.1.4/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw= +github.com/charmbracelet/x/ansi v0.1.5-0.20240814160751-e2dc8b53b604 h1:UBOeE/UdmNlTa7KVGdCdYGYpOBZPD0LMMw0Bcp6fCj4= +github.com/charmbracelet/x/ansi v0.1.5-0.20240814160751-e2dc8b53b604/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw= github.com/charmbracelet/x/exp/golden v0.0.0-20240715153702-9ba8adf781c4 h1:6KzMkQeAF56rggw2NZu1L+TH7j9+DM1/2Kmh7KUxg1I= github.com/charmbracelet/x/exp/golden v0.0.0-20240715153702-9ba8adf781c4/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= github.com/charmbracelet/x/exp/teatest v0.0.0-20240521184646-23081fb03b28 h1:sOWKNRjt8uOEVgPiJVIJCse1+mUDM2F/vYY6W0Go640= diff --git a/go.mod b/go.mod index e84a49b908..d1c2fb6f4d 100644 --- a/go.mod +++ b/go.mod @@ -3,11 +3,12 @@ module github.com/charmbracelet/bubbletea go 1.18 require ( - github.com/charmbracelet/x/ansi v0.1.5-0.20240814155920-d72bfb0444d7 + github.com/charmbracelet/x/ansi v0.1.5-0.20240814160751-e2dc8b53b604 github.com/charmbracelet/x/term v0.1.1 github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 github.com/muesli/cancelreader v0.2.2 + github.com/rivo/uniseg v0.4.7 github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e golang.org/x/sync v0.8.0 golang.org/x/sys v0.24.0 @@ -17,5 +18,4 @@ require ( github.com/charmbracelet/x/input v0.1.0 // indirect github.com/charmbracelet/x/windows v0.1.0 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect - github.com/rivo/uniseg v0.4.7 // indirect ) diff --git a/go.sum b/go.sum index 29768a7219..6b8c345d95 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,5 @@ -github.com/charmbracelet/x/ansi v0.1.4 h1:IEU3D6+dWwPSgZ6HBH+v6oUuZ/nVawMiWj5831KfiLM= -github.com/charmbracelet/x/ansi v0.1.4/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw= -github.com/charmbracelet/x/ansi v0.1.5-0.20240813204730-a29dab672bf2 h1:/BRVAHphENSsvuYG+iVP/qgC6Bjjc9zdOmY3pQzg7mE= -github.com/charmbracelet/x/ansi v0.1.5-0.20240813204730-a29dab672bf2/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw= -github.com/charmbracelet/x/ansi v0.1.5-0.20240814155920-d72bfb0444d7 h1:wcBKMnW8Fm0Oiw6b7pTXe+5eCntEuKI6s82J41nZcEY= -github.com/charmbracelet/x/ansi v0.1.5-0.20240814155920-d72bfb0444d7/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw= +github.com/charmbracelet/x/ansi v0.1.5-0.20240814160751-e2dc8b53b604 h1:UBOeE/UdmNlTa7KVGdCdYGYpOBZPD0LMMw0Bcp6fCj4= +github.com/charmbracelet/x/ansi v0.1.5-0.20240814160751-e2dc8b53b604/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw= github.com/charmbracelet/x/input v0.1.0 h1:TEsGSfZYQyOtp+STIjyBq6tpRaorH0qpwZUj8DavAhQ= github.com/charmbracelet/x/input v0.1.0/go.mod h1:ZZwaBxPF7IG8gWWzPUVqHEtWhc1+HXJPNuerJGRGZ28= github.com/charmbracelet/x/term v0.1.1 h1:3cosVAiPOig+EV4X9U+3LDgtwwAoEzJjNdwbXDjF6yI= diff --git a/kitty.go b/kitty.go index aa1c4affda..fac3986d4a 100644 --- a/kitty.go +++ b/kitty.go @@ -45,18 +45,6 @@ func KittyKeyboard() Msg { return kittyKeyboardMsg{} } -// EnableEnhancedKeyboard is a command to enable enhanced keyboard features. -// This unambiguously reports more key combinations than traditional terminal -// keyboard sequences. This will also enable reporting of release key events. -func EnableEnhancedKeyboard() Msg { - return setKittyKeyboardFlagsMsg(3) -} - -// DisableEnhancedKeyboard is a command to disable enhanced keyboard features. -func DisableEnhancedKeyboard() Msg { - return setKittyKeyboardFlagsMsg(0) -} - // KittyKeyboardMsg represents Kitty keyboard progressive enhancement flags message. type KittyKeyboardMsg int diff --git a/options.go b/options.go index cb2ee41424..6ff48f47ea 100644 --- a/options.go +++ b/options.go @@ -237,11 +237,15 @@ func WithFPS(fps int) ProgramOption { // WithEnhancedKeyboard enables support for enhanced keyboard features. This // unambiguously reports more key combinations than traditional terminal -// keyboard sequences. This will also enable reporting of release key events. +// 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(3). +// This is a syntactic sugar for WithKittyKeyboard(3) and WithXtermModifyOtherKeys(1). func WithEnhancedKeyboard() ProgramOption { - return WithKittyKeyboard(3) + return func(p *Program) { + WithKittyKeyboard(3)(p) + WithModifyOtherKeys(1)(p) + } } // WithKittyKeyboard enables support for the Kitty keyboard protocol. This @@ -264,3 +268,22 @@ func WithKittyKeyboard(flags int) ProgramOption { p.startupOptions |= withKittyKeyboard } } + +// 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- +// +// 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 + } +} diff --git a/tea.go b/tea.go index 29e1befdd4..2c616ceedb 100644 --- a/tea.go +++ b/tea.go @@ -98,6 +98,7 @@ const ( withoutCatchPanics withoutBracketedPaste withKittyKeyboard + withModifyOtherKeys ) // channelHandlers manages the series of channels returned by various processes. @@ -177,6 +178,9 @@ type Program struct { // kittyFlags stores kitty keyboard protocol progressive enhancement flags. kittyFlags int + + // modifyOtherKeys stores the XTerm modifyOtherKeys mode. + modifyOtherKeys int } // Quit is a special command that tells the Bubble Tea program to exit. @@ -407,6 +411,21 @@ func (p *Program) eventLoop(model Model, cmds chan Cmd) (Model, error) { case kittyKeyboardMsg: p.renderer.execute(ansi.RequestKittyKeyboard) + case setModifyOtherKeysMsg: + p.modifyOtherKeys = int(msg) + p.renderer.execute(ansi.ModifyOtherKeys(p.modifyOtherKeys)) + + case setEnhancedKeyboardMsg: + if bool(msg) { + p.kittyFlags = 3 + p.modifyOtherKeys = 1 + } else { + p.kittyFlags = 0 + p.modifyOtherKeys = 0 + } + p.renderer.execute(ansi.ModifyOtherKeys(p.modifyOtherKeys)) + p.renderer.execute(ansi.PushKittyKeyboard(p.kittyFlags)) + case execMsg: // NB: this blocks. p.exec(msg.cmd, msg.fn) @@ -571,6 +590,9 @@ func (p *Program) Run() (Model, error) { p.renderer.execute(ansi.EnableMouseAllMotion) p.renderer.execute(ansi.EnableMouseSgrExt) } + if p.startupOptions&withModifyOtherKeys != 0 { + p.renderer.execute(ansi.ModifyOtherKeys(p.modifyOtherKeys)) + } if p.startupOptions&withKittyKeyboard != 0 { p.renderer.execute(ansi.PushKittyKeyboard(p.kittyFlags)) } @@ -745,6 +767,9 @@ func (p *Program) RestoreTerminal() error { p.renderer.hideCursor() p.renderer.execute(ansi.EnableBracketedPaste) p.bpActive = true + if p.modifyOtherKeys != 0 { + p.renderer.execute(ansi.ModifyOtherKeys(p.modifyOtherKeys)) + } if p.kittyFlags != 0 { p.renderer.execute(ansi.PushKittyKeyboard(p.kittyFlags)) } diff --git a/tty.go b/tty.go index 52c1e1f4d9..b1740fe3d2 100644 --- a/tty.go +++ b/tty.go @@ -37,6 +37,9 @@ func (p *Program) restoreTerminalState() error { p.bpActive = false p.renderer.showCursor() p.disableMouse() + if p.modifyOtherKeys != 0 { + p.renderer.execute(ansi.DisableModifyOtherKeys) + } if p.kittyFlags != 0 { p.renderer.execute(ansi.DisableKittyKeyboard) } diff --git a/xterm.go b/xterm.go index f09caeba52..0589e21a1f 100644 --- a/xterm.go +++ b/xterm.go @@ -4,6 +4,29 @@ 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 { + return func() Msg { + return setModifyOtherKeysMsg(mode) + } +} + +// DisableModifyOtherKeys is a command to disable XTerm modifyOtherKeys mode. +func DisableModifyOtherKeys() Msg { + return setModifyOtherKeysMsg(0) +} + func parseXTermModifyOtherKeys(csi *ansi.CsiSequence) Msg { // XTerm modify other keys starts with ESC [ 27 ; ; ~ mod := KeyMod(csi.Param(1) - 1) From e16559d1b7c26d7b103b2332587bb853a34f9830 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Wed, 14 Aug 2024 12:23:20 -0400 Subject: [PATCH 08/12] feat(examples): add print-key example --- examples/print-key/main.go | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 examples/print-key/main.go diff --git a/examples/print-key/main.go b/examples/print-key/main.go new file mode 100644 index 0000000000..dbdb814ab4 --- /dev/null +++ b/examples/print-key/main.go @@ -0,0 +1,36 @@ +package main + +import ( + "log" + + tea "github.com/charmbracelet/bubbletea" +) + +type model struct{} + +func (m model) Init() tea.Cmd { + return nil +} + +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 + } + return m, tea.Printf("You pressed: %s\n", msg.String()) + } + return m, nil +} + +func (m model) View() string { + return "Press any key to see it printed to the terminal. Press 'ctrl+c' to quit." +} + +func main() { + p := tea.NewProgram(model{}, tea.WithEnhancedKeyboard()) + if _, err := p.Run(); err != nil { + log.Printf("Error running program: %v", err) + } +} From d36b2597a51fbf2360c0b363027c412d76ff787f Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Thu, 15 Aug 2024 14:10:10 -0400 Subject: [PATCH 09/12] chore: add modifyOtherKeys test cases --- key_test.go | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/key_test.go b/key_test.go index bea16ed316..0b3eae31f1 100644 --- a/key_test.go +++ b/key_test.go @@ -113,6 +113,28 @@ func buildBaseSeqTests() []seqTest { func TestParseSequence(t *testing.T) { td := buildBaseSeqTests() td = append(td, + // Xterm modifyOtherKeys CSI 27 ; ; ~ + seqTest{ + []byte("\x1b[27;3;20320~"), + []Msg{KeyPressMsg{Runes: []rune{'你'}, Mod: ModAlt}}, + }, + seqTest{ + []byte("\x1b[27;3;65~"), + []Msg{KeyPressMsg{Runes: []rune{'A'}, Mod: ModAlt}}, + }, + seqTest{ + []byte("\x1b[27;3;8~"), + []Msg{KeyPressMsg{Type: KeyBackspace, Mod: ModAlt}}, + }, + seqTest{ + []byte("\x1b[27;3;27~"), + []Msg{KeyPressMsg{Type: KeyEscape, Mod: ModAlt}}, + }, + seqTest{ + []byte("\x1b[27;3;127~"), + []Msg{KeyPressMsg{Type: KeyBackspace, Mod: ModAlt}}, + }, + // Kitty keyboard / CSI u (fixterms) seqTest{ []byte("\x1b[1B"), From e0865cfed36c594d6be4ea783548d03d8f6efeca Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Fri, 16 Aug 2024 16:51:34 -0400 Subject: [PATCH 10/12] refactor: simplify key modifier matching Use bitmap matching to check if a modifier contains another --- key.go | 12 +++++----- mod.go | 53 +++++++++------------------------------------ mouse.go | 6 ++--- mouse_deprecated.go | 6 ++--- tty.go | 8 +++---- 5 files changed, 26 insertions(+), 59 deletions(-) diff --git a/key.go b/key.go index 9997321037..e8c9c3c06f 100644 --- a/key.go +++ b/key.go @@ -323,22 +323,22 @@ func (k Key) Rune() rune { // "shift+ctrl+alt+a". func (k Key) String() string { var s string - if k.Mod.HasCtrl() && k.Type != KeyLeftCtrl && k.Type != KeyRightCtrl { + if k.Mod.Contains(ModCtrl) && k.Type != KeyLeftCtrl && k.Type != KeyRightCtrl { s += "ctrl+" } - if k.Mod.HasAlt() && k.Type != KeyLeftAlt && k.Type != KeyRightAlt { + if k.Mod.Contains(ModAlt) && k.Type != KeyLeftAlt && k.Type != KeyRightAlt { s += "alt+" } - if k.Mod.HasShift() && k.Type != KeyLeftShift && k.Type != KeyRightShift { + if k.Mod.Contains(ModShift) && k.Type != KeyLeftShift && k.Type != KeyRightShift { s += "shift+" } - if k.Mod.HasMeta() && k.Type != KeyLeftMeta && k.Type != KeyRightMeta { + if k.Mod.Contains(ModMeta) && k.Type != KeyLeftMeta && k.Type != KeyRightMeta { s += "meta+" } - if k.Mod.HasHyper() && k.Type != KeyLeftHyper && k.Type != KeyRightHyper { + if k.Mod.Contains(ModHyper) && k.Type != KeyLeftHyper && k.Type != KeyRightHyper { s += "hyper+" } - if k.Mod.HasSuper() && k.Type != KeyLeftSuper && k.Type != KeyRightSuper { + if k.Mod.Contains(ModSuper) && k.Type != KeyLeftSuper && k.Type != KeyRightSuper { s += "super+" } diff --git a/mod.go b/mod.go index a93ec72df4..9bb43a0cb3 100644 --- a/mod.go +++ b/mod.go @@ -24,47 +24,14 @@ const ( ModScrollLock // Defined in Windows API only ) -// HasShift reports whether the Shift modifier is set. -func (m KeyMod) HasShift() bool { - return m&ModShift != 0 -} - -// HasAlt reports whether the Alt modifier is set. -func (m KeyMod) HasAlt() bool { - return m&ModAlt != 0 -} - -// HasCtrl reports whether the Ctrl modifier is set. -func (m KeyMod) HasCtrl() bool { - return m&ModCtrl != 0 -} - -// HasMeta reports whether the Meta modifier is set. -func (m KeyMod) HasMeta() bool { - return m&ModMeta != 0 -} - -// HasHyper reports whether the Hyper modifier is set. -func (m KeyMod) HasHyper() bool { - return m&ModHyper != 0 -} - -// HasSuper reports whether the Super modifier is set. -func (m KeyMod) HasSuper() bool { - return m&ModSuper != 0 -} - -// HasCapsLock reports whether the CapsLock key is enabled. -func (m KeyMod) HasCapsLock() bool { - return m&ModCapsLock != 0 -} - -// HasNumLock reports whether the NumLock key is enabled. -func (m KeyMod) HasNumLock() bool { - return m&ModNumLock != 0 -} - -// HasScrollLock reports whether the ScrollLock key is enabled. -func (m KeyMod) HasScrollLock() bool { - return m&ModScrollLock != 0 +// Contains reports whether m contains the given modifiers. +// +// Example: +// +// m := ModAlt | ModCtrl +// m.Contains(ModCtrl) // true +// m.Contains(ModAlt | ModCtrl) // true +// m.Contains(ModAlt | ModCtrl | ModShift) // false +func (m KeyMod) Contains(mods KeyMod) bool { + return m&mods == mods } diff --git a/mouse.go b/mouse.go index 710770d2bb..f3460fa8f8 100644 --- a/mouse.go +++ b/mouse.go @@ -63,13 +63,13 @@ type Mouse struct { // String returns a string representation of the mouse message. func (m Mouse) String() (s string) { - if m.Mod.HasCtrl() { + if m.Mod.Contains(ModCtrl) { s += "ctrl+" } - if m.Mod.HasAlt() { + if m.Mod.Contains(ModAlt) { s += "alt+" } - if m.Mod.HasShift() { + if m.Mod.Contains(ModShift) { s += "shift+" } diff --git a/mouse_deprecated.go b/mouse_deprecated.go index fb2c41da6c..714b63fd22 100644 --- a/mouse_deprecated.go +++ b/mouse_deprecated.go @@ -154,9 +154,9 @@ func toMouseMsg(m Mouse) MouseMsg { return MouseMsg{ X: m.X, Y: m.Y, - Shift: m.Mod.HasShift(), - Alt: m.Mod.HasAlt(), - Ctrl: m.Mod.HasCtrl(), + Shift: m.Mod.Contains(ModShift), + Alt: m.Mod.Contains(ModAlt), + Ctrl: m.Mod.Contains(ModCtrl), Button: m.Button, } } diff --git a/tty.go b/tty.go index 9d5612cfbb..9ec3d2e03f 100644 --- a/tty.go +++ b/tty.go @@ -116,14 +116,14 @@ func readInputs(ctx context.Context, msgs chan<- Msg, reader *driver) error { incomingMsgs = append(incomingMsgs, k) case KeyPressMsg: k := KeyMsg{ - Alt: e.Mod.HasAlt(), + Alt: e.Mod.Contains(ModAlt), Runes: e.Runes, Type: e.Type, } // Backwards compatibility for ctrl- and shift- keys switch { - case e.Mod.HasCtrl() && e.Mod.HasShift(): + case e.Mod.Contains(ModCtrl | ModShift): switch e.Type { case KeyUp, KeyDown, KeyRight, KeyLeft: k.Runes = nil @@ -132,7 +132,7 @@ func readInputs(ctx context.Context, msgs chan<- Msg, reader *driver) error { k.Runes = nil k.Type = KeyCtrlShiftHome - e.Type + KeyHome } - case e.Mod.HasCtrl(): + case e.Mod.Contains(ModCtrl): switch e.Type { case KeyRunes: // KeyRunes switch r := e.Rune(); r { @@ -158,7 +158,7 @@ func readInputs(ctx context.Context, msgs chan<- Msg, reader *driver) error { k.Runes = nil k.Type = KeyCtrlUp - e.Type + KeyUp } - case e.Mod.HasShift(): + case e.Mod.Contains(ModShift): switch e.Type { case KeyTab: k.Runes = nil From 3f5fb9a3f9d6b5f84330f78cd7879d0e40d5bbe9 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Mon, 19 Aug 2024 10:27:20 -0400 Subject: [PATCH 11/12] fix(lint): reorder key struct to fix fieldalignment --- key.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/key.go b/key.go index e8c9c3c06f..33679414f2 100644 --- a/key.go +++ b/key.go @@ -226,14 +226,14 @@ const ( // one character, though certain input method editors (most notably Chinese // IMEs) can input multiple runes at once. type Key struct { - // Type is a special key, like enter, tab, backspace, and so on. - Type KeyType - // Runes contains the actual characters received. This usually has a length // of 1. Use [Rune()] to get the first key rune received. If the user // presses shift+a, the Runes will be `[]rune{'A'}`. Runes []rune + // Type is a special key, like enter, tab, backspace, and so on. + Type KeyType + // altRune is the actual, unshifted key pressed by the user. For example, // if the user presses shift+a, or caps lock is on, the altRune will be // 'a'. From c630d5e94e5b1c7dd31ddf6a441e034ae533b4bd Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Mon, 19 Aug 2024 10:31:32 -0400 Subject: [PATCH 12/12] fix: simplify kitty keyboard msg flags --- kitty.go | 33 +++++++++++---------------------- 1 file changed, 11 insertions(+), 22 deletions(-) diff --git a/kitty.go b/kitty.go index aa1c4affda..826c0352be 100644 --- a/kitty.go +++ b/kitty.go @@ -60,29 +60,18 @@ func DisableEnhancedKeyboard() Msg { // KittyKeyboardMsg represents Kitty keyboard progressive enhancement flags message. type KittyKeyboardMsg int -// HasDisambiguateEscapeCodes returns true if the DisambiguateEscapeCodes flag is set. -func (e KittyKeyboardMsg) HasDisambiguateEscapeCodes() bool { - return e&ansi.KittyDisambiguateEscapeCodes != 0 -} - -// HasReportEventTypes returns true if the ReportEventTypes flag is set. -func (e KittyKeyboardMsg) HasReportEventTypes() bool { - return e&ansi.KittyReportEventTypes != 0 -} - -// HasReportAlternateKeys returns true if the ReportAlternateKeys flag is set. -func (e KittyKeyboardMsg) HasReportAlternateKeys() bool { - return e&ansi.KittyReportAlternateKeys != 0 -} - -// HasReportAllKeys returns true if the ReportAllKeys flag is set. -func (e KittyKeyboardMsg) HasReportAllKeys() bool { - return e&ansi.KittyReportAllKeys != 0 -} +// Kitty Keyboard Protocol flags. +const ( + KittyDisambiguateEscapeCodes KittyKeyboardMsg = 1 << iota + KittyReportEventTypes + KittyReportAlternateKeys + KittyReportAllKeys + KittyReportAssociatedKeys +) -// HasReportAssociatedKeys returns true if the ReportAssociatedKeys flag is set. -func (e KittyKeyboardMsg) HasReportAssociatedKeys() bool { - return e&ansi.KittyReportAssociatedKeys != 0 +// Contains reports whether m contains the given flags. +func (m KittyKeyboardMsg) Contains(flags KittyKeyboardMsg) bool { + return m&flags == flags } // Kitty Clipboard Control Sequences