Skip to content

Commit

Permalink
feat: combine keyboard enhancements into a nicer API
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
aymanbagabas committed Sep 17, 2024
1 parent a26ecc5 commit 390631c
Show file tree
Hide file tree
Showing 9 changed files with 142 additions and 229 deletions.
48 changes: 31 additions & 17 deletions commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package tea

import (
"time"

"github.com/charmbracelet/x/ansi"
)

// Batch performs a bunch of commands concurrently with no ordering guarantees
Expand Down Expand Up @@ -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
}
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.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
}
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.WithReleaseKeys))
if _, err := p.Run(); err != nil {
log.Printf("Error running program: %v", err)
}
Expand Down
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
107 changes: 49 additions & 58 deletions options.go
Original file line number Diff line number Diff line change
Expand Up @@ -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-<key>
// and Meta-<key>
// 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
}
}

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(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",
},
}

Expand Down
Loading

0 comments on commit 390631c

Please sign in to comment.