diff options
Diffstat (limited to 'interfacer/src/browsh/vim_mode.go')
-rw-r--r-- | interfacer/src/browsh/vim_mode.go | 256 |
1 files changed, 166 insertions, 90 deletions
diff --git a/interfacer/src/browsh/vim_mode.go b/interfacer/src/browsh/vim_mode.go index 6a11219..cee6ee1 100644 --- a/interfacer/src/browsh/vim_mode.go +++ b/interfacer/src/browsh/vim_mode.go @@ -19,15 +19,17 @@ type vimMode int const ( normalMode vimMode = iota + 1 insertMode + insertModeHard findMode linkMode linkModeNewTab + linkModeMultipleNewTab linkModeCopy waitMode visualMode caretMode - makeMarkMode - gotoMarkMode + markModeMake + markModeGoto ) // TODO: What's a mark? @@ -49,11 +51,13 @@ type hintRect struct { } var ( - currentVimMode = normalMode - vimCommandsBindings = make(map[string]string) - keyEvents = make([]*tcell.EventKey, 0, 11) - waitModeStartTime time.Time - findText string + currentVimMode = normalMode + vimKeyMap = make(map[string]string) + keyEvents = make([]*tcell.EventKey, 0, 11) + waitModeStartTime time.Time + waitModeMaxMilliseconds = 1000 + findText string + latestKeyCombination string // Marks globalMarkMap = make(map[rune]*mark) localMarkMap = make(map[int]map[rune]*mark) @@ -156,7 +160,7 @@ func makeMark() *mark { } func goIntoWaitMode() { - currentVimMode = waitMode + changeVimMode(waitMode) waitModeStartTime = time.Now() } @@ -219,96 +223,111 @@ func eraseLinkHints() { linkHintRects = nil } +func resetLinkHints() { + linkText = "" + updateLinkHintDisplay() +} + func isNormalModeKey(ev *tcell.EventKey) bool { - if ev.Key() == tcell.KeyESC { + if ev != nil && ev.Key() == tcell.KeyESC { return true } return false } -func handleVimControl(ev *tcell.EventKey) { - var lastRune rune - command := "" +func keyEventToString(ev *tcell.EventKey) string { + if ev == nil { + return "" + } + + r := string(ev.Rune()) + if ev.Modifiers()&tcell.ModAlt != 0 && ev.Modifiers()&tcell.ModCtrl != 0 { + return "<C-M-" + r + ">" + } else if ev.Modifiers()&tcell.ModAlt != 0 { + return "<M-" + r + ">" + } else if ev.Modifiers()&tcell.ModCtrl != 0 { + return "<C-" + strings.ToLower(ev.Name()[5:]) + ">" + } + + switch ev.Key() { + case tcell.KeyEnter: + return "<Enter>" + } + + return r +} + - if len(keyEvents) > 0 && keyEvents[0] != nil { - lastRune = keyEvents[len(keyEvents)-1].Rune() +func getNLastKeyEvent(n int) *tcell.EventKey { + if n < 0 || keyEvents == nil { + return nil } + if len(keyEvents) > n { + return keyEvents[len(keyEvents)-n-1] + } + return nil +} + +func mapVimKeyEvents(ev *tcell.EventKey, mapMode string) string { + var lastEvent *tcell.EventKey + command := "" keyEvents = append(keyEvents, ev) if len(keyEvents) > 10 { keyEvents = keyEvents[1:] } - keyCombination := string(lastRune) + string(ev.Rune()) + lastEvent = getNLastKeyEvent(1) + + latestKeyCombination = keyEventToString(lastEvent) + keyEventToString(ev) + + command = vimKeyMap[mapMode+" "+latestKeyCombination] + if len(command) == 0 { + latestKeyCombination = keyEventToString(ev) + command = vimKeyMap[mapMode+" "+latestKeyCombination] + } + if len(command) <= 0 { + latestKeyCombination = "" + } else { + // Since len(command) must be greather than 0 here, + // a key mapping did match, therefore we reset keyEvents + keyEvents = nil + } + return command +} + +func handleVimMode(ev *tcell.EventKey, mode string) string { + if isNormalModeKey(ev) { + return "normalMode" + } else { + return mapVimKeyEvents(ev, mode) + } +} +func handleVimControl(ev *tcell.EventKey) { + var command string switch currentVimMode { case waitMode: - if time.Since(waitModeStartTime) < time.Millisecond*1000 { + if time.Since(waitModeStartTime) < time.Millisecond*time.Duration(waitModeMaxMilliseconds) { return } - currentVimMode = normalMode + changeVimMode(normalMode) fallthrough case normalMode: - command = vimCommandsBindings[keyCombination] - if len(command) == 0 { - keyCombination := string(ev.Rune()) - command = vimCommandsBindings[keyCombination] - } + command = mapVimKeyEvents(ev, "normal") case insertMode: - if isNormalModeKey(ev) { - command = "normalMode" - } - case visualMode: - if isNormalModeKey(ev) { + command = handleVimMode(ev, "insert") + case insertModeHard: + if isNormalModeKey(ev) && isNormalModeKey(getNLastKeyEvent(0)) && isNormalModeKey(getNLastKeyEvent(1)) && isNormalModeKey(getNLastKeyEvent(2)) { command = "normalMode" } else { - if ev.Rune() == 'c' { - command = "caretMode" - } - if ev.Rune() == 'o' { - //swap cursor position begin->end or end->begin - } - if ev.Rune() == 'y' { - //clipboard - } + command = mapVimKeyEvents(ev, "insertHard") } + case visualMode: + command = handleVimMode(ev, "visual") case caretMode: - if isNormalModeKey(ev) { - command = "normalMode" - } else { - switch ev.Key() { - case tcell.KeyEnter: - generateLeftClick(caretPos.X, caretPos.Y-uiHeight) - } - switch ev.Rune() { - case 'v': - command = "visualMode" - case 'h': - moveVimCaret(func() bool { return caretPos.X > 0 }, &caretPos.X, -1) - case 'l': - width, _ := screen.Size() - moveVimCaret(func() bool { return caretPos.X < width }, &caretPos.X, 1) - case 'k': - _, height := screen.Size() - moveVimCaret(func() bool { return caretPos.Y >= uiHeight }, &caretPos.Y, -1) - if caretPos.Y < uiHeight { - command = "scrollHalfPageUp" - if CurrentTab.frame.yScroll == 0 { - caretPos.Y = uiHeight - } else { - caretPos.Y += (height - uiHeight) / 2 - } - } - case 'j': - _, height := screen.Size() - moveVimCaret(func() bool { return caretPos.Y <= height-uiHeight }, &caretPos.Y, 1) - if caretPos.Y > height-uiHeight { - command = "scrollHalfPageDown" - caretPos.Y -= (height - uiHeight) / 2 - } - } - } - case makeMarkMode: + command = handleVimMode(ev, "caret") + case markModeMake: if unicode.IsLower(ev.Rune()) { if localMarkMap[CurrentTab.ID] == nil { localMarkMap[CurrentTab.ID] = make(map[rune]*mark) @@ -319,7 +338,7 @@ func handleVimControl(ev *tcell.EventKey) { } command = "normalMode" - case gotoMarkMode: + case markModeGoto: if mark, ok := globalMarkMap[ev.Rune()]; ok { gotoMark(mark) } else if m, ok := localMarkMap[CurrentTab.ID]; unicode.IsLower(ev.Rune()) && ok { @@ -335,7 +354,7 @@ func handleVimControl(ev *tcell.EventKey) { findText = "" } else { if ev.Key() == tcell.KeyEnter { - currentVimMode = normalMode + changeVimMode(normalMode) command = "findText" break } @@ -347,7 +366,7 @@ func handleVimControl(ev *tcell.EventKey) { findText += string(ev.Rune()) } } - case linkMode, linkModeNewTab, linkModeCopy: + case linkMode, linkModeNewTab, linkModeMultipleNewTab, linkModeCopy: if isNormalModeKey(ev) { command = "normalMode" eraseLinkHints() @@ -366,6 +385,9 @@ func handleVimControl(ev *tcell.EventKey) { } case linkModeNewTab: sendMessageToWebExtension("/new_tab," + r.Href) + case linkModeMultipleNewTab: + resetLinkHints() + return case linkModeCopy: clipboard.WriteAll(r.Href) } @@ -386,7 +408,7 @@ func handleVimControl(ev *tcell.EventKey) { linkText = "" return } else if len(coords) == 0 { - currentVimMode = normalMode + changeVimMode(normalMode) linkText = "" return } @@ -394,13 +416,17 @@ func handleVimControl(ev *tcell.EventKey) { } } - if len(command) > 0 { - executeVimCommand(command) - } + executeVimCommand(command) } func executeVimCommand(command string) { - switch command { + if len(command) == 0 { + return + } + + currentCommand := command + command = "" + switch currentCommand { case "urlUp": sendMessageToWebExtension("/tab_command,/url_up") case "urlRoot": @@ -472,15 +498,19 @@ func executeVimCommand(command string) { case "viewHelp": sendMessageToWebExtension("/new_tab,https://www.brow.sh/docs/keybindings/") case "openLinkInCurrentTab": - currentVimMode = linkMode + changeVimMode(linkMode) sendMessageToWebExtension("/tab_command,/get_clickable_hints") eraseLinkHints() case "openLinkInNewTab": - currentVimMode = linkModeNewTab + changeVimMode(linkModeNewTab) + sendMessageToWebExtension("/tab_command,/get_link_hints") + eraseLinkHints() + case "openMultipleLinksInNewTab": + changeVimMode(linkModeMultipleNewTab) sendMessageToWebExtension("/tab_command,/get_link_hints") eraseLinkHints() case "copyLinkURL": - currentVimMode = linkModeCopy + changeVimMode(linkModeCopy) sendMessageToWebExtension("/tab_command,/get_link_hints") eraseLinkHints() case "findText": @@ -490,22 +520,68 @@ func executeVimCommand(command string) { case "findPrevious": sendMessageToWebExtension("/tab_command,/find_previous," + findText) case "makeMark": - currentVimMode = makeMarkMode + changeVimMode(markModeMake) case "gotoMark": - currentVimMode = gotoMarkMode + changeVimMode(markModeGoto) case "insertMode": - currentVimMode = insertMode + changeVimMode(insertMode) + case "insertModeHard": + changeVimMode(insertModeHard) case "findMode": - currentVimMode = findMode + changeVimMode(findMode) case "normalMode": - currentVimMode = normalMode + changeVimMode(normalMode) + // Visual mode case "visualMode": - currentVimMode = visualMode + changeVimMode(visualMode) + case "swapVisualModeCursorPosition": + // Stub + case "copyVisualModeSelection": + // Caret mode case "caretMode": - currentVimMode = caretMode + changeVimMode(caretMode) width, height := screen.Size() caretPos.X, caretPos.Y = width/2, height/2 + case "clickAtCaretPosition": + generateLeftClick(caretPos.X, caretPos.Y-uiHeight) + case "moveCaretLeft": + moveVimCaret(func() bool { return caretPos.X > 0 }, &caretPos.X, -1) + case "moveCaretRight": + width, _ := screen.Size() + moveVimCaret(func() bool { return caretPos.X < width }, &caretPos.X, 1) + case "moveCaretUp": + _, height := screen.Size() + moveVimCaret(func() bool { return caretPos.Y >= uiHeight }, &caretPos.Y, -1) + if caretPos.Y < uiHeight { + command = "scrollHalfPageUp" + if CurrentTab.frame.yScroll == 0 { + caretPos.Y = uiHeight + } else { + caretPos.Y += (height - uiHeight) / 2 + } + } + case "moveCaretDown": + _, height := screen.Size() + moveVimCaret(func() bool { return caretPos.Y <= height-uiHeight }, &caretPos.Y, 1) + if caretPos.Y > height-uiHeight { + command = "scrollHalfPageDown" + caretPos.Y -= (height - uiHeight) / 2 + } } + + // A command can spawn another + executeVimCommand(command) +} + +func changeVimMode(mode vimMode) { + if currentVimMode == mode { + // No change + return + } + + currentVimMode = mode + // Reset keyEvents + keyEvents = nil } func searchVisibleScreenForText(text string) []Coordinate { |