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
|
// readline operating parameters
|
||||||
line []rune // This is the input line, with entered text: full line = mlnPrompt + line
|
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
|
pos int
|
||||||
|
hpos int // The line on which the cursor is (differs from posY, which accounts for wraps)
|
||||||
posX int // Cursor position X
|
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)
|
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.
|
fullY int // Y offset to the end of input line.
|
||||||
|
|
||||||
// Buffer received from host programms
|
// Buffer received from host programms
|
||||||
|
@ -183,11 +185,12 @@ type Instance struct {
|
||||||
// $EDITOR. This will default to os.TempDir()
|
// $EDITOR. This will default to os.TempDir()
|
||||||
TempDirectory string
|
TempDirectory string
|
||||||
|
|
||||||
// GetMultiLine is a callback to your host program. Since multiline support
|
// AcceptMultiline enables the caller to decide if the shell should keep reading for user input
|
||||||
// is handled by the application rather than readline itself, this callback
|
// on a new line (therefore, with the secondary prompt), or if it should return the current
|
||||||
// is required when calling $EDITOR. However if this function is not set
|
// line at the end of the `rl.Readline()` call.
|
||||||
// then readline will just use the current line.
|
// This function should return "true" if the line is deemed complete (thus asking the shell
|
||||||
GetMultiLine func([]rune) []rune
|
// to return from its Readline() loop), or "false" if the shell should keep reading input.
|
||||||
|
AcceptMultiline func([]rune) bool
|
||||||
|
|
||||||
EnableGetCursorPos bool
|
EnableGetCursorPos bool
|
||||||
|
|
||||||
|
@ -229,6 +232,9 @@ func NewInstance() *Instance {
|
||||||
rl.HintFormatting = "\x1b[2m"
|
rl.HintFormatting = "\x1b[2m"
|
||||||
rl.evtKeyPress = make(map[string]func(string, []rune, int) *EventReturn)
|
rl.evtKeyPress = make(map[string]func(string, []rune, int) *EventReturn)
|
||||||
rl.TempDirectory = os.TempDir()
|
rl.TempDirectory = os.TempDir()
|
||||||
|
rl.AcceptMultiline = func([]rune) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// Registers
|
// Registers
|
||||||
rl.initRegisters()
|
rl.initRegisters()
|
||||||
|
|
173
readline/line.go
173
readline/line.go
|
@ -1,6 +1,7 @@
|
||||||
package readline
|
package readline
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -25,6 +26,118 @@ func (rl *Instance) GetLine() []rune {
|
||||||
return rl.line
|
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.
|
// echo - refresh the current input line, either virtually completed or not.
|
||||||
// also renders the current completions and hints. To be noted, the updateReferences()
|
// 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
|
// 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
|
// Go back to prompt position, and clear everything below
|
||||||
moveCursorBackwards(GetTermWidth())
|
moveCursorBackwards(GetTermWidth())
|
||||||
moveCursorUp(rl.posY)
|
moveCursorUp(rl.posY)
|
||||||
print(seqClearScreenBelow)
|
|
||||||
|
|
||||||
// Print the prompt
|
// Print the prompt
|
||||||
print(string(rl.realPrompt))
|
print(string(rl.realPrompt))
|
||||||
|
@ -55,23 +167,19 @@ func (rl *Instance) echo() {
|
||||||
line = rl.line
|
line = rl.line
|
||||||
}
|
}
|
||||||
|
|
||||||
|
printed := string(line)
|
||||||
// Print the input line with optional syntax highlighting
|
// Print the input line with optional syntax highlighting
|
||||||
if rl.SyntaxHighlighter != nil {
|
if rl.SyntaxHighlighter != nil {
|
||||||
print(rl.SyntaxHighlighter(line))
|
printed = (rl.SyntaxHighlighter(line))
|
||||||
} else {
|
|
||||||
print(string(line))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rl.computeLinePos()
|
||||||
|
print(printed)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update references with new coordinates only now, because
|
// Update references with new coordinates only now, because
|
||||||
// the new line may be longer/shorter than the previous one.
|
// the new line may be longer/shorter than the previous one.
|
||||||
rl.updateReferences()
|
//rl.updateReferences()
|
||||||
|
|
||||||
// Go back to the current cursor position, with new coordinates
|
|
||||||
moveCursorBackwards(GetTermWidth())
|
|
||||||
moveCursorUp(rl.fullY)
|
|
||||||
moveCursorDown(rl.posY)
|
|
||||||
moveCursorForwards(rl.posX)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rl *Instance) insert(r []rune) {
|
func (rl *Instance) insert(r []rune) {
|
||||||
|
@ -107,6 +215,49 @@ func (rl *Instance) insert(r []rune) {
|
||||||
rl.updateHelpers()
|
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) {
|
func (rl *Instance) Insert(t string) {
|
||||||
rl.insert([]rune(t))
|
rl.insert([]rune(t))
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,22 +52,6 @@ func (rl *Instance) Readline() (string, error) {
|
||||||
rl.histOffset = 0
|
rl.histOffset = 0
|
||||||
rl.viUndoHistory = []undoItem{{line: "", pos: 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
|
// Finally, print any info or completions
|
||||||
// if the TabCompletion engines so desires
|
// if the TabCompletion engines so desires
|
||||||
rl.renderHelpers()
|
rl.renderHelpers()
|
||||||
|
@ -98,33 +82,6 @@ func (rl *Instance) Readline() (string, error) {
|
||||||
rl.RawInputCallback(r[:i])
|
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])
|
s := string(r[:i])
|
||||||
if rl.evtKeyPress[s] != nil {
|
if rl.evtKeyPress[s] != nil {
|
||||||
rl.clearHelpers()
|
rl.clearHelpers()
|
||||||
|
@ -448,7 +405,7 @@ func (rl *Instance) Readline() (string, error) {
|
||||||
completion := cur.getCurrentCell(rl)
|
completion := cur.getCurrentCell(rl)
|
||||||
prefix := len(rl.tcPrefix)
|
prefix := len(rl.tcPrefix)
|
||||||
if prefix > len(completion) {
|
if prefix > len(completion) {
|
||||||
rl.carridgeReturn()
|
rl.lineCarriageReturn()
|
||||||
return string(rl.line), nil
|
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 we were in history completion, immediately execute the line.
|
||||||
if rl.modeAutoFind && rl.searchMode == HistoryFind {
|
if rl.modeAutoFind && rl.searchMode == HistoryFind {
|
||||||
rl.carridgeReturn()
|
rl.lineCarriageReturn()
|
||||||
return string(rl.line), nil
|
return string(rl.line), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -470,8 +427,10 @@ func (rl *Instance) Readline() (string, error) {
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
rl.carridgeReturn()
|
rl.lineCarriageReturn()
|
||||||
|
if rl.accepted {
|
||||||
return string(rl.line), nil
|
return string(rl.line), nil
|
||||||
|
}
|
||||||
|
|
||||||
// Vim --------------------------------------------------------------------------------------
|
// Vim --------------------------------------------------------------------------------------
|
||||||
case charEscape:
|
case charEscape:
|
||||||
|
|
|
@ -238,11 +238,11 @@ func (rl *Instance) vi(r rune) {
|
||||||
case 'v':
|
case 'v':
|
||||||
rl.clearHelpers()
|
rl.clearHelpers()
|
||||||
var multiline []rune
|
var multiline []rune
|
||||||
if rl.GetMultiLine == nil {
|
/*if rl.GetMultiLine == nil {
|
||||||
multiline = rl.line
|
multiline = rl.line
|
||||||
} else {
|
} else {
|
||||||
multiline = rl.GetMultiLine(rl.line)
|
multiline = rl.GetMultiLine(rl.line)
|
||||||
}
|
}*/
|
||||||
|
|
||||||
// Keep the previous cursor position
|
// Keep the previous cursor position
|
||||||
//prev := rl.pos
|
//prev := rl.pos
|
||||||
|
|
Loading…
Reference in New Issue