Skip to content

Commit

Permalink
Merge branch 'master' into input-background-foreground
Browse files Browse the repository at this point in the history
  • Loading branch information
aymanbagabas authored Aug 19, 2024
2 parents 8f6ac01 + 828ff70 commit 35c7e09
Show file tree
Hide file tree
Showing 17 changed files with 377 additions and 94 deletions.
22 changes: 22 additions & 0 deletions commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
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.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
Expand Down
4 changes: 2 additions & 2 deletions examples/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
36 changes: 36 additions & 0 deletions examples/print-key/main.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ 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.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
Expand All @@ -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
)
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +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.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=
Expand Down
18 changes: 9 additions & 9 deletions key.go
Original file line number Diff line number Diff line change
Expand Up @@ -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'.
Expand Down Expand Up @@ -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+"
}

Expand Down
100 changes: 100 additions & 0 deletions key_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,106 @@ func buildBaseSeqTests() []seqTest {
func TestParseSequence(t *testing.T) {
td := buildBaseSeqTests()
td = append(td,
// Xterm modifyOtherKeys CSI 27 ; <modifier> ; <code> ~
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"),
[]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'},
Expand Down
77 changes: 52 additions & 25 deletions kitty.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,32 +7,59 @@ import (
"github.com/charmbracelet/x/ansi"
)

// KittyKeyboardMsg represents Kitty keyboard progressive enhancement flags message.
type KittyKeyboardMsg int
// setKittyKeyboardFlagsMsg is a message to set Kitty keyboard progressive
// enhancement protocol flags.
type setKittyKeyboardFlagsMsg int

// IsDisambiguateEscapeCodes returns true if the DisambiguateEscapeCodes flag is set.
func (e KittyKeyboardMsg) IsDisambiguateEscapeCodes() bool {
return e&ansi.KittyDisambiguateEscapeCodes != 0
// 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 {
return func() Msg {
return setKittyKeyboardFlagsMsg(flags)
}
}

// IsReportEventTypes returns true if the ReportEventTypes flag is set.
func (e KittyKeyboardMsg) IsReportEventTypes() bool {
return e&ansi.KittyReportEventTypes != 0
// DisableKittyKeyboard is a command to disable Kitty keyboard progressive
// enhancements.
func DisableKittyKeyboard() Msg {
return setKittyKeyboardFlagsMsg(0)
}

// IsReportAlternateKeys returns true if the ReportAlternateKeys flag is set.
func (e KittyKeyboardMsg) IsReportAlternateKeys() bool {
return e&ansi.KittyReportAlternateKeys != 0
}
// kittyKeyboardMsg is a message that queries the current Kitty keyboard
// progressive enhancement flags.
type kittyKeyboardMsg struct{}

// IsReportAllKeys returns true if the ReportAllKeys flag is set.
func (e KittyKeyboardMsg) IsReportAllKeys() bool {
return e&ansi.KittyReportAllKeys != 0
// KittyKeyboard is a command that queries the current Kitty keyboard
// progressive enhancement flags from the terminal.
func KittyKeyboard() Msg {
return kittyKeyboardMsg{}
}

// IsReportAssociatedKeys returns true if the ReportAssociatedKeys flag is set.
func (e KittyKeyboardMsg) IsReportAssociatedKeys() bool {
return e&ansi.KittyReportAssociatedKeys != 0
// KittyKeyboardMsg represents Kitty keyboard progressive enhancement flags message.
type KittyKeyboardMsg int

// Kitty Keyboard Protocol flags.
const (
KittyDisambiguateEscapeCodes KittyKeyboardMsg = 1 << iota
KittyReportEventTypes
KittyReportAlternateKeys
KittyReportAllKeys
KittyReportAssociatedKeys
)

// Contains reports whether m contains the given flags.
func (m KittyKeyboardMsg) Contains(flags KittyKeyboardMsg) bool {
return m&flags == flags
}

// Kitty Clipboard Control Sequences
Expand Down Expand Up @@ -268,13 +295,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 {

Check failure on line 298 in kitty.go

View workflow job for this annotation

GitHub Actions / lint-soft

Magic number: 2, in <argument> detected (gomnd)
r := rune(params[0])
if unicode.IsPrint(r) {
key.altRune = key.Rune()
key.Runes = []rune{r}
}
}
if isRelease {
return KeyReleaseMsg(key)
}
Expand Down
Loading

0 comments on commit 35c7e09

Please sign in to comment.