summaryrefslogtreecommitdiff
path: root/tui/termfo/keys
diff options
context:
space:
mode:
Diffstat (limited to 'tui/termfo/keys')
-rw-r--r--tui/termfo/keys/key.go169
-rw-r--r--tui/termfo/keys/key_test.go31
-rw-r--r--tui/termfo/keys/keys.go120
3 files changed, 320 insertions, 0 deletions
diff --git a/tui/termfo/keys/key.go b/tui/termfo/keys/key.go
new file mode 100644
index 0000000..a4e454f
--- /dev/null
+++ b/tui/termfo/keys/key.go
@@ -0,0 +1,169 @@
+package keys
+
+import (
+ "fmt"
+ "strings"
+ "unicode/utf8"
+)
+
+// Modifiers keys.
+const (
+ Shift = 1 << 63
+ Ctrl = 1 << 62
+ Alt = 1 << 61
+
+ modmask = Shift | Ctrl | Alt
+)
+
+// Common control sequences better known by their name than letter+Ctrl
+// combination.
+const (
+ Tab = 'i' | Ctrl
+ Escape = '[' | Ctrl
+)
+
+// Key represents a keypress. This is formatted as follows:
+//
+// - First 32 bits → rune (int32)
+// - Next 16 bits → Named key constant.
+// - Bits 49-61 → Currently unused.
+// - Bit 62 → Alt
+// - Bit 63 → Ctrl
+// - Bit 64 → Shift
+//
+// The upshot of this is that you can now use a single value to test for all
+// combinations:
+//
+// switch Key(0x61) {
+// case 'a': // 'a' w/o modifiers
+// case 'a' | keys.Ctrl: // 'a' with control
+// case 'a' | keys.Ctrl | keys.Shift: // 'a' with shift and control
+//
+// case keys.Up: // Arrow up
+// case keys.Up | keys.Ctrl: // Arrow up with control
+// }
+//
+// Which is nicer than using two or three different variables to signal various
+// things.
+//
+// Letters are always in lower-case; use keys.Shift to test for upper case.
+//
+// Control sequences (0x00-0x1f, 0x1f), are used sent as key + Ctrl. So this:
+//
+// switch k {
+// case 0x09:
+// }
+//
+// Won't work. you need to use:
+//
+// switch k {
+// case 'i' | key.Ctrl:
+// }
+//
+// Or better yet:
+//
+// ti := termfo.New("")
+//
+// ...
+//
+// switch k {
+// case ti.Keys[keys.Tab]:
+// }
+//
+// This just makes more sense, because people press "<C-a>" not "Start of
+// heading".
+//
+// It's better to use the variables from the terminfo, in case it's something
+// different. Especially with things like Shift and Ctrl modifiers this can
+// differ.
+//
+// Note that support for multiple modifier keys is flaky across terminals.
+type Key uint64
+
+// Shift reports if the Shift modifier is set.
+func (k Key) Shift() bool { return k&Shift != 0 }
+
+// Ctrl reports if the Ctrl modifier is set.
+func (k Key) Ctrl() bool { return k&Ctrl != 0 }
+
+// Alt reports if the Alt modifier is set.
+func (k Key) Alt() bool { return k&Alt != 0 }
+
+// WithoutMods returns a copy of k without any modifier keys set.
+func (k Key) WithoutMods() Key { return k &^ modmask }
+
+// Valid reports if this key is valid.
+func (k Key) Valid() bool { return k&^modmask <= 1<<31 || k.Named() }
+
+// Named reports if this is a named key.
+func (k Key) Named() bool {
+ _, ok := keyNames[k&^modmask]
+ return ok
+}
+
+// Name gets the key name. This doesn't show if any modifiers are set; use
+// String() for that.
+func (k Key) Name() string {
+ k &^= modmask
+
+ n, ok := keyNames[k]
+ if ok {
+ return n
+ }
+ if !k.Valid() {
+ return fmt.Sprintf("Unknown key: 0x%x", uint64(k))
+ }
+ if rune(k) == utf8.RuneError {
+ return fmt.Sprintf("Invalid UTF-8: 0x%x", rune(k))
+ }
+
+ // TODO: maybe also other spaces like nbsp etc?
+ switch k {
+ case ' ':
+ return "Space"
+ case '\t':
+ return "Tab"
+ case Escape:
+ return "Esc"
+ }
+ return fmt.Sprintf("%c", rune(k))
+}
+
+func (k Key) String() string {
+ var (
+ hasMod = k.Ctrl() || k.Shift() || k.Alt()
+ name = k.Name()
+ named = utf8.RuneCountInString(name) > 1
+ b strings.Builder
+ )
+
+ b.Grow(8)
+
+ if hasMod || named {
+ b.WriteRune('<')
+ }
+
+ if k.Shift() {
+ b.WriteString("S-")
+ }
+ if k.Alt() {
+ b.WriteString("A-")
+ }
+
+ switch k {
+ case Tab:
+ b.WriteString("Tab")
+ case Escape:
+ b.WriteString("Esc")
+ default:
+ if k.Ctrl() {
+ b.WriteString("C-")
+ }
+ b.WriteString(k.Name())
+ }
+
+ if hasMod || named {
+ b.WriteRune('>')
+ }
+ return b.String()
+}
diff --git a/tui/termfo/keys/key_test.go b/tui/termfo/keys/key_test.go
new file mode 100644
index 0000000..2536ddf
--- /dev/null
+++ b/tui/termfo/keys/key_test.go
@@ -0,0 +1,31 @@
+package keys
+
+import (
+ "testing"
+)
+
+func TestKey(t *testing.T) {
+ t.Skip()
+ tests := []struct {
+ k Key
+ want string
+ }{
+ {'a', "<a>"},
+ {'a' | Shift, "<S-a>"},
+ {'a' | Ctrl | Shift, "<S-C-a>"},
+ {'a' | Ctrl | Shift | Alt, "<S-C-A-a>"},
+ {Tab, "<Tab>"},
+ {Tab | Ctrl, "<C-Tab>"},
+ {Up, "<Up>"},
+ {Up | Ctrl, "<C-Up>"},
+ }
+
+ for _, tt := range tests {
+ t.Run("", func(t *testing.T) {
+ h := tt.k.String()
+ if h != tt.want {
+ t.Errorf("\nwant: %s\nhave: %s", tt.want, h)
+ }
+ })
+ }
+}
diff --git a/tui/termfo/keys/keys.go b/tui/termfo/keys/keys.go
new file mode 100644
index 0000000..fe2424c
--- /dev/null
+++ b/tui/termfo/keys/keys.go
@@ -0,0 +1,120 @@
+// Code generated by term.h.zsh; DO NOT EDIT.
+
+package keys
+
+import "citrons.xyz/talk/tui/termfo/caps"
+
+// CursesVersion is the version of curses this data was generated with, as [implementation]-[version].
+const CursesVersion = `ncurses-6.5.20240511`
+
+// Keys maps caps.Cap to Key constants
+var Keys = map[*caps.Cap]Key{
+ caps.TableStrs[55]: Backspace,
+ caps.TableStrs[59]: Delete,
+ caps.TableStrs[61]: Down,
+ caps.TableStrs[66]: F1,
+ caps.TableStrs[67]: F10,
+ caps.TableStrs[68]: F2,
+ caps.TableStrs[69]: F3,
+ caps.TableStrs[70]: F4,
+ caps.TableStrs[71]: F5,
+ caps.TableStrs[72]: F6,
+ caps.TableStrs[73]: F7,
+ caps.TableStrs[74]: F8,
+ caps.TableStrs[75]: F9,
+ caps.TableStrs[76]: Home,
+ caps.TableStrs[77]: Insert,
+ caps.TableStrs[79]: Left,
+ caps.TableStrs[81]: PageDown,
+ caps.TableStrs[82]: PageUp,
+ caps.TableStrs[83]: Right,
+ caps.TableStrs[87]: Up,
+ caps.TableStrs[148]: BackTab,
+ caps.TableStrs[164]: End,
+ caps.TableStrs[165]: Enter,
+ caps.TableStrs[191]: ShiftDelete,
+ caps.TableStrs[194]: ShiftEnd,
+ caps.TableStrs[199]: ShiftHome,
+ caps.TableStrs[200]: ShiftInsert,
+ caps.TableStrs[201]: ShiftLeft,
+ caps.TableStrs[210]: ShiftRight,
+ caps.TableStrs[216]: F11,
+ caps.TableStrs[217]: F12,
+ caps.TableStrs[355]: Mouse,
+}
+
+// List of all key sequences we know about. This excludes most obscure ones not
+// present on modern devices.
+const (
+ // Special key used to signal errors.
+ UnknownSequence Key = iota + (1 << 32)
+
+ ShiftLeft
+ PageUp
+ Insert
+ ShiftInsert
+ Up
+ Right
+ F1
+ ShiftRight
+ F2
+ ShiftHome
+ F3
+ Delete
+ PageDown
+ F4
+ F10
+ F11
+ ShiftDelete
+ F5
+ Down
+ Mouse
+ F12
+ ShiftEnd
+ F6
+ Enter
+ Left
+ F7
+ End
+ F8
+ F9
+ Backspace
+ BackTab
+ Home
+)
+
+// Names of named key constants.
+var keyNames = map[Key]string{
+ ShiftLeft: `ShiftLeft`,
+ PageUp: `PageUp`,
+ Insert: `Insert`,
+ ShiftInsert: `ShiftInsert`,
+ Up: `Up`,
+ Right: `Right`,
+ F1: `F1`,
+ ShiftRight: `ShiftRight`,
+ F2: `F2`,
+ ShiftHome: `ShiftHome`,
+ F3: `F3`,
+ Delete: `Delete`,
+ PageDown: `PageDown`,
+ F4: `F4`,
+ F10: `F10`,
+ F11: `F11`,
+ ShiftDelete: `ShiftDelete`,
+ F5: `F5`,
+ Down: `Down`,
+ Mouse: `Mouse`,
+ F12: `F12`,
+ ShiftEnd: `ShiftEnd`,
+ F6: `F6`,
+ Enter: `Enter`,
+ Left: `Left`,
+ F7: `F7`,
+ End: `End`,
+ F8: `F8`,
+ F9: `F9`,
+ Backspace: `Backspace`,
+ BackTab: `BackTab`,
+ Home: `Home`,
+}