pax_global_header00006660000000000000000000000064140301517620014511gustar00rootroot0000000000000052 comment=0119fc8ff008084fb5ee4d06cda84fe1f0e202ce cbind/000077500000000000000000000000001403015176200121345ustar00rootroot00000000000000cbind/.gitignore000066400000000000000000000000141403015176200141170ustar00rootroot00000000000000.idea/ *.sh cbind/.gitlab-ci.yml000066400000000000000000000004341403015176200145710ustar00rootroot00000000000000image: golang:latest stages: - validate - build fmt: stage: validate script: - gofmt -l -s -e . - exit $(gofmt -l -s -e . | wc -l) vet: stage: validate script: - go vet -composites=false ./... test: stage: validate script: - go test -race -v ./... cbind/CHANGELOG000066400000000000000000000005021403015176200133430ustar00rootroot000000000000000.1.5: - Update docs 0.1.4: - Add UnifyEnterKeys option to interpret KPEnter as Enter (enabled by default) 0.1.3: - Add Configuration.Set - Return ErrInvalidKeyEvent when encoding or decoding fails 0.1.2: - Add method to remove all handlers - Upgrade tcell to v2 0.1.1: - Add CtrlKey support 0.1.0: - Initial release cbind/LICENSE000066400000000000000000000021101403015176200131330ustar00rootroot00000000000000MIT License Copyright (c) 2020 Trevor Slocum Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. cbind/README.md000066400000000000000000000035251403015176200134200ustar00rootroot00000000000000!!! cbind has moved to [code.rocketnine.space](https://code.rocketnine.space/tslocum/cbind) !!! ==== Please visit https://code.rocketnine.space/tslocum/cbind ------------ # cbind [![GoDoc](https://code.rocketnine.space/tslocum/godoc-static/raw/branch/master/badge.svg)](https://docs.rocketnine.space/code.rocketnine.space/tslocum/cbind) [![Donate](https://img.shields.io/liberapay/receives/rocketnine.space.svg?logo=liberapay)](https://liberapay.com/rocketnine.space) Key event handling library for tcell ## Features - Set `KeyEvent` handlers - Encode and decode `KeyEvent`s as human-readable strings ## Usage ```go // Create a new input configuration to store the key bindings. c := NewConfiguration() // Define save event handler. handleSave := func(ev *tcell.EventKey) *tcell.EventKey { return nil } // Define open event handler. handleOpen := func(ev *tcell.EventKey) *tcell.EventKey { return nil } // Define exit event handler. handleExit := func(ev *tcell.EventKey) *tcell.EventKey { return nil } // Bind Alt+s. if err := c.Set("Alt+s", handleSave); err != nil { log.Fatalf("failed to set keybind: %s", err) } // Bind Alt+o. c.SetRune(tcell.ModAlt, 'o', handleOpen) // Bind Escape. c.SetKey(tcell.ModNone, tcell.KeyEscape, handleExit) // Capture input. This will differ based on the framework in use (if any). // When using tview or cview, call Application.SetInputCapture before calling // Application.Run. app.SetInputCapture(c.Capture) ``` ## Documentation Documentation is available via [gdooc](https://docs.rocketnine.space/code.rocketnine.space/tslocum/cbind). The utility program `whichkeybind` is available to determine and validate key combinations. ```bash go get code.rocketnine.space/tslocum/cbind/whichkeybind ``` ## Support Please share issues and suggestions [here](https://code.rocketnine.space/tslocum/cbind/issues). cbind/configuration.go000066400000000000000000000051041403015176200153320ustar00rootroot00000000000000package cbind import ( "fmt" "sync" "unicode" "github.com/gdamore/tcell/v2" ) type eventHandler func(ev *tcell.EventKey) *tcell.EventKey // Configuration maps keys to event handlers and processes key events. type Configuration struct { handlers map[string]eventHandler mutex *sync.RWMutex } // NewConfiguration returns a new input configuration. func NewConfiguration() *Configuration { c := Configuration{ handlers: make(map[string]eventHandler), mutex: new(sync.RWMutex), } return &c } // Set sets the handler for a key event string. func (c *Configuration) Set(s string, handler func(ev *tcell.EventKey) *tcell.EventKey) error { mod, key, ch, err := Decode(s) if err != nil { return err } if key == tcell.KeyRune { c.SetRune(mod, ch, handler) } else { c.SetKey(mod, key, handler) } return nil } // SetKey sets the handler for a key. func (c *Configuration) SetKey(mod tcell.ModMask, key tcell.Key, handler func(ev *tcell.EventKey) *tcell.EventKey) { c.mutex.Lock() defer c.mutex.Unlock() if mod&tcell.ModShift != 0 && key == tcell.KeyTab { mod ^= tcell.ModShift key = tcell.KeyBacktab } if mod&tcell.ModCtrl == 0 && key != tcell.KeyBackspace && key != tcell.KeyTab && key != tcell.KeyEnter { for _, ctrlKey := range ctrlKeys { if key == ctrlKey { mod |= tcell.ModCtrl break } } } c.handlers[fmt.Sprintf("%d-%d", mod, key)] = handler } // SetRune sets the handler for a rune. func (c *Configuration) SetRune(mod tcell.ModMask, ch rune, handler func(ev *tcell.EventKey) *tcell.EventKey) { // Some runes are identical to named keys. Set the bind on the matching // named key instead. switch ch { case '\t': c.SetKey(mod, tcell.KeyTab, handler) return case '\n': c.SetKey(mod, tcell.KeyEnter, handler) return } if mod&tcell.ModCtrl != 0 { k, ok := ctrlKeys[unicode.ToLower(ch)] if ok { c.SetKey(mod, k, handler) return } } c.mutex.Lock() defer c.mutex.Unlock() c.handlers[fmt.Sprintf("%d:%d", mod, ch)] = handler } // Capture handles key events. func (c *Configuration) Capture(ev *tcell.EventKey) *tcell.EventKey { c.mutex.RLock() defer c.mutex.RUnlock() if ev == nil { return nil } var keyName string if ev.Key() != tcell.KeyRune { keyName = fmt.Sprintf("%d-%d", ev.Modifiers(), ev.Key()) } else { keyName = fmt.Sprintf("%d:%d", ev.Modifiers(), ev.Rune()) } handler := c.handlers[keyName] if handler != nil { return handler(ev) } return ev } // Clear removes all handlers. func (c *Configuration) Clear() { c.mutex.Lock() defer c.mutex.Unlock() c.handlers = make(map[string]eventHandler) } cbind/configuration_test.go000066400000000000000000000054661403015176200164040ustar00rootroot00000000000000package cbind import ( "fmt" "log" "sync" "testing" "time" "github.com/gdamore/tcell/v2" ) const pressTimes = 7 func TestConfiguration(t *testing.T) { t.Parallel() wg := make([]*sync.WaitGroup, len(testCases)) config := NewConfiguration() for i, c := range testCases { wg[i] = new(sync.WaitGroup) wg[i].Add(pressTimes) i := i // Capture if c.key != tcell.KeyRune { config.SetKey(c.mod, c.key, func(ev *tcell.EventKey) *tcell.EventKey { wg[i].Done() return nil }) } else { config.SetRune(c.mod, c.ch, func(ev *tcell.EventKey) *tcell.EventKey { wg[i].Done() return nil }) } } done := make(chan struct{}) timeout := time.After(5 * time.Second) go func() { for i := range testCases { wg[i].Wait() } done <- struct{}{} }() errs := make(chan error) for j := 0; j < pressTimes; j++ { for i, c := range testCases { i, c := i, c // Capture go func() { k := tcell.NewEventKey(c.key, c.ch, c.mod) if k.Key() != c.key { errs <- fmt.Errorf("failed to test capturing keybinds: tcell modified EventKey.Key: expected %d, got %d", c.key, k.Key()) return } else if k.Rune() != c.ch { errs <- fmt.Errorf("failed to test capturing keybinds: tcell modified EventKey.Rune: expected %d, got %d", c.ch, k.Rune()) return } else if k.Modifiers() != c.mod { errs <- fmt.Errorf("failed to test capturing keybinds: tcell modified EventKey.Modifiers: expected %d, got %d", c.mod, k.Modifiers()) return } ev := config.Capture(tcell.NewEventKey(c.key, c.ch, c.mod)) if ev != nil { errs <- fmt.Errorf("failed to test capturing keybinds: failed to register case %d event %d %d %d", i, c.mod, c.key, c.ch) } }() } } select { case err := <-errs: t.Fatal(err) case <-timeout: t.Fatal("timeout") case <-done: } } // Example of creating and using an input configuration. func ExampleNewConfiguration() { // Create a new input configuration to store the key bindings. c := NewConfiguration() handleSave := func(ev *tcell.EventKey) *tcell.EventKey { // Save return nil } handleOpen := func(ev *tcell.EventKey) *tcell.EventKey { // Open return nil } handleExit := func(ev *tcell.EventKey) *tcell.EventKey { // Exit return nil } // Bind Alt+s. if err := c.Set("Alt+s", handleSave); err != nil { log.Fatalf("failed to set keybind: %s", err) } // Bind Alt+o. c.SetRune(tcell.ModAlt, 'o', handleOpen) // Bind Escape. c.SetKey(tcell.ModNone, tcell.KeyEscape, handleExit) // Capture input. This will differ based on the framework in use (if any). // When using tview or cview, call Application.SetInputCapture before calling // Application.Run. // app.SetInputCapture(c.Capture) } // Example of capturing key events. func ExampleConfiguration_Capture() { // See the end of the NewConfiguration example. } cbind/doc.go000066400000000000000000000004751403015176200132360ustar00rootroot00000000000000/* Package cbind provides tcell key event encoding, decoding and handling. The NewConfiguration example demonstrates how to use cbind. There are some limitations on reading keyboard input, which is explained in the tcell.EventKey documentation: https://godoc.org/github.com/gdamore/tcell#EventKey */ package cbind cbind/go.mod000066400000000000000000000005601403015176200132430ustar00rootroot00000000000000module code.rocketnine.space/tslocum/cbind go 1.15 require ( github.com/gdamore/tcell/v2 v2.2.0 github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/rivo/uniseg v0.2.0 // indirect golang.org/x/sys v0.0.0-20210309040221-94ec62e08169 // indirect golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d // indirect golang.org/x/text v0.3.5 // indirect ) cbind/go.sum000066400000000000000000000037561403015176200133020ustar00rootroot00000000000000github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko= github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= github.com/gdamore/tcell/v2 v2.2.0 h1:vSyEgKwraXPSOkvCk7IwOSyX+Pv3V2cV9CikJMXg4U4= github.com/gdamore/tcell/v2 v2.2.0/go.mod h1:cTTuF84Dlj/RqmaCIV5p4w8uG1zWdk0SF6oBpwHp4fU= github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/mattn/go-runewidth v0.0.10 h1:CoZ3S2P7pvtP45xOtBw+/mDL2z0RKI576gSkzRRpdGg= github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210309040221-94ec62e08169 h1:fpeMGRM6A+XFcw4RPCO8s8hH7ppgrGR22pSIjwM7YUI= golang.org/x/sys v0.0.0-20210309040221-94ec62e08169/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d h1:SZxvLBoTP5yHO3Frd4z4vrF+DBX9vMVanchswa69toE= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= cbind/key.go000066400000000000000000000116301403015176200132540ustar00rootroot00000000000000package cbind import ( "errors" "strings" "unicode" "github.com/gdamore/tcell/v2" ) // Modifier labels const ( LabelCtrl = "ctrl" LabelAlt = "alt" LabelMeta = "meta" LabelShift = "shift" ) // ErrInvalidKeyEvent is the error returned when encoding or decoding a key event fails. var ErrInvalidKeyEvent = errors.New("invalid key event") // UnifyEnterKeys is a flag that determines whether or not KPEnter (keypad // enter) key events are interpreted as Enter key events. When enabled, Ctrl+J // key events are also interpreted as Enter key events. var UnifyEnterKeys = true var fullKeyNames = map[string]string{ "backspace2": "Backspace", "pgup": "PageUp", "pgdn": "PageDown", "esc": "Escape", } var ctrlKeys = map[rune]tcell.Key{ ' ': tcell.KeyCtrlSpace, 'a': tcell.KeyCtrlA, 'b': tcell.KeyCtrlB, 'c': tcell.KeyCtrlC, 'd': tcell.KeyCtrlD, 'e': tcell.KeyCtrlE, 'f': tcell.KeyCtrlF, 'g': tcell.KeyCtrlG, 'h': tcell.KeyCtrlH, 'i': tcell.KeyCtrlI, 'j': tcell.KeyCtrlJ, 'k': tcell.KeyCtrlK, 'l': tcell.KeyCtrlL, 'm': tcell.KeyCtrlM, 'n': tcell.KeyCtrlN, 'o': tcell.KeyCtrlO, 'p': tcell.KeyCtrlP, 'q': tcell.KeyCtrlQ, 'r': tcell.KeyCtrlR, 's': tcell.KeyCtrlS, 't': tcell.KeyCtrlT, 'u': tcell.KeyCtrlU, 'v': tcell.KeyCtrlV, 'w': tcell.KeyCtrlW, 'x': tcell.KeyCtrlX, 'y': tcell.KeyCtrlY, 'z': tcell.KeyCtrlZ, '\\': tcell.KeyCtrlBackslash, ']': tcell.KeyCtrlRightSq, '^': tcell.KeyCtrlCarat, '_': tcell.KeyCtrlUnderscore, } // Decode decodes a string as a key or combination of keys. func Decode(s string) (mod tcell.ModMask, key tcell.Key, ch rune, err error) { if len(s) == 0 { return 0, 0, 0, ErrInvalidKeyEvent } // Special case for plus rune decoding if s[len(s)-1:] == "+" { key = tcell.KeyRune ch = '+' if len(s) == 1 { return mod, key, ch, nil } else if len(s) == 2 { return 0, 0, 0, ErrInvalidKeyEvent } else { s = s[:len(s)-2] } } split := strings.Split(s, "+") DECODEPIECE: for _, piece := range split { // Decode modifiers pieceLower := strings.ToLower(piece) switch pieceLower { case LabelCtrl: mod |= tcell.ModCtrl continue case LabelAlt: mod |= tcell.ModAlt continue case LabelMeta: mod |= tcell.ModMeta continue case LabelShift: mod |= tcell.ModShift continue } // Decode key for shortKey, fullKey := range fullKeyNames { if pieceLower == strings.ToLower(fullKey) { pieceLower = shortKey break } } switch pieceLower { case "backspace": key = tcell.KeyBackspace2 continue case "space", "spacebar": key = tcell.KeyRune ch = ' ' continue } for k, keyName := range tcell.KeyNames { if pieceLower == strings.ToLower(strings.ReplaceAll(keyName, "-", "+")) { key = k if key < 0x80 { ch = rune(k) } continue DECODEPIECE } } // Decode rune if len(piece) > 1 { return 0, 0, 0, ErrInvalidKeyEvent } key = tcell.KeyRune ch = rune(piece[0]) } if mod&tcell.ModCtrl != 0 { k, ok := ctrlKeys[unicode.ToLower(ch)] if ok { key = k if UnifyEnterKeys && key == ctrlKeys['j'] { key = tcell.KeyEnter } else if key < 0x80 { ch = rune(key) } } } return mod, key, ch, nil } // Encode encodes a key or combination of keys a string. func Encode(mod tcell.ModMask, key tcell.Key, ch rune) (string, error) { var b strings.Builder var wrote bool if mod&tcell.ModCtrl != 0 { if key == tcell.KeyBackspace || key == tcell.KeyTab || key == tcell.KeyEnter { mod ^= tcell.ModCtrl } else { for _, ctrlKey := range ctrlKeys { if key == ctrlKey { mod ^= tcell.ModCtrl break } } } } if key != tcell.KeyRune { if UnifyEnterKeys && key == ctrlKeys['j'] { key = tcell.KeyEnter } else if key < 0x80 { ch = rune(key) } } // Encode modifiers if mod&tcell.ModCtrl != 0 { b.WriteString(upperFirst(LabelCtrl)) wrote = true } if mod&tcell.ModAlt != 0 { if wrote { b.WriteRune('+') } b.WriteString(upperFirst(LabelAlt)) wrote = true } if mod&tcell.ModMeta != 0 { if wrote { b.WriteRune('+') } b.WriteString(upperFirst(LabelMeta)) wrote = true } if mod&tcell.ModShift != 0 { if wrote { b.WriteRune('+') } b.WriteString(upperFirst(LabelShift)) wrote = true } if key == tcell.KeyRune && ch == ' ' { if wrote { b.WriteRune('+') } b.WriteString("Space") } else if key != tcell.KeyRune { // Encode key keyName := tcell.KeyNames[key] if keyName == "" { return "", ErrInvalidKeyEvent } keyName = strings.ReplaceAll(keyName, "-", "+") fullKeyName := fullKeyNames[strings.ToLower(keyName)] if fullKeyName != "" { keyName = fullKeyName } if wrote { b.WriteRune('+') } b.WriteString(keyName) } else { // Encode rune if wrote { b.WriteRune('+') } b.WriteRune(ch) } return b.String(), nil } func upperFirst(s string) string { if len(s) <= 1 { return strings.ToUpper(s) } return strings.ToUpper(s[:1]) + s[1:] } cbind/key_test.go000066400000000000000000000052731403015176200143210ustar00rootroot00000000000000package cbind import ( "testing" "github.com/gdamore/tcell/v2" ) type testCase struct { mod tcell.ModMask key tcell.Key ch rune encoded string } var testCases = []testCase{ {mod: tcell.ModNone, key: tcell.KeyRune, ch: 'a', encoded: "a"}, {mod: tcell.ModNone, key: tcell.KeyRune, ch: '+', encoded: "+"}, {mod: tcell.ModNone, key: tcell.KeyRune, ch: ';', encoded: ";"}, {mod: tcell.ModNone, key: tcell.KeyTab, ch: rune(tcell.KeyTab), encoded: "Tab"}, {mod: tcell.ModNone, key: tcell.KeyEnter, ch: rune(tcell.KeyEnter), encoded: "Enter"}, {mod: tcell.ModNone, key: tcell.KeyPgDn, ch: 0, encoded: "PageDown"}, {mod: tcell.ModAlt, key: tcell.KeyRune, ch: 'a', encoded: "Alt+a"}, {mod: tcell.ModAlt, key: tcell.KeyRune, ch: '+', encoded: "Alt++"}, {mod: tcell.ModAlt, key: tcell.KeyRune, ch: ';', encoded: "Alt+;"}, {mod: tcell.ModAlt, key: tcell.KeyRune, ch: ' ', encoded: "Alt+Space"}, {mod: tcell.ModAlt, key: tcell.KeyRune, ch: '1', encoded: "Alt+1"}, {mod: tcell.ModAlt, key: tcell.KeyTab, ch: rune(tcell.KeyTab), encoded: "Alt+Tab"}, {mod: tcell.ModAlt, key: tcell.KeyEnter, ch: rune(tcell.KeyEnter), encoded: "Alt+Enter"}, {mod: tcell.ModAlt, key: tcell.KeyBackspace2, ch: rune(tcell.KeyBackspace2), encoded: "Alt+Backspace"}, {mod: tcell.ModCtrl, key: tcell.KeyCtrlC, ch: rune(tcell.KeyCtrlC), encoded: "Ctrl+C"}, {mod: tcell.ModCtrl, key: tcell.KeyCtrlD, ch: rune(tcell.KeyCtrlD), encoded: "Ctrl+D"}, {mod: tcell.ModCtrl, key: tcell.KeyCtrlSpace, ch: rune(tcell.KeyCtrlSpace), encoded: "Ctrl+Space"}, {mod: tcell.ModCtrl, key: tcell.KeyCtrlRightSq, ch: rune(tcell.KeyCtrlRightSq), encoded: "Ctrl+]"}, {mod: tcell.ModCtrl | tcell.ModAlt, key: tcell.KeyRune, ch: '+', encoded: "Ctrl+Alt++"}, {mod: tcell.ModCtrl | tcell.ModShift, key: tcell.KeyRune, ch: '+', encoded: "Ctrl+Shift++"}, } func TestEncode(t *testing.T) { t.Parallel() for _, c := range testCases { encoded, err := Encode(c.mod, c.key, c.ch) if err != nil { t.Errorf("failed to encode key %d %d %d: %s", c.mod, c.key, c.ch, err) } if encoded != c.encoded { t.Errorf("failed to encode key %d %d %d: got %s, want %s", c.mod, c.key, c.ch, encoded, c.encoded) } } } func TestDecode(t *testing.T) { t.Parallel() for _, c := range testCases { mod, key, ch, err := Decode(c.encoded) if err != nil { t.Errorf("failed to decode key %s: %s", c.encoded, err) } if mod != c.mod { t.Errorf("failed to decode key %s: invalid modifiers: got %d, want %d", c.encoded, mod, c.mod) } if key != c.key { t.Errorf("failed to decode key %s: invalid key: got %d, want %d", c.encoded, key, c.key) } if ch != c.ch { t.Errorf("failed to decode key %s: invalid rune: got %d, want %d", c.encoded, ch, c.ch) } } } cbind/whichkeybind/000077500000000000000000000000001403015176200146045ustar00rootroot00000000000000cbind/whichkeybind/main.go000066400000000000000000000045071403015176200160650ustar00rootroot00000000000000package main import ( "fmt" "os" "github.com/gdamore/tcell/v2" "code.rocketnine.space/tslocum/cbind" ) func main() { tcell.SetEncodingFallback(tcell.EncodingFallbackASCII) s, e := tcell.NewScreen() if e != nil { fmt.Fprintf(os.Stderr, "%v\n", e) os.Exit(1) } if e = s.Init(); e != nil { fmt.Fprintf(os.Stderr, "%v\n", e) os.Exit(1) } quit := make(chan struct{}) quitApp := func(ev *tcell.EventKey) *tcell.EventKey { quit <- struct{}{} return nil } configuration := cbind.NewConfiguration() configuration.SetKey(tcell.ModNone, tcell.KeyEscape, quitApp) configuration.SetRune(tcell.ModCtrl, 'c', quitApp) s.SetStyle(tcell.StyleDefault. Foreground(tcell.ColorWhite). Background(tcell.ColorBlack)) s.Clear() go func() { for { ev := s.PollEvent() switch ev := ev.(type) { case *tcell.EventResize: s.Sync() case *tcell.EventKey: s.SetStyle(tcell.StyleDefault. Foreground(tcell.ColorWhite). Background(tcell.ColorBlack)) s.Clear() putln(s, 0, fmt.Sprintf("Event: %d %d %d", ev.Modifiers(), ev.Key(), ev.Rune())) str, err := cbind.Encode(ev.Modifiers(), ev.Key(), ev.Rune()) if err != nil { str = fmt.Sprintf("error: %s", err) } putln(s, 2, str) mod, key, ch, err := cbind.Decode(str) if err != nil { putln(s, 4, err.Error()) } else { putln(s, 4, fmt.Sprintf("Re-encoded as: %d %d %d", mod, key, ch)) } configuration.Capture(ev) s.Sync() } } }() s.Show() <-quit s.Fini() } // putln and puts functions are copied from the tcell unicode demo. // Apache License, Version 2.0 func putln(s tcell.Screen, y int, str string) { puts(s, tcell.StyleDefault, 0, y, str) } func puts(s tcell.Screen, style tcell.Style, x, y int, str string) { i := 0 var deferred []rune dwidth := 0 zwj := false for _, r := range str { if r == '\u200d' { if len(deferred) == 0 { deferred = append(deferred, ' ') dwidth = 1 } deferred = append(deferred, r) zwj = true continue } if zwj { deferred = append(deferred, r) zwj = false continue } if len(deferred) != 0 { s.SetContent(x+i, y, deferred[0], deferred[1:], style) i += dwidth } deferred = nil dwidth = 1 deferred = append(deferred, r) } if len(deferred) != 0 { s.SetContent(x+i, y, deferred[0], deferred[1:], style) i += dwidth } }