Skip to content

Commit f03c467

Browse files
authored
refactor: keyboard enhancements api improvements (#1534)
1 parent d9f7210 commit f03c467

5 files changed

Lines changed: 125 additions & 77 deletions

File tree

cursed_renderer.go

Lines changed: 11 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -120,16 +120,11 @@ func (s *cursedRenderer) start() {
120120
if s.lastView.ProgressBar != nil {
121121
setProgressBar(s, s.lastView.ProgressBar)
122122
}
123-
if !s.lastView.DisableKeyEnhancements {
124-
kittyFlags := ansi.KittyDisambiguateEscapeCodes
125-
if s.lastView.KeyReleases {
126-
kittyFlags |= ansi.KittyReportEventTypes
127-
}
128-
if s.lastView.UniformKeyLayout {
129-
kittyFlags |= ansi.KittyReportAlternateKeys | ansi.KittyReportAllKeysAsEscapeCodes
130-
}
131-
_, _ = s.scr.WriteString(ansi.KittyKeyboard(kittyFlags, 1))
123+
kittyFlags := ansi.KittyDisambiguateEscapeCodes
124+
if s.lastView.KeyboardEnhancements.ReportEventTypes {
125+
kittyFlags |= ansi.KittyReportEventTypes
132126
}
127+
_, _ = s.scr.WriteString(ansi.KittyKeyboard(kittyFlags, 1))
133128
}
134129

135130
// close implements renderer.
@@ -193,10 +188,8 @@ func (s *cursedRenderer) close() (err error) {
193188
_, _ = s.scr.WriteString(ansi.ResetProgressBar)
194189
}
195190

196-
if s.lastView != nil && !s.lastView.DisableKeyEnhancements {
197-
// NOTE: This needs to happen after we exit the alt screen.
198-
_, _ = s.scr.WriteString(ansi.KittyKeyboard(0, 1))
199-
}
191+
// NOTE: This needs to happen after we exit the alt screen.
192+
_, _ = s.scr.WriteString(ansi.KittyKeyboard(0, 1))
200193
}
201194

202195
if err := s.scr.Flush(); err != nil {
@@ -287,26 +280,16 @@ func (s *cursedRenderer) flush(closing bool) error {
287280
}
288281

289282
// kitty keyboard protocol
290-
//nolint:nestif
291-
if s.lastView == nil || view.DisableKeyEnhancements != s.lastView.DisableKeyEnhancements ||
292-
view.KeyReleases != s.lastView.KeyReleases ||
293-
view.UniformKeyLayout != s.lastView.UniformKeyLayout ||
283+
if s.lastView == nil || view.KeyboardEnhancements != s.lastView.KeyboardEnhancements ||
294284
view.AltScreen != s.lastView.AltScreen {
295285
// NOTE: We need to reset the keyboard protocol when switching
296286
// between main and alt screen. This is because the specs specify
297287
// two different states for the main and alt screen.
298-
if view.DisableKeyEnhancements {
299-
_, _ = s.scr.WriteString(ansi.KittyKeyboard(0, 1))
300-
} else {
301-
kittyFlags := ansi.KittyDisambiguateEscapeCodes // always enable basic key disambiguation
302-
if view.KeyReleases {
303-
kittyFlags |= ansi.KittyReportEventTypes
304-
}
305-
if view.UniformKeyLayout {
306-
kittyFlags |= ansi.KittyReportAlternateKeys | ansi.KittyReportAllKeysAsEscapeCodes
307-
}
308-
_, _ = s.scr.WriteString(ansi.KittyKeyboard(kittyFlags, 1))
288+
kittyFlags := ansi.KittyDisambiguateEscapeCodes // always enable basic key disambiguation
289+
if view.KeyboardEnhancements.ReportEventTypes {
290+
kittyFlags |= ansi.KittyReportEventTypes
309291
}
292+
_, _ = s.scr.WriteString(ansi.KittyKeyboard(kittyFlags, 1))
310293
if !closing {
311294
// Request keyboard enhancements when they change
312295
_, _ = s.scr.WriteString(ansi.RequestKittyKeyboard)

examples/keyboard-enhancements/main.go

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ type styles struct {
1717
}
1818

1919
type model struct {
20-
supportsRelease bool
2120
supportsDisambiguation bool
21+
supportsEventTypes bool
2222
styles styles
2323
}
2424

@@ -28,14 +28,24 @@ func (m model) Init() tea.Cmd {
2828

2929
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
3030
switch msg := msg.(type) {
31-
// When tea.RequestKeyboardEnhancements is called, the program will receive
32-
// a tea.KeyboardEnhancementsMsg message. This means that an attempt to
33-
// enable keyboard enhancements was made, however it doesn't guarantee that
34-
// it was successful.
31+
// Bubble Tea will send a [tea.KeyboardEnhancementsMsg] on startup if the
32+
// terminal supports keyboard enhancements features.
33+
//
34+
// These features extend the capabilities of keyboard input beyond the basic legacy
35+
// support found in most terminals. This includes features like:
36+
// - Key disambiguation: Improved ability to distinguish between certain key presses
37+
// like "enter" and "shift+enter" or "tab" and "ctrl+i".
38+
// - Key event types: The ability to report different types of key events such as
39+
// key presses and key releases.
40+
//
41+
// This allows for more nuanced input handling in terminal applications.
42+
// You can ask Bubble Tea to request additional keyboard enhancements
43+
// features by setting fields on the [tea.View.KeyboardEnhancements] struct
44+
// in your [tea.View] method.
3545
case tea.KeyboardEnhancementsMsg:
3646
// Check which features were able to be enabled.
37-
m.supportsRelease = msg.SupportsKeyReleases()
38-
m.supportsDisambiguation = msg.SupportsKeyDisambiguation()
47+
m.supportsDisambiguation = true // This is always enabled when this msg is received.
48+
m.supportsEventTypes = msg.SupportsEventTypes()
3949

4050
case tea.KeyPressMsg:
4151
switch msg.String() {
@@ -58,15 +68,16 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
5868
func (m model) View() tea.View {
5969
var v tea.View
6070
var b strings.Builder
61-
fmt.Fprintf(&b, "Terminal supports key releases: %v\n", m.supportsRelease)
71+
fmt.Fprintf(&b, "Terminal supports key releases: %v\n", m.supportsEventTypes)
6272
fmt.Fprintf(&b, "Terminal supports key disambiguation: %v\n", m.supportsDisambiguation)
6373
fmt.Fprint(&b, "This demo logs key events. Press ctrl+c to quit.")
6474
v.SetContent(b.String() + "\n")
6575

66-
// Attempt to enable keyboard enhancements. By default, this just
67-
// enables key disabiguation. For key releases, you'll need to opt-in
68-
// to that feature.
69-
v.KeyReleases = true
76+
// Attempt to enable reporting key event types (key presses and key
77+
// releases). By default, only key disambiguation is enabled which improves
78+
// the ability to distinguish between certain key presses like "enter" and
79+
// "shift+enter" or "tab" and "ctrl+i".
80+
v.KeyboardEnhancements.ReportEventTypes = true
7081

7182
return v
7283
}

examples/print-key/main.go

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,8 @@ func (m model) Init() tea.Cmd {
1515
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
1616
switch msg := msg.(type) {
1717
case tea.KeyboardEnhancementsMsg:
18-
return m, tea.Printf("Keyboard enhancements: Disambiguation: %v, ReleaseKeys: %v, Uniform keys: %v\n",
19-
msg.SupportsKeyDisambiguation(),
20-
msg.SupportsKeyReleases(),
21-
msg.SupportsUniformKeyLayout())
18+
return m, tea.Printf("Keyboard enhancements: EventTypes: %v\n",
19+
msg.SupportsEventTypes())
2220
case tea.KeyMsg:
2321
key := msg.Key()
2422
switch msg := msg.(type) {
@@ -41,8 +39,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
4139

4240
func (m model) View() tea.View {
4341
v := tea.NewView("Press any key to see its details printed to the terminal. Press 'ctrl+c' to quit.")
44-
v.KeyReleases = true
45-
v.UniformKeyLayout = true
42+
v.KeyboardEnhancements.ReportEventTypes = true
4643
return v
4744
}
4845

keyboard.go

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,25 +7,27 @@ import (
77
// KeyboardEnhancementsMsg is a message that gets sent when the terminal
88
// supports keyboard enhancements.
99
type KeyboardEnhancementsMsg struct {
10+
// Flags is a bitmask of supported keyboard enhancement features.
11+
// See [ansi.KittyReportEventTypes] and other constants for details.
12+
//
13+
// Example:
14+
//
15+
// ```go
16+
// // The hard way
17+
// if msg.Flags&ansi.KittyReportEventTypes != 0 {
18+
// // Terminal supports reporting different key event types
19+
// }
20+
//
21+
// // The easy way
22+
// if msg.SupportsEventTypes() {
23+
// // Terminal supports reporting different key event types
24+
// }
25+
// ```
1026
Flags int
1127
}
1228

13-
// SupportsKeyDisambiguation returns whether the terminal supports reporting
14-
// disambiguous keys as escape codes.
15-
func (k KeyboardEnhancementsMsg) SupportsKeyDisambiguation() bool {
16-
return k.Flags&ansi.KittyDisambiguateEscapeCodes != 0
17-
}
18-
19-
// SupportsKeyReleases returns whether the terminal supports key release
20-
// events.
21-
func (k KeyboardEnhancementsMsg) SupportsKeyReleases() bool {
29+
// SupportsEventTypes returns whether the terminal supports reporting
30+
// different types of key events (press, release, and repeat).
31+
func (k KeyboardEnhancementsMsg) SupportsEventTypes() bool {
2232
return k.Flags&ansi.KittyReportEventTypes != 0
2333
}
24-
25-
// SupportsUniformKeyLayout returns whether the terminal supports reporting key
26-
// events as though they were on a PC-101 layout.
27-
func (k KeyboardEnhancementsMsg) SupportsUniformKeyLayout() bool {
28-
return k.SupportsKeyDisambiguation() &&
29-
k.Flags&ansi.KittyReportAlternateKeys != 0 &&
30-
k.Flags&ansi.KittyReportAllKeysAsEscapeCodes != 0
31-
}

tea.go

Lines changed: 69 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -168,20 +168,75 @@ type View struct {
168168
// [MouseModeNone], [MouseModeCellMotion], or [MouseModeAllMotion].
169169
MouseMode MouseMode
170170

171-
// DisableKeyEnhancements disables all key enhancements for this view.
172-
DisableKeyEnhancements bool
173-
174-
// KeyReleases enables support for reporting key release events. This is
175-
// useful for terminals that support the Kitty keyboard protocol "Report
176-
// event types" progressive enhancement feature.
177-
KeyReleases bool
178-
179-
// UniformKeyLayout enables support for reporting key events as though they
180-
// were on a PC-101 layout. This is useful for uniform key event reporting
181-
// across different keyboard layouts. This is equivalent to the Kitty
182-
// keyboard protocol "Report alternate keys" and "Report all keys as escape
183-
// codes" progressive enhancement features.
184-
UniformKeyLayout bool
171+
// KeyboardEnhancements describes what keyboard enhancement features Bubble
172+
// Tea should request from the terminal.
173+
//
174+
// Bubble Tea supports requesting the following keyboard enhancement features:
175+
// - ReportEventTypes: requests the terminal to report key repeat and
176+
// release events.
177+
//
178+
// If the terminal supports any of these features, your program will
179+
// receive a [KeyboardEnhancementsMsg] that indicates which features are
180+
// available.
181+
KeyboardEnhancements KeyboardEnhancements
182+
}
183+
184+
// KeyboardEnhancements describes the requested keyboard enhancement features.
185+
// If the terminal supports any of them, it will respond with a
186+
// [KeyboardEnhancementsMsg] that indicates which features are supported.
187+
188+
// KeyboardEnhancements defines different keyboard enhancement features that
189+
// can be requested from the terminal.
190+
191+
// KeyboardEnhancements defines different keyboard enhancement features that
192+
// can be requested from the terminal.
193+
//
194+
// By default, Bubble Tea requests basic key disambiguation features from the
195+
// terminal. If the terminal supports keyboard enhancements, or any of its
196+
// additional features, it will respond with a [KeyboardEnhancementsMsg] that
197+
// indicates which features are supported.
198+
//
199+
// Example:
200+
//
201+
// ```go
202+
// func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
203+
// switch msg := msg.(type) {
204+
// case tea.KeyboardEnhancementsMsg:
205+
// // We have basic key disambiguation support.
206+
// // We can handle "shift+enter", "ctrl+i", etc.
207+
// m.keyboardEnhancements = msg
208+
// if msg.ReportEventTypes {
209+
// // Even better! We can now handle key repeat and release events.
210+
// }
211+
// case tea.KeyPressMsg:
212+
// switch msg.String() {
213+
// case "shift+enter":
214+
// // Handle shift+enter
215+
// // This would not be possible without keyboard enhancements.
216+
// case "ctrl+j":
217+
// // Handle ctrl+j
218+
// }
219+
// case tea.KeyReleaseMsg:
220+
// // Whoa! A key was released!
221+
// }
222+
//
223+
// return m, nil
224+
// }
225+
//
226+
// func (m model) View() tea.View {
227+
// v := tea.NewView("Press some keys!")
228+
// // Request reporting key repeat and release events.
229+
// v.KeyboardEnhancements.ReportEventTypes = true
230+
// return v
231+
// }
232+
// ```
233+
type KeyboardEnhancements struct {
234+
// ReportEventTypes requests the terminal to report key repeat and release
235+
// events.
236+
// If supported, your program will receive [KeyReleaseMsg]s and
237+
// [KeyPressMsg] with the [Key.IsRepeat] field set indicating that this is
238+
// a it's part of a key repeat sequence.
239+
ReportEventTypes bool
185240
}
186241

187242
// SetContent sets the content of the view to the value.

0 commit comments

Comments
 (0)