mirror of
https://github.com/wavetermdev/backup.git
synced 2026-08-05 13:57:07 -07:00
working on getting comppos to work recursively
This commit is contained in:
+113
-37
@@ -166,6 +166,13 @@ func (qc QuoteContext) cur() string {
|
||||
return qc[len(qc)-1]
|
||||
}
|
||||
|
||||
func (qc QuoteContext) clone() QuoteContext {
|
||||
if len(qc) == 0 {
|
||||
return nil
|
||||
}
|
||||
return append([]string(nil), qc...)
|
||||
}
|
||||
|
||||
func makeRepeatStr(ch byte, slen int) string {
|
||||
if slen == 0 {
|
||||
return ""
|
||||
@@ -184,10 +191,10 @@ func (w *WordType) isBlank() bool {
|
||||
func (w *WordType) uncompletable() bool {
|
||||
switch w.Type {
|
||||
case WordTypeRaw, WordTypeOp, WordTypeKey, WordTypeDPP, WordTypePP, WordTypeDB:
|
||||
return false
|
||||
return true
|
||||
|
||||
default:
|
||||
return true
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,9 +247,9 @@ func dumpWords(words []*WordType, indentStr string, offset int) {
|
||||
|
||||
func dumpCommands(cmds []*CmdType, indentStr string, pos int) {
|
||||
for _, cmd := range cmds {
|
||||
fmt.Printf("%sCMD: %s [%d]\n", indentStr, cmd.Type, len(cmd.Words))
|
||||
dumpWords(cmd.AssignmentWords, indentStr+" *", -1)
|
||||
dumpWords(cmd.Words, indentStr+" ", -1)
|
||||
fmt.Printf("%sCMD: %s [%d] pos:%d\n", indentStr, cmd.Type, len(cmd.Words), pos)
|
||||
dumpWords(cmd.AssignmentWords, indentStr+" *", pos)
|
||||
dumpWords(cmd.Words, indentStr+" ", pos)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -487,8 +494,9 @@ func identifyReservedWords(words []*WordType) {
|
||||
}
|
||||
|
||||
type CompletionPos struct {
|
||||
RawPos int // the raw position of cursor
|
||||
Cmd *CmdType // nil if between commands (otherwise will be a SimpleCommand)
|
||||
RawPos int // the raw position of cursor
|
||||
SuperOffset int // adjust all offsets in Cmd and CmdWord by SuperOffset
|
||||
Cmd *CmdType // nil if between commands (otherwise will be a SimpleCommand)
|
||||
|
||||
// index into cmd.Words (only useful when Cmd is not nil, otherwise we look at CompCommand)
|
||||
// 0 means command-word
|
||||
@@ -502,7 +510,7 @@ type CompletionPos struct {
|
||||
CompCommand bool // set when we think we are the first word of an existing or new command. otherwise we default to file completion
|
||||
}
|
||||
|
||||
func (cmd *CmdType) findCompletionPos_simple(pos int) CompletionPos {
|
||||
func (cmd *CmdType) findCompletionPos_simple(pos int, superOffset int) CompletionPos {
|
||||
if cmd.Type != CmdTypeSimple {
|
||||
panic("findCompletetionPos_simple only works for CmdTypeSimple")
|
||||
}
|
||||
@@ -511,11 +519,11 @@ func (cmd *CmdType) findCompletionPos_simple(pos int) CompletionPos {
|
||||
endOffset := word.Offset + len(word.Raw)
|
||||
if pos <= startOffset {
|
||||
// starting a new word at this position (before the current assignment word)
|
||||
return CompletionPos{RawPos: pos, Cmd: cmd, CmdWordPos: idx - len(cmd.AssignmentWords) - 1}
|
||||
return CompletionPos{RawPos: pos, SuperOffset: superOffset, Cmd: cmd, CmdWordPos: idx - len(cmd.AssignmentWords) - 1}
|
||||
}
|
||||
if pos <= endOffset {
|
||||
// completing an assignment word
|
||||
return CompletionPos{RawPos: pos, Cmd: cmd, CmdWordPos: idx - len(cmd.AssignmentWords), CmdWord: word, CmdWordOffset: pos - word.Offset}
|
||||
return CompletionPos{RawPos: pos, SuperOffset: superOffset, Cmd: cmd, CmdWordPos: idx - len(cmd.AssignmentWords), CmdWord: word, CmdWordOffset: pos - word.Offset}
|
||||
}
|
||||
}
|
||||
var foundWord *WordType
|
||||
@@ -525,9 +533,10 @@ func (cmd *CmdType) findCompletionPos_simple(pos int) CompletionPos {
|
||||
endOffset := word.Offset + len(word.Raw)
|
||||
if pos <= startOffset {
|
||||
// starting a new word at this position
|
||||
return CompletionPos{RawPos: pos, Cmd: cmd, CmdWordPos: idx}
|
||||
return CompletionPos{RawPos: pos, SuperOffset: superOffset, Cmd: cmd, CmdWordPos: idx}
|
||||
}
|
||||
if pos == endOffset && word.uncompletable() {
|
||||
if pos == endOffset && word.Type == WordTypeOp {
|
||||
// operators are special, they can allow a full-word completion at endpos
|
||||
continue
|
||||
}
|
||||
if pos <= endOffset {
|
||||
@@ -539,12 +548,12 @@ func (cmd *CmdType) findCompletionPos_simple(pos int) CompletionPos {
|
||||
if foundWord != nil {
|
||||
if foundWord.uncompletable() {
|
||||
// invalid completion point
|
||||
return CompletionPos{RawPos: pos, Cmd: cmd, CmdWordPos: foundWordIdx, CmdWord: foundWord, CmdWordOffset: pos - foundWord.Offset, CompInvalid: true}
|
||||
return CompletionPos{RawPos: pos, SuperOffset: superOffset, Cmd: cmd, CmdWordPos: foundWordIdx, CmdWord: foundWord, CmdWordOffset: pos - foundWord.Offset, CompInvalid: true}
|
||||
}
|
||||
return CompletionPos{RawPos: pos, Cmd: cmd, CmdWordPos: foundWordIdx, CmdWord: foundWord, CmdWordOffset: pos - foundWord.Offset}
|
||||
return CompletionPos{RawPos: pos, SuperOffset: superOffset, Cmd: cmd, CmdWordPos: foundWordIdx, CmdWord: foundWord, CmdWordOffset: pos - foundWord.Offset}
|
||||
}
|
||||
// past the end, so we're starting a new word in Cmd
|
||||
return CompletionPos{RawPos: pos, Cmd: cmd, CmdWordPos: len(cmd.Words)}
|
||||
return CompletionPos{RawPos: pos, SuperOffset: superOffset, Cmd: cmd, CmdWordPos: len(cmd.Words)}
|
||||
}
|
||||
|
||||
func (cmd *CmdType) findWordAtPos_none(pos int) *WordType {
|
||||
@@ -558,12 +567,9 @@ func (cmd *CmdType) findWordAtPos_none(pos int) *WordType {
|
||||
return nil
|
||||
}
|
||||
if pos <= endOffset {
|
||||
if word.uncompletable() {
|
||||
// only return an uncompletable word if we are really in the middle of it
|
||||
if pos == endOffset {
|
||||
continue
|
||||
}
|
||||
return word
|
||||
if pos == endOffset && word.Type == WordTypeOp {
|
||||
// operators are special, they can allow a full-word completion at endpos
|
||||
continue
|
||||
}
|
||||
return word
|
||||
}
|
||||
@@ -571,14 +577,61 @@ func (cmd *CmdType) findWordAtPos_none(pos int) *WordType {
|
||||
return nil
|
||||
}
|
||||
|
||||
func findWordAtPos(words []*WordType, pos int) *WordType {
|
||||
for _, word := range words {
|
||||
if pos > word.Offset && pos < word.Offset+len(word.Raw) {
|
||||
return word
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// recursively descend down the word, parse commands and find a sub completion point if any.
|
||||
// return nil if there is no sub completion point in this word
|
||||
func findCompletionPosInWord(word *WordType, pos int, superOffset int) *CompletionPos {
|
||||
if word.Type == WordTypeGroup || word.Type == WordTypeDQ || word.Type == WordTypeDDQ {
|
||||
// need to descend further
|
||||
wmeta := wordMetaMap[word.Type]
|
||||
if pos <= wmeta.PrefixLen {
|
||||
return nil
|
||||
}
|
||||
endPos := len(word.Raw)
|
||||
if word.Complete {
|
||||
endPos = endPos - wmeta.SuffixLen
|
||||
}
|
||||
if pos >= endPos {
|
||||
return nil
|
||||
}
|
||||
subWord := findWordAtPos(word.Subs, pos-wmeta.PrefixLen)
|
||||
if subWord == nil {
|
||||
return nil
|
||||
}
|
||||
fullOffset := subWord.Offset + wmeta.PrefixLen
|
||||
return findCompletionPosInWord(subWord, pos-fullOffset, superOffset+fullOffset)
|
||||
}
|
||||
if word.Type == WordTypeDP || word.Type == WordTypeBQ {
|
||||
wmeta := wordMetaMap[word.Type]
|
||||
if pos < wmeta.PrefixLen {
|
||||
return nil
|
||||
}
|
||||
if word.Complete && pos > len(word.Raw)-wmeta.SuffixLen {
|
||||
return nil
|
||||
}
|
||||
subCmds := ParseCommands(word.Subs)
|
||||
newPos := FindCompletionPos(subCmds, pos-wmeta.PrefixLen, superOffset+wmeta.PrefixLen)
|
||||
return &newPos
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// returns the context for completion
|
||||
// if we are completing in a simple-command, the returns the Cmd. the Cmd can be used for specialized completion (command name, arg position, etc.)
|
||||
// if we are completing in a word, returns the Word. Word might be a group-word or DQ word, so it may need additional resolution (done in extend)
|
||||
// otherwise we are going to create a new word to insert at offset (so the context does not matter)
|
||||
func FindCompletionPos(cmds []*CmdType, pos int) CompletionPos {
|
||||
func findCompletionPosCmds(cmds []*CmdType, pos int, superOffset int) CompletionPos {
|
||||
if len(cmds) == 0 {
|
||||
// set CompCommand because we're starting a new command
|
||||
return CompletionPos{RawPos: pos, CompCommand: true}
|
||||
return CompletionPos{RawPos: pos, SuperOffset: superOffset, CompCommand: true}
|
||||
}
|
||||
for _, cmd := range cmds {
|
||||
endOffset := cmd.endOffset()
|
||||
@@ -588,45 +641,58 @@ func FindCompletionPos(cmds []*CmdType, pos int) CompletionPos {
|
||||
startOffset := cmd.offset()
|
||||
if cmd.Type == CmdTypeSimple {
|
||||
if pos <= startOffset {
|
||||
return CompletionPos{RawPos: pos, CompCommand: true}
|
||||
return CompletionPos{RawPos: pos, SuperOffset: superOffset, CompCommand: true}
|
||||
}
|
||||
return cmd.findCompletionPos_simple(pos)
|
||||
return cmd.findCompletionPos_simple(pos, superOffset)
|
||||
} else {
|
||||
// not in a simple-command
|
||||
// if we're before the none-command, just start a new command
|
||||
if pos <= startOffset {
|
||||
return CompletionPos{RawPos: pos, CompCommand: true}
|
||||
return CompletionPos{RawPos: pos, SuperOffset: superOffset, CompCommand: true}
|
||||
}
|
||||
word := cmd.findWordAtPos_none(pos)
|
||||
if word == nil {
|
||||
// just revert to a file completion
|
||||
return CompletionPos{RawPos: pos, CompCommand: false}
|
||||
return CompletionPos{RawPos: pos, SuperOffset: superOffset, CompCommand: false}
|
||||
}
|
||||
if word.uncompletable() {
|
||||
// ok, we're inside of a word in CmdTypeNone. if we're in an uncompletable word, return CompInvalid
|
||||
return CompletionPos{RawPos: pos, CompInvalid: true}
|
||||
return CompletionPos{RawPos: pos, SuperOffset: superOffset, CmdWord: word, CmdWordOffset: pos - word.Offset, CompInvalid: true}
|
||||
}
|
||||
// revert to file completion
|
||||
return CompletionPos{RawPos: pos, CmdWord: word, CmdWordOffset: pos - word.Offset}
|
||||
return CompletionPos{RawPos: pos, SuperOffset: superOffset, CmdWord: word, CmdWordOffset: pos - word.Offset}
|
||||
}
|
||||
}
|
||||
// past the end
|
||||
lastCmd := cmds[len(cmds)-1]
|
||||
if lastCmd.Type == CmdTypeSimple {
|
||||
// just extend last command
|
||||
return CompletionPos{RawPos: pos, Cmd: lastCmd, CmdWordPos: len(lastCmd.Words)}
|
||||
return CompletionPos{RawPos: pos, SuperOffset: superOffset, Cmd: lastCmd, CmdWordPos: len(lastCmd.Words)}
|
||||
}
|
||||
// use lastCmd.NoneComplete to see if last command ended on a "separator". use that to set CompCommand
|
||||
return CompletionPos{RawPos: pos, CompCommand: lastCmd.NoneComplete}
|
||||
return CompletionPos{RawPos: pos, SuperOffset: superOffset, CompCommand: lastCmd.NoneComplete}
|
||||
}
|
||||
|
||||
func ResetWordOffsets(words []*WordType) {
|
||||
pos := 0
|
||||
func FindCompletionPos(cmds []*CmdType, pos int, superOffset int) CompletionPos {
|
||||
cpos := findCompletionPosCmds(cmds, pos, superOffset)
|
||||
if cpos.CmdWord == nil {
|
||||
return cpos
|
||||
}
|
||||
subPos := findCompletionPosInWord(cpos.CmdWord, cpos.CmdWordOffset, superOffset+cpos.CmdWord.Offset)
|
||||
if subPos == nil {
|
||||
return cpos
|
||||
} else {
|
||||
return *subPos
|
||||
}
|
||||
}
|
||||
|
||||
func ResetWordOffsets(words []*WordType, startIdx int) {
|
||||
pos := startIdx
|
||||
for _, word := range words {
|
||||
pos += len(word.Prefix)
|
||||
word.Offset = pos
|
||||
if len(word.Subs) > 0 {
|
||||
ResetWordOffsets(word.Subs)
|
||||
ResetWordOffsets(word.Subs, 0)
|
||||
}
|
||||
pos += len(word.Raw)
|
||||
}
|
||||
@@ -644,13 +710,23 @@ func (c *CmdType) stripPrefix() []rune {
|
||||
if len(c.AssignmentWords) > 0 {
|
||||
w := c.AssignmentWords[0]
|
||||
prefix := w.Prefix
|
||||
w.Prefix = nil
|
||||
if len(prefix) == 0 {
|
||||
return nil
|
||||
}
|
||||
newWord := *w
|
||||
newWord.Prefix = nil
|
||||
c.AssignmentWords[0] = &newWord
|
||||
return prefix
|
||||
}
|
||||
if len(c.Words) > 0 {
|
||||
w := c.Words[0]
|
||||
prefix := w.Prefix
|
||||
w.Prefix = nil
|
||||
if len(prefix) == 0 {
|
||||
return nil
|
||||
}
|
||||
newWord := *w
|
||||
newWord.Prefix = nil
|
||||
c.Words[0] = &newWord
|
||||
return prefix
|
||||
}
|
||||
return nil
|
||||
@@ -727,7 +803,7 @@ func cmdWhitespaceFixup(cmds []*CmdType) {
|
||||
nextCmd := cmds[idx+1]
|
||||
nextPrefix := nextCmd.stripPrefix()
|
||||
if len(nextPrefix) > 0 {
|
||||
blankWord := &WordType{Type: WordTypeLit, QC: cmd.lastWord().QC, Offset: cmd.endOffset(), Prefix: nextPrefix, Complete: true}
|
||||
blankWord := &WordType{Type: WordTypeLit, QC: cmd.lastWord().QC, Offset: cmd.endOffset() + len(nextPrefix), Prefix: nextPrefix, Complete: true}
|
||||
cmd.Words = append(cmd.Words, blankWord)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package shparse
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/scripthaus-dev/sh2-server/pkg/utilfn"
|
||||
)
|
||||
|
||||
// $(ls f[*]); ./x
|
||||
@@ -102,3 +104,56 @@ func TestCmd(t *testing.T) {
|
||||
testParseCommands(t, `ls f"oo" "${x:"hello$y"}"`)
|
||||
testParseCommands(t, `x="foo $y" z=10 ls`)
|
||||
}
|
||||
|
||||
func testCompPos(t *testing.T, cmdStr string, hasCommand bool, cmdWordPos int, hasWord bool, compInvalid bool, compCommand bool) {
|
||||
cmdSP := utilfn.ParseToSP(cmdStr)
|
||||
words := Tokenize(cmdSP.Str)
|
||||
cmds := ParseCommands(words)
|
||||
cpos := FindCompletionPos(cmds, cmdSP.Pos, 0)
|
||||
fmt.Printf("testCompPos [%d] %q => %v\n", cmdSP.Pos, cmdStr, cpos)
|
||||
if cpos.CmdWord != nil {
|
||||
fmt.Printf(" found-word: %d %s\n", cpos.CmdWordOffset, cpos.CmdWord.stringWithPos(cpos.CmdWordOffset))
|
||||
}
|
||||
if cpos.Cmd != nil {
|
||||
fmt.Printf(" found-cmd: ")
|
||||
dumpCommands([]*CmdType{cpos.Cmd}, " ", cpos.RawPos)
|
||||
}
|
||||
dumpCommands(cmds, " ", cmdSP.Pos)
|
||||
fmt.Printf("\n")
|
||||
if cpos.RawPos+cpos.SuperOffset != cmdSP.Pos {
|
||||
t.Errorf("testCompPos %q => bad rawpos:%d superoffset:%d expected:%d", cmdStr, cpos.RawPos, cpos.SuperOffset, cmdSP.Pos)
|
||||
}
|
||||
if (cpos.Cmd != nil) != hasCommand {
|
||||
t.Errorf("testCompPos %q => bad has-command exp:%v", cmdStr, hasCommand)
|
||||
}
|
||||
if (cpos.CmdWord != nil) != hasWord {
|
||||
t.Errorf("testCompPos %q => bad has-word exp:%v", cmdStr, hasWord)
|
||||
}
|
||||
if cpos.CmdWordPos != cmdWordPos {
|
||||
t.Errorf("testCompPos %q => bad cmd-word-pos got:%d exp:%d", cmdStr, cpos.CmdWordPos, cmdWordPos)
|
||||
}
|
||||
if cpos.CompInvalid != compInvalid {
|
||||
t.Errorf("testCompPos %q => bad comp-invalid exp:%v", cmdStr, compInvalid)
|
||||
}
|
||||
if cpos.CompCommand != compCommand {
|
||||
t.Errorf("testCompPos %q => bad comp-command exp:%v", cmdStr, compCommand)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompPos(t *testing.T) {
|
||||
testCompPos(t, "ls [*]foo", true, 1, false, false, false)
|
||||
testCompPos(t, "ls foo [*];", true, 2, false, false, false)
|
||||
testCompPos(t, "ls foo ;[*]", false, 0, false, false, true)
|
||||
testCompPos(t, "ls foo >[*]> ./bar", true, 2, true, true, false)
|
||||
testCompPos(t, "l[*]s", true, 0, true, false, false)
|
||||
testCompPos(t, "ls[*]", true, 0, true, false, false)
|
||||
testCompPos(t, "x=10 { (ls ./f[*] more); ls }", true, 1, true, false, false)
|
||||
testCompPos(t, "for x in 1[*] 2 3; do ", false, 0, true, false, false)
|
||||
testCompPos(t, "for[*] x in 1 2 3;", false, 0, true, true, false)
|
||||
|
||||
testCompPos(t, "ls \"abc $(ls -l t[*])\" && foo", true, 2, true, false, false)
|
||||
|
||||
testCompPos(t, "ls ${abc:$(ls -l [*])}", true, 1, true, false, false)
|
||||
|
||||
testCompPos(t, `ls abc"$(ls $"echo $(ls ./[*]x) foo)" `, true, 1, true, false, false)
|
||||
}
|
||||
|
||||
@@ -128,6 +128,14 @@ func (sp StrWithPos) String() string {
|
||||
return strWithCursor(sp.Str, sp.Pos)
|
||||
}
|
||||
|
||||
func ParseToSP(s string) StrWithPos {
|
||||
idx := strings.Index(s, "[*]")
|
||||
if idx == -1 {
|
||||
return StrWithPos{Str: s}
|
||||
}
|
||||
return StrWithPos{Str: s[0:idx] + s[idx+3:], Pos: idx}
|
||||
}
|
||||
|
||||
func strWithCursor(str string, pos int) string {
|
||||
if pos < 0 {
|
||||
return "[*]_" + str
|
||||
|
||||
Reference in New Issue
Block a user