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

Proposal: use x/input to handle input events #1014

Open
aymanbagabas opened this issue May 10, 2024 · 1 comment
Open

Proposal: use x/input to handle input events #1014

aymanbagabas opened this issue May 10, 2024 · 1 comment
Labels
proposal Introducing an API change

Comments

@aymanbagabas
Copy link
Member

aymanbagabas commented May 10, 2024

Current Bubble Tea input handling is quite limiting as it doesn't support parsing input events such as cursor position report, fixterms & Kitty keyboard, and other terminal features. This is because we only do map lookups for common key sequences used by most terminals.

This is where the work on charmbracelet/x/input began. This new input package can replace the existing Bubble Tea input handler to enhance and improve parsing input events and support. This means we can support more terminal features since we can now parse input sequences such as the ones found in the Kitty keyboard protocol and others.

Advanced Bubble Tea Input Handling

This gives us high fidelity input events like key up events and formerly unavailable keybindings like shift+enter!

So what’s new?

Well the big news is that, in modern terminals, you are freed from the shackles of old terminal ways and now have access to a bunch of key and mouse commands that weren’t previously available. That’s right the shift+enter dream is now a reality. Bubble Tea game engine? Now you can.

Mouse events also received an upgrade, now you can distingiush between mouse events based on the msg type.

switch msg := msg.(type) {
case tea.KeyDownMsg:
    switch msg.String() {
    case "space":
        // Push-to-talk: mic on
    }
case tea.KeyUpMsg:
    switch msg.String() {
    case "space":
        // Push-to-talk: mic off
    }
case tea.MouseDownMsg:
    // Pen: start drawing
case tea.MouseUpMsg:
    // Pen: stop drawing
case tea.MouseWheelMsg:
    // View: scroll up/down/left/right?
case tea.MouseMotionMsg:
    // Drag-n-drop
}

You can still use tea.KeyMsg since it's just an alias to tea.KeyDownMsg. The same goes for tea.MouseMsg and tea.MouseDownMsg.

Keep in mind that, at the moment, the enhanced keyboard is only supported on Windows and the following terminals…

  • Ghostty
  • Kitty
  • Alacrity
  • iTerm2
  • Foot
  • WezTerm
  • Rio
  • Windows Terminal

Note: on non-Windows platforms, the enhanced keyboard relies on the Kitty Keyboard protocol which all the above (except Windows Terminal) support. In case of an unsupported terminal, the application won't get tea.KeyUpMsgs. You can check ctx.SupportsEnhancedKeyboard() from the Bubble Tea Context proposal to check whether the terminal supports enhanced keyboard.

Common keys are still the same except that the KeyCtrl and KeyShift variants and the key.Alt property are now merged into tea.KeyMod. Here's a some of the common keys available.

	KeyBackspace
	KeyTab
	KeyEnter
	KeyEscape
	KeyUp
	KeyDown
	KeyRight
	KeyLeft
	KeyBegin
	KeyFind
	KeyInsert
	KeySelect
	KeyPgUp
	KeyPgDown
	KeyHome
	KeyEnd
	KeyF1
	KeyF2
	KeyF3
	KeyF4
	KeyF5
	KeyF6
	KeyF7
	KeyF8
	KeyF9
	KeyF10
	...

How to upgrade

If you’re matching strings, there’s nothing to do:

switch msg.String() {
case "ctrl+a":
    // Gotcha!
}

Special keys are now indicated in tea.KeySym:

// Before
switch msg.Type {
case tea.KeyEnter:
    // Enter!
case tea.KeyEsc:
    // Escape!
case tea.KeyBackspace:
    // Back!
}

// After
switch msg.Sym {
case tea.KeyEnter:
    // Enter!
case tea.KeyEsc:
    // Escape!
case tea.KeyBackspace:
    // Back!
}

If you’re going for ultimate safety:

// Before
switch msg.Type {
case tea.KeyCtrlA:
    // Ceçi
case tea.KeyCtrlB:
    // Cela
}

// After
if msg.Sym == KeyNone && msg.Mod == tea.Ctrl {
    switch msg.Rune {
    case 'a':
        // Ceçi
    case 'b':
        // Cela
    }
}

If you're matching a modifier key:

// Before
if msg.Alt {
    // alt pressed
}

// After
if msg.Mod.IsAlt() {
    // alt pressed
}

If you're matching multiple modifier keys:

// Before
switch msg.Type {
case tea.KeyCtrlA:
    if msg.Alt {
        // ctrl+alt+a
    }
}

