2022-03-13 17:48:49 +00:00
|
|
|
package readline
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
2022-04-19 14:30:04 +00:00
|
|
|
"errors"
|
2022-03-13 17:48:49 +00:00
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
"regexp"
|
2022-04-08 14:46:25 +00:00
|
|
|
"syscall"
|
2022-03-13 17:48:49 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
var rxMultiline = regexp.MustCompile(`[\r\n]+`)
|
|
|
|
|
|
|
|
// Readline displays the readline prompt.
|
|
|
|
// It will return a string (user entered data) or an error.
|
|
|
|
func (rl *Instance) Readline() (string, error) {
|
|
|
|
fd := int(os.Stdin.Fd())
|
|
|
|
state, err := MakeRaw(fd)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
defer Restore(fd, state)
|
|
|
|
|
|
|
|
// In Vim mode, we always start in Input mode. The prompt needs this.
|
|
|
|
rl.modeViMode = VimInsert
|
|
|
|
|
|
|
|
// Prompt Init
|
|
|
|
// Here we have to either print prompt
|
|
|
|
// and return new line (multiline)
|
|
|
|
if rl.Multiline {
|
|
|
|
fmt.Println(rl.mainPrompt)
|
|
|
|
}
|
|
|
|
rl.stillOnRefresh = false
|
|
|
|
rl.computePrompt() // initialise the prompt for first print
|
|
|
|
|
|
|
|
// Line Init & Cursor
|
|
|
|
rl.line = []rune{}
|
|
|
|
rl.currentComp = []rune{} // No virtual completion yet
|
|
|
|
rl.lineComp = []rune{} // So no virtual line either
|
|
|
|
rl.modeViMode = VimInsert
|
|
|
|
rl.pos = 0
|
|
|
|
rl.posY = 0
|
2022-03-15 20:26:56 +00:00
|
|
|
rl.tcPrefix = ""
|
2022-03-13 17:48:49 +00:00
|
|
|
|
2022-03-26 21:34:09 +00:00
|
|
|
// Completion && infos init
|
|
|
|
rl.resetInfoText()
|
2022-03-13 17:48:49 +00:00
|
|
|
rl.resetTabCompletion()
|
2022-03-26 21:34:09 +00:00
|
|
|
rl.getInfoText()
|
2022-03-13 17:48:49 +00:00
|
|
|
|
|
|
|
// History Init
|
|
|
|
// We need this set to the last command, so that we can access it quickly
|
2022-11-30 17:28:21 +00:00
|
|
|
rl.histOffset = 0
|
2022-03-13 17:48:49 +00:00
|
|
|
rl.viUndoHistory = []undoItem{{line: "", pos: 0}}
|
|
|
|
|
|
|
|
// Multisplit
|
|
|
|
if len(rl.multisplit) > 0 {
|
|
|
|
r := []rune(rl.multisplit[0])
|
2022-06-20 19:35:25 +00:00
|
|
|
if len(r) >= 1 {
|
|
|
|
rl.editorInput(r)
|
|
|
|
}
|
|
|
|
|
2022-03-13 17:48:49 +00:00
|
|
|
rl.carridgeReturn()
|
|
|
|
if len(rl.multisplit) > 1 {
|
|
|
|
rl.multisplit = rl.multisplit[1:]
|
|
|
|
} else {
|
|
|
|
rl.multisplit = []string{}
|
|
|
|
}
|
|
|
|
return string(rl.line), nil
|
|
|
|
}
|
|
|
|
|
2022-03-26 21:34:09 +00:00
|
|
|
// Finally, print any info or completions
|
2022-03-13 17:48:49 +00:00
|
|
|
// if the TabCompletion engines so desires
|
|
|
|
rl.renderHelpers()
|
|
|
|
|
|
|
|
// Start handling keystrokes. Classified by subject for most.
|
|
|
|
for {
|
|
|
|
rl.viUndoSkipAppend = false
|
|
|
|
b := make([]byte, 1024)
|
|
|
|
var i int
|
|
|
|
|
|
|
|
if !rl.skipStdinRead {
|
|
|
|
var err error
|
|
|
|
i, err = os.Stdin.Read(b)
|
|
|
|
if err != nil {
|
2022-04-19 14:30:04 +00:00
|
|
|
if errors.Is(err, syscall.EAGAIN) {
|
2022-04-08 14:46:25 +00:00
|
|
|
err = syscall.SetNonblock(syscall.Stdin, false)
|
|
|
|
if err == nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
2022-03-13 17:48:49 +00:00
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
rl.skipStdinRead = false
|
|
|
|
r := []rune(string(b))
|
2022-07-13 19:35:33 +00:00
|
|
|
if rl.RawInputCallback != nil {
|
|
|
|
rl.RawInputCallback(r[:i])
|
|
|
|
}
|
2022-03-13 17:48:49 +00:00
|
|
|
|
|
|
|
if isMultiline(r[:i]) || len(rl.multiline) > 0 {
|
|
|
|
rl.multiline = append(rl.multiline, b[:i]...)
|
|
|
|
if i == len(b) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if !rl.allowMultiline(rl.multiline) {
|
|
|
|
rl.multiline = []byte{}
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
s := string(rl.multiline)
|
|
|
|
rl.multisplit = rxMultiline.Split(s, -1)
|
|
|
|
|
|
|
|
r = []rune(rl.multisplit[0])
|
|
|
|
rl.modeViMode = VimInsert
|
|
|
|
rl.editorInput(r)
|
|
|
|
rl.carridgeReturn()
|
|
|
|
rl.multiline = []byte{}
|
|
|
|
if len(rl.multisplit) > 1 {
|
|
|
|
rl.multisplit = rl.multisplit[1:]
|
|
|
|
} else {
|
|
|
|
rl.multisplit = []string{}
|
|
|
|
}
|
|
|
|
return string(rl.line), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
s := string(r[:i])
|
|
|
|
if rl.evtKeyPress[s] != nil {
|
|
|
|
rl.clearHelpers()
|
|
|
|
|
|
|
|
ret := rl.evtKeyPress[s](s, rl.line, rl.pos)
|
|
|
|
|
|
|
|
rl.clearLine()
|
|
|
|
rl.line = append(ret.NewLine, []rune{}...)
|
|
|
|
rl.updateHelpers() // rl.echo
|
|
|
|
rl.pos = ret.NewPos
|
|
|
|
|
|
|
|
if ret.ClearHelpers {
|
|
|
|
rl.resetHelpers()
|
|
|
|
} else {
|
|
|
|
rl.updateHelpers()
|
|
|
|
}
|
|
|
|
|
2022-03-26 21:34:09 +00:00
|
|
|
if len(ret.InfoText) > 0 {
|
|
|
|
rl.infoText = ret.InfoText
|
2022-03-13 17:48:49 +00:00
|
|
|
rl.clearHelpers()
|
|
|
|
rl.renderHelpers()
|
|
|
|
}
|
|
|
|
if !ret.ForwardKey {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if ret.CloseReadline {
|
|
|
|
rl.clearHelpers()
|
|
|
|
return string(rl.line), nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Before anything: we can never be both in modeTabCompletion and compConfirmWait,
|
|
|
|
// because we need to confirm before entering completion. If both are true, there
|
|
|
|
// is a problem (at least, the user has escaped the confirm hint some way).
|
|
|
|
if (rl.modeTabCompletion && rl.searchMode != HistoryFind) && rl.compConfirmWait {
|
|
|
|
rl.compConfirmWait = false
|
|
|
|
}
|
|
|
|
|
|
|
|
switch b[0] {
|
|
|
|
// Errors & Returns --------------------------------------------------------------------------------
|
|
|
|
case charCtrlC:
|
|
|
|
if rl.modeTabCompletion {
|
|
|
|
rl.resetVirtualComp(true)
|
|
|
|
rl.resetHelpers()
|
|
|
|
rl.renderHelpers()
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
rl.clearHelpers()
|
|
|
|
return "", CtrlC
|
|
|
|
|
2022-04-13 01:02:01 +00:00
|
|
|
case charEOF: // ctrl d
|
|
|
|
if len(rl.line) == 0 {
|
|
|
|
rl.clearHelpers()
|
|
|
|
return "", EOF
|
|
|
|
}
|
|
|
|
if rl.modeTabFind {
|
|
|
|
rl.backspaceTabFind()
|
|
|
|
} else {
|
|
|
|
if (rl.pos < len(rl.line)) {
|
|
|
|
rl.deleteBackspace(true)
|
|
|
|
}
|
|
|
|
}
|
2022-03-13 17:48:49 +00:00
|
|
|
|
|
|
|
// Clear screen
|
|
|
|
case charCtrlL:
|
|
|
|
print(seqClearScreen)
|
|
|
|
print(seqCursorTopLeft)
|
|
|
|
if rl.Multiline {
|
|
|
|
fmt.Println(rl.mainPrompt)
|
|
|
|
}
|
|
|
|
print(seqClearScreenBelow)
|
|
|
|
|
2022-03-26 21:34:09 +00:00
|
|
|
rl.resetInfoText()
|
|
|
|
rl.getInfoText()
|
2022-03-13 17:48:49 +00:00
|
|
|
rl.renderHelpers()
|
|
|
|
|
|
|
|
// Line Editing ------------------------------------------------------------------------------------
|
|
|
|
case charCtrlU:
|
|
|
|
if rl.modeTabCompletion {
|
|
|
|
rl.resetVirtualComp(true)
|
|
|
|
}
|
|
|
|
// Delete everything from the beginning of the line to the cursor position
|
|
|
|
rl.saveBufToRegister(rl.line[:rl.pos])
|
|
|
|
rl.deleteToBeginning()
|
|
|
|
rl.resetHelpers()
|
|
|
|
rl.updateHelpers()
|
|
|
|
|
2022-03-27 03:43:30 +00:00
|
|
|
case charCtrlK:
|
|
|
|
if rl.modeTabCompletion {
|
|
|
|
rl.resetVirtualComp(true)
|
|
|
|
}
|
|
|
|
// Delete everything after the cursor position
|
|
|
|
rl.saveBufToRegister(rl.line[rl.pos:])
|
|
|
|
rl.deleteToEnd()
|
|
|
|
rl.resetHelpers()
|
|
|
|
rl.updateHelpers()
|
|
|
|
|
2022-03-13 17:48:49 +00:00
|
|
|
case charBackspace, charBackspace2:
|
|
|
|
// When currently in history completion, we refresh and automatically
|
|
|
|
// insert the first (filtered) candidate, virtually
|
|
|
|
if rl.modeAutoFind && rl.searchMode == HistoryFind {
|
|
|
|
rl.resetVirtualComp(true)
|
|
|
|
rl.backspaceTabFind()
|
|
|
|
|
|
|
|
// Then update the printing, with the new candidate
|
|
|
|
rl.updateVirtualComp()
|
|
|
|
rl.renderHelpers()
|
|
|
|
rl.viUndoSkipAppend = true
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// Normal completion search does only refresh the search pattern and the comps
|
|
|
|
if rl.modeTabFind || rl.modeAutoFind {
|
2022-11-25 23:08:38 +00:00
|
|
|
rl.resetVirtualComp(false)
|
2022-03-13 17:48:49 +00:00
|
|
|
rl.backspaceTabFind()
|
2022-11-25 23:08:38 +00:00
|
|
|
rl.renderHelpers()
|
2022-03-13 17:48:49 +00:00
|
|
|
rl.viUndoSkipAppend = true
|
|
|
|
} else {
|
|
|
|
// Always cancel any virtual completion
|
|
|
|
rl.resetVirtualComp(false)
|
|
|
|
|
|
|
|
// Vim mode has different behaviors
|
|
|
|
if rl.InputMode == Vim {
|
|
|
|
if rl.modeViMode == VimInsert {
|
2022-03-24 01:11:24 +00:00
|
|
|
rl.backspace(false)
|
2022-03-14 00:00:11 +00:00
|
|
|
} else if rl.pos != 0 {
|
2022-03-13 17:48:49 +00:00
|
|
|
rl.pos--
|
|
|
|
}
|
|
|
|
rl.renderHelpers()
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// Else emacs deletes a character
|
2022-03-24 01:11:24 +00:00
|
|
|
rl.backspace(false)
|
2022-03-13 17:48:49 +00:00
|
|
|
rl.renderHelpers()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Emacs Bindings ----------------------------------------------------------------------------------
|
|
|
|
case charCtrlW:
|
|
|
|
if rl.modeTabCompletion {
|
|
|
|
rl.resetVirtualComp(false)
|
|
|
|
}
|
|
|
|
// This is only available in Insert mode
|
|
|
|
if rl.modeViMode != VimInsert {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
rl.saveToRegister(rl.viJumpB(tokeniseLine))
|
|
|
|
rl.viDeleteByAdjust(rl.viJumpB(tokeniseLine))
|
|
|
|
rl.updateHelpers()
|
|
|
|
|
|
|
|
case charCtrlY:
|
|
|
|
if rl.modeTabCompletion {
|
|
|
|
rl.resetVirtualComp(false)
|
|
|
|
}
|
|
|
|
// paste after the cursor position
|
|
|
|
rl.viUndoSkipAppend = true
|
|
|
|
buffer := rl.pasteFromRegister()
|
|
|
|
rl.insert(buffer)
|
|
|
|
rl.updateHelpers()
|
|
|
|
|
|
|
|
case charCtrlE:
|
|
|
|
if rl.modeTabCompletion {
|
|
|
|
rl.resetVirtualComp(false)
|
|
|
|
}
|
|
|
|
// This is only available in Insert mode
|
|
|
|
if rl.modeViMode != VimInsert {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if len(rl.line) > 0 {
|
|
|
|
rl.pos = len(rl.line)
|
|
|
|
}
|
|
|
|
rl.viUndoSkipAppend = true
|
|
|
|
rl.updateHelpers()
|
|
|
|
|
|
|
|
case charCtrlA:
|
|
|
|
if rl.modeTabCompletion {
|
|
|
|
rl.resetVirtualComp(false)
|
|
|
|
}
|
|
|
|
// This is only available in Insert mode
|
|
|
|
if rl.modeViMode != VimInsert {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
rl.viUndoSkipAppend = true
|
|
|
|
rl.pos = 0
|
|
|
|
rl.updateHelpers()
|
|
|
|
|
|
|
|
// Command History ---------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
// NOTE: The alternative history source is triggered by Alt+r,
|
|
|
|
// but because this is a sequence, the alternative history code
|
|
|
|
// trigger is in the below rl.escapeSeq(r) function.
|
|
|
|
case charCtrlR:
|
|
|
|
rl.resetVirtualComp(false)
|
|
|
|
// For some modes only, if we are in vim Keys mode,
|
|
|
|
// we toogle back to insert mode. For others, we return
|
|
|
|
// without getting the completions.
|
|
|
|
if rl.modeViMode != VimInsert {
|
|
|
|
rl.modeViMode = VimInsert
|
|
|
|
rl.computePrompt()
|
|
|
|
}
|
|
|
|
|
|
|
|
rl.mainHist = true // false before
|
|
|
|
rl.searchMode = HistoryFind
|
|
|
|
rl.modeAutoFind = true
|
|
|
|
rl.modeTabCompletion = true
|
|
|
|
|
|
|
|
rl.modeTabFind = true
|
|
|
|
rl.updateTabFind([]rune{})
|
2022-11-26 00:14:29 +00:00
|
|
|
rl.updateVirtualComp()
|
|
|
|
rl.renderHelpers()
|
2022-03-13 17:48:49 +00:00
|
|
|
rl.viUndoSkipAppend = true
|
|
|
|
|
|
|
|
// Tab Completion & Completion Search ---------------------------------------------------------------
|
|
|
|
case charTab:
|
|
|
|
// The user cannot show completions if currently in Vim Normal mode
|
|
|
|
if rl.InputMode == Vim && rl.modeViMode != VimInsert {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we have asked for completions, already printed, and we want to move selection.
|
|
|
|
if rl.modeTabCompletion && !rl.compConfirmWait {
|
|
|
|
rl.tabCompletionSelect = true
|
|
|
|
rl.moveTabCompletionHighlight(1, 0)
|
|
|
|
rl.updateVirtualComp()
|
|
|
|
rl.renderHelpers()
|
|
|
|
rl.viUndoSkipAppend = true
|
|
|
|
} else {
|
|
|
|
// Else we might be asked to confirm printing (if too many suggestions), or not.
|
|
|
|
rl.getTabCompletion()
|
|
|
|
|
|
|
|
// If too many completions and no yet confirmed, ask user for completion
|
|
|
|
// comps, lines := rl.getCompletionCount()
|
|
|
|
// if ((lines > GetTermLength()) || (lines > rl.MaxTabCompleterRows)) && !rl.compConfirmWait {
|
|
|
|
// sentence := fmt.Sprintf("%s show all %d completions (%d lines) ? tab to confirm",
|
|
|
|
// FOREWHITE, comps, lines)
|
|
|
|
// rl.promptCompletionConfirm(sentence)
|
|
|
|
// continue
|
|
|
|
// }
|
|
|
|
|
|
|
|
rl.compConfirmWait = false
|
|
|
|
rl.modeTabCompletion = true
|
|
|
|
|
|
|
|
// Also here, if only one candidate is available, automatically
|
|
|
|
// insert it and don't bother printing completions.
|
|
|
|
// Quit the tab completion mode to avoid asking to the user
|
|
|
|
// to press Enter twice to actually run the command.
|
|
|
|
if rl.hasOneCandidate() {
|
|
|
|
rl.insertCandidate()
|
|
|
|
|
|
|
|
// Refresh first, and then quit the completion mode
|
|
|
|
rl.updateHelpers() // REDUNDANT WITH getTabCompletion()
|
|
|
|
rl.viUndoSkipAppend = true
|
|
|
|
rl.resetTabCompletion()
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
rl.updateHelpers() // REDUNDANT WITH getTabCompletion()
|
|
|
|
rl.viUndoSkipAppend = true
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
case charCtrlF:
|
|
|
|
rl.resetVirtualComp(true)
|
|
|
|
|
|
|
|
if !rl.modeTabCompletion {
|
|
|
|
rl.modeTabCompletion = true
|
|
|
|
}
|
|
|
|
|
|
|
|
if rl.compConfirmWait {
|
|
|
|
rl.resetHelpers()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Both these settings apply to when we already
|
|
|
|
// are in completion mode and when we are not.
|
|
|
|
rl.searchMode = CompletionFind
|
|
|
|
rl.modeAutoFind = true
|
|
|
|
|
|
|
|
// Switch from history to completion search
|
|
|
|
if rl.modeTabCompletion && rl.searchMode == HistoryFind {
|
|
|
|
rl.searchMode = CompletionFind
|
|
|
|
}
|
|
|
|
|
|
|
|
rl.updateTabFind([]rune{})
|
|
|
|
rl.viUndoSkipAppend = true
|
|
|
|
|
|
|
|
case charCtrlG:
|
|
|
|
if rl.modeAutoFind && rl.searchMode == HistoryFind {
|
|
|
|
rl.resetVirtualComp(false)
|
|
|
|
rl.resetTabFind()
|
|
|
|
rl.resetHelpers()
|
|
|
|
rl.renderHelpers()
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if rl.modeAutoFind {
|
|
|
|
rl.resetTabFind()
|
|
|
|
rl.resetHelpers()
|
|
|
|
rl.renderHelpers()
|
|
|
|
}
|
|
|
|
|
2022-04-13 20:58:36 +00:00
|
|
|
case charCtrlUnderscore:
|
|
|
|
rl.undoLast()
|
|
|
|
rl.viUndoSkipAppend = true
|
|
|
|
|
2022-03-13 17:48:49 +00:00
|
|
|
case '\r':
|
|
|
|
fallthrough
|
|
|
|
case '\n':
|
|
|
|
if rl.modeTabCompletion {
|
|
|
|
cur := rl.getCurrentGroup()
|
|
|
|
|
|
|
|
// Check that there is a group indeed, as we might have no completions.
|
|
|
|
if cur == nil {
|
|
|
|
rl.clearHelpers()
|
|
|
|
rl.resetTabCompletion()
|
|
|
|
rl.renderHelpers()
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// IF we have a prefix and completions printed, but no candidate
|
|
|
|
// (in which case the completion is ""), we immediately return.
|
|
|
|
completion := cur.getCurrentCell(rl)
|
|
|
|
prefix := len(rl.tcPrefix)
|
|
|
|
if prefix > len(completion) {
|
|
|
|
rl.carridgeReturn()
|
|
|
|
return string(rl.line), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Else, we insert the completion candidate in the real input line.
|
|
|
|
// By default we add a space, unless completion group asks otherwise.
|
|
|
|
rl.compAddSpace = true
|
|
|
|
rl.resetVirtualComp(false)
|
|
|
|
|
|
|
|
// If we were in history completion, immediately execute the line.
|
|
|
|
if rl.modeAutoFind && rl.searchMode == HistoryFind {
|
|
|
|
rl.carridgeReturn()
|
|
|
|
return string(rl.line), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Reset completions and update input line
|
|
|
|
rl.clearHelpers()
|
|
|
|
rl.resetTabCompletion()
|
|
|
|
rl.renderHelpers()
|
|
|
|
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
rl.carridgeReturn()
|
|
|
|
return string(rl.line), nil
|
|
|
|
|
|
|
|
// Vim --------------------------------------------------------------------------------------
|
|
|
|
case charEscape:
|
|
|
|
|
|
|
|
// If we were waiting for completion confirm, abort
|
|
|
|
if rl.compConfirmWait {
|
|
|
|
rl.compConfirmWait = false
|
|
|
|
rl.renderHelpers()
|
|
|
|
}
|
|
|
|
|
|
|
|
// We always refresh the completion candidates, except if we are currently
|
|
|
|
// cycling through them, because then it would just append the candidate.
|
|
|
|
if rl.modeTabCompletion {
|
|
|
|
if string(r[:i]) != seqShiftTab &&
|
|
|
|
string(r[:i]) != seqForwards && string(r[:i]) != seqBackwards &&
|
|
|
|
string(r[:i]) != seqUp && string(r[:i]) != seqDown {
|
2022-11-26 00:14:29 +00:00
|
|
|
// basically only applies except on 1st ctrl r open
|
|
|
|
// so if we have not explicitly selected something
|
|
|
|
// (tabCompletionSelect is false) drop virtual completion
|
|
|
|
rl.resetVirtualComp(!rl.tabCompletionSelect)
|
2022-03-13 17:48:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Once helpers of all sorts are cleared, we can process
|
|
|
|
// the change of input modes, etc.
|
|
|
|
rl.escapeSeq(r[:i])
|
|
|
|
|
|
|
|
// Dispatch --------------------------------------------------------------------------------------
|
|
|
|
default:
|
|
|
|
|
|
|
|
// If we were waiting for completion confirm, abort
|
|
|
|
if rl.compConfirmWait {
|
|
|
|
rl.resetVirtualComp(false)
|
|
|
|
rl.compConfirmWait = false
|
|
|
|
rl.renderHelpers()
|
|
|
|
}
|
|
|
|
|
|
|
|
// When currently in history completion, we refresh and automatically
|
|
|
|
// insert the first (filtered) candidate, virtually
|
|
|
|
if rl.modeAutoFind && rl.searchMode == HistoryFind {
|
|
|
|
rl.resetVirtualComp(true)
|
|
|
|
rl.updateTabFind(r[:i])
|
|
|
|
rl.updateVirtualComp()
|
|
|
|
rl.renderHelpers()
|
|
|
|
rl.viUndoSkipAppend = true
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// Not sure that CompletionFind is useful, nor one of the other two
|
|
|
|
if rl.modeAutoFind || rl.modeTabFind {
|
|
|
|
rl.resetVirtualComp(false)
|
|
|
|
rl.updateTabFind(r[:i])
|
2022-11-25 23:08:38 +00:00
|
|
|
rl.renderHelpers()
|
2022-03-13 17:48:49 +00:00
|
|
|
rl.viUndoSkipAppend = true
|
2022-11-25 23:08:38 +00:00
|
|
|
continue
|
2022-03-13 17:48:49 +00:00
|
|
|
} else {
|
|
|
|
rl.resetVirtualComp(false)
|
|
|
|
rl.editorInput(r[:i])
|
|
|
|
if len(rl.multiline) > 0 && rl.modeViMode == VimKeys {
|
|
|
|
rl.skipStdinRead = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
rl.clearHelpers()
|
|
|
|
}
|
|
|
|
|
|
|
|
rl.undoAppendHistory()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// editorInput is an unexported function used to determine what mode of text
|
|
|
|
// entry readline is currently configured for and then update the line entries
|
|
|
|
// accordingly.
|
|
|
|
func (rl *Instance) editorInput(r []rune) {
|
|
|
|
switch rl.modeViMode {
|
|
|
|
case VimKeys:
|
|
|
|
rl.vi(r[0])
|
|
|
|
rl.refreshVimStatus()
|
|
|
|
|
|
|
|
case VimDelete:
|
|
|
|
rl.viDelete(r[0])
|
|
|
|
rl.refreshVimStatus()
|
|
|
|
|
|
|
|
case VimReplaceOnce:
|
|
|
|
rl.modeViMode = VimKeys
|
|
|
|
rl.deleteX()
|
|
|
|
rl.insert([]rune{r[0]})
|
|
|
|
rl.refreshVimStatus()
|
|
|
|
|
|
|
|
case VimReplaceMany:
|
|
|
|
for _, char := range r {
|
2022-03-24 01:10:04 +00:00
|
|
|
if rl.pos != len(rl.line) {
|
|
|
|
rl.deleteX()
|
|
|
|
}
|
2022-03-13 17:48:49 +00:00
|
|
|
rl.insert([]rune{char})
|
|
|
|
}
|
|
|
|
rl.refreshVimStatus()
|
|
|
|
|
|
|
|
default:
|
2022-04-13 23:36:18 +00:00
|
|
|
// Don't insert control keys
|
|
|
|
if r[0] >= 1 && r[0] <= 31 {
|
2022-03-13 17:48:49 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
// We reset the history nav counter each time we come here:
|
|
|
|
// We don't need it when inserting text.
|
|
|
|
rl.histNavIdx = 0
|
|
|
|
rl.insert(r)
|
2022-03-26 21:34:09 +00:00
|
|
|
rl.writeHintText()
|
2022-03-13 17:48:49 +00:00
|
|
|
}
|
|
|
|
|
2022-04-13 14:13:46 +00:00
|
|
|
rl.echoRightPrompt()
|
|
|
|
|
2022-03-13 17:48:49 +00:00
|
|
|
if len(rl.multisplit) == 0 {
|
|
|
|
rl.syntaxCompletion()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// viEscape - In case th user is using Vim input, and the escape sequence has not
|
|
|
|
// been handled by other cases, we dispatch it to Vim and handle a few cases here.
|
|
|
|
func (rl *Instance) viEscape(r []rune) {
|
|
|
|
|
|
|
|
// Sometimes the escape sequence is interleaved with another one,
|
|
|
|
// but key strokes might be in the wrong order, so we double check
|
2022-03-13 18:26:44 +00:00
|
|
|
// and escape the mode only if needed.
|
2022-03-13 17:48:49 +00:00
|
|
|
if rl.modeViMode == VimInsert && len(r) == 1 && r[0] == 27 {
|
|
|
|
if len(rl.line) > 0 && rl.pos > 0 {
|
|
|
|
rl.pos--
|
|
|
|
}
|
|
|
|
rl.modeViMode = VimKeys
|
|
|
|
rl.viIteration = ""
|
|
|
|
rl.refreshVimStatus()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (rl *Instance) escapeSeq(r []rune) {
|
|
|
|
switch string(r) {
|
|
|
|
// Vim escape sequences & dispatching --------------------------------------------------------
|
|
|
|
case string(charEscape):
|
|
|
|
switch {
|
|
|
|
case rl.modeAutoFind:
|
2022-11-26 00:14:29 +00:00
|
|
|
rl.resetVirtualComp(true)
|
2022-03-13 17:48:49 +00:00
|
|
|
rl.resetTabFind()
|
|
|
|
rl.clearHelpers()
|
|
|
|
rl.resetTabCompletion()
|
|
|
|
rl.resetHelpers()
|
|
|
|
rl.renderHelpers()
|
|
|
|
|
|
|
|
case rl.modeTabFind:
|
2022-11-26 00:14:29 +00:00
|
|
|
rl.resetVirtualComp(true)
|
2022-03-13 17:48:49 +00:00
|
|
|
rl.resetTabFind()
|
|
|
|
rl.resetTabCompletion()
|
|
|
|
|
|
|
|
case rl.modeTabCompletion:
|
|
|
|
rl.clearHelpers()
|
|
|
|
rl.resetTabCompletion()
|
|
|
|
rl.renderHelpers()
|
|
|
|
|
|
|
|
default:
|
|
|
|
// No matter the input mode, we exit
|
|
|
|
// any completion confirm if there's one.
|
|
|
|
if rl.compConfirmWait {
|
|
|
|
rl.compConfirmWait = false
|
|
|
|
rl.clearHelpers()
|
|
|
|
rl.renderHelpers()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we are in Vim mode, the escape key has its usage.
|
|
|
|
// Otherwise in emacs mode the escape key does nothing.
|
|
|
|
if rl.InputMode == Vim {
|
|
|
|
rl.viEscape(r)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// This refreshed and actually prints the new Vim status
|
|
|
|
// if we have indeed change the Vim mode.
|
|
|
|
rl.clearHelpers()
|
|
|
|
rl.renderHelpers()
|
|
|
|
|
|
|
|
}
|
|
|
|
rl.viUndoSkipAppend = true
|
|
|
|
|
|
|
|
// Tab completion movements ------------------------------------------------------------------
|
|
|
|
case seqShiftTab:
|
|
|
|
if rl.modeTabCompletion && !rl.compConfirmWait {
|
|
|
|
rl.tabCompletionReverse = true
|
|
|
|
rl.moveTabCompletionHighlight(-1, 0)
|
|
|
|
rl.updateVirtualComp()
|
|
|
|
rl.tabCompletionReverse = false
|
|
|
|
rl.renderHelpers()
|
|
|
|
rl.viUndoSkipAppend = true
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
case seqUp:
|
|
|
|
if rl.modeTabCompletion {
|
|
|
|
rl.tabCompletionSelect = true
|
|
|
|
rl.tabCompletionReverse = true
|
|
|
|
rl.moveTabCompletionHighlight(-1, 0)
|
|
|
|
rl.updateVirtualComp()
|
|
|
|
rl.tabCompletionReverse = false
|
|
|
|
rl.renderHelpers()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
rl.mainHist = true
|
|
|
|
rl.walkHistory(1)
|
2022-03-21 10:47:14 +00:00
|
|
|
moveCursorForwards(len(rl.line) - rl.pos)
|
|
|
|
rl.pos = len(rl.line)
|
2022-03-13 17:48:49 +00:00
|
|
|
|
|
|
|
case seqDown:
|
|
|
|
if rl.modeTabCompletion {
|
|
|
|
rl.tabCompletionSelect = true
|
|
|
|
rl.moveTabCompletionHighlight(1, 0)
|
|
|
|
rl.updateVirtualComp()
|
|
|
|
rl.renderHelpers()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
rl.mainHist = true
|
|
|
|
rl.walkHistory(-1)
|
2022-03-21 10:47:14 +00:00
|
|
|
moveCursorForwards(len(rl.line) - rl.pos)
|
|
|
|
rl.pos = len(rl.line)
|
2022-03-13 17:48:49 +00:00
|
|
|
|
|
|
|
case seqForwards:
|
|
|
|
if rl.modeTabCompletion {
|
|
|
|
rl.tabCompletionSelect = true
|
|
|
|
rl.moveTabCompletionHighlight(1, 0)
|
|
|
|
rl.updateVirtualComp()
|
|
|
|
rl.renderHelpers()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if (rl.modeViMode == VimInsert && rl.pos < len(rl.line)) ||
|
|
|
|
(rl.modeViMode != VimInsert && rl.pos < len(rl.line)-1) {
|
2022-04-18 20:04:56 +00:00
|
|
|
rl.moveCursorByAdjust(1)
|
2022-03-13 17:48:49 +00:00
|
|
|
}
|
|
|
|
rl.updateHelpers()
|
|
|
|
rl.viUndoSkipAppend = true
|
|
|
|
|
|
|
|
case seqBackwards:
|
|
|
|
if rl.modeTabCompletion {
|
|
|
|
rl.tabCompletionSelect = true
|
|
|
|
rl.tabCompletionReverse = true
|
|
|
|
rl.moveTabCompletionHighlight(-1, 0)
|
|
|
|
rl.updateVirtualComp()
|
|
|
|
rl.tabCompletionReverse = false
|
|
|
|
rl.renderHelpers()
|
|
|
|
return
|
|
|
|
}
|
2022-04-18 20:04:56 +00:00
|
|
|
rl.moveCursorByAdjust(-1)
|
2022-03-13 17:48:49 +00:00
|
|
|
rl.viUndoSkipAppend = true
|
|
|
|
rl.updateHelpers()
|
|
|
|
|
|
|
|
// Registers -------------------------------------------------------------------------------
|
|
|
|
case seqAltQuote:
|
|
|
|
if rl.modeViMode != VimInsert {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
rl.modeTabCompletion = true
|
|
|
|
rl.modeAutoFind = true
|
|
|
|
rl.searchMode = RegisterFind
|
|
|
|
// Else we might be asked to confirm printing (if too many suggestions), or not.
|
|
|
|
rl.getTabCompletion()
|
|
|
|
rl.viUndoSkipAppend = true
|
|
|
|
rl.renderHelpers()
|
|
|
|
|
|
|
|
// Movement -------------------------------------------------------------------------------
|
|
|
|
case seqCtrlLeftArrow:
|
|
|
|
rl.moveCursorByAdjust(rl.viJumpB(tokeniseLine))
|
|
|
|
rl.updateHelpers()
|
|
|
|
return
|
|
|
|
case seqCtrlRightArrow:
|
2022-03-26 21:34:09 +00:00
|
|
|
rl.insert(rl.hintText)
|
2022-03-13 17:48:49 +00:00
|
|
|
rl.moveCursorByAdjust(rl.viJumpW(tokeniseLine))
|
|
|
|
rl.updateHelpers()
|
|
|
|
return
|
|
|
|
|
2022-03-30 00:35:51 +00:00
|
|
|
case seqDelete,seqDelete2:
|
2022-03-13 17:48:49 +00:00
|
|
|
if rl.modeTabFind {
|
|
|
|
rl.backspaceTabFind()
|
|
|
|
} else {
|
2022-03-30 00:35:51 +00:00
|
|
|
if (rl.pos < len(rl.line)) {
|
|
|
|
rl.deleteBackspace(true)
|
|
|
|
}
|
2022-03-13 17:48:49 +00:00
|
|
|
}
|
2022-03-30 00:35:51 +00:00
|
|
|
|
2022-03-13 17:48:49 +00:00
|
|
|
case seqHome, seqHomeSc:
|
|
|
|
if rl.modeTabCompletion {
|
|
|
|
return
|
|
|
|
}
|
2022-04-18 20:04:56 +00:00
|
|
|
rl.moveCursorByAdjust(-rl.pos)
|
|
|
|
rl.updateHelpers()
|
2022-03-13 17:48:49 +00:00
|
|
|
rl.viUndoSkipAppend = true
|
|
|
|
|
|
|
|
case seqEnd, seqEndSc:
|
|
|
|
if rl.modeTabCompletion {
|
|
|
|
return
|
|
|
|
}
|
2022-04-18 20:04:56 +00:00
|
|
|
rl.moveCursorByAdjust(len(rl.line) - rl.pos)
|
|
|
|
rl.updateHelpers()
|
2022-03-13 17:48:49 +00:00
|
|
|
rl.viUndoSkipAppend = true
|
|
|
|
|
2022-04-14 11:42:46 +00:00
|
|
|
case seqAltB:
|
|
|
|
if rl.modeTabCompletion {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// This is only available in Insert mode
|
|
|
|
if rl.modeViMode != VimInsert {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
move := rl.emacsBackwardWord(tokeniseLine)
|
|
|
|
rl.moveCursorByAdjust(-move)
|
|
|
|
rl.updateHelpers()
|
|
|
|
|
|
|
|
case seqAltF:
|
|
|
|
if rl.modeTabCompletion {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// This is only available in Insert mode
|
|
|
|
if rl.modeViMode != VimInsert {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
move := rl.emacsForwardWord(tokeniseLine)
|
|
|
|
rl.moveCursorByAdjust(move)
|
|
|
|
rl.updateHelpers()
|
|
|
|
|
2022-03-13 17:48:49 +00:00
|
|
|
case seqAltR:
|
|
|
|
rl.resetVirtualComp(false)
|
|
|
|
// For some modes only, if we are in vim Keys mode,
|
|
|
|
// we toogle back to insert mode. For others, we return
|
|
|
|
// without getting the completions.
|
|
|
|
if rl.modeViMode != VimInsert {
|
|
|
|
rl.modeViMode = VimInsert
|
|
|
|
}
|
|
|
|
|
|
|
|
rl.mainHist = false // true before
|
|
|
|
rl.searchMode = HistoryFind
|
|
|
|
rl.modeAutoFind = true
|
|
|
|
rl.modeTabCompletion = true
|
|
|
|
|
|
|
|
rl.modeTabFind = true
|
|
|
|
rl.updateTabFind([]rune{})
|
|
|
|
rl.viUndoSkipAppend = true
|
|
|
|
|
2022-03-30 02:15:23 +00:00
|
|
|
case seqAltBackspace:
|
|
|
|
if rl.modeTabCompletion {
|
|
|
|
rl.resetVirtualComp(false)
|
|
|
|
}
|
|
|
|
// This is only available in Insert mode
|
|
|
|
if rl.modeViMode != VimInsert {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
rl.saveToRegister(rl.viJumpB(tokeniseLine))
|
|
|
|
rl.viDeleteByAdjust(rl.viJumpB(tokeniseLine))
|
|
|
|
rl.updateHelpers()
|
|
|
|
|
2022-04-14 11:42:46 +00:00
|
|
|
case seqCtrlDelete, seqCtrlDelete2, seqAltD:
|
2022-04-13 03:08:44 +00:00
|
|
|
if rl.modeTabCompletion {
|
|
|
|
rl.resetVirtualComp(false)
|
|
|
|
}
|
|
|
|
rl.saveToRegister(rl.emacsForwardWord(tokeniseLine))
|
|
|
|
// vi delete, emacs forward, funny huh
|
|
|
|
rl.viDeleteByAdjust(rl.emacsForwardWord(tokeniseLine))
|
|
|
|
rl.updateHelpers()
|
|
|
|
|
2022-04-14 11:42:46 +00:00
|
|
|
case seqAltDelete:
|
|
|
|
if rl.modeTabCompletion {
|
|
|
|
rl.resetVirtualComp(false)
|
|
|
|
}
|
|
|
|
rl.saveToRegister(-rl.emacsBackwardWord(tokeniseLine))
|
|
|
|
rl.viDeleteByAdjust(-rl.emacsBackwardWord(tokeniseLine))
|
|
|
|
rl.updateHelpers()
|
|
|
|
|
2022-03-13 17:48:49 +00:00
|
|
|
default:
|
|
|
|
if rl.modeTabFind {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
// alt+numeric append / delete
|
|
|
|
if len(r) == 2 && '1' <= r[1] && r[1] <= '9' {
|
|
|
|
if rl.modeViMode == VimDelete {
|
|
|
|
rl.viDelete(r[1])
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
line, err := rl.mainHistory.GetLine(rl.mainHistory.Len() - 1)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if !rl.mainHist {
|
|
|
|
line, err = rl.altHistory.GetLine(rl.altHistory.Len() - 1)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
tokens, _, _ := tokeniseSplitSpaces([]rune(line), 0)
|
|
|
|
pos := int(r[1]) - 48 // convert ASCII to integer
|
|
|
|
if pos > len(tokens) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
rl.insert([]rune(tokens[pos-1]))
|
|
|
|
} else {
|
|
|
|
rl.viUndoSkipAppend = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (rl *Instance) carridgeReturn() {
|
2022-04-19 02:36:54 +00:00
|
|
|
rl.moveCursorByAdjust(len(rl.line))
|
|
|
|
rl.updateHelpers()
|
2022-03-13 17:48:49 +00:00
|
|
|
rl.clearHelpers()
|
|
|
|
print("\r\n")
|
|
|
|
if rl.HistoryAutoWrite {
|
|
|
|
var err error
|
|
|
|
|
|
|
|
// Main history
|
|
|
|
if rl.mainHistory != nil {
|
|
|
|
rl.histPos, err = rl.mainHistory.Write(string(rl.line))
|
|
|
|
if err != nil {
|
|
|
|
print(err.Error() + "\r\n")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Alternative history
|
|
|
|
if rl.altHistory != nil {
|
|
|
|
rl.histPos, err = rl.altHistory.Write(string(rl.line))
|
|
|
|
if err != nil {
|
|
|
|
print(err.Error() + "\r\n")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func isMultiline(r []rune) bool {
|
|
|
|
for i := range r {
|
|
|
|
if (r[i] == '\r' || r[i] == '\n') && i != len(r)-1 {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
func (rl *Instance) allowMultiline(data []byte) bool {
|
|
|
|
rl.clearHelpers()
|
|
|
|
printf("\r\nWARNING: %d bytes of multiline data was dumped into the shell!", len(data))
|
|
|
|
for {
|
|
|
|
print("\r\nDo you wish to proceed (yes|no|preview)? [y/n/p] ")
|
|
|
|
|
|
|
|
b := make([]byte, 1024)
|
|
|
|
|
|
|
|
i, err := os.Stdin.Read(b)
|
|
|
|
if err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
s := string(b[:i])
|
|
|
|
print(s)
|
|
|
|
|
|
|
|
switch s {
|
|
|
|
case "y", "Y":
|
|
|
|
print("\r\n" + rl.mainPrompt)
|
|
|
|
return true
|
|
|
|
|
|
|
|
case "n", "N":
|
|
|
|
print("\r\n" + rl.mainPrompt)
|
|
|
|
return false
|
|
|
|
|
|
|
|
case "p", "P":
|
|
|
|
preview := string(bytes.Replace(data, []byte{'\r'}, []byte{'\r', '\n'}, -1))
|
|
|
|
if rl.SyntaxHighlighter != nil {
|
|
|
|
preview = rl.SyntaxHighlighter([]rune(preview))
|
|
|
|
}
|
|
|
|
print("\r\n" + preview)
|
|
|
|
|
|
|
|
default:
|
|
|
|
print("\r\nInvalid response. Please answer `y` (yes), `n` (no) or `p` (preview)")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|