mirror of
https://github.com/wavetermdev/backup.git
synced 2026-08-05 13:57:07 -07:00
checkpoint on more completion/expansion
This commit is contained in:
+61
-8
@@ -145,12 +145,26 @@ func simpleExpandSubs(buf *bytes.Buffer, info *ExpandInfo, ectx ExpandContext, w
|
||||
}
|
||||
}
|
||||
|
||||
func canExpand(ectx ExpandContext, wtype string) bool {
|
||||
return wtype == WordTypeLit || wtype == WordTypeSQ || wtype == WordTypeDSQ ||
|
||||
wtype == WordTypeDQ || wtype == WordTypeDDQ || wtype == WordTypeGroup
|
||||
}
|
||||
|
||||
func simpleExpandWord(buf *bytes.Buffer, info *ExpandInfo, ectx ExpandContext, word *WordType, pos int) {
|
||||
if pos >= word.contentEndPos() {
|
||||
pos = len(word.Raw)
|
||||
}
|
||||
if pos <= word.contentStartPos() {
|
||||
return
|
||||
if canExpand(ectx, word.Type) {
|
||||
if pos >= word.contentEndPos() {
|
||||
pos = word.contentEndPos()
|
||||
}
|
||||
if pos <= word.contentStartPos() {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if pos >= len(word.Raw) {
|
||||
pos = len(word.Raw)
|
||||
}
|
||||
if pos <= 0 {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
switch word.Type {
|
||||
@@ -177,16 +191,19 @@ func simpleExpandWord(buf *bytes.Buffer, info *ExpandInfo, ectx ExpandContext, w
|
||||
simpleExpandSubs(buf, info, ectx, word, pos)
|
||||
return
|
||||
|
||||
// not expanded
|
||||
case WordTypeSimpleVar:
|
||||
info.HasVar = true
|
||||
buf.WriteString(string(word.Raw[:pos]))
|
||||
return
|
||||
|
||||
// not expanded
|
||||
case WordTypeVarBrace:
|
||||
info.HasVar = true
|
||||
buf.WriteString(string(word.Raw[:pos]))
|
||||
return
|
||||
|
||||
default:
|
||||
if pos > len(word.Raw) {
|
||||
pos = len(word.Raw)
|
||||
}
|
||||
info.HasSpecial = true
|
||||
buf.WriteString(string(word.Raw[:pos]))
|
||||
return
|
||||
@@ -203,3 +220,39 @@ func SimpleExpandPrefix(ectx ExpandContext, word *WordType, pos int) (string, Ex
|
||||
func SimpleExpand(ectx ExpandContext, word *WordType) (string, ExpandInfo) {
|
||||
return SimpleExpandPrefix(ectx, word, len(word.Raw))
|
||||
}
|
||||
|
||||
// returns varname (no '$') and ok (whether this is a valid varname expansion)
|
||||
func SimpleVarNamePrefix(ectx ExpandContext, word *WordType, pos int) (string, bool) {
|
||||
if word.Type != WordTypeSimpleVar && word.Type != WordTypeVarBrace {
|
||||
return "", false
|
||||
}
|
||||
if word.Type == WordTypeSimpleVar {
|
||||
if pos == 0 {
|
||||
return "", false
|
||||
}
|
||||
if pos == 1 {
|
||||
return "", true
|
||||
}
|
||||
if pos > len(word.Raw) {
|
||||
pos = len(word.Raw)
|
||||
}
|
||||
return string(word.Raw[1:pos]), true
|
||||
}
|
||||
|
||||
// word.Type == WordTypeVarBrace
|
||||
// knock '${' off the front, then see if the rest is a valid var name.
|
||||
if pos == 0 || pos == 1 {
|
||||
return "", false
|
||||
}
|
||||
if pos == 2 {
|
||||
return "", true
|
||||
}
|
||||
if pos > word.contentEndPos() {
|
||||
pos = word.contentEndPos()
|
||||
}
|
||||
rawVarName := word.Raw[2:pos]
|
||||
if isSimpleVarName(rawVarName) {
|
||||
return string(rawVarName), true
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
+76
-25
@@ -65,6 +65,15 @@ const (
|
||||
CmdTypeSimple = "simple" // holds real commands
|
||||
)
|
||||
|
||||
const (
|
||||
CompTypeCommand = "command"
|
||||
CompTypeArg = "command-arg"
|
||||
CompTypeInvalid = "invalid"
|
||||
CompTypeVar = "var"
|
||||
CompTypeAssignment = "assignment"
|
||||
CompTypeBasic = "basic"
|
||||
)
|
||||
|
||||
type WordType struct {
|
||||
Type string
|
||||
Offset int
|
||||
@@ -507,36 +516,52 @@ func identifyReservedWords(words []*WordType) {
|
||||
}
|
||||
|
||||
type CompletionPos struct {
|
||||
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)
|
||||
RawPos int // the raw position of cursor
|
||||
SuperOffset int // adjust all offsets in Cmd and CmdWord by SuperOffset
|
||||
|
||||
// index into cmd.Words (only useful when Cmd is not nil, otherwise we look at CompCommand)
|
||||
CompType string // see CompType* constants
|
||||
Cmd *CmdType // nil if between commands or a special completion (otherwise will be a SimpleCommand)
|
||||
// index into cmd.Words (only set when Cmd is not nil, otherwise we look at CompCommand)
|
||||
// 0 means command-word
|
||||
// negative means assignment-words.
|
||||
// can be past the end of Words (means start new word).
|
||||
CmdWordPos int
|
||||
CmdWordPos int
|
||||
CompWord *WordType // set to the word we are completing (nil if we are starting a new word)
|
||||
CompWordOffset int // offset into compword (only if CmdWord is not nil)
|
||||
|
||||
CmdWord *WordType // set to the word we are completing (nil if we are starting a new word)
|
||||
CmdWordOffset int // offset into cmdword (only if CmdWord is not nil)
|
||||
CompInvalid bool // some words cannot be completed (e.g. in the middle of an operator, inside a control structure, etc.)
|
||||
CompCommand bool // set when we think we are the first word of an existing or new command. otherwise we default to file completion
|
||||
}
|
||||
|
||||
func compTypeFromPos(cmdWordPos int) string {
|
||||
if cmdWordPos == 0 {
|
||||
return CompTypeCommand
|
||||
}
|
||||
if cmdWordPos < 0 {
|
||||
return CompTypeAssignment
|
||||
}
|
||||
return CompTypeArg
|
||||
}
|
||||
|
||||
func (cmd *CmdType) findCompletionPos_simple(pos int, superOffset int) CompletionPos {
|
||||
if cmd.Type != CmdTypeSimple {
|
||||
panic("findCompletetionPos_simple only works for CmdTypeSimple")
|
||||
}
|
||||
rtn := CompletionPos{RawPos: pos, SuperOffset: superOffset, Cmd: cmd}
|
||||
for idx, word := range cmd.AssignmentWords {
|
||||
startOffset := word.Offset
|
||||
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, SuperOffset: superOffset, Cmd: cmd, CmdWordPos: idx - len(cmd.AssignmentWords) - 1}
|
||||
rtn.CmdWordPos = idx - len(cmd.AssignmentWords)
|
||||
rtn.CompType = CompTypeAssignment
|
||||
return rtn
|
||||
}
|
||||
if pos <= endOffset {
|
||||
// completing an assignment word
|
||||
return CompletionPos{RawPos: pos, SuperOffset: superOffset, Cmd: cmd, CmdWordPos: idx - len(cmd.AssignmentWords), CmdWord: word, CmdWordOffset: pos - word.Offset}
|
||||
rtn.CmdWordPos = idx - len(cmd.AssignmentWords)
|
||||
rtn.CompWord = word
|
||||
rtn.CompWordOffset = pos - word.Offset
|
||||
rtn.CompType = CompTypeAssignment
|
||||
return rtn
|
||||
}
|
||||
}
|
||||
var foundWord *WordType
|
||||
@@ -546,7 +571,9 @@ func (cmd *CmdType) findCompletionPos_simple(pos int, superOffset int) Completio
|
||||
endOffset := word.Offset + len(word.Raw)
|
||||
if pos <= startOffset {
|
||||
// starting a new word at this position
|
||||
return CompletionPos{RawPos: pos, SuperOffset: superOffset, Cmd: cmd, CmdWordPos: idx}
|
||||
rtn.CmdWordPos = idx
|
||||
rtn.CompType = compTypeFromPos(idx)
|
||||
return rtn
|
||||
}
|
||||
if pos == endOffset && word.Type == WordTypeOp {
|
||||
// operators are special, they can allow a full-word completion at endpos
|
||||
@@ -559,14 +586,21 @@ func (cmd *CmdType) findCompletionPos_simple(pos int, superOffset int) Completio
|
||||
}
|
||||
}
|
||||
if foundWord != nil {
|
||||
rtn.CmdWordPos = foundWordIdx
|
||||
rtn.CompWord = foundWord
|
||||
rtn.CompWordOffset = pos - foundWord.Offset
|
||||
if foundWord.uncompletable() {
|
||||
// invalid completion point
|
||||
return CompletionPos{RawPos: pos, SuperOffset: superOffset, Cmd: cmd, CmdWordPos: foundWordIdx, CmdWord: foundWord, CmdWordOffset: pos - foundWord.Offset, CompInvalid: true}
|
||||
rtn.CompType = CompTypeInvalid
|
||||
return rtn
|
||||
}
|
||||
return CompletionPos{RawPos: pos, SuperOffset: superOffset, Cmd: cmd, CmdWordPos: foundWordIdx, CmdWord: foundWord, CmdWordOffset: pos - foundWord.Offset}
|
||||
rtn.CompType = compTypeFromPos(foundWordIdx)
|
||||
return rtn
|
||||
}
|
||||
// past the end, so we're starting a new word in Cmd
|
||||
return CompletionPos{RawPos: pos, SuperOffset: superOffset, Cmd: cmd, CmdWordPos: len(cmd.Words)}
|
||||
rtn.CmdWordPos = len(cmd.Words)
|
||||
rtn.CompType = CompTypeArg
|
||||
return rtn
|
||||
}
|
||||
|
||||
func (cmd *CmdType) findWordAtPos_none(pos int) *WordType {
|
||||
@@ -642,9 +676,11 @@ func findCompletionPosInWord(word *WordType, pos int, superOffset int) *Completi
|
||||
// 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 findCompletionPosCmds(cmds []*CmdType, pos int, superOffset int) CompletionPos {
|
||||
rtn := CompletionPos{RawPos: pos, SuperOffset: superOffset}
|
||||
if len(cmds) == 0 {
|
||||
// set CompCommand because we're starting a new command
|
||||
return CompletionPos{RawPos: pos, SuperOffset: superOffset, CompCommand: true}
|
||||
rtn.CompType = CompTypeCommand
|
||||
return rtn
|
||||
}
|
||||
for _, cmd := range cmds {
|
||||
endOffset := cmd.endOffset()
|
||||
@@ -654,44 +690,59 @@ func findCompletionPosCmds(cmds []*CmdType, pos int, superOffset int) Completion
|
||||
startOffset := cmd.offset()
|
||||
if cmd.Type == CmdTypeSimple {
|
||||
if pos <= startOffset {
|
||||
return CompletionPos{RawPos: pos, SuperOffset: superOffset, CompCommand: true}
|
||||
rtn.CompType = CompTypeCommand
|
||||
return rtn
|
||||
}
|
||||
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, SuperOffset: superOffset, CompCommand: true}
|
||||
rtn.CompType = CompTypeCommand
|
||||
return rtn
|
||||
}
|
||||
word := cmd.findWordAtPos_none(pos)
|
||||
if word == nil {
|
||||
// just revert to a file completion
|
||||
return CompletionPos{RawPos: pos, SuperOffset: superOffset, CompCommand: false}
|
||||
rtn.CompType = CompTypeBasic
|
||||
return rtn
|
||||
}
|
||||
rtn.CompWord = word
|
||||
rtn.CompWordOffset = pos - word.Offset
|
||||
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, SuperOffset: superOffset, CmdWord: word, CmdWordOffset: pos - word.Offset, CompInvalid: true}
|
||||
rtn.CompType = CompTypeInvalid
|
||||
return rtn
|
||||
}
|
||||
// revert to file completion
|
||||
return CompletionPos{RawPos: pos, SuperOffset: superOffset, CmdWord: word, CmdWordOffset: pos - word.Offset}
|
||||
rtn.CompType = CompTypeBasic
|
||||
return rtn
|
||||
}
|
||||
}
|
||||
// past the end
|
||||
lastCmd := cmds[len(cmds)-1]
|
||||
if lastCmd.Type == CmdTypeSimple {
|
||||
// just extend last command
|
||||
return CompletionPos{RawPos: pos, SuperOffset: superOffset, Cmd: lastCmd, CmdWordPos: len(lastCmd.Words)}
|
||||
rtn.Cmd = lastCmd
|
||||
rtn.CmdWordPos = len(lastCmd.Words)
|
||||
rtn.CompType = CompTypeArg
|
||||
return rtn
|
||||
}
|
||||
// use lastCmd.NoneComplete to see if last command ended on a "separator". use that to set CompCommand
|
||||
return CompletionPos{RawPos: pos, SuperOffset: superOffset, CompCommand: lastCmd.NoneComplete}
|
||||
if lastCmd.NoneComplete {
|
||||
rtn.CompType = CompTypeCommand
|
||||
} else {
|
||||
rtn.CompType = CompTypeBasic
|
||||
}
|
||||
return rtn
|
||||
}
|
||||
|
||||
func FindCompletionPos(cmds []*CmdType, pos int, superOffset int) CompletionPos {
|
||||
cpos := findCompletionPosCmds(cmds, pos, superOffset)
|
||||
if cpos.CmdWord == nil {
|
||||
if cpos.CompWord == nil {
|
||||
return cpos
|
||||
}
|
||||
subPos := findCompletionPosInWord(cpos.CmdWord, cpos.CmdWordOffset, superOffset+cpos.CmdWord.Offset)
|
||||
subPos := findCompletionPosInWord(cpos.CompWord, cpos.CompWordOffset, superOffset+cpos.CompWord.Offset)
|
||||
if subPos == nil {
|
||||
return cpos
|
||||
} else {
|
||||
|
||||
+23
-27
@@ -105,14 +105,17 @@ func TestCmd(t *testing.T) {
|
||||
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) {
|
||||
func testCompPos(t *testing.T, cmdStr string, compType string, hasCommand bool, cmdWordPos int, hasWord 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))
|
||||
fmt.Printf("testCompPos [%d] %q => [%s] %v\n", cmdSP.Pos, cmdStr, cpos.CompType, cpos)
|
||||
if cpos.CompType != compType {
|
||||
t.Errorf("testCompPos %q => invalid comp-type %q, expected %q", cmdStr, cpos.CompType, compType)
|
||||
}
|
||||
if cpos.CompWord != nil {
|
||||
fmt.Printf(" found-word: %d %s\n", cpos.CompWordOffset, cpos.CompWord.stringWithPos(cpos.CompWordOffset))
|
||||
}
|
||||
if cpos.Cmd != nil {
|
||||
fmt.Printf(" found-cmd: ")
|
||||
@@ -126,36 +129,27 @@ func testCompPos(t *testing.T, cmdStr string, hasCommand bool, cmdWordPos int, h
|
||||
if (cpos.Cmd != nil) != hasCommand {
|
||||
t.Errorf("testCompPos %q => bad has-command exp:%v", cmdStr, hasCommand)
|
||||
}
|
||||
if (cpos.CmdWord != nil) != hasWord {
|
||||
if (cpos.CompWord != 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)
|
||||
testCompPos(t, "ls [*]foo", CompTypeArg, true, 1, false)
|
||||
testCompPos(t, "ls foo [*];", CompTypeArg, true, 2, false)
|
||||
testCompPos(t, "ls foo ;[*]", CompTypeCommand, false, 0, false)
|
||||
testCompPos(t, "ls foo >[*]> ./bar", CompTypeInvalid, true, 2, true)
|
||||
testCompPos(t, "l[*]s", CompTypeCommand, true, 0, true)
|
||||
testCompPos(t, "ls[*]", CompTypeCommand, true, 0, true)
|
||||
testCompPos(t, "x=10 { (ls ./f[*] more); ls }", CompTypeArg, true, 1, true)
|
||||
testCompPos(t, "for x in 1[*] 2 3; do ", CompTypeBasic, false, 0, true)
|
||||
testCompPos(t, "for[*] x in 1 2 3;", CompTypeInvalid, false, 0, true)
|
||||
testCompPos(t, "ls \"abc $(ls -l t[*])\" && foo", CompTypeArg, true, 2, true)
|
||||
testCompPos(t, "ls ${abc:$(ls -l [*])}", CompTypeArg, true, 1, true) // we don't sub-parse inside of ${}
|
||||
testCompPos(t, `ls abc"$(ls $"echo $(ls ./[*]x) foo)" `, CompTypeArg, true, 1, true)
|
||||
}
|
||||
|
||||
func testExpand(t *testing.T, str string, pos int, expStr string, expInfo *ExpandInfo) {
|
||||
@@ -182,9 +176,11 @@ func testExpand(t *testing.T, str string, pos int, expStr string, expInfo *Expan
|
||||
func TestExpand(t *testing.T) {
|
||||
testExpand(t, "hello", 3, "hel", nil)
|
||||
testExpand(t, "he\\$xabc", 6, "he$xa", nil)
|
||||
testExpand(t, "he${x}abc", 6, "he$xa", nil)
|
||||
testExpand(t, "he${x}abc", 6, "he${x}", nil)
|
||||
testExpand(t, "'hello\"mike'", 8, "hello\"m", nil)
|
||||
testExpand(t, `$'abc\x01def`, 10, "abc\x01d", nil)
|
||||
testExpand(t, `$((2 + 2))`, 6, "$((2 +", &ExpandInfo{HasSpecial: true})
|
||||
testExpand(t, `abc"def"`, 6, "abcde", nil)
|
||||
testExpand(t, `"abc$x$'"'""`, 12, "abc$x\"", nil)
|
||||
testExpand(t, `'he'\''s'`, 9, "he's", nil)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user