// After
if msg.Rune == 'a' && msg.Mod == tea.Ctrl|tea.Alt {
    // ctrl+alt+a
}

If you’re matching runes:

// Before
switch msg.Type {
case tea.Runes == []rune{'a'}:
    // The letter a
}

// After
switch msg.Type {
case tea.Rune == 'a':
    // The letter a
}

If bracketed paste is enabled, which it is by default, you now need to handle pastes like so:

switch msg {
case tea.PasteMsg:
    // Here’s a big ol’ paste!
}

Matching against mouse events is almost the same. We've dropped msg.Action and msg.Ctrl|Alt|Shift in favor of msg.Mod. The action is now split into different types and the modifiers are combinied into a single field msg.Mod.

A mouse message can have on of the following msg.Buttons:

	MouseNone MouseButton = iota
	MouseLeft
	MouseMiddle
	MouseRight
	MouseWheelUp
	MouseWheelDown
	MouseWheelLeft
	MouseWheelRight
	MouseBackward
	MouseForward
	MouseExtra1
	MouseExtra2

Use the fmt.Stringer interface to easily match against different button combos:

switch msg := msg.(type) {
case tea.MouseMsg:
    switch msg.String() {
    case "left":
        // left press
    case "ctrl+left":
        // ctrl+left press
    case "ctrl+alt+shift+left":
        // ctrl+alt+shift+left press
    }
}

You can now use the same string matchin to match against release, motion, and wheel events.

// Before
switch msg := msg.(type) {
case tea.MouseMsg:
    if msg.Action != tea.MouseRelease {
        break
    }
    switch msg.String() {
    case "left release":
        // left release
    case "ctrl+left release":
        // ctrl+left release
    case "ctrl+alt+shift+left release":
        // ctrl+alt+shift+left release
    }
}

// After
switch msg := msg.(type) {
case tea.MouseUpMsg:
    switch msg.String() {
    case "left":
        // left release
    case "ctrl+left":
        // ctrl+left 
    case "ctrl+alt+shift+left":
        // whoa!
    }
}
// Simpler, eh?

If you're matching against mouse clicks:

// Before
switch msg := msg.(type) {
case tea.MouseMsg:
    xPos, yPos := msg.X, msg.Y
}

// After
switch msg := msg.(type) {
case tea.MouseDownMsg:
    xPos, yPos := msg.X, msg.Y
}

Wanna also capture wheel events?

// Before
switch msg := msg.(type) {
case tea.MouseMsg:
    xPos, yPos := msg.X, msg.Y
    if msg.IsWheel() {
        // scroll down
    }
}

// After
switch msg := msg.(type) {
case tea.MouseDownMsg:
    xPos, yPos := msg.X, msg.Y
case tea.MouseWheelMsg:
    // scroll down
}

Track mouse movements?

// Before
switch msg := msg.(type) {
case tea.MouseMsg:
    if msg.Action == tea.MouseMotion {
        xPos, yPos := msg.X, msg.Y
    }
}

// After
switch msg := msg.(type) {
case tea.MouseMotionMsg:
    xPos, yPos := msg.X, msg.Y
}

If you want to detect modifier keys:

// Before
switch msg := msg.(type) {
case tea.MouseMsg:
    if msg.Shift {
        // shift key
    }
}

// After
switch msg := msg.(type) {
case tea.MouseDownMsg:
    if msg.Mod.IsShift() {
        // shift key
    }
}

You can try this proposal in the beta branch here

@aymanbagabas aymanbagabas added the proposal Introducing an API change label May 10, 2024
@topi314
Copy link

topi314 commented Jul 10, 2024

I have been playing around with the beta and it's working quite well, one bug I ran into is a stack overflow panic which seems to be caused by the new tea.Context.

The issue seems to be the wrapping of the old context with context.WithCancel. The issue is fixed if I change the line to:

-p.ctx.Context, p.cancel = context.WithCancel(p.ctx)
+p.ctx.Context, p.cancel = context.WithCancel(p.ctx.Context)

I am not sure what the correct fix here should be, but it at least works for me for now

aymanbagabas added a commit that referenced this issue Aug 12, 2024
Currently, Bubble Tea uses a simple lookup table to detect input events.
Here, we're introducing an actual input sequence parser instead of
simply using a lookup table. This will allow Bubble Tea programs to read
all sorts of input events such Kitty keyboard, background color, mode
report, and all sorts of ANSI sequence input events.

Supersedes: #1014
Related: #869
Related: #163
Related: #918
Related: #850
Related: #207
aymanbagabas added a commit that referenced this issue Aug 12, 2024
Currently, Bubble Tea uses a simple lookup table to detect input events.
Here, we're introducing an actual input sequence parser instead of
simply using a lookup table. This will allow Bubble Tea programs to read
all sorts of input events such Kitty keyboard, background color, mode
report, and all sorts of ANSI sequence input events.

Supersedes: #1079
Supersedes: #1014
Related: #869
Related: #163
Related: #918
Related: #850
Related: #207
aymanbagabas added a commit that referenced this issue Aug 12, 2024
Currently, Bubble Tea uses a simple lookup table to detect input events.
Here, we're introducing an actual input sequence parser instead of
simply using a lookup table. This will allow Bubble Tea programs to read
all sorts of input events such Kitty keyboard, background color, mode
report, and all sorts of ANSI sequence input events.

Supersedes: #1079
Supersedes: #1014
Related: #869
Related: #163
Related: #918
Related: #850
Related: #207
aymanbagabas added a commit that referenced this issue Aug 19, 2024
Currently, Bubble Tea uses a simple lookup table to detect input events.
Here, we're introducing an actual input sequence parser instead of
simply using a lookup table. This will allow Bubble Tea programs to read
all sorts of input events such Kitty keyboard, background color, mode
report, and all sorts of ANSI sequence input events.

This PR includes the following changes:
- Support clipboard OSC52 read messages (`OSC 52 ?`)
- Support terminal foreground/background/cursor color report messages
(OSC10, OSC11, OSC12)
- Support terminal focus events (mode 1004)
- Deprecate the old `KeyMsg` API in favor of `KeyPressMsg` and
`KeyReleaseMsg`
- `KeyType` const values are different now. Programs that use int value
comparison **will** break. E.g. `key.Type == 13` where `13` is the
control code for `CR` that corresponds to the <kbd>enter</kbd> key.
(BREAKING CHANGE!)
- Bubble Tea will send two messages for key presses, the first of type
`KeyMsg` and the second of type `KeyPressMsg`. This is to keep backwards
compatibility and _not_ break the API
  - `tea.Key` contains breaking changes (BREAKING CHANGE!)
- Deprecate `MouseMsg` in favor of `MouseClickMsg`, `MouseReleaseMsg`,
`MouseWheelMsg`, and `MouseMotionMsg`
- Bubble Tea will send two messages for mouse clicks, releases, wheel,
and motion. The first message will be a `MouseMsg` type. And the second
will have the new corresponding type. This is to keep backwards
compatibility and _not_ break the API
  - `tea.Mouse` contains breaking changes (BREAKING CHANGE!)
- Support reading Kitty keyboard reports (reading the results of sending
`CSI ? u` to the terminal)
- Support reading Kitty keyboard and fixterms keys `CSI u`
- Support reading terminal mode reports (DECRPM)
- Bracketed-paste messages now have their own message type `PasteMsg`.
Use `PasteStartMsg` and `PasteEndMsg` to listen to the start/end of the
paste message.
- Bubble Tea will send two messages for bracketed-paste, the first is of
type `KeyMsg` and the second is of type `PasteMsg`. This is to keep
backwards compatibility and _not_ break the API
- Support more obscure key input sequences found in URxvt and others
- Support reading termcap/terminfo capabilities through `XTGETTCAP`.
These capabilities will get reported as `TermcapMsg`
- Support reading terminfo databases for key input sequences (disabled
for now)
- Support reading [Win32 Input Mode
keys](https://github.com/microsoft/terminal/blob/main/doc/specs/%234999%20-%20Improved%20keyboard%20handling%20in%20Conpty.md#win32-input-mode-sequences)
- Support reading Xterm `modifyOtherKeys` keys

TODO:
- [x] Parse multi-rune graphemes as one `KeyPressMsg` storing it in
`key.Runes`
- [x] Kitty keyboard startup settings and options
#1083
- [x] Xterm modify other keys startup settings and options
#1084
- [x] Focus events startup settings and options
#1081
- [x] Fg/bg/cursor terminal color startup settings and options
#1085

Supersedes: #1079
Supersedes: #1014
Related: #869
Related: #163
Related: #918
Related: #850
Related: #207
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
proposal Introducing an API change
Projects
None yet
Development

No branches or pull requests

2 participants