mirror of https://github.com/Hilbis/Hilbish
feat: initial multiline support
parent
f97a04179d
commit
d22428bd08
|
@ -47,10 +47,12 @@ type Instance struct {
|
|||
|
||||
// readline operating parameters
|
||||
line []rune // This is the input line, with entered text: full line = mlnPrompt + line
|
||||
accepted bool // Set by 'accept-line' widget, to notify return the line to the caller
|
||||
pos int
|
||||
hpos int // The line on which the cursor is (differs from posY, which accounts for wraps)
|
||||
posX int // Cursor position X
|
||||
fullX int // X coordinate of the full input line, including the prompt if needed.
|
||||
posY int // Cursor position Y (if multiple lines span)
|
||||
fullX int // X coordinate of the full input line, including the prompt if needed.
|
||||
fullY int // Y offset to the end of input line.
|
||||
|
||||
// Buffer received from host programms
|
||||
|
@ -183,11 +185,12 @@ type Instance struct {
|
|||
// $EDITOR. This will default to os.TempDir()
|
||||
TempDirectory string
|
||||
|
||||
// GetMultiLine is a callback to your host program. Since multiline support
|
||||
// is handled by the application rather than readline itself, this callback
|
||||
// is required when calling $EDITOR. However if this function is not set
|
||||
// then readline will just use the current line.
|
||||
GetMultiLine func([]rune) []rune
|
||||
// AcceptMultiline enables the caller to decide if the shell should keep reading for user input
|
||||
// on a new line (therefore, with the secondary prompt), or if it should return the current
|
||||
// line at the end of the `rl.Readline()` call.
|
||||
// This function should return "true" if the line is deemed complete (thus asking the shell
|
||||
// to return from its Readline() loop), or "false" if the shell should keep reading input.
|
||||
AcceptMultiline func([]rune) bool
|
||||
|
||||
EnableGetCursorPos bool
|
||||
|
||||
|
@ -229,6 +232,9 @@ func NewInstance() *Instance {
|
|||
rl.HintFormatting = "\x1b[2m"
|
||||
rl.evtKeyPress = make(map[string]func(string, []rune, int) *EventReturn)
|
||||
rl.TempDirectory = os.TempDir()
|
||||
rl.AcceptMultiline = func([]rune) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Registers
|
||||
rl.initRegisters()
|
||||
|
|
173
readline/line.go
173
readline/line.go
|
@ -1,6 +1,7 @@
|
|||
package readline
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
|
@ -25,6 +26,118 @@ func (rl *Instance) GetLine() []rune {
|
|||
return rl.line
|
||||
}
|
||||
|
||||
func (rl *Instance) lineSuggested() (line []rune, cpos int) {
|
||||
//rl.checkCursorBounds()
|
||||
|
||||
if len(rl.currentComp) > 0 {
|
||||
line = rl.lineComp
|
||||
cpos = len(rl.lineComp[:rl.pos])
|
||||
} else {
|
||||
line = rl.line
|
||||
cpos = len(rl.line[:rl.pos])
|
||||
}
|
||||
|
||||
/*
|
||||
if len(rl.histSuggested) > 0 {
|
||||
line = append(line, rl.histSuggested...)
|
||||
}
|
||||
*/
|
||||
|
||||
return line, cpos
|
||||
}
|
||||
|
||||
// computeLinePos determines the X and Y coordinates of the cursor.
|
||||
func (rl *Instance) computeLinePos() {
|
||||
// Use the line including any completion or line suggestion,
|
||||
// and compute buffer/cursor length. Only add a newline when
|
||||
// the current buffer does not end with one.
|
||||
line, cpos := rl.lineSuggested()
|
||||
line = append(line, '\n')
|
||||
|
||||
// Get the index of each newline in the buffer.
|
||||
nl := regexp.MustCompile("\n")
|
||||
newlinesIdx := nl.FindAllStringIndex(string(line), -1)
|
||||
|
||||
rl.posY = 0
|
||||
rl.fullY = 0
|
||||
startLine := 0
|
||||
cursorSet := false
|
||||
|
||||
for pos, newline := range newlinesIdx {
|
||||
// Compute any adjustment in case this line must be wrapped.
|
||||
// Here, compute if line must be wrapped, to adjust posY.
|
||||
lineY := rl.realLineLen(line[startLine:newline[0]], pos)
|
||||
|
||||
// All lines add to the global offset.
|
||||
rl.fullY += lineY
|
||||
|
||||
switch {
|
||||
case newline[0] < cpos:
|
||||
// If we are not on the cursor line yet.
|
||||
rl.posY += lineY
|
||||
case !cursorSet:
|
||||
// We are on the cursor line, since we didn't catch
|
||||
// the first case, and that our cursor X coordinate
|
||||
// has not been set yet.
|
||||
rl.computeCursorPos(startLine, cpos, pos)
|
||||
cursorSet = true
|
||||
rl.hpos = pos
|
||||
}
|
||||
|
||||
startLine = newline[1]
|
||||
}
|
||||
}
|
||||
|
||||
// computeCursorPos computes the X/Y coordinates of the cursor on a given line.
|
||||
func (rl *Instance) computeCursorPos(startLine, cpos, lineIdx int) {
|
||||
termWidth := GetTermWidth()
|
||||
cursorStart := cpos - startLine
|
||||
//cursorStart += rl.Prompt.inputAt(rl)
|
||||
|
||||
cursorY := cursorStart / termWidth
|
||||
cursorX := cursorStart % termWidth
|
||||
|
||||
// The very first (unreal) line counts for nothing,
|
||||
// so by opposition all others count for one more.
|
||||
if lineIdx == 0 {
|
||||
cursorY--
|
||||
}
|
||||
|
||||
// Any excess wrap means a newline.
|
||||
if cursorX > 0 {
|
||||
cursorY++
|
||||
}
|
||||
|
||||
rl.posY += cursorY
|
||||
rl.posX = cursorX
|
||||
}
|
||||
|
||||
func (rl *Instance) realLineLen(line []rune, idx int) (lineY int) {
|
||||
lineLen := getRealLength(string(line))
|
||||
termWidth := GetTermWidth()
|
||||
//lineLen += rl.Prompt.inputAt(rl)
|
||||
|
||||
lineY = lineLen / termWidth
|
||||
restY := lineLen % termWidth
|
||||
|
||||
// The very first line counts for nothing.
|
||||
if idx == 0 {
|
||||
lineY--
|
||||
}
|
||||
|
||||
// Any excess wrap means a newline.
|
||||
if restY > 0 {
|
||||
lineY++
|
||||
}
|
||||
|
||||
// Empty lines are still considered a line.
|
||||
if lineY == 0 && idx != 0 {
|
||||
lineY++
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// echo - refresh the current input line, either virtually completed or not.
|
||||
// also renders the current completions and hints. To be noted, the updateReferences()
|
||||
// function is only ever called once, and after having moved back to prompt position
|
||||
|
@ -42,7 +155,6 @@ func (rl *Instance) echo() {
|
|||
// Go back to prompt position, and clear everything below
|
||||
moveCursorBackwards(GetTermWidth())
|
||||
moveCursorUp(rl.posY)
|
||||
print(seqClearScreenBelow)
|
||||
|
||||
// Print the prompt
|
||||
print(string(rl.realPrompt))
|
||||
|
@ -55,23 +167,19 @@ func (rl *Instance) echo() {
|
|||
line = rl.line
|
||||
}
|
||||
|
||||
printed := string(line)
|
||||
// Print the input line with optional syntax highlighting
|
||||
if rl.SyntaxHighlighter != nil {
|
||||
print(rl.SyntaxHighlighter(line))
|
||||
} else {
|
||||
print(string(line))
|
||||
printed = (rl.SyntaxHighlighter(line))
|
||||
}
|
||||
|
||||
rl.computeLinePos()
|
||||
print(printed)
|
||||
}
|
||||
|
||||
// Update references with new coordinates only now, because
|
||||
// the new line may be longer/shorter than the previous one.
|
||||
rl.updateReferences()
|
||||
|
||||
// Go back to the current cursor position, with new coordinates
|
||||
moveCursorBackwards(GetTermWidth())
|
||||
moveCursorUp(rl.fullY)
|
||||
moveCursorDown(rl.posY)
|
||||
moveCursorForwards(rl.posX)
|
||||
//rl.updateReferences()
|
||||
}
|
||||
|
||||
func (rl *Instance) insert(r []rune) {
|
||||
|
@ -107,6 +215,49 @@ func (rl *Instance) insert(r []rune) {
|
|||
rl.updateHelpers()
|
||||
}
|
||||
|
||||
// lineSlice returns a subset of the current input line.
|
||||
func (rl *Instance) lineSlice(adjust int) (slice string) {
|
||||
switch {
|
||||
case rl.pos+adjust > len(rl.line):
|
||||
slice = string(rl.line[rl.pos:])
|
||||
case adjust < 0:
|
||||
if rl.pos+adjust < 0 {
|
||||
slice = string(rl.line[:rl.pos])
|
||||
} else {
|
||||
slice = string(rl.line[rl.pos+adjust : rl.pos])
|
||||
}
|
||||
default:
|
||||
slice = string(rl.line[rl.pos : rl.pos+adjust])
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (rl *Instance) lineCarriageReturn() {
|
||||
//rl.histSuggested = []rune{}
|
||||
|
||||
// Ask the caller if the line should be accepted as is.
|
||||
if rl.AcceptMultiline(rl.GetLine()) {
|
||||
// Clear the tooltip prompt if any,
|
||||
// then go down and clear hints/completions.
|
||||
//rl.moveToLineEnd()
|
||||
//rl.Prompt.clearRprompt(rl, false)
|
||||
print("\r\n")
|
||||
print(seqClearScreenBelow)
|
||||
|
||||
// Save the command line and accept it.
|
||||
//rl.writeHistoryLine()
|
||||
rl.accepted = true
|
||||
return
|
||||
}
|
||||
|
||||
// If not, we should start editing another line,
|
||||
// and insert a newline where our cursor value is.
|
||||
// This has the nice advantage of being able to work
|
||||
// in multiline mode even in the middle of the buffer.
|
||||
rl.insert([]rune{'\n'})
|
||||
}
|
||||
|
||||
func (rl *Instance) Insert(t string) {
|
||||
rl.insert([]rune(t))
|
||||
}
|
||||
|
|
|
@ -52,22 +52,6 @@ func (rl *Instance) Readline() (string, error) {
|
|||
rl.histOffset = 0
|
||||
rl.viUndoHistory = []undoItem{{line: "", pos: 0}}
|
||||
|
||||
// Multisplit
|
||||
if len(rl.multisplit) > 0 {
|
||||
r := []rune(rl.multisplit[0])
|
||||
if len(r) >= 1 {
|
||||
rl.editorInput(r)
|
||||
}
|
||||
|
||||
rl.carridgeReturn()
|
||||
if len(rl.multisplit) > 1 {
|
||||
rl.multisplit = rl.multisplit[1:]
|
||||
} else {
|
||||
rl.multisplit = []string{}
|
||||
}
|
||||
return string(rl.line), nil
|
||||
}
|
||||
|
||||
// Finally, print any info or completions
|
||||
// if the TabCompletion engines so desires
|
||||
rl.renderHelpers()
|
||||
|
@ -98,33 +82,6 @@ func (rl *Instance) Readline() (string, error) {
|
|||
rl.RawInputCallback(r[:i])
|
||||
}
|
||||
|
||||
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()
|
||||
|
@ -448,7 +405,7 @@ func (rl *Instance) Readline() (string, error) {
|
|||
completion := cur.getCurrentCell(rl)
|
||||
prefix := len(rl.tcPrefix)
|
||||
if prefix > len(completion) {
|
||||
rl.carridgeReturn()
|
||||
rl.lineCarriageReturn()
|
||||
return string(rl.line), nil
|
||||
}
|
||||
|
||||
|
@ -459,7 +416,7 @@ func (rl *Instance) Readline() (string, error) {
|
|||
|
||||
// If we were in history completion, immediately execute the line.
|
||||
if rl.modeAutoFind && rl.searchMode == HistoryFind {
|
||||
rl.carridgeReturn()
|
||||
rl.lineCarriageReturn()
|
||||
return string(rl.line), nil
|
||||
}
|
||||
|
||||
|
@ -470,8 +427,10 @@ func (rl *Instance) Readline() (string, error) {
|
|||
|
||||
continue
|
||||
}
|
||||
rl.carridgeReturn()
|
||||
rl.lineCarriageReturn()
|
||||
if rl.accepted {
|
||||
return string(rl.line), nil
|
||||
}
|
||||
|
||||
// Vim --------------------------------------------------------------------------------------
|
||||
case charEscape:
|
||||
|
|
|
@ -238,11 +238,11 @@ func (rl *Instance) vi(r rune) {
|
|||
case 'v':
|
||||
rl.clearHelpers()
|
||||
var multiline []rune
|
||||
if rl.GetMultiLine == nil {
|
||||
/*if rl.GetMultiLine == nil {
|
||||
multiline = rl.line
|
||||
} else {
|
||||
multiline = rl.GetMultiLine(rl.line)
|
||||
}
|
||||
}*/
|
||||
|
||||
// Keep the previous cursor position
|
||||
//prev := rl.pos
|
||||
|
|
Loading…
Reference in New Issue