mirror of https://github.com/Hilbis/Hilbish
504 lines
11 KiB
Go
504 lines
11 KiB
Go
|
package readline
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"strconv"
|
||
|
)
|
||
|
|
||
|
// InputMode - The shell input mode
|
||
|
type InputMode int
|
||
|
|
||
|
const (
|
||
|
// Vim - Vim editing mode
|
||
|
Vim = iota
|
||
|
// Emacs - Emacs (classic) editing mode
|
||
|
Emacs
|
||
|
)
|
||
|
|
||
|
type ViMode int
|
||
|
|
||
|
const (
|
||
|
VimInsert ViMode = iota
|
||
|
VimReplaceOnce
|
||
|
VimReplaceMany
|
||
|
VimDelete
|
||
|
VimKeys
|
||
|
)
|
||
|
|
||
|
var (
|
||
|
VimInsertStr = "[I]"
|
||
|
VimReplaceOnceStr = "[V]"
|
||
|
VimReplaceManyStr = "[R]"
|
||
|
VimDeleteStr = "[D]"
|
||
|
VimKeysStr = "[N]"
|
||
|
)
|
||
|
|
||
|
var (
|
||
|
// registerFreeKeys - Some Vim keys don't act on/ aren't affected by registers,
|
||
|
// and using these keys will automatically cancel any active register.
|
||
|
// NOTE: Don't forget to update if you add Vim bindings !!
|
||
|
registerFreeKeys = []rune{'a', 'A', 'h', 'i', 'I', 'j', 'k', 'l', 'r', 'R', 'u', 'v', '$', '%', '[', ']'}
|
||
|
|
||
|
// validRegisterKeys - All valid register IDs (keys) for read/write Vim registers
|
||
|
validRegisterKeys = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789/-\""
|
||
|
)
|
||
|
|
||
|
// vi - Apply a key to a Vi action. Note that as in the rest of the code, all cursor movements
|
||
|
// have been moved away, and only the rl.pos is adjusted: when echoing the input line, the shell
|
||
|
// will compute the new cursor pos accordingly.
|
||
|
func (rl *Instance) vi(r rune) {
|
||
|
|
||
|
// Check if we are in register mode. If yes, and for some characters,
|
||
|
// we select the register and exit this func immediately.
|
||
|
if rl.registers.registerSelectWait {
|
||
|
for _, char := range validRegisterKeys {
|
||
|
if r == char {
|
||
|
rl.registers.setActiveRegister(r)
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// If we are on register mode and one is already selected,
|
||
|
// check if the key stroke to be evaluated is acting on it
|
||
|
// or not: if not, we cancel the active register now.
|
||
|
if rl.registers.onRegister {
|
||
|
for _, char := range registerFreeKeys {
|
||
|
if char == r {
|
||
|
rl.registers.resetRegister()
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Then evaluate the key.
|
||
|
switch r {
|
||
|
case 'a':
|
||
|
if len(rl.line) > 0 {
|
||
|
rl.pos++
|
||
|
}
|
||
|
rl.modeViMode = VimInsert
|
||
|
rl.viIteration = ""
|
||
|
rl.viUndoSkipAppend = true
|
||
|
|
||
|
case 'A':
|
||
|
if len(rl.line) > 0 {
|
||
|
rl.pos = len(rl.line)
|
||
|
}
|
||
|
rl.modeViMode = VimInsert
|
||
|
rl.viIteration = ""
|
||
|
rl.viUndoSkipAppend = true
|
||
|
|
||
|
case 'b':
|
||
|
if rl.viIsYanking {
|
||
|
vii := rl.getViIterations()
|
||
|
rl.saveToRegisterTokenize(tokeniseLine, rl.viJumpB, vii)
|
||
|
rl.viIsYanking = false
|
||
|
return
|
||
|
}
|
||
|
rl.viUndoSkipAppend = true
|
||
|
vii := rl.getViIterations()
|
||
|
for i := 1; i <= vii; i++ {
|
||
|
rl.moveCursorByAdjust(rl.viJumpB(tokeniseLine))
|
||
|
}
|
||
|
|
||
|
case 'B':
|
||
|
if rl.viIsYanking {
|
||
|
vii := rl.getViIterations()
|
||
|
rl.saveToRegisterTokenize(tokeniseSplitSpaces, rl.viJumpB, vii)
|
||
|
rl.viIsYanking = false
|
||
|
return
|
||
|
}
|
||
|
rl.viUndoSkipAppend = true
|
||
|
vii := rl.getViIterations()
|
||
|
for i := 1; i <= vii; i++ {
|
||
|
rl.moveCursorByAdjust(rl.viJumpB(tokeniseSplitSpaces))
|
||
|
}
|
||
|
|
||
|
case 'd':
|
||
|
rl.modeViMode = VimDelete
|
||
|
rl.viUndoSkipAppend = true
|
||
|
|
||
|
case 'D':
|
||
|
rl.saveBufToRegister(rl.line[rl.pos-1:])
|
||
|
rl.line = rl.line[:rl.pos]
|
||
|
// Only go back if there is an input
|
||
|
if len(rl.line) > 0 {
|
||
|
rl.pos--
|
||
|
}
|
||
|
rl.resetHelpers()
|
||
|
rl.updateHelpers()
|
||
|
rl.viIteration = ""
|
||
|
|
||
|
case 'e':
|
||
|
if rl.viIsYanking {
|
||
|
vii := rl.getViIterations()
|
||
|
rl.saveToRegisterTokenize(tokeniseLine, rl.viJumpE, vii)
|
||
|
rl.viIsYanking = false
|
||
|
return
|
||
|
}
|
||
|
|
||
|
rl.viUndoSkipAppend = true
|
||
|
vii := rl.getViIterations()
|
||
|
for i := 1; i <= vii; i++ {
|
||
|
rl.moveCursorByAdjust(rl.viJumpE(tokeniseLine))
|
||
|
}
|
||
|
|
||
|
case 'E':
|
||
|
if rl.viIsYanking {
|
||
|
vii := rl.getViIterations()
|
||
|
rl.saveToRegisterTokenize(tokeniseSplitSpaces, rl.viJumpE, vii)
|
||
|
rl.viIsYanking = false
|
||
|
return
|
||
|
}
|
||
|
|
||
|
rl.viUndoSkipAppend = true
|
||
|
vii := rl.getViIterations()
|
||
|
for i := 1; i <= vii; i++ {
|
||
|
rl.moveCursorByAdjust(rl.viJumpE(tokeniseSplitSpaces))
|
||
|
}
|
||
|
|
||
|
case 'h':
|
||
|
if rl.pos > 0 {
|
||
|
rl.pos--
|
||
|
}
|
||
|
rl.viUndoSkipAppend = true
|
||
|
|
||
|
case 'i':
|
||
|
rl.modeViMode = VimInsert
|
||
|
rl.viIteration = ""
|
||
|
rl.viUndoSkipAppend = true
|
||
|
rl.registers.resetRegister()
|
||
|
|
||
|
case 'I':
|
||
|
rl.modeViMode = VimInsert
|
||
|
rl.viIteration = ""
|
||
|
rl.viUndoSkipAppend = true
|
||
|
rl.pos = 0
|
||
|
|
||
|
case 'j':
|
||
|
// Set the main history as the one we navigate, by default
|
||
|
rl.mainHist = true
|
||
|
rl.walkHistory(-1)
|
||
|
case 'k':
|
||
|
// Set the main history as the one we navigate, by default
|
||
|
rl.mainHist = true
|
||
|
rl.walkHistory(1)
|
||
|
|
||
|
case 'l':
|
||
|
if (rl.modeViMode == VimInsert && rl.pos < len(rl.line)) ||
|
||
|
(rl.modeViMode != VimInsert && rl.pos < len(rl.line)-1) {
|
||
|
rl.pos++
|
||
|
}
|
||
|
rl.viUndoSkipAppend = true
|
||
|
|
||
|
case 'p':
|
||
|
// paste after the cursor position
|
||
|
rl.viUndoSkipAppend = true
|
||
|
rl.pos += 2
|
||
|
|
||
|
buffer := rl.pasteFromRegister()
|
||
|
vii := rl.getViIterations()
|
||
|
for i := 1; i <= vii; i++ {
|
||
|
rl.insert(buffer)
|
||
|
}
|
||
|
rl.pos--
|
||
|
|
||
|
case 'P':
|
||
|
// paste before
|
||
|
rl.viUndoSkipAppend = true
|
||
|
buffer := rl.pasteFromRegister()
|
||
|
vii := rl.getViIterations()
|
||
|
for i := 1; i <= vii; i++ {
|
||
|
rl.insert(buffer)
|
||
|
}
|
||
|
|
||
|
case 'r':
|
||
|
rl.modeViMode = VimReplaceOnce
|
||
|
rl.viIteration = ""
|
||
|
rl.viUndoSkipAppend = true
|
||
|
|
||
|
case 'R':
|
||
|
rl.modeViMode = VimReplaceMany
|
||
|
rl.viIteration = ""
|
||
|
rl.viUndoSkipAppend = true
|
||
|
|
||
|
case 'u':
|
||
|
rl.undoLast()
|
||
|
rl.viUndoSkipAppend = true
|
||
|
|
||
|
case 'v':
|
||
|
rl.clearHelpers()
|
||
|
var multiline []rune
|
||
|
if rl.GetMultiLine == nil {
|
||
|
multiline = rl.line
|
||
|
} else {
|
||
|
multiline = rl.GetMultiLine(rl.line)
|
||
|
}
|
||
|
|
||
|
// Keep the previous cursor position
|
||
|
prev := rl.pos
|
||
|
|
||
|
new, err := rl.StartEditorWithBuffer(multiline, "")
|
||
|
if err != nil || len(new) == 0 || string(new) == string(multiline) {
|
||
|
fmt.Println(err)
|
||
|
rl.viUndoSkipAppend = true
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// Clean the shell and put the new buffer, with adjusted pos if needed.
|
||
|
rl.clearLine()
|
||
|
rl.line = new
|
||
|
if prev > len(rl.line) {
|
||
|
rl.pos = len(rl.line) - 1
|
||
|
} else {
|
||
|
rl.pos = prev
|
||
|
}
|
||
|
|
||
|
case 'w':
|
||
|
// If we were not yanking
|
||
|
rl.viUndoSkipAppend = true
|
||
|
// If the input line is empty, we don't do anything
|
||
|
if rl.pos == 0 && len(rl.line) == 0 {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// If we were yanking, we forge the new yank buffer
|
||
|
// and return without moving the cursor.
|
||
|
if rl.viIsYanking {
|
||
|
vii := rl.getViIterations()
|
||
|
rl.saveToRegisterTokenize(tokeniseLine, rl.viJumpW, vii)
|
||
|
rl.viIsYanking = false
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// Else get iterations and move
|
||
|
vii := rl.getViIterations()
|
||
|
for i := 1; i <= vii; i++ {
|
||
|
rl.moveCursorByAdjust(rl.viJumpW(tokeniseLine))
|
||
|
}
|
||
|
|
||
|
case 'W':
|
||
|
// If the input line is empty, we don't do anything
|
||
|
if rl.pos == 0 && len(rl.line) == 0 {
|
||
|
return
|
||
|
}
|
||
|
rl.viUndoSkipAppend = true
|
||
|
|
||
|
if rl.viIsYanking {
|
||
|
vii := rl.getViIterations()
|
||
|
rl.saveToRegisterTokenize(tokeniseSplitSpaces, rl.viJumpW, vii)
|
||
|
rl.viIsYanking = false
|
||
|
return
|
||
|
}
|
||
|
vii := rl.getViIterations()
|
||
|
for i := 1; i <= vii; i++ {
|
||
|
rl.moveCursorByAdjust(rl.viJumpW(tokeniseSplitSpaces))
|
||
|
}
|
||
|
|
||
|
case 'x':
|
||
|
vii := rl.getViIterations()
|
||
|
|
||
|
// We might be on an active register, but not yanking...
|
||
|
rl.saveToRegister(vii)
|
||
|
|
||
|
// Delete the chars in the line anyway
|
||
|
for i := 1; i <= vii; i++ {
|
||
|
rl.deleteX()
|
||
|
}
|
||
|
if rl.pos == len(rl.line) && len(rl.line) > 0 {
|
||
|
rl.pos--
|
||
|
}
|
||
|
|
||
|
case 'y':
|
||
|
if rl.viIsYanking {
|
||
|
rl.saveBufToRegister(rl.line)
|
||
|
rl.viIsYanking = false
|
||
|
}
|
||
|
rl.viIsYanking = true
|
||
|
rl.viUndoSkipAppend = true
|
||
|
|
||
|
case 'Y':
|
||
|
rl.saveBufToRegister(rl.line)
|
||
|
rl.viUndoSkipAppend = true
|
||
|
|
||
|
case '[':
|
||
|
if rl.viIsYanking {
|
||
|
rl.saveToRegister(rl.viJumpPreviousBrace())
|
||
|
rl.viIsYanking = false
|
||
|
return
|
||
|
}
|
||
|
rl.viUndoSkipAppend = true
|
||
|
rl.moveCursorByAdjust(rl.viJumpPreviousBrace())
|
||
|
|
||
|
case ']':
|
||
|
if rl.viIsYanking {
|
||
|
rl.saveToRegister(rl.viJumpNextBrace())
|
||
|
rl.viIsYanking = false
|
||
|
return
|
||
|
}
|
||
|
rl.viUndoSkipAppend = true
|
||
|
rl.moveCursorByAdjust(rl.viJumpNextBrace())
|
||
|
|
||
|
case '$':
|
||
|
if rl.viIsYanking {
|
||
|
rl.saveBufToRegister(rl.line[rl.pos:])
|
||
|
rl.viIsYanking = false
|
||
|
return
|
||
|
}
|
||
|
rl.pos = len(rl.line)
|
||
|
rl.viUndoSkipAppend = true
|
||
|
|
||
|
case '%':
|
||
|
if rl.viIsYanking {
|
||
|
rl.saveToRegister(rl.viJumpBracket())
|
||
|
rl.viIsYanking = false
|
||
|
return
|
||
|
}
|
||
|
rl.viUndoSkipAppend = true
|
||
|
rl.moveCursorByAdjust(rl.viJumpBracket())
|
||
|
|
||
|
case '"':
|
||
|
// We might be on a register already, so reset it,
|
||
|
// and then wait again for a new register ID.
|
||
|
if rl.registers.onRegister {
|
||
|
rl.registers.resetRegister()
|
||
|
}
|
||
|
rl.registers.registerSelectWait = true
|
||
|
|
||
|
default:
|
||
|
if r <= '9' && '0' <= r {
|
||
|
rl.viIteration += string(r)
|
||
|
}
|
||
|
rl.viUndoSkipAppend = true
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (rl *Instance) getViIterations() int {
|
||
|
i, _ := strconv.Atoi(rl.viIteration)
|
||
|
if i < 1 {
|
||
|
i = 1
|
||
|
}
|
||
|
rl.viIteration = ""
|
||
|
return i
|
||
|
}
|
||
|
|
||
|
func (rl *Instance) refreshVimStatus() {
|
||
|
rl.ViModeCallback(rl.modeViMode)
|
||
|
rl.computePrompt()
|
||
|
rl.updateHelpers()
|
||
|
}
|
||
|
|
||
|
// viHintMessage - lmorg's way of showing Vim status is to overwrite the hint.
|
||
|
// Currently not used, as there is a possibility to show the current Vim mode in the prompt.
|
||
|
func (rl *Instance) viHintMessage() {
|
||
|
switch rl.modeViMode {
|
||
|
case VimKeys:
|
||
|
rl.hintText = []rune("-- VIM KEYS -- (press `i` to return to normal editing mode)")
|
||
|
case VimInsert:
|
||
|
rl.hintText = []rune("-- INSERT --")
|
||
|
case VimReplaceOnce:
|
||
|
rl.hintText = []rune("-- REPLACE CHARACTER --")
|
||
|
case VimReplaceMany:
|
||
|
rl.hintText = []rune("-- REPLACE --")
|
||
|
case VimDelete:
|
||
|
rl.hintText = []rune("-- DELETE --")
|
||
|
default:
|
||
|
rl.getHintText()
|
||
|
}
|
||
|
|
||
|
rl.clearHelpers()
|
||
|
rl.renderHelpers()
|
||
|
}
|
||
|
|
||
|
func (rl *Instance) viJumpB(tokeniser tokeniser) (adjust int) {
|
||
|
split, index, pos := tokeniser(rl.line, rl.pos)
|
||
|
switch {
|
||
|
case len(split) == 0:
|
||
|
return
|
||
|
case index == 0 && pos == 0:
|
||
|
return
|
||
|
case pos == 0:
|
||
|
adjust = len(split[index-1])
|
||
|
default:
|
||
|
adjust = pos
|
||
|
}
|
||
|
return adjust * -1
|
||
|
}
|
||
|
|
||
|
func (rl *Instance) viJumpE(tokeniser tokeniser) (adjust int) {
|
||
|
split, index, pos := tokeniser(rl.line, rl.pos)
|
||
|
if len(split) == 0 {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
word := rTrimWhiteSpace(split[index])
|
||
|
|
||
|
switch {
|
||
|
case len(split) == 0:
|
||
|
return
|
||
|
case index == len(split)-1 && pos >= len(word)-1:
|
||
|
return
|
||
|
case pos >= len(word)-1:
|
||
|
word = rTrimWhiteSpace(split[index+1])
|
||
|
adjust = len(split[index]) - pos
|
||
|
adjust += len(word) - 1
|
||
|
default:
|
||
|
adjust = len(word) - pos - 1
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func (rl *Instance) viJumpW(tokeniser tokeniser) (adjust int) {
|
||
|
split, index, pos := tokeniser(rl.line, rl.pos)
|
||
|
switch {
|
||
|
case len(split) == 0:
|
||
|
return
|
||
|
case index+1 == len(split):
|
||
|
adjust = len(rl.line) - rl.pos
|
||
|
default:
|
||
|
adjust = len(split[index]) - pos
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func (rl *Instance) viJumpPreviousBrace() (adjust int) {
|
||
|
if rl.pos == 0 {
|
||
|
return 0
|
||
|
}
|
||
|
|
||
|
for i := rl.pos - 1; i != 0; i-- {
|
||
|
if rl.line[i] == '{' {
|
||
|
return i - rl.pos
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return 0
|
||
|
}
|
||
|
|
||
|
func (rl *Instance) viJumpNextBrace() (adjust int) {
|
||
|
if rl.pos >= len(rl.line)-1 {
|
||
|
return 0
|
||
|
}
|
||
|
|
||
|
for i := rl.pos + 1; i < len(rl.line); i++ {
|
||
|
if rl.line[i] == '{' {
|
||
|
return i - rl.pos
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return 0
|
||
|
}
|
||
|
|
||
|
func (rl *Instance) viJumpBracket() (adjust int) {
|
||
|
split, index, pos := tokeniseBrackets(rl.line, rl.pos)
|
||
|
switch {
|
||
|
case len(split) == 0:
|
||
|
return
|
||
|
case pos == 0:
|
||
|
adjust = len(split[index])
|
||
|
default:
|
||
|
adjust = pos * -1
|
||
|
}
|
||
|
return
|
||
|
}
|