make keybinds configurable and consolidate
This commit is contained in:
148
text_editor.go
148
text_editor.go
@@ -39,12 +39,13 @@ type textEditor struct {
|
||||
err error
|
||||
hardwareCursor bool
|
||||
highlightMarkdown bool
|
||||
keys KeyBindings
|
||||
}
|
||||
|
||||
func newTextEditor(text string, modal bool) textEditor {
|
||||
editor := textEditor{
|
||||
Text: text, Modal: modal, Mode: textEditorInsert,
|
||||
clipboard: systemTextClipboard{},
|
||||
clipboard: systemTextClipboard{}, keys: defaultKeyBindings(),
|
||||
}
|
||||
editor.Cursor = len([]rune(text))
|
||||
if modal {
|
||||
@@ -63,7 +64,7 @@ func (e *textEditor) handleKeyAtWidth(key tea.KeyMsg, multiline bool, wrapWidth
|
||||
return e.handleStandardKey(key, multiline, wrapWidth)
|
||||
}
|
||||
if e.Mode == textEditorInsert {
|
||||
if key.String() == "esc" {
|
||||
if keyMatches(key.String(), e.keys.Input.Cancel) {
|
||||
e.Mode = textEditorNormal
|
||||
e.clearPending()
|
||||
start, _ := editorLineBounds(e.Text, e.Cursor, wrapWidth)
|
||||
@@ -87,30 +88,35 @@ func (e *textEditor) movePage(delta, wrapWidth int) {
|
||||
}
|
||||
|
||||
func (e *textEditor) handleStandardKey(key tea.KeyMsg, multiline bool, wrapWidth int) bool {
|
||||
switch key.String() {
|
||||
case "left":
|
||||
k := key.String()
|
||||
switch {
|
||||
case key.Type != tea.KeyRunes && key.Type != tea.KeySpace &&
|
||||
keyMatches(k, e.keys.Navigation.Left):
|
||||
e.Cursor = max(0, e.Cursor-1)
|
||||
case "right":
|
||||
case key.Type != tea.KeyRunes && key.Type != tea.KeySpace &&
|
||||
keyMatches(k, e.keys.Navigation.Right):
|
||||
e.Cursor = min(len([]rune(e.Text)), e.Cursor+1)
|
||||
case "up":
|
||||
case key.Type != tea.KeyRunes && key.Type != tea.KeySpace &&
|
||||
keyMatches(k, e.keys.Navigation.Up):
|
||||
if !multiline {
|
||||
return false
|
||||
}
|
||||
e.Cursor = moveEditorCursorLine(e.Text, e.Cursor, -1, wrapWidth, false)
|
||||
case "down":
|
||||
case key.Type != tea.KeyRunes && key.Type != tea.KeySpace &&
|
||||
keyMatches(k, e.keys.Navigation.Down):
|
||||
if !multiline {
|
||||
return false
|
||||
}
|
||||
e.Cursor = moveEditorCursorLine(e.Text, e.Cursor, 1, wrapWidth, false)
|
||||
case "home", "ctrl+a":
|
||||
case keyMatches(k, e.keys.Input.LineStart):
|
||||
e.Cursor, _ = editorLineBounds(e.Text, e.Cursor, wrapWidth)
|
||||
case "end", "ctrl+e":
|
||||
case keyMatches(k, e.keys.Input.LineEnd):
|
||||
_, e.Cursor = editorLineBounds(e.Text, e.Cursor, wrapWidth)
|
||||
case "backspace":
|
||||
case keyMatches(k, e.keys.Input.DeleteBackward):
|
||||
e.deleteBefore()
|
||||
case "delete":
|
||||
case keyMatches(k, e.keys.Input.DeleteForward):
|
||||
e.deleteAt()
|
||||
case "enter":
|
||||
case keyMatches(k, e.keys.Input.Newline):
|
||||
if !multiline {
|
||||
return false
|
||||
}
|
||||
@@ -145,36 +151,37 @@ func (e *textEditor) handleNormalKey(key tea.KeyMsg, multiline bool, wrapWidth i
|
||||
}
|
||||
if e.pendingG {
|
||||
e.pendingG = false
|
||||
if key.String() == "g" {
|
||||
if keyMatches(key.String(), e.keys.Vim.GoPrefix) {
|
||||
e.Cursor = firstNonBlank(e.Text, 0)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
switch key.String() {
|
||||
case "esc":
|
||||
k := key.String()
|
||||
switch {
|
||||
case keyMatches(k, e.keys.Input.Cancel):
|
||||
e.clearPending()
|
||||
return false
|
||||
case "i":
|
||||
case keyMatches(k, e.keys.Vim.Insert):
|
||||
e.Mode = textEditorInsert
|
||||
case "a":
|
||||
case keyMatches(k, e.keys.Vim.Append):
|
||||
_, end := editorLineBounds(e.Text, e.Cursor, wrapWidth)
|
||||
e.Cursor = min(end, e.Cursor+1)
|
||||
e.Mode = textEditorInsert
|
||||
case "I":
|
||||
case keyMatches(k, e.keys.Vim.InsertLineStart):
|
||||
e.Cursor = firstNonBlankAtWidth(e.Text, e.Cursor, wrapWidth)
|
||||
e.Mode = textEditorInsert
|
||||
case "A":
|
||||
case keyMatches(k, e.keys.Vim.AppendLineEnd):
|
||||
_, e.Cursor = editorLineBounds(e.Text, e.Cursor, wrapWidth)
|
||||
e.Mode = textEditorInsert
|
||||
case "o":
|
||||
case keyMatches(k, e.keys.Vim.OpenBelow):
|
||||
if !multiline {
|
||||
return false
|
||||
}
|
||||
_, e.Cursor = editorLineBounds(e.Text, e.Cursor, wrapWidth)
|
||||
e.insert("\n")
|
||||
e.Mode = textEditorInsert
|
||||
case "O":
|
||||
case keyMatches(k, e.keys.Vim.OpenAbove):
|
||||
if !multiline {
|
||||
return false
|
||||
}
|
||||
@@ -183,69 +190,75 @@ func (e *textEditor) handleNormalKey(key tea.KeyMsg, multiline bool, wrapWidth i
|
||||
e.insert("\n")
|
||||
e.Cursor = start
|
||||
e.Mode = textEditorInsert
|
||||
case "h", "left":
|
||||
case keyMatches(k, e.keys.Navigation.Left):
|
||||
start, _ := editorLineBounds(e.Text, e.Cursor, wrapWidth)
|
||||
e.Cursor = max(start, e.Cursor-1)
|
||||
case "l", "right":
|
||||
case keyMatches(k, e.keys.Navigation.Right):
|
||||
e.Cursor = min(normalEditorLineLast(e.Text, e.Cursor, wrapWidth), e.Cursor+1)
|
||||
case "j", "down":
|
||||
case keyMatches(k, e.keys.Navigation.Down):
|
||||
if multiline {
|
||||
e.Cursor = moveEditorCursorLine(e.Text, e.Cursor, 1, wrapWidth, true)
|
||||
}
|
||||
case "k", "up":
|
||||
case keyMatches(k, e.keys.Navigation.Up):
|
||||
if multiline {
|
||||
e.Cursor = moveEditorCursorLine(e.Text, e.Cursor, -1, wrapWidth, true)
|
||||
}
|
||||
case "0", "home":
|
||||
case keyMatches(k, e.keys.Vim.LineStart):
|
||||
e.Cursor, _ = editorLineBounds(e.Text, e.Cursor, wrapWidth)
|
||||
case "^":
|
||||
case keyMatches(k, e.keys.Vim.FirstNonBlank):
|
||||
e.Cursor = firstNonBlankAtWidth(e.Text, e.Cursor, wrapWidth)
|
||||
case "$", "end":
|
||||
case keyMatches(k, e.keys.Vim.LineEnd):
|
||||
e.Cursor = normalEditorLineLast(e.Text, e.Cursor, wrapWidth)
|
||||
case "w":
|
||||
case keyMatches(k, e.keys.Vim.WordForward):
|
||||
e.Cursor = nextWordStart(e.Text, e.Cursor, false)
|
||||
case "W":
|
||||
case keyMatches(k, e.keys.Vim.WORDForward):
|
||||
e.Cursor = nextWordStart(e.Text, e.Cursor, true)
|
||||
case "b":
|
||||
case keyMatches(k, e.keys.Vim.WordBackward):
|
||||
e.Cursor = previousWordStart(e.Text, e.Cursor, false)
|
||||
case "B":
|
||||
case keyMatches(k, e.keys.Vim.WORDBackward):
|
||||
e.Cursor = previousWordStart(e.Text, e.Cursor, true)
|
||||
case "e":
|
||||
case keyMatches(k, e.keys.Vim.WordEnd):
|
||||
e.Cursor = wordEndAtWidth(e.Text, e.Cursor, false, wrapWidth)
|
||||
case "E":
|
||||
case keyMatches(k, e.keys.Vim.WORDEnd):
|
||||
e.Cursor = wordEndAtWidth(e.Text, e.Cursor, true, wrapWidth)
|
||||
case "g":
|
||||
case keyMatches(k, e.keys.Vim.GoPrefix):
|
||||
e.pendingG = true
|
||||
case "G":
|
||||
case keyMatches(k, e.keys.Navigation.Last):
|
||||
e.Cursor = firstNonBlank(e.Text, len([]rune(e.Text)))
|
||||
case "v":
|
||||
case keyMatches(k, e.keys.Vim.Visual):
|
||||
e.startVisual(false)
|
||||
case "V":
|
||||
case keyMatches(k, e.keys.Vim.VisualLine):
|
||||
e.startVisual(true)
|
||||
case "p":
|
||||
case keyMatches(k, e.keys.Vim.Paste):
|
||||
e.pasteClipboard(false, wrapWidth)
|
||||
case "s":
|
||||
case keyMatches(k, e.keys.Vim.ReplaceCharacter):
|
||||
_, end := editorLineBounds(e.Text, e.Cursor, wrapWidth)
|
||||
if e.Cursor < end {
|
||||
e.deleteAt()
|
||||
}
|
||||
e.Mode = textEditorInsert
|
||||
case "f", "F", "t", "T":
|
||||
e.pendingFind = []rune(key.String())[0]
|
||||
case ";":
|
||||
case keyMatches(k, e.keys.Vim.FindForward):
|
||||
e.pendingFind = 'f'
|
||||
case keyMatches(k, e.keys.Vim.FindBackward):
|
||||
e.pendingFind = 'F'
|
||||
case keyMatches(k, e.keys.Vim.TillForward):
|
||||
e.pendingFind = 't'
|
||||
case keyMatches(k, e.keys.Vim.TillBackward):
|
||||
e.pendingFind = 'T'
|
||||
case keyMatches(k, e.keys.Vim.RepeatFind):
|
||||
if e.lastFind.valid {
|
||||
e.performFind(e.lastFind.command, e.lastFind.target, false, wrapWidth)
|
||||
}
|
||||
case ",":
|
||||
case keyMatches(k, e.keys.Vim.RepeatFindReverse):
|
||||
if e.lastFind.valid {
|
||||
e.performFind(reverseFind(e.lastFind.command), e.lastFind.target, false, wrapWidth)
|
||||
}
|
||||
case "x", "delete":
|
||||
case keyMatches(k, e.keys.Vim.Delete):
|
||||
_, end := editorLineBounds(e.Text, e.Cursor, wrapWidth)
|
||||
if e.Cursor < end {
|
||||
e.deleteAt()
|
||||
}
|
||||
case "X", "backspace":
|
||||
case keyMatches(k, e.keys.Vim.DeleteBefore):
|
||||
start, _ := editorLineBounds(e.Text, e.Cursor, wrapWidth)
|
||||
if e.Cursor > start {
|
||||
e.deleteBefore()
|
||||
@@ -263,25 +276,26 @@ func (e *textEditor) handleVisualKey(key tea.KeyMsg, multiline bool, wrapWidth i
|
||||
e.Mode = textEditorVisual
|
||||
return handled
|
||||
}
|
||||
switch key.String() {
|
||||
case "esc", "v":
|
||||
k := key.String()
|
||||
switch {
|
||||
case keyMatches(k, e.keys.Input.Cancel), keyMatches(k, e.keys.Vim.Visual):
|
||||
e.stopVisual()
|
||||
case "V":
|
||||
case keyMatches(k, e.keys.Vim.VisualLine):
|
||||
if e.visualLine {
|
||||
e.stopVisual()
|
||||
} else {
|
||||
e.visualLine = true
|
||||
}
|
||||
case "o":
|
||||
case keyMatches(k, e.keys.Vim.SelectionOtherEnd):
|
||||
e.Cursor, e.visualAnchor = e.visualAnchor, e.Cursor
|
||||
case "y":
|
||||
case keyMatches(k, e.keys.Vim.Yank):
|
||||
e.yankSelection(wrapWidth)
|
||||
case "d", "x", "delete":
|
||||
case keyMatches(k, e.keys.Vim.Delete):
|
||||
e.deleteSelection(wrapWidth)
|
||||
case "p":
|
||||
case keyMatches(k, e.keys.Vim.Paste):
|
||||
e.pasteClipboard(true, wrapWidth)
|
||||
default:
|
||||
if !isVisualMotion(key.String()) {
|
||||
if !e.isVisualMotion(k) {
|
||||
return false
|
||||
}
|
||||
e.Mode = textEditorNormal
|
||||
@@ -292,16 +306,24 @@ func (e *textEditor) handleVisualKey(key tea.KeyMsg, multiline bool, wrapWidth i
|
||||
return true
|
||||
}
|
||||
|
||||
func isVisualMotion(key string) bool {
|
||||
switch key {
|
||||
case "h", "j", "k", "l", "left", "down", "up", "right",
|
||||
"0", "^", "$", "home", "end",
|
||||
"w", "W", "b", "B", "e", "E", "g", "G",
|
||||
"f", "F", "t", "T", ";", ",":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
func (e textEditor) isVisualMotion(key string) bool {
|
||||
groups := [][]string{
|
||||
e.keys.Navigation.Left, e.keys.Navigation.Down, e.keys.Navigation.Up,
|
||||
e.keys.Navigation.Right, e.keys.Navigation.Last,
|
||||
e.keys.Vim.LineStart, e.keys.Vim.FirstNonBlank, e.keys.Vim.LineEnd,
|
||||
e.keys.Vim.WordForward, e.keys.Vim.WORDForward,
|
||||
e.keys.Vim.WordBackward, e.keys.Vim.WORDBackward,
|
||||
e.keys.Vim.WordEnd, e.keys.Vim.WORDEnd, e.keys.Vim.GoPrefix,
|
||||
e.keys.Vim.FindForward, e.keys.Vim.FindBackward,
|
||||
e.keys.Vim.TillForward, e.keys.Vim.TillBackward,
|
||||
e.keys.Vim.RepeatFind, e.keys.Vim.RepeatFindReverse,
|
||||
}
|
||||
for _, group := range groups {
|
||||
if keyMatches(key, group) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (e *textEditor) startVisual(linewise bool) {
|
||||
|
||||
Reference in New Issue
Block a user