Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(v2) feat: combine keyboard enhancements into a nicer API #1152

Merged
merged 8 commits into from
Sep 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 0 additions & 22 deletions commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -214,25 +214,3 @@ 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)
}
2 changes: 1 addition & 1 deletion examples/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions examples/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
17 changes: 11 additions & 6 deletions examples/print-key/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.SupportsKeyReleases())
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
}
Expand All @@ -29,7 +34,7 @@ func (m model) View() string {
}

func main() {
p := tea.NewProgram(model{}, tea.WithEnhancedKeyboard())
p := tea.NewProgram(model{}, tea.WithKeyboardEnhancements(tea.WithKeyReleases))
if _, err := p.Run(); err != nil {
log.Printf("Error running program: %v", err)
}
Expand Down
104 changes: 104 additions & 0 deletions keyboard.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package tea

import (
"runtime"

"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)

// 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 WithKeyReleases(k *keyboardEnhancements) {
k.kittyFlags |= ansi.KittyReportEventTypes
}

// 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 withKeyDisambiguation(k *keyboardEnhancements) {
k.kittyFlags |= ansi.KittyDisambiguateEscapeCodes
if k.modifyOtherKeys < 1 {
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, withKeyDisambiguation))
}
}

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

// SupportsKeyDisambiguation returns whether the terminal supports reporting
// disambiguous keys as escape codes.
func (k KeyboardEnhancementsMsg) SupportsKeyDisambiguation() 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
}

// SupportsKeyReleases returns whether the terminal supports key release
// events.
func (k KeyboardEnhancementsMsg) SupportsKeyReleases() bool {
if runtime.GOOS == "windows" {
// We use Windows Console API which supports key release events.
return true
}
return k.kittyFlags&ansi.KittyReportEventTypes != 0
}
53 changes: 0 additions & 53 deletions kitty.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
78 changes: 12 additions & 66 deletions options.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -253,72 +251,20 @@ 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)
}
}

// _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
}
}

// _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-<key>
// and Meta-<key>
//
// 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
// WithKeyboardEnhancements enables support for enhanced keyboard features. You
// can enable different keyboard features by passing one or more
// KeyboardEnhancement functions.
//
// This is not supported on all terminals. On Windows, these features are
// enabled by default.
func WithKeyboardEnhancements(enhancements ...KeyboardEnhancement) ProgramOption {
var ke keyboardEnhancements
for _, e := range append(enhancements, withKeyDisambiguation) {
e(&ke)
}
}

// _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.
//
// 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
return func(p *Program) {
p.startupOptions |= withWindowsInputMode
p.startupOptions |= withKeyboardEnhancements
p.keyboard = ke
}
}

Expand Down
4 changes: 2 additions & 2 deletions parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ func parseCsi(b []byte) (int, Msg) {
case 'u' | '?'<<parser.MarkerShift:
// Kitty keyboard flags
if param := csi.Param(0); param != -1 {
return i, KittyKeyboardMsg(param)
return i, KeyboardEnhancementsMsg{kittyFlags: param}
}
case 'R' | '?'<<parser.MarkerShift:
// This report may return a third parameter representing the page
Expand All @@ -266,7 +266,7 @@ func parseCsi(b []byte) (int, Msg) {
case 'm' | '>'<<parser.MarkerShift:
// XTerm modifyOtherKeys
if paramsLen == 2 && csi.Param(0) == 4 && csi.Param(1) != -1 {
return i, ModifyOtherKeysMsg(csi.Param(1))
return i, KeyboardEnhancementsMsg{modifyOtherKeys: csi.Param(1)}
}
case 'I':
return i, FocusMsg{}
Expand Down
4 changes: 2 additions & 2 deletions screen_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@ func TestClearMsg(t *testing.T) {
},
{
name: "kitty_start",
cmds: []Cmd{DisableKittyKeyboard, EnableKittyKeyboard(3)},
expected: "\x1b[?25l\x1b[?2004h\x1b[?2027h\x1b[?2027$p\x1b[>u\x1b[>3u\rsuccess\r\n\x1b[D\x1b[2K\r\x1b[?2004l\x1b[?25h\x1b[>0u",
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",
},
}

Expand Down
Loading
Loading