mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-06-09 22:05:16 +02:00
Move modifiers into Key
This changes not only how we store modifiers (inside of Key instead of passing it separately), but also how we parse keybinding strings: it supports all combinations of modifiers now (if the terminal supports it, that is).
This commit is contained in:
@@ -221,8 +221,8 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` mouse wheel down (fn+up) `` | Scroll down | |
|
||||
| `` mouse wheel up (fn+down) `` | Scroll up | |
|
||||
| `` <mouse wheel down> (fn+up) `` | Scroll down | |
|
||||
| `` <mouse wheel up> (fn+down) `` | Scroll up | |
|
||||
| `` <tab> `` | Switch view | Switch to other view (staged/unstaged changes). |
|
||||
| `` <esc> `` | Exit back to side panel | |
|
||||
| `` / `` | Search the current view by text | |
|
||||
|
||||
@@ -304,8 +304,8 @@ _凡例:`<c-b>` はctrl+b、`<a-b>` はalt+b、`B` はshift+bを意味
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` mouse wheel down (fn+up) `` | 下にスクロール | |
|
||||
| `` mouse wheel up (fn+down) `` | 上にスクロール | |
|
||||
| `` <mouse wheel down> (fn+up) `` | 下にスクロール | |
|
||||
| `` <mouse wheel up> (fn+down) `` | 上にスクロール | |
|
||||
| `` <tab> `` | ビューを切り替え | 他のビュー(ステージされた変更/ステージされていない変更)に切り替えます。 |
|
||||
| `` <esc> `` | サイドパネルに戻る | |
|
||||
| `` / `` | 現在のビューをテキストで検索 | |
|
||||
|
||||
@@ -160,8 +160,8 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` mouse wheel down (fn+up) `` | 아래로 스크롤 | |
|
||||
| `` mouse wheel up (fn+down) `` | 위로 스크롤 | |
|
||||
| `` <mouse wheel down> (fn+up) `` | 아래로 스크롤 | |
|
||||
| `` <mouse wheel up> (fn+down) `` | 위로 스크롤 | |
|
||||
| `` <tab> `` | 패널 전환 | Switch to other view (staged/unstaged changes). |
|
||||
| `` <esc> `` | Exit back to side panel | |
|
||||
| `` / `` | 검색 시작 | |
|
||||
|
||||
@@ -229,8 +229,8 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` mouse wheel down (fn+up) `` | Scroll omlaag | |
|
||||
| `` mouse wheel up (fn+down) `` | Scroll omhoog | |
|
||||
| `` <mouse wheel down> (fn+up) `` | Scroll omlaag | |
|
||||
| `` <mouse wheel up> (fn+down) `` | Scroll omhoog | |
|
||||
| `` <tab> `` | Ga naar een ander paneel | Switch to other view (staged/unstaged changes). |
|
||||
| `` <esc> `` | Exit back to side panel | |
|
||||
| `` / `` | Start met zoeken | |
|
||||
|
||||
@@ -179,8 +179,8 @@ _Legenda: `<c-b>` oznacza ctrl+b, `<a-b>` oznacza alt+b, `B` oznacza shift+b_
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` mouse wheel down (fn+up) `` | Przewiń w dół | |
|
||||
| `` mouse wheel up (fn+down) `` | Przewiń w górę | |
|
||||
| `` <mouse wheel down> (fn+up) `` | Przewiń w dół | |
|
||||
| `` <mouse wheel up> (fn+down) `` | Przewiń w górę | |
|
||||
| `` <tab> `` | Przełącz widok | Przełącz na inny widok (zatwierdzone/niezatwierdzone zmiany). |
|
||||
| `` <esc> `` | Exit back to side panel | |
|
||||
| `` / `` | Szukaj w bieżącym widoku po tekście | |
|
||||
|
||||
@@ -233,8 +233,8 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` mouse wheel down (fn+up) `` | Rolar para baixo | |
|
||||
| `` mouse wheel up (fn+down) `` | Rolar para cima | |
|
||||
| `` <mouse wheel down> (fn+up) `` | Rolar para baixo | |
|
||||
| `` <mouse wheel up> (fn+down) `` | Rolar para cima | |
|
||||
| `` <tab> `` | Mudar de visão | Alternar para outra visão (staged/não processadas alterações). |
|
||||
| `` <esc> `` | Exit back to side panel | |
|
||||
| `` / `` | Pesquisar na visualização atual por texto | |
|
||||
|
||||
@@ -104,8 +104,8 @@ _Связки клавиш_
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` mouse wheel down (fn+up) `` | Прокрутить вниз | |
|
||||
| `` mouse wheel up (fn+down) `` | Прокрутить вверх | |
|
||||
| `` <mouse wheel down> (fn+up) `` | Прокрутить вниз | |
|
||||
| `` <mouse wheel up> (fn+down) `` | Прокрутить вверх | |
|
||||
| `` <tab> `` | Переключиться на другую панель (проиндексированные/непроиндексированные изменения) | Switch to other view (staged/unstaged changes). |
|
||||
| `` <esc> `` | Exit back to side panel | |
|
||||
| `` / `` | Найти | |
|
||||
|
||||
@@ -332,8 +332,8 @@ _图例:`<c-b>` 意味着ctrl+b, `<a-b>意味着Alt+b, `B` 意味着shift+b_
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` mouse wheel down (fn+up) `` | 向下滚动 | |
|
||||
| `` mouse wheel up (fn+down) `` | 向上滚动 | |
|
||||
| `` <mouse wheel down> (fn+up) `` | 向下滚动 | |
|
||||
| `` <mouse wheel up> (fn+down) `` | 向上滚动 | |
|
||||
| `` <tab> `` | 切换到其他面板 | 切换到其他视图(已暂存/未暂存的变更) |
|
||||
| `` <esc> `` | 退出回到侧边面板 | |
|
||||
| `` / `` | 开始搜索 | |
|
||||
|
||||
@@ -80,8 +80,8 @@ _說明:`<c-b>` 表示 Ctrl+B、`<a-b>` 表示 Alt+B,`B`表示 Shift+B
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` mouse wheel down (fn+up) `` | 向下捲動 | |
|
||||
| `` mouse wheel up (fn+down) `` | 向上捲動 | |
|
||||
| `` <mouse wheel down> (fn+up) `` | 向下捲動 | |
|
||||
| `` <mouse wheel up> (fn+down) `` | 向上捲動 | |
|
||||
| `` <tab> `` | 切換至另一個面板 (已預存/未預存更改) | Switch to other view (staged/unstaged changes). |
|
||||
| `` <esc> `` | Exit back to side panel | |
|
||||
| `` / `` | 搜尋 | |
|
||||
|
||||
+153
-70
@@ -14,63 +14,35 @@ import (
|
||||
// docs/keybindings/Custom_Keybindings.md as well
|
||||
|
||||
var labelByKey = map[gocui.KeyName]string{
|
||||
gocui.KeyF1: "<f1>",
|
||||
gocui.KeyF2: "<f2>",
|
||||
gocui.KeyF3: "<f3>",
|
||||
gocui.KeyF4: "<f4>",
|
||||
gocui.KeyF5: "<f5>",
|
||||
gocui.KeyF6: "<f6>",
|
||||
gocui.KeyF7: "<f7>",
|
||||
gocui.KeyF8: "<f8>",
|
||||
gocui.KeyF9: "<f9>",
|
||||
gocui.KeyF10: "<f10>",
|
||||
gocui.KeyF11: "<f11>",
|
||||
gocui.KeyF12: "<f12>",
|
||||
gocui.KeyInsert: "<insert>",
|
||||
gocui.KeyDelete: "<delete>",
|
||||
gocui.KeyHome: "<home>",
|
||||
gocui.KeyEnd: "<end>",
|
||||
gocui.KeyPgup: "<pgup>",
|
||||
gocui.KeyPgdn: "<pgdown>",
|
||||
gocui.KeyArrowUp: "<up>",
|
||||
gocui.KeyShiftArrowUp: "<s-up>",
|
||||
gocui.KeyArrowDown: "<down>",
|
||||
gocui.KeyShiftArrowDown: "<s-down>",
|
||||
gocui.KeyArrowLeft: "<left>",
|
||||
gocui.KeyArrowRight: "<right>",
|
||||
gocui.KeyTab: "<tab>", // <c-i>
|
||||
gocui.KeyBacktab: "<backtab>",
|
||||
gocui.KeyEnter: "<enter>", // <c-m>
|
||||
gocui.KeyAltEnter: "<a-enter>",
|
||||
gocui.KeyEsc: "<esc>", // <c-[>, <c-3>
|
||||
gocui.KeyBackspace: "<backspace>", // <c-h>
|
||||
gocui.KeySpace: "<space>",
|
||||
gocui.KeyCtrlA: "<c-a>",
|
||||
gocui.KeyCtrlB: "<c-b>",
|
||||
gocui.KeyCtrlC: "<c-c>",
|
||||
gocui.KeyCtrlD: "<c-d>",
|
||||
gocui.KeyCtrlE: "<c-e>",
|
||||
gocui.KeyCtrlF: "<c-f>",
|
||||
gocui.KeyCtrlG: "<c-g>",
|
||||
gocui.KeyCtrlJ: "<c-j>",
|
||||
gocui.KeyCtrlK: "<c-k>",
|
||||
gocui.KeyCtrlL: "<c-l>",
|
||||
gocui.KeyCtrlN: "<c-n>",
|
||||
gocui.KeyCtrlO: "<c-o>",
|
||||
gocui.KeyCtrlP: "<c-p>",
|
||||
gocui.KeyCtrlQ: "<c-q>",
|
||||
gocui.KeyCtrlR: "<c-r>",
|
||||
gocui.KeyCtrlS: "<c-s>",
|
||||
gocui.KeyCtrlT: "<c-t>",
|
||||
gocui.KeyCtrlU: "<c-u>",
|
||||
gocui.KeyCtrlV: "<c-v>",
|
||||
gocui.KeyCtrlW: "<c-w>",
|
||||
gocui.KeyCtrlX: "<c-x>",
|
||||
gocui.KeyCtrlY: "<c-y>",
|
||||
gocui.KeyCtrlZ: "<c-z>",
|
||||
gocui.KeyCtrl8: "<c-8>",
|
||||
gocui.MouseWheelUp: "mouse wheel up",
|
||||
gocui.MouseWheelDown: "mouse wheel down",
|
||||
gocui.KeyF1: "f1",
|
||||
gocui.KeyF2: "f2",
|
||||
gocui.KeyF3: "f3",
|
||||
gocui.KeyF4: "f4",
|
||||
gocui.KeyF5: "f5",
|
||||
gocui.KeyF6: "f6",
|
||||
gocui.KeyF7: "f7",
|
||||
gocui.KeyF8: "f8",
|
||||
gocui.KeyF9: "f9",
|
||||
gocui.KeyF10: "f10",
|
||||
gocui.KeyF11: "f11",
|
||||
gocui.KeyF12: "f12",
|
||||
gocui.KeyInsert: "insert",
|
||||
gocui.KeyDelete: "delete",
|
||||
gocui.KeyHome: "home",
|
||||
gocui.KeyEnd: "end",
|
||||
gocui.KeyPgup: "pgup",
|
||||
gocui.KeyPgdn: "pgdown",
|
||||
gocui.KeyArrowUp: "up",
|
||||
gocui.KeyArrowDown: "down",
|
||||
gocui.KeyArrowLeft: "left",
|
||||
gocui.KeyArrowRight: "right",
|
||||
gocui.KeyTab: "tab",
|
||||
gocui.KeyBacktab: "backtab",
|
||||
gocui.KeyEnter: "enter",
|
||||
gocui.KeyEsc: "esc",
|
||||
gocui.KeyBackspace: "backspace",
|
||||
gocui.MouseWheelUp: "mouse wheel up",
|
||||
gocui.MouseWheelDown: "mouse wheel down",
|
||||
}
|
||||
|
||||
var keyByLabel = lo.Invert(labelByKey)
|
||||
@@ -80,16 +52,44 @@ func LabelForKey(key gocui.Key) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
label := ""
|
||||
if key.Mod()&gocui.ModCtrl != 0 {
|
||||
label += "c-"
|
||||
}
|
||||
if key.Mod()&gocui.ModAlt != 0 {
|
||||
label += "a-"
|
||||
}
|
||||
if key.Mod()&gocui.ModShift != 0 {
|
||||
label += "s-"
|
||||
}
|
||||
if key.Mod()&gocui.ModMeta != 0 {
|
||||
label += "m-"
|
||||
}
|
||||
|
||||
if key.KeyName() == gocui.KeyName(tcell.KeyRune) {
|
||||
return key.Str()
|
||||
if key.Str() == " " {
|
||||
label += "space"
|
||||
} else if key.Str() == "-" && key.Mod() != gocui.ModNone {
|
||||
label += "minus"
|
||||
} else if key.Str() == "+" && key.Mod() != gocui.ModNone {
|
||||
label += "plus"
|
||||
} else {
|
||||
label += key.Str()
|
||||
}
|
||||
} else {
|
||||
value, ok := labelByKey[key.KeyName()]
|
||||
if ok {
|
||||
label += value
|
||||
} else {
|
||||
label += "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
value, ok := labelByKey[key.KeyName()]
|
||||
if ok {
|
||||
return value
|
||||
if utf8.RuneCountInString(label) > 1 {
|
||||
label = "<" + label + ">"
|
||||
}
|
||||
|
||||
return "unknown"
|
||||
return label
|
||||
}
|
||||
|
||||
func KeyFromLabel(label string) (gocui.Key, bool) {
|
||||
@@ -97,16 +97,99 @@ func KeyFromLabel(label string) (gocui.Key, bool) {
|
||||
return gocui.Key{}, true
|
||||
}
|
||||
|
||||
runeCount := utf8.RuneCountInString(label)
|
||||
if runeCount > 1 {
|
||||
keyName, ok := keyByLabel[strings.ToLower(label)]
|
||||
if !ok {
|
||||
return gocui.Key{}, false
|
||||
}
|
||||
return gocui.NewKeyName(keyName), true
|
||||
if strings.HasPrefix(label, "<") && strings.HasSuffix(label, ">") {
|
||||
label = label[1 : len(label)-1]
|
||||
}
|
||||
|
||||
return gocui.NewKeyRune([]rune(label)[0]), true
|
||||
mod := gocui.ModNone
|
||||
for {
|
||||
// A bare "-" or "+" with any (or no) modifiers is a literal rune
|
||||
// key; this also covers lenient forms like `<c-->` and `<c++>`,
|
||||
// neither of which we emit (we use `<c-minus>` and `<c-+>`).
|
||||
if label == "-" || label == "+" {
|
||||
return gocui.NewKeyStrMod(label, mod), true
|
||||
}
|
||||
|
||||
sepIdx := strings.IndexAny(label, "-+")
|
||||
if sepIdx == -1 {
|
||||
break
|
||||
}
|
||||
modStr, remainder := label[:sepIdx], label[sepIdx+1:]
|
||||
|
||||
label = remainder
|
||||
|
||||
switch modStr {
|
||||
case "s", "shift":
|
||||
if (mod & gocui.ModShift) != 0 {
|
||||
return gocui.Key{}, false
|
||||
}
|
||||
mod |= gocui.ModShift
|
||||
case "c", "ctrl":
|
||||
if (mod & gocui.ModCtrl) != 0 {
|
||||
return gocui.Key{}, false
|
||||
}
|
||||
mod |= gocui.ModCtrl
|
||||
case "a", "alt":
|
||||
if (mod & gocui.ModAlt) != 0 {
|
||||
return gocui.Key{}, false
|
||||
}
|
||||
mod |= gocui.ModAlt
|
||||
case "m", "meta":
|
||||
if (mod & gocui.ModMeta) != 0 {
|
||||
return gocui.Key{}, false
|
||||
}
|
||||
mod |= gocui.ModMeta
|
||||
default:
|
||||
return gocui.Key{}, false
|
||||
}
|
||||
}
|
||||
|
||||
if label == "space" {
|
||||
return gocui.NewKeyStrMod(" ", mod), true
|
||||
}
|
||||
|
||||
if label == "minus" {
|
||||
if mod == gocui.ModShift {
|
||||
return gocui.Key{}, false
|
||||
}
|
||||
return gocui.NewKeyStrMod("-", mod), true
|
||||
}
|
||||
|
||||
if label == "plus" {
|
||||
if mod == gocui.ModShift {
|
||||
return gocui.Key{}, false
|
||||
}
|
||||
return gocui.NewKeyStrMod("+", mod), true
|
||||
}
|
||||
|
||||
if keyName, ok := keyByLabel[label]; ok {
|
||||
return gocui.NewKey(keyName, "", mod), true
|
||||
}
|
||||
|
||||
runeCount := utf8.RuneCountInString(label)
|
||||
if runeCount != 1 {
|
||||
return gocui.Key{}, false
|
||||
}
|
||||
|
||||
// Shift on a bare rune is invalid: terminals fold shift into the rune
|
||||
// itself (shift+a arrives as "A"), so the binding could never fire.
|
||||
// Space is exempt and handled above; combined with other modifiers,
|
||||
// shift is fine because the terminal can't fold it into the rune then.
|
||||
if mod == gocui.ModShift {
|
||||
return gocui.Key{}, false
|
||||
}
|
||||
|
||||
// An ASCII uppercase letter with any modifier is invalid. Ctrl+letter
|
||||
// events always arrive with a lowercase rune — control codes have no
|
||||
// case distinction (the terminal sends the same byte for ctrl+a and
|
||||
// ctrl+A), and CSI-u protocols report the unshifted codepoint with
|
||||
// shift as a separate modifier (alt+shift+a → rune='a' mod=Alt|Shift).
|
||||
// Users should write <c-s-a> rather than <c-A>.
|
||||
if mod != gocui.ModNone && len(label) == 1 && label[0] >= 'A' && label[0] <= 'Z' {
|
||||
return gocui.Key{}, false
|
||||
}
|
||||
|
||||
return gocui.NewKeyStrMod(label, mod), true
|
||||
}
|
||||
|
||||
func isValidKeybindingKey(key string) bool {
|
||||
|
||||
@@ -0,0 +1,686 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/gocui"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestKeyFromLabel(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
name string
|
||||
label string
|
||||
expectedKey gocui.Key
|
||||
expectedOk bool
|
||||
}{
|
||||
// Empty / disabled
|
||||
{
|
||||
name: "empty string returns unset key",
|
||||
label: "",
|
||||
expectedKey: gocui.Key{},
|
||||
expectedOk: true,
|
||||
},
|
||||
{
|
||||
name: "<disabled> returns unset key",
|
||||
label: "<disabled>",
|
||||
expectedKey: gocui.Key{},
|
||||
expectedOk: true,
|
||||
},
|
||||
|
||||
// Plain runes (unwrapped)
|
||||
{
|
||||
name: "single lowercase letter",
|
||||
label: "a",
|
||||
expectedKey: gocui.NewKeyStrMod("a", gocui.ModNone),
|
||||
expectedOk: true,
|
||||
},
|
||||
{
|
||||
name: "single uppercase letter",
|
||||
label: "A",
|
||||
expectedKey: gocui.NewKeyStrMod("A", gocui.ModNone),
|
||||
expectedOk: true,
|
||||
},
|
||||
{
|
||||
name: "single digit",
|
||||
label: "5",
|
||||
expectedKey: gocui.NewKeyStrMod("5", gocui.ModNone),
|
||||
expectedOk: true,
|
||||
},
|
||||
{
|
||||
name: "punctuation rune",
|
||||
label: "?",
|
||||
expectedKey: gocui.NewKeyStrMod("?", gocui.ModNone),
|
||||
expectedOk: true,
|
||||
},
|
||||
{
|
||||
name: "multibyte rune",
|
||||
label: "ñ",
|
||||
expectedKey: gocui.NewKeyStrMod("ñ", gocui.ModNone),
|
||||
expectedOk: true,
|
||||
},
|
||||
{
|
||||
name: "bare dash is treated as a rune",
|
||||
label: "-",
|
||||
expectedKey: gocui.NewKeyRune('-'),
|
||||
expectedOk: true,
|
||||
},
|
||||
|
||||
// Special key names (no modifiers, no brackets — though these are
|
||||
// always wrapped in brackets in real configs, KeyFromLabel accepts
|
||||
// the unwrapped form too)
|
||||
{
|
||||
name: "function key",
|
||||
label: "f1",
|
||||
expectedKey: gocui.NewKey(gocui.KeyF1, "", gocui.ModNone),
|
||||
expectedOk: true,
|
||||
},
|
||||
{
|
||||
name: "function key wrapped in brackets",
|
||||
label: "<f12>",
|
||||
expectedKey: gocui.NewKey(gocui.KeyF12, "", gocui.ModNone),
|
||||
expectedOk: true,
|
||||
},
|
||||
{
|
||||
name: "arrow key",
|
||||
label: "<up>",
|
||||
expectedKey: gocui.NewKey(gocui.KeyArrowUp, "", gocui.ModNone),
|
||||
expectedOk: true,
|
||||
},
|
||||
{
|
||||
name: "tab",
|
||||
label: "<tab>",
|
||||
expectedKey: gocui.NewKey(gocui.KeyTab, "", gocui.ModNone),
|
||||
expectedOk: true,
|
||||
},
|
||||
{
|
||||
name: "enter",
|
||||
label: "<enter>",
|
||||
expectedKey: gocui.NewKey(gocui.KeyEnter, "", gocui.ModNone),
|
||||
expectedOk: true,
|
||||
},
|
||||
{
|
||||
name: "esc",
|
||||
label: "<esc>",
|
||||
expectedKey: gocui.NewKey(gocui.KeyEsc, "", gocui.ModNone),
|
||||
expectedOk: true,
|
||||
},
|
||||
{
|
||||
name: "backspace",
|
||||
label: "<backspace>",
|
||||
expectedKey: gocui.NewKey(gocui.KeyBackspace, "", gocui.ModNone),
|
||||
expectedOk: true,
|
||||
},
|
||||
{
|
||||
name: "pgup",
|
||||
label: "<pgup>",
|
||||
expectedKey: gocui.NewKey(gocui.KeyPgup, "", gocui.ModNone),
|
||||
expectedOk: true,
|
||||
},
|
||||
{
|
||||
name: "pgdown",
|
||||
label: "<pgdown>",
|
||||
expectedKey: gocui.NewKey(gocui.KeyPgdn, "", gocui.ModNone),
|
||||
expectedOk: true,
|
||||
},
|
||||
{
|
||||
name: "mouse wheel up",
|
||||
label: "<mouse wheel up>",
|
||||
expectedKey: gocui.NewKey(gocui.MouseWheelUp, "", gocui.ModNone),
|
||||
expectedOk: true,
|
||||
},
|
||||
|
||||
// Space
|
||||
{
|
||||
name: "space keyword maps to space rune",
|
||||
label: "<space>",
|
||||
expectedKey: gocui.NewKeyStrMod(" ", gocui.ModNone),
|
||||
expectedOk: true,
|
||||
},
|
||||
{
|
||||
name: "space keyword without brackets",
|
||||
label: "space",
|
||||
expectedKey: gocui.NewKeyStrMod(" ", gocui.ModNone),
|
||||
expectedOk: true,
|
||||
},
|
||||
{
|
||||
name: "ctrl+space",
|
||||
label: "<c-space>",
|
||||
expectedKey: gocui.NewKeyStrMod(" ", gocui.ModCtrl),
|
||||
expectedOk: true,
|
||||
},
|
||||
|
||||
// Minus
|
||||
{
|
||||
name: "minus keyword maps to dash rune",
|
||||
label: "<minus>",
|
||||
expectedKey: gocui.NewKeyStrMod("-", gocui.ModNone),
|
||||
expectedOk: true,
|
||||
},
|
||||
{
|
||||
name: "ctrl+minus via keyword",
|
||||
label: "<c-minus>",
|
||||
expectedKey: gocui.NewKeyStrMod("-", gocui.ModCtrl),
|
||||
expectedOk: true,
|
||||
},
|
||||
{
|
||||
name: "ctrl+minus via lenient dash form",
|
||||
label: "<c-->",
|
||||
expectedKey: gocui.NewKeyStrMod("-", gocui.ModCtrl),
|
||||
expectedOk: true,
|
||||
},
|
||||
{
|
||||
name: "alt+ctrl+minus via lenient dash form",
|
||||
label: "<a-c-->",
|
||||
expectedKey: gocui.NewKeyStrMod("-", gocui.ModAlt|gocui.ModCtrl),
|
||||
expectedOk: true,
|
||||
},
|
||||
|
||||
// Plus
|
||||
{
|
||||
name: "plus keyword maps to plus rune",
|
||||
label: "<plus>",
|
||||
expectedKey: gocui.NewKeyStrMod("+", gocui.ModNone),
|
||||
expectedOk: true,
|
||||
},
|
||||
{
|
||||
name: "ctrl+plus via keyword",
|
||||
label: "<c-plus>",
|
||||
expectedKey: gocui.NewKeyStrMod("+", gocui.ModCtrl),
|
||||
expectedOk: true,
|
||||
},
|
||||
{
|
||||
name: "ctrl+plus via long keyword and plus separator",
|
||||
label: "<ctrl+plus>",
|
||||
expectedKey: gocui.NewKeyStrMod("+", gocui.ModCtrl),
|
||||
expectedOk: true,
|
||||
},
|
||||
{
|
||||
name: "alt+shift+plus via keyword",
|
||||
label: "<a-s-plus>",
|
||||
expectedKey: gocui.NewKeyStrMod("+", gocui.ModAlt|gocui.ModShift),
|
||||
expectedOk: true,
|
||||
},
|
||||
{
|
||||
name: "shift alone on plus is rejected",
|
||||
label: "<s-plus>",
|
||||
expectedKey: gocui.Key{},
|
||||
expectedOk: false,
|
||||
},
|
||||
|
||||
// Modifiers with runes
|
||||
{
|
||||
name: "ctrl+letter",
|
||||
label: "<c-a>",
|
||||
expectedKey: gocui.NewKeyStrMod("a", gocui.ModCtrl),
|
||||
expectedOk: true,
|
||||
},
|
||||
{
|
||||
name: "alt+letter",
|
||||
label: "<a-x>",
|
||||
expectedKey: gocui.NewKeyStrMod("x", gocui.ModAlt),
|
||||
expectedOk: true,
|
||||
},
|
||||
{
|
||||
name: "meta+letter",
|
||||
label: "<m-z>",
|
||||
expectedKey: gocui.NewKeyStrMod("z", gocui.ModMeta),
|
||||
expectedOk: true,
|
||||
},
|
||||
|
||||
// Long modifier names are accepted as synonyms for the short forms.
|
||||
{
|
||||
name: "ctrl long form",
|
||||
label: "<ctrl-a>",
|
||||
expectedKey: gocui.NewKeyStrMod("a", gocui.ModCtrl),
|
||||
expectedOk: true,
|
||||
},
|
||||
{
|
||||
name: "alt long form",
|
||||
label: "<alt-x>",
|
||||
expectedKey: gocui.NewKeyStrMod("x", gocui.ModAlt),
|
||||
expectedOk: true,
|
||||
},
|
||||
{
|
||||
name: "meta long form",
|
||||
label: "<meta-z>",
|
||||
expectedKey: gocui.NewKeyStrMod("z", gocui.ModMeta),
|
||||
expectedOk: true,
|
||||
},
|
||||
{
|
||||
name: "shift long form combined with ctrl",
|
||||
label: "<shift-ctrl-a>",
|
||||
expectedKey: gocui.NewKeyStrMod("a", gocui.ModShift|gocui.ModCtrl),
|
||||
expectedOk: true,
|
||||
},
|
||||
{
|
||||
name: "long forms work with special keys",
|
||||
label: "<ctrl-up>",
|
||||
expectedKey: gocui.NewKey(gocui.KeyArrowUp, "", gocui.ModCtrl),
|
||||
expectedOk: true,
|
||||
},
|
||||
{
|
||||
name: "short and long forms can be mixed",
|
||||
label: "<ctrl-s-up>",
|
||||
expectedKey: gocui.NewKey(gocui.KeyArrowUp, "", gocui.ModCtrl|gocui.ModShift),
|
||||
expectedOk: true,
|
||||
},
|
||||
{
|
||||
name: "duplicate via mixed short and long form is rejected",
|
||||
label: "<shift-s-a>",
|
||||
expectedKey: gocui.Key{},
|
||||
expectedOk: false,
|
||||
},
|
||||
{
|
||||
name: "unknown long modifier is rejected",
|
||||
label: "<control-a>",
|
||||
expectedKey: gocui.Key{},
|
||||
expectedOk: false,
|
||||
},
|
||||
|
||||
// Plus is accepted as an alternative modifier separator.
|
||||
{
|
||||
name: "plus separator with short form",
|
||||
label: "<c+a>",
|
||||
expectedKey: gocui.NewKeyStrMod("a", gocui.ModCtrl),
|
||||
expectedOk: true,
|
||||
},
|
||||
{
|
||||
name: "plus separator with long form",
|
||||
label: "<ctrl+alt+a>",
|
||||
expectedKey: gocui.NewKeyStrMod("a", gocui.ModCtrl|gocui.ModAlt),
|
||||
expectedOk: true,
|
||||
},
|
||||
{
|
||||
name: "plus separator with special key",
|
||||
label: "<ctrl+up>",
|
||||
expectedKey: gocui.NewKey(gocui.KeyArrowUp, "", gocui.ModCtrl),
|
||||
expectedOk: true,
|
||||
},
|
||||
{
|
||||
name: "mixed plus and dash separators",
|
||||
label: "<ctrl+shift-up>",
|
||||
expectedKey: gocui.NewKey(gocui.KeyArrowUp, "", gocui.ModCtrl|gocui.ModShift),
|
||||
expectedOk: true,
|
||||
},
|
||||
{
|
||||
name: "duplicate detection works across separators",
|
||||
label: "<c+c-a>",
|
||||
expectedKey: gocui.Key{},
|
||||
expectedOk: false,
|
||||
},
|
||||
{
|
||||
name: "ctrl+plus rune via plus separator",
|
||||
label: "<c++>",
|
||||
expectedKey: gocui.NewKeyStrMod("+", gocui.ModCtrl),
|
||||
expectedOk: true,
|
||||
},
|
||||
{
|
||||
name: "ctrl+dash rune via plus separator",
|
||||
label: "<c+->",
|
||||
expectedKey: gocui.NewKeyStrMod("-", gocui.ModCtrl),
|
||||
expectedOk: true,
|
||||
},
|
||||
{
|
||||
name: "ctrl+plus rune via dash separator",
|
||||
label: "<c-+>",
|
||||
expectedKey: gocui.NewKeyStrMod("+", gocui.ModCtrl),
|
||||
expectedOk: true,
|
||||
},
|
||||
{
|
||||
name: "bare plus rune",
|
||||
label: "+",
|
||||
expectedKey: gocui.NewKeyStrMod("+", gocui.ModNone),
|
||||
expectedOk: true,
|
||||
},
|
||||
{
|
||||
name: "bare plus wrapped in brackets",
|
||||
label: "<+>",
|
||||
expectedKey: gocui.NewKeyStrMod("+", gocui.ModNone),
|
||||
expectedOk: true,
|
||||
},
|
||||
|
||||
// Shift-on-rune is rejected: terminals fold shift into the rune
|
||||
// itself, so the binding could never fire. Combined with other
|
||||
// modifiers it's allowed (the terminal can't fold it then).
|
||||
{
|
||||
name: "shift alone on a letter is rejected",
|
||||
label: "<s-q>",
|
||||
expectedKey: gocui.Key{},
|
||||
expectedOk: false,
|
||||
},
|
||||
{
|
||||
name: "shift alone on uppercase letter is rejected",
|
||||
label: "<s-A>",
|
||||
expectedKey: gocui.Key{},
|
||||
expectedOk: false,
|
||||
},
|
||||
{
|
||||
name: "shift alone on minus is rejected",
|
||||
label: "<s-minus>",
|
||||
expectedKey: gocui.Key{},
|
||||
expectedOk: false,
|
||||
},
|
||||
{
|
||||
name: "shift on space is allowed (rune does not change)",
|
||||
label: "<s-space>",
|
||||
expectedKey: gocui.NewKeyStrMod(" ", gocui.ModShift),
|
||||
expectedOk: true,
|
||||
},
|
||||
{
|
||||
name: "shift combined with ctrl on a letter is allowed",
|
||||
label: "<c-s-x>",
|
||||
expectedKey: gocui.NewKeyStrMod("x", gocui.ModCtrl|gocui.ModShift),
|
||||
expectedOk: true,
|
||||
},
|
||||
{
|
||||
name: "shift combined with alt on minus is allowed",
|
||||
label: "<a-s-minus>",
|
||||
expectedKey: gocui.NewKeyStrMod("-", gocui.ModAlt|gocui.ModShift),
|
||||
expectedOk: true,
|
||||
},
|
||||
|
||||
// Uppercase ASCII letter with a modifier is rejected: ctrl+letter
|
||||
// always arrives with a lowercase rune (control codes have no case
|
||||
// distinction), and CSI-u reports the unshifted codepoint with
|
||||
// shift as a separate modifier.
|
||||
{
|
||||
name: "ctrl+uppercase letter is rejected",
|
||||
label: "<c-A>",
|
||||
expectedKey: gocui.Key{},
|
||||
expectedOk: false,
|
||||
},
|
||||
{
|
||||
name: "alt+uppercase letter is rejected",
|
||||
label: "<a-A>",
|
||||
expectedKey: gocui.Key{},
|
||||
expectedOk: false,
|
||||
},
|
||||
{
|
||||
name: "meta+uppercase letter is rejected",
|
||||
label: "<m-A>",
|
||||
expectedKey: gocui.Key{},
|
||||
expectedOk: false,
|
||||
},
|
||||
{
|
||||
name: "combined modifier on uppercase letter is rejected",
|
||||
label: "<c-a-A>",
|
||||
expectedKey: gocui.Key{},
|
||||
expectedOk: false,
|
||||
},
|
||||
{
|
||||
name: "bare uppercase letter is allowed",
|
||||
label: "A",
|
||||
expectedKey: gocui.NewKeyStrMod("A", gocui.ModNone),
|
||||
expectedOk: true,
|
||||
},
|
||||
{
|
||||
name: "modifier on digit is allowed",
|
||||
label: "<c-1>",
|
||||
expectedKey: gocui.NewKeyStrMod("1", gocui.ModCtrl),
|
||||
expectedOk: true,
|
||||
},
|
||||
{
|
||||
name: "modifier on non-ASCII uppercase letter is allowed",
|
||||
label: "<a-Ñ>",
|
||||
expectedKey: gocui.NewKeyStrMod("Ñ", gocui.ModAlt),
|
||||
expectedOk: true,
|
||||
},
|
||||
|
||||
// Modifiers with special keys
|
||||
{
|
||||
name: "ctrl+enter",
|
||||
label: "<c-enter>",
|
||||
expectedKey: gocui.NewKey(gocui.KeyEnter, "", gocui.ModCtrl),
|
||||
expectedOk: true,
|
||||
},
|
||||
{
|
||||
name: "alt+up",
|
||||
label: "<a-up>",
|
||||
expectedKey: gocui.NewKey(gocui.KeyArrowUp, "", gocui.ModAlt),
|
||||
expectedOk: true,
|
||||
},
|
||||
{
|
||||
name: "shift+f1",
|
||||
label: "<s-f1>",
|
||||
expectedKey: gocui.NewKey(gocui.KeyF1, "", gocui.ModShift),
|
||||
expectedOk: true,
|
||||
},
|
||||
{
|
||||
name: "meta+enter",
|
||||
label: "<m-enter>",
|
||||
expectedKey: gocui.NewKey(gocui.KeyEnter, "", gocui.ModMeta),
|
||||
expectedOk: true,
|
||||
},
|
||||
|
||||
// Combined modifiers
|
||||
{
|
||||
name: "ctrl+alt+letter",
|
||||
label: "<c-a-x>",
|
||||
expectedKey: gocui.NewKeyStrMod("x", gocui.ModCtrl|gocui.ModAlt),
|
||||
expectedOk: true,
|
||||
},
|
||||
{
|
||||
name: "all four modifiers on a letter",
|
||||
label: "<s-c-a-m-x>",
|
||||
expectedKey: gocui.NewKeyStrMod("x", gocui.ModShift|gocui.ModCtrl|gocui.ModAlt|gocui.ModMeta),
|
||||
expectedOk: true,
|
||||
},
|
||||
{
|
||||
name: "ctrl+shift+arrow key",
|
||||
label: "<c-s-up>",
|
||||
expectedKey: gocui.NewKey(gocui.KeyArrowUp, "", gocui.ModCtrl|gocui.ModShift),
|
||||
expectedOk: true,
|
||||
},
|
||||
|
||||
// Bracket handling
|
||||
{
|
||||
name: "single rune wrapped in brackets is unwrapped",
|
||||
label: "<a>",
|
||||
expectedKey: gocui.NewKeyStrMod("a", gocui.ModNone),
|
||||
expectedOk: true,
|
||||
},
|
||||
{
|
||||
name: "dash wrapped in brackets",
|
||||
label: "<->",
|
||||
expectedKey: gocui.NewKeyRune('-'),
|
||||
expectedOk: true,
|
||||
},
|
||||
|
||||
// Invalid inputs
|
||||
{
|
||||
name: "unknown special key name",
|
||||
label: "<nope>",
|
||||
expectedKey: gocui.Key{},
|
||||
expectedOk: false,
|
||||
},
|
||||
{
|
||||
name: "unknown modifier letter",
|
||||
label: "<x-a>",
|
||||
expectedKey: gocui.Key{},
|
||||
expectedOk: false,
|
||||
},
|
||||
{
|
||||
name: "uppercase modifier is not accepted",
|
||||
label: "<C-a>",
|
||||
expectedKey: gocui.Key{},
|
||||
expectedOk: false,
|
||||
},
|
||||
{
|
||||
name: "duplicate ctrl modifier",
|
||||
label: "<c-c-a>",
|
||||
expectedKey: gocui.Key{},
|
||||
expectedOk: false,
|
||||
},
|
||||
{
|
||||
name: "duplicate shift modifier",
|
||||
label: "<s-s-a>",
|
||||
expectedKey: gocui.Key{},
|
||||
expectedOk: false,
|
||||
},
|
||||
{
|
||||
name: "duplicate alt modifier",
|
||||
label: "<a-a-x>",
|
||||
expectedKey: gocui.Key{},
|
||||
expectedOk: false,
|
||||
},
|
||||
{
|
||||
name: "duplicate meta modifier",
|
||||
label: "<m-m-x>",
|
||||
expectedKey: gocui.Key{},
|
||||
expectedOk: false,
|
||||
},
|
||||
{
|
||||
name: "trailing modifier with no key",
|
||||
label: "<c->",
|
||||
expectedKey: gocui.Key{},
|
||||
expectedOk: false,
|
||||
},
|
||||
{
|
||||
name: "multi-character non-special label",
|
||||
label: "ab",
|
||||
expectedKey: gocui.Key{},
|
||||
expectedOk: false,
|
||||
},
|
||||
{
|
||||
name: "empty brackets",
|
||||
label: "<>",
|
||||
expectedKey: gocui.Key{},
|
||||
expectedOk: false,
|
||||
},
|
||||
{
|
||||
name: "modifier on unknown key name",
|
||||
label: "<c-nope>",
|
||||
expectedKey: gocui.Key{},
|
||||
expectedOk: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
t.Run(s.name, func(t *testing.T) {
|
||||
key, ok := KeyFromLabel(s.label)
|
||||
assert.Equal(t, s.expectedOk, ok)
|
||||
assert.Equal(t, s.expectedKey, key)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLabelForKey(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
name string
|
||||
key gocui.Key
|
||||
expected string
|
||||
}{
|
||||
// Unset
|
||||
{"unset key produces empty string", gocui.Key{}, ""},
|
||||
|
||||
// Plain runes — single-character output, no brackets
|
||||
{"lowercase letter", gocui.NewKeyStrMod("a", gocui.ModNone), "a"},
|
||||
{"uppercase letter", gocui.NewKeyStrMod("A", gocui.ModNone), "A"},
|
||||
{"digit", gocui.NewKeyStrMod("5", gocui.ModNone), "5"},
|
||||
{"punctuation", gocui.NewKeyStrMod("?", gocui.ModNone), "?"},
|
||||
{"slash", gocui.NewKeyStrMod("/", gocui.ModNone), "/"},
|
||||
{"multibyte rune", gocui.NewKeyStrMod("ñ", gocui.ModNone), "ñ"},
|
||||
|
||||
// Space and dash — special-cased rune output
|
||||
{"plain dash uses literal", gocui.NewKeyStrMod("-", gocui.ModNone), "-"},
|
||||
{"plain space uses keyword", gocui.NewKeyStrMod(" ", gocui.ModNone), "<space>"},
|
||||
{"ctrl+dash uses minus keyword", gocui.NewKeyStrMod("-", gocui.ModCtrl), "<c-minus>"},
|
||||
{"alt+dash uses minus keyword", gocui.NewKeyStrMod("-", gocui.ModAlt), "<a-minus>"},
|
||||
{"plain plus uses literal", gocui.NewKeyStrMod("+", gocui.ModNone), "+"},
|
||||
{"ctrl+plus uses plus keyword", gocui.NewKeyStrMod("+", gocui.ModCtrl), "<c-plus>"},
|
||||
{"alt+plus uses plus keyword", gocui.NewKeyStrMod("+", gocui.ModAlt), "<a-plus>"},
|
||||
{"ctrl+space", gocui.NewKeyStrMod(" ", gocui.ModCtrl), "<c-space>"},
|
||||
|
||||
// Single modifier on a rune
|
||||
{"ctrl+letter", gocui.NewKeyStrMod("a", gocui.ModCtrl), "<c-a>"},
|
||||
{"alt+letter", gocui.NewKeyStrMod("x", gocui.ModAlt), "<a-x>"},
|
||||
{"meta+letter", gocui.NewKeyStrMod("z", gocui.ModMeta), "<m-z>"},
|
||||
{"shift+space", gocui.NewKeyStrMod(" ", gocui.ModShift), "<s-space>"},
|
||||
|
||||
// Modifier ordering — canonical output is c-, a-, s-, m-
|
||||
{"ctrl+alt orders c before a", gocui.NewKeyStrMod("x", gocui.ModCtrl|gocui.ModAlt), "<c-a-x>"},
|
||||
{"shift+ctrl orders c before s", gocui.NewKeyStrMod("x", gocui.ModShift|gocui.ModCtrl), "<c-s-x>"},
|
||||
{"meta+shift orders s before m", gocui.NewKeyStrMod("x", gocui.ModMeta|gocui.ModShift), "<s-m-x>"},
|
||||
{
|
||||
"all four modifiers ordered c-a-s-m",
|
||||
gocui.NewKeyStrMod("x", gocui.ModCtrl|gocui.ModAlt|gocui.ModShift|gocui.ModMeta),
|
||||
"<c-a-s-m-x>",
|
||||
},
|
||||
|
||||
// Special keys (always wrapped, even unmodified)
|
||||
{"f1", gocui.NewKey(gocui.KeyF1, "", gocui.ModNone), "<f1>"},
|
||||
{"f12", gocui.NewKey(gocui.KeyF12, "", gocui.ModNone), "<f12>"},
|
||||
{"insert", gocui.NewKey(gocui.KeyInsert, "", gocui.ModNone), "<insert>"},
|
||||
{"delete", gocui.NewKey(gocui.KeyDelete, "", gocui.ModNone), "<delete>"},
|
||||
{"home", gocui.NewKey(gocui.KeyHome, "", gocui.ModNone), "<home>"},
|
||||
{"end", gocui.NewKey(gocui.KeyEnd, "", gocui.ModNone), "<end>"},
|
||||
{"pgup", gocui.NewKey(gocui.KeyPgup, "", gocui.ModNone), "<pgup>"},
|
||||
{"pgdown", gocui.NewKey(gocui.KeyPgdn, "", gocui.ModNone), "<pgdown>"},
|
||||
{"arrow up", gocui.NewKey(gocui.KeyArrowUp, "", gocui.ModNone), "<up>"},
|
||||
{"arrow down", gocui.NewKey(gocui.KeyArrowDown, "", gocui.ModNone), "<down>"},
|
||||
{"arrow left", gocui.NewKey(gocui.KeyArrowLeft, "", gocui.ModNone), "<left>"},
|
||||
{"arrow right", gocui.NewKey(gocui.KeyArrowRight, "", gocui.ModNone), "<right>"},
|
||||
{"tab", gocui.NewKey(gocui.KeyTab, "", gocui.ModNone), "<tab>"},
|
||||
{"backtab", gocui.NewKey(gocui.KeyBacktab, "", gocui.ModNone), "<backtab>"},
|
||||
{"enter", gocui.NewKey(gocui.KeyEnter, "", gocui.ModNone), "<enter>"},
|
||||
{"esc", gocui.NewKey(gocui.KeyEsc, "", gocui.ModNone), "<esc>"},
|
||||
{"backspace", gocui.NewKey(gocui.KeyBackspace, "", gocui.ModNone), "<backspace>"},
|
||||
{"mouse wheel up", gocui.NewKey(gocui.MouseWheelUp, "", gocui.ModNone), "<mouse wheel up>"},
|
||||
{"mouse wheel down", gocui.NewKey(gocui.MouseWheelDown, "", gocui.ModNone), "<mouse wheel down>"},
|
||||
|
||||
// Modifiers on special keys
|
||||
{"shift+f1", gocui.NewKey(gocui.KeyF1, "", gocui.ModShift), "<s-f1>"},
|
||||
{"alt+up", gocui.NewKey(gocui.KeyArrowUp, "", gocui.ModAlt), "<a-up>"},
|
||||
{"meta+enter", gocui.NewKey(gocui.KeyEnter, "", gocui.ModMeta), "<m-enter>"},
|
||||
{"ctrl+shift+up", gocui.NewKey(gocui.KeyArrowUp, "", gocui.ModCtrl|gocui.ModShift), "<c-s-up>"},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
t.Run(s.name, func(t *testing.T) {
|
||||
assert.Equal(t, s.expected, LabelForKey(s.key))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Round-trip: every label produced by LabelForKey should parse back to the
|
||||
// same key via KeyFromLabel.
|
||||
func TestKeyFromLabel_RoundTripFromLabelForKey(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
name string
|
||||
key gocui.Key
|
||||
}{
|
||||
{"unset key", gocui.Key{}},
|
||||
{"plain letter", gocui.NewKeyStrMod("a", gocui.ModNone)},
|
||||
{"plain digit", gocui.NewKeyStrMod("7", gocui.ModNone)},
|
||||
{"space", gocui.NewKeyStrMod(" ", gocui.ModNone)},
|
||||
{"ctrl+letter", gocui.NewKeyStrMod("a", gocui.ModCtrl)},
|
||||
{"alt+letter", gocui.NewKeyStrMod("x", gocui.ModAlt)},
|
||||
{"meta+letter", gocui.NewKeyStrMod("z", gocui.ModMeta)},
|
||||
{"shift+space", gocui.NewKeyStrMod(" ", gocui.ModShift)},
|
||||
{"ctrl+shift+letter", gocui.NewKeyStrMod("x", gocui.ModCtrl|gocui.ModShift)},
|
||||
{"ctrl+alt+letter", gocui.NewKeyStrMod("x", gocui.ModCtrl|gocui.ModAlt)},
|
||||
{"f1", gocui.NewKey(gocui.KeyF1, "", gocui.ModNone)},
|
||||
{"shift+f1", gocui.NewKey(gocui.KeyF1, "", gocui.ModShift)},
|
||||
{"alt+up", gocui.NewKey(gocui.KeyArrowUp, "", gocui.ModAlt)},
|
||||
{"meta+enter", gocui.NewKey(gocui.KeyEnter, "", gocui.ModMeta)},
|
||||
{"esc", gocui.NewKey(gocui.KeyEsc, "", gocui.ModNone)},
|
||||
{"mouse wheel up", gocui.NewKey(gocui.MouseWheelUp, "", gocui.ModNone)},
|
||||
{"ctrl+space", gocui.NewKeyStrMod(" ", gocui.ModCtrl)},
|
||||
{"plain dash", gocui.NewKeyStrMod("-", gocui.ModNone)},
|
||||
{"ctrl+dash", gocui.NewKeyStrMod("-", gocui.ModCtrl)},
|
||||
{"alt+shift+dash", gocui.NewKeyStrMod("-", gocui.ModAlt|gocui.ModShift)},
|
||||
{"plain plus", gocui.NewKeyStrMod("+", gocui.ModNone)},
|
||||
{"ctrl+plus", gocui.NewKeyStrMod("+", gocui.ModCtrl)},
|
||||
{"alt+shift+plus", gocui.NewKeyStrMod("+", gocui.ModAlt|gocui.ModShift)},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
t.Run(s.name, func(t *testing.T) {
|
||||
label := LabelForKey(s.key)
|
||||
parsed, ok := KeyFromLabel(label)
|
||||
assert.True(t, ok, "expected label %q to parse", label)
|
||||
assert.Equal(t, s.key, parsed)
|
||||
})
|
||||
}
|
||||
}
|
||||
+30
-27
@@ -6,63 +6,66 @@ package gocui
|
||||
|
||||
// Editor interface must be satisfied by gocui editors.
|
||||
type Editor interface {
|
||||
Edit(v *View, key Key, mod Modifier) bool
|
||||
Edit(v *View, key Key) bool
|
||||
}
|
||||
|
||||
// The EditorFunc type is an adapter to allow the use of ordinary functions as
|
||||
// Editors. If f is a function with the appropriate signature, EditorFunc(f)
|
||||
// is an Editor object that calls f.
|
||||
type EditorFunc func(v *View, key Key, mod Modifier) bool
|
||||
type EditorFunc func(v *View, key Key) bool
|
||||
|
||||
// Edit calls f(v, key, mod)
|
||||
func (f EditorFunc) Edit(v *View, key Key, mod Modifier) bool {
|
||||
return f(v, key, mod)
|
||||
func (f EditorFunc) Edit(v *View, key Key) bool {
|
||||
return f(v, key)
|
||||
}
|
||||
|
||||
// DefaultEditor is the default editor.
|
||||
var DefaultEditor Editor = EditorFunc(SimpleEditor)
|
||||
|
||||
// SimpleEditor is used as the default gocui editor.
|
||||
func SimpleEditor(v *View, key Key, mod Modifier) bool {
|
||||
func SimpleEditor(v *View, key Key) bool {
|
||||
switch {
|
||||
case (key.KeyName() == KeyBackspace || key.KeyName() == KeyBackspace2) && (mod&ModAlt) != 0,
|
||||
key.KeyName() == KeyCtrlW:
|
||||
case key.Equals(NewKey(KeyBackspace, "", ModAlt)),
|
||||
key.Equals(NewKeyStrMod("w", ModCtrl)):
|
||||
v.TextArea.BackSpaceWord()
|
||||
case key.KeyName() == KeyBackspace || key.KeyName() == KeyBackspace2 || key.KeyName() == KeyCtrlH:
|
||||
case key.Equals(NewKeyName(KeyBackspace)):
|
||||
v.TextArea.BackSpaceChar()
|
||||
case key.KeyName() == KeyCtrlD || key.KeyName() == KeyDelete:
|
||||
case key.Equals(NewKeyStrMod("d", ModCtrl)),
|
||||
key.Equals(NewKeyName(KeyDelete)):
|
||||
v.TextArea.DeleteChar()
|
||||
case key.KeyName() == KeyArrowDown:
|
||||
case key.Equals(NewKeyName(KeyArrowDown)):
|
||||
v.TextArea.MoveCursorDown()
|
||||
case key.KeyName() == KeyArrowUp:
|
||||
case key.Equals(NewKeyName(KeyArrowUp)):
|
||||
v.TextArea.MoveCursorUp()
|
||||
case (key.KeyName() == KeyArrowLeft || key.Equals(NewKeyRune('b'))) && (mod&ModAlt) != 0:
|
||||
case key.Equals(NewKeyStrMod("b", ModAlt)),
|
||||
key.Equals(NewKey(KeyArrowLeft, "", ModAlt)):
|
||||
v.TextArea.MoveLeftWord()
|
||||
case key.KeyName() == KeyArrowLeft || key.KeyName() == KeyCtrlB:
|
||||
case key.Equals(NewKeyName(KeyArrowLeft)),
|
||||
key.Equals(NewKeyStrMod("b", ModCtrl)):
|
||||
v.TextArea.MoveCursorLeft()
|
||||
case (key.KeyName() == KeyArrowRight || key.Equals(NewKeyRune('f'))) && (mod&ModAlt) != 0:
|
||||
case key.Equals(NewKeyStrMod("f", ModAlt)),
|
||||
key.Equals(NewKey(KeyArrowRight, "", ModAlt)):
|
||||
v.TextArea.MoveRightWord()
|
||||
case key.KeyName() == KeyArrowRight || key.KeyName() == KeyCtrlF:
|
||||
case key.Equals(NewKeyName(KeyArrowRight)),
|
||||
key.Equals(NewKeyStrMod("b", ModCtrl)):
|
||||
v.TextArea.MoveCursorRight()
|
||||
case key.KeyName() == KeyEnter:
|
||||
case key.Equals(NewKeyName(KeyEnter)):
|
||||
v.TextArea.TypeCharacter("\n")
|
||||
case key.KeyName() == KeySpace:
|
||||
v.TextArea.TypeCharacter(" ")
|
||||
case key.KeyName() == KeyInsert:
|
||||
case key.Equals(NewKeyName(KeyInsert)):
|
||||
v.TextArea.ToggleOverwrite()
|
||||
case key.KeyName() == KeyCtrlU:
|
||||
case key.Equals(NewKeyStrMod("u", ModCtrl)):
|
||||
v.TextArea.DeleteToStartOfLine()
|
||||
case key.KeyName() == KeyCtrlK:
|
||||
case key.Equals(NewKeyStrMod("k", ModCtrl)):
|
||||
v.TextArea.DeleteToEndOfLine()
|
||||
case key.KeyName() == KeyCtrlA || key.KeyName() == KeyHome:
|
||||
case key.Equals(NewKeyStrMod("a", ModCtrl)),
|
||||
key.Equals(NewKeyName(KeyHome)):
|
||||
v.TextArea.GoToStartOfLine()
|
||||
case key.KeyName() == KeyCtrlE || key.KeyName() == KeyEnd:
|
||||
case key.Equals(NewKeyStrMod("e", ModCtrl)),
|
||||
key.Equals(NewKeyName(KeyEnd)):
|
||||
v.TextArea.GoToEndOfLine()
|
||||
case key.KeyName() == KeyCtrlW:
|
||||
v.TextArea.BackSpaceWord()
|
||||
case key.KeyName() == KeyCtrlY:
|
||||
case key.Equals(NewKeyStrMod("y", ModCtrl)):
|
||||
v.TextArea.Yank()
|
||||
case key.Str() != "":
|
||||
case key.Str() != "" && key.Mod() == 0:
|
||||
v.TextArea.TypeCharacter(key.Str())
|
||||
default:
|
||||
return false
|
||||
|
||||
+11
-12
@@ -1264,17 +1264,16 @@ func (g *Gui) onKey(ev *GocuiEvent) error {
|
||||
switch ev.Type {
|
||||
case eventKey:
|
||||
|
||||
// When pasting text in Ghostty, it sends us '\r' instead of '\n' for
|
||||
// newlines. I actually don't quite understand why, because from reading
|
||||
// Ghostty's source code (e.g.
|
||||
// When pasting text in Ghostty, it sends us '\r' (which is delivered as
|
||||
// ctrl-j by tcell) instead of '\n' for newlines. I actually don't quite
|
||||
// understand why, because from reading Ghostty's source code (e.g.
|
||||
// https://github.com/ghostty-org/ghostty/commit/010338354a0) it does
|
||||
// this conversion only for non-bracketed paste mode, but I'm seeing it
|
||||
// in bracketed paste mode. Whatever I'm missing here, converting '\r'
|
||||
// back to '\n' fixes pasting multi-line text from Ghostty, and doesn't
|
||||
// seem harmful for other terminal emulators.
|
||||
//
|
||||
// KeyCtrlJ (int value 10) is '\r'.
|
||||
if g.IsPasting && ev.Key.KeyName() == KeyCtrlJ {
|
||||
if g.IsPasting && ev.Key.Equals(NewKeyStrMod("j", ModCtrl)) {
|
||||
ev.Key = NewKeyName(KeyEnter)
|
||||
}
|
||||
|
||||
@@ -1316,7 +1315,7 @@ func (g *Gui) onKey(ev *GocuiEvent) error {
|
||||
}
|
||||
}
|
||||
|
||||
if ev.Key.KeyName() == MouseLeft && (ev.Mod&ModMotion) == 0 && !v.Editable && g.openHyperlink != nil {
|
||||
if ev.Key.KeyName() == MouseLeft && (ev.Key.Mod()&ModMotion) == 0 && !v.Editable && g.openHyperlink != nil {
|
||||
if newY >= 0 && newY <= len(v.viewLines)-1 && newX >= 0 && newX <= len(v.viewLines[newY].line)-1 {
|
||||
if link := v.viewLines[newY].line[newX].hyperlink; link != "" {
|
||||
return g.openHyperlink(link, v.name)
|
||||
@@ -1424,7 +1423,7 @@ func (g *Gui) execMouseKeybindings(view *View, ev *GocuiEvent, opts ViewMouseBin
|
||||
isMatch := func(binding *ViewMouseBinding) bool {
|
||||
return binding.ViewName == view.Name() &&
|
||||
ev.Key.KeyName() == binding.Key &&
|
||||
ev.Mod == binding.Modifier
|
||||
ev.Key.Mod() == binding.Modifier
|
||||
}
|
||||
|
||||
// first pass looks for ones that match the focused view
|
||||
@@ -1486,7 +1485,7 @@ func (g *Gui) execKeybindings(v *View, ev *GocuiEvent) error {
|
||||
}
|
||||
|
||||
// if we're searching, and we've hit n/N/Esc, we ignore the default keybinding
|
||||
if v != nil && v.IsSearching() && ev.Mod == ModNone {
|
||||
if v != nil && v.IsSearching() {
|
||||
if ev.Key.Equals(g.NextSearchMatchKey) {
|
||||
return v.gotoNextMatch()
|
||||
} else if ev.Key.Equals(g.PrevSearchMatchKey) {
|
||||
@@ -1508,7 +1507,7 @@ func (g *Gui) execKeybindings(v *View, ev *GocuiEvent) error {
|
||||
if kb.handler == nil {
|
||||
continue
|
||||
}
|
||||
if !kb.matchKeypress(ev.Key, ev.Mod) {
|
||||
if !kb.matchKeypress(ev.Key) {
|
||||
continue
|
||||
}
|
||||
if g.matchView(v, kb) {
|
||||
@@ -1523,7 +1522,7 @@ func (g *Gui) execKeybindings(v *View, ev *GocuiEvent) error {
|
||||
if v != nil && g.matchView(v.ParentView, kb) {
|
||||
matchingParentViewKb = kb
|
||||
}
|
||||
if globalKb == nil && kb.viewName == "" && ((v != nil && !v.Editable) || (kb.key.keyName != KeyCtrlU && kb.key.keyName != KeyCtrlA && kb.key.keyName != KeyCtrlE)) {
|
||||
if globalKb == nil && kb.viewName == "" {
|
||||
globalKb = kb
|
||||
}
|
||||
}
|
||||
@@ -1535,7 +1534,7 @@ func (g *Gui) execKeybindings(v *View, ev *GocuiEvent) error {
|
||||
}
|
||||
|
||||
if g.currentView != nil && g.currentView.Editable && g.currentView.Editor != nil {
|
||||
matched := g.currentView.Editor.Edit(g.currentView, ev.Key, ev.Mod)
|
||||
matched := g.currentView.Editor.Edit(g.currentView, ev.Key)
|
||||
if matched {
|
||||
return nil
|
||||
}
|
||||
@@ -1595,7 +1594,7 @@ func (g *Gui) matchView(v *View, kb *keybinding) bool {
|
||||
if v == nil {
|
||||
return false
|
||||
}
|
||||
if v.Editable && kb.key.Str() != "" {
|
||||
if v.Editable && kb.key.Str() != "" && kb.key.Mod() == 0 {
|
||||
return false
|
||||
}
|
||||
if kb.viewName != v.name {
|
||||
|
||||
+19
-2
@@ -9,12 +9,15 @@ import "github.com/gdamore/tcell/v3"
|
||||
type Key struct {
|
||||
keyName KeyName
|
||||
str string
|
||||
|
||||
mod Modifier
|
||||
}
|
||||
|
||||
func NewKey(keyName KeyName, str string) Key {
|
||||
func NewKey(keyName KeyName, str string, mod Modifier) Key {
|
||||
return Key{
|
||||
keyName: keyName,
|
||||
str: str,
|
||||
mod: mod,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +25,7 @@ func NewKeyName(keyName KeyName) Key {
|
||||
return Key{
|
||||
keyName: keyName,
|
||||
str: "",
|
||||
mod: ModNone,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +33,15 @@ func NewKeyRune(ch rune) Key {
|
||||
return Key{
|
||||
keyName: KeyName(tcell.KeyRune),
|
||||
str: string(ch),
|
||||
mod: ModNone,
|
||||
}
|
||||
}
|
||||
|
||||
func NewKeyStrMod(str string, mod Modifier) Key {
|
||||
return Key{
|
||||
keyName: KeyName(tcell.KeyRune),
|
||||
str: str,
|
||||
mod: mod,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,10 +53,14 @@ func (k Key) Str() string {
|
||||
return k.str
|
||||
}
|
||||
|
||||
func (k Key) Mod() Modifier {
|
||||
return k.mod
|
||||
}
|
||||
|
||||
func (k Key) IsSet() bool {
|
||||
return k.keyName != 0
|
||||
}
|
||||
|
||||
func (k Key) Equals(otherKey Key) bool {
|
||||
return k.keyName == otherKey.keyName && k.str == otherKey.str
|
||||
return k.keyName == otherKey.keyName && k.str == otherKey.str && k.mod == otherKey.mod
|
||||
}
|
||||
|
||||
+12
-40
@@ -35,8 +35,8 @@ func newKeybinding(viewname string, key Key, mod Modifier, handler func(*Gui, *V
|
||||
}
|
||||
|
||||
// matchKeypress returns if the keybinding matches the keypress.
|
||||
func (kb *keybinding) matchKeypress(key Key, mod Modifier) bool {
|
||||
return kb.key.Equals(key) && kb.mod == mod
|
||||
func (kb *keybinding) matchKeypress(key Key) bool {
|
||||
return kb.key.Equals(key)
|
||||
}
|
||||
|
||||
// Special keys.
|
||||
@@ -69,41 +69,12 @@ const (
|
||||
|
||||
// Keys combinations.
|
||||
const (
|
||||
KeyCtrlTilde = KeyName(tcell.KeyF64) // arbitrary assignment
|
||||
KeyCtrlA = KeyName(tcell.KeyCtrlA)
|
||||
KeyCtrlB = KeyName(tcell.KeyCtrlB)
|
||||
KeyCtrlC = KeyName(tcell.KeyCtrlC)
|
||||
KeyCtrlD = KeyName(tcell.KeyCtrlD)
|
||||
KeyCtrlE = KeyName(tcell.KeyCtrlE)
|
||||
KeyCtrlF = KeyName(tcell.KeyCtrlF)
|
||||
KeyCtrlG = KeyName(tcell.KeyCtrlG)
|
||||
KeyBackspace = KeyName(tcell.KeyBackspace)
|
||||
KeyCtrlH = KeyName(tcell.KeyCtrlH)
|
||||
KeyTab = KeyName(tcell.KeyTab)
|
||||
KeyBacktab = KeyName(tcell.KeyBacktab)
|
||||
KeyCtrlI = KeyName(tcell.KeyCtrlI)
|
||||
KeyCtrlJ = KeyName(tcell.KeyCtrlJ)
|
||||
KeyCtrlK = KeyName(tcell.KeyCtrlK)
|
||||
KeyCtrlL = KeyName(tcell.KeyCtrlL)
|
||||
KeyEnter = KeyName(tcell.KeyEnter)
|
||||
KeyCtrlM = KeyName(tcell.KeyCtrlM)
|
||||
KeyCtrlN = KeyName(tcell.KeyCtrlN)
|
||||
KeyCtrlO = KeyName(tcell.KeyCtrlO)
|
||||
KeyCtrlP = KeyName(tcell.KeyCtrlP)
|
||||
KeyCtrlQ = KeyName(tcell.KeyCtrlQ)
|
||||
KeyCtrlR = KeyName(tcell.KeyCtrlR)
|
||||
KeyCtrlS = KeyName(tcell.KeyCtrlS)
|
||||
KeyCtrlT = KeyName(tcell.KeyCtrlT)
|
||||
KeyCtrlU = KeyName(tcell.KeyCtrlU)
|
||||
KeyCtrlV = KeyName(tcell.KeyCtrlV)
|
||||
KeyCtrlW = KeyName(tcell.KeyCtrlW)
|
||||
KeyCtrlX = KeyName(tcell.KeyCtrlX)
|
||||
KeyCtrlY = KeyName(tcell.KeyCtrlY)
|
||||
KeyCtrlZ = KeyName(tcell.KeyCtrlZ)
|
||||
KeyEsc = KeyName(tcell.KeyEscape)
|
||||
KeySpace = KeyName(32)
|
||||
KeyBackspace2 = KeyName(tcell.KeyBackspace2)
|
||||
KeyCtrl8 = KeyName(tcell.KeyBackspace2) // same key as in termbox-go
|
||||
KeyCtrlTilde = KeyName(tcell.KeyF64) // arbitrary assignment
|
||||
KeyBackspace = KeyName(tcell.KeyBackspace)
|
||||
KeyTab = KeyName(tcell.KeyTab)
|
||||
KeyBacktab = KeyName(tcell.KeyBacktab)
|
||||
KeyEnter = KeyName(tcell.KeyEnter)
|
||||
KeyEsc = KeyName(tcell.KeyEscape)
|
||||
|
||||
// The following assignments were used in termbox implementation.
|
||||
// In tcell, these are not keys per se. But in gocui we have them
|
||||
@@ -123,8 +94,9 @@ const (
|
||||
// Modifiers.
|
||||
const (
|
||||
ModNone Modifier = Modifier(0)
|
||||
ModShift = Modifier(tcell.ModShift)
|
||||
ModCtrl = Modifier(tcell.ModCtrl)
|
||||
ModAlt = Modifier(tcell.ModAlt)
|
||||
ModMotion = Modifier(2) // just picking an arbitrary number here that doesn't clash with tcell.ModAlt
|
||||
// ModCtrl doesn't work with keyboard keys. Use CtrlKey in Key and ModNone. This is was for mouse clicks only (tcell.v1)
|
||||
// ModCtrl = Modifier(tcell.ModCtrl)
|
||||
ModMeta = Modifier(tcell.ModMeta)
|
||||
ModMotion = Modifier(16) // just picking an arbitrary number here that doesn't clash with tcell's modifiers
|
||||
)
|
||||
|
||||
@@ -163,7 +163,6 @@ type gocuiEventType uint8
|
||||
// The 'Err' field is valid if 'Type' is 'eventError'.
|
||||
type GocuiEvent struct {
|
||||
Type gocuiEventType
|
||||
Mod Modifier
|
||||
Key Key
|
||||
Width int
|
||||
Height int
|
||||
@@ -294,42 +293,15 @@ func (g *Gui) pollEvent() GocuiEvent {
|
||||
ch := ""
|
||||
if k == tcell.KeyRune {
|
||||
ch = tev.Str()
|
||||
if ch == " " {
|
||||
// special handling for spacebar
|
||||
k = tcell.Key(KeySpace)
|
||||
ch = ""
|
||||
}
|
||||
} else if k >= tcell.KeyCtrlA && k <= tcell.KeyCtrlZ {
|
||||
ch = string(rune('a' + (k - tcell.KeyCtrlA)))
|
||||
k = tcell.KeyRune
|
||||
}
|
||||
mod := tev.Modifiers()
|
||||
// remove control modifier and setup special handling of ctrl+spacebar, etc.
|
||||
if mod == tcell.ModCtrl && k == 32 {
|
||||
ch = " "
|
||||
k = tcell.KeyRune
|
||||
} else if mod == tcell.ModShift && k == tcell.KeyUp {
|
||||
mod = 0
|
||||
ch = ""
|
||||
k = tcell.KeyF62
|
||||
} else if mod == tcell.ModShift && k == tcell.KeyDown {
|
||||
mod = 0
|
||||
ch = ""
|
||||
k = tcell.KeyF63
|
||||
} else if mod == tcell.ModCtrl || mod == tcell.ModShift {
|
||||
// remove Ctrl or Shift if specified
|
||||
// - shift - will be translated to the final code of rune
|
||||
// - ctrl - is translated in the key
|
||||
mod = 0
|
||||
} else if mod == tcell.ModAlt && k == tcell.KeyEnter {
|
||||
// for the sake of convenience I'm having a KeyAltEnter key. I will likely
|
||||
// regret this laziness in the future. We're arbitrarily mapping that to tcell's
|
||||
// KeyF64.
|
||||
mod = 0
|
||||
k = tcell.KeyF64
|
||||
}
|
||||
|
||||
return GocuiEvent{
|
||||
Type: eventKey,
|
||||
Key: NewKey(KeyName(k), ch),
|
||||
Mod: Modifier(mod),
|
||||
Key: NewKey(KeyName(k), ch, Modifier(mod)),
|
||||
}
|
||||
case *tcell.EventMouse:
|
||||
x, y := tev.Position()
|
||||
@@ -411,8 +383,7 @@ func (g *Gui) pollEvent() GocuiEvent {
|
||||
Type: eventMouse,
|
||||
MouseX: x,
|
||||
MouseY: y,
|
||||
Key: NewKeyName(mouseKey),
|
||||
Mod: mouseMod,
|
||||
Key: NewKey(mouseKey, "", mouseMod),
|
||||
}
|
||||
case *tcell.EventFocus:
|
||||
return GocuiEvent{
|
||||
|
||||
@@ -119,7 +119,7 @@ func (self *CommitDescriptionController) handleTogglePanel() error {
|
||||
// which is common in pasted code snippets.
|
||||
view := self.Context().GetView()
|
||||
for range 4 {
|
||||
view.Editor.Edit(view, gocui.NewKeyRune(' '), 0)
|
||||
view.Editor.Edit(view, gocui.NewKeyRune(' '))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -130,7 +130,7 @@ func (self *CommitMessageController) handleTogglePanel() error {
|
||||
// switch to the description panel.
|
||||
view := self.context().GetView()
|
||||
for range 4 {
|
||||
view.Editor.Edit(view, gocui.NewKeyRune(' '), 0)
|
||||
view.Editor.Edit(view, gocui.NewKeyRune(' '))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
+11
-11
@@ -4,33 +4,33 @@ import (
|
||||
"github.com/jesseduffield/lazygit/pkg/gocui"
|
||||
)
|
||||
|
||||
func (gui *Gui) handleEditorKeypress(v *gocui.View, key gocui.Key, mod gocui.Modifier, allowMultiline bool) bool {
|
||||
if key.KeyName() == gocui.KeyEnter && allowMultiline {
|
||||
func (gui *Gui) handleEditorKeypress(v *gocui.View, key gocui.Key, allowMultiline bool) bool {
|
||||
if key.Equals(gocui.NewKeyName(gocui.KeyEnter)) && allowMultiline {
|
||||
v.TextArea.TypeCharacter("\n")
|
||||
v.RenderTextArea()
|
||||
return true
|
||||
}
|
||||
|
||||
return gocui.DefaultEditor.Edit(v, key, mod)
|
||||
return gocui.DefaultEditor.Edit(v, key)
|
||||
}
|
||||
|
||||
// we've just copy+pasted the editor from gocui to here so that we can also re-
|
||||
// render the commit message length on each keypress
|
||||
func (gui *Gui) commitMessageEditor(v *gocui.View, key gocui.Key, mod gocui.Modifier) bool {
|
||||
matched := gui.handleEditorKeypress(v, key, mod, false)
|
||||
func (gui *Gui) commitMessageEditor(v *gocui.View, key gocui.Key) bool {
|
||||
matched := gui.handleEditorKeypress(v, key, false)
|
||||
v.RenderTextArea()
|
||||
gui.c.Contexts().CommitMessage.RenderSubtitle()
|
||||
return matched
|
||||
}
|
||||
|
||||
func (gui *Gui) commitDescriptionEditor(v *gocui.View, key gocui.Key, mod gocui.Modifier) bool {
|
||||
matched := gui.handleEditorKeypress(v, key, mod, true)
|
||||
func (gui *Gui) commitDescriptionEditor(v *gocui.View, key gocui.Key) bool {
|
||||
matched := gui.handleEditorKeypress(v, key, true)
|
||||
v.RenderTextArea()
|
||||
return matched
|
||||
}
|
||||
|
||||
func (gui *Gui) promptEditor(v *gocui.View, key gocui.Key, mod gocui.Modifier) bool {
|
||||
matched := gui.handleEditorKeypress(v, key, mod, false)
|
||||
func (gui *Gui) promptEditor(v *gocui.View, key gocui.Key) bool {
|
||||
matched := gui.handleEditorKeypress(v, key, false)
|
||||
|
||||
v.RenderTextArea()
|
||||
|
||||
@@ -46,8 +46,8 @@ func (gui *Gui) promptEditor(v *gocui.View, key gocui.Key, mod gocui.Modifier) b
|
||||
return matched
|
||||
}
|
||||
|
||||
func (gui *Gui) searchEditor(v *gocui.View, key gocui.Key, mod gocui.Modifier) bool {
|
||||
matched := gui.handleEditorKeypress(v, key, mod, false)
|
||||
func (gui *Gui) searchEditor(v *gocui.View, key gocui.Key) bool {
|
||||
matched := gui.handleEditorKeypress(v, key, false)
|
||||
v.RenderTextArea()
|
||||
|
||||
searchString := v.TextArea.GetContent()
|
||||
|
||||
@@ -34,7 +34,7 @@ func (self *GuiDriver) PressKey(keyStr string) {
|
||||
}
|
||||
|
||||
self.gui.g.ReplayedEvents.Keys <- gocui.NewTcellKeyEventWrapper(
|
||||
tcell.NewEventKey(tcell.Key(key.KeyName()), key.Str(), tcell.ModNone),
|
||||
tcell.NewEventKey(tcell.Key(key.KeyName()), key.Str(), tcell.ModMask(key.Mod())),
|
||||
0,
|
||||
)
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ func RunTUI(raceDetector bool) {
|
||||
log.Panicln(err)
|
||||
}
|
||||
|
||||
if err := g.SetKeybinding("list", gocui.NewKeyName(gocui.KeyCtrlC), gocui.ModNone, quit); err != nil {
|
||||
if err := g.SetKeybinding("list", gocui.NewKeyStrMod("c", gocui.ModCtrl), gocui.ModNone, quit); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
|
||||
@@ -273,9 +273,9 @@ func (self *app) renderTests() {
|
||||
}
|
||||
}
|
||||
|
||||
func (self *app) wrapEditor(f func(v *gocui.View, key gocui.Key, mod gocui.Modifier) bool) func(v *gocui.View, key gocui.Key, mod gocui.Modifier) bool {
|
||||
return func(v *gocui.View, key gocui.Key, mod gocui.Modifier) bool {
|
||||
matched := f(v, key, mod)
|
||||
func (self *app) wrapEditor(f func(v *gocui.View, key gocui.Key) bool) func(v *gocui.View, key gocui.Key) bool {
|
||||
return func(v *gocui.View, key gocui.Key) bool {
|
||||
matched := f(v, key)
|
||||
if matched {
|
||||
self.filterWithString(v.TextArea.GetContent())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user