From 848f7164a3599206f28bc94982cd8167c645b6e1 Mon Sep 17 00:00:00 2001 From: sawka Date: Wed, 9 Nov 2022 20:38:28 -0800 Subject: [PATCH] checkpoint on completion --- pkg/cmdrunner/shparse.go | 17 ++++------ pkg/comp/comp.go | 71 ++++++++++++++++++++++++++++++++-------- pkg/comp/simplecomp.go | 54 ++++++++++++++++++++++++++++++ 3 files changed, 118 insertions(+), 24 deletions(-) create mode 100644 pkg/comp/simplecomp.go diff --git a/pkg/cmdrunner/shparse.go b/pkg/cmdrunner/shparse.go index 0450b2e3..ccdb7f40 100644 --- a/pkg/cmdrunner/shparse.go +++ b/pkg/cmdrunner/shparse.go @@ -149,12 +149,9 @@ func setBracketArgs(argMap map[string]string, bracketStr string) error { strReader := strings.NewReader(bracketStr) parser := syntax.NewParser(syntax.Variant(syntax.LangBash)) var wordErr error + var ectx shexec.SimpleExpandContext // do not set HomeDir (we don't expand ~ in bracket args) err := parser.Words(strReader, func(w *syntax.Word) bool { - litStr, err := shexec.QuotedLitToStr(w) - if err != nil { - wordErr = fmt.Errorf("invalid expr in bracket args: %v", err) - return false - } + litStr := shexec.SimpleExpandWord(ectx, w, bracketStr) eqIdx := strings.Index(litStr, "=") var varName, varVal string if eqIdx == -1 { @@ -299,7 +296,7 @@ func EvalMetaCommand(ctx context.Context, origPk *scpacket.FeCommandPacketType) return rtnPk, nil } -func parseAliasStmt(stmt *syntax.Stmt) (string, string, error) { +func parseAliasStmt(stmt *syntax.Stmt, sourceStr string) (string, string, error) { cmd := stmt.Cmd callExpr, ok := cmd.(*syntax.CallExpr) if !ok { @@ -313,10 +310,8 @@ func parseAliasStmt(stmt *syntax.Stmt) (string, string, error) { return "", "", fmt.Errorf("invalid alias cmd word (not 'alias')") } secondWord := callExpr.Args[1] - val, err := shexec.QuotedLitToStr(secondWord) - if err != nil { - return "", "", err - } + var ectx shexec.SimpleExpandContext // no homedir, do not want ~ expansion + val := shexec.SimpleExpandWord(ectx, secondWord, sourceStr) eqIdx := strings.Index(val, "=") if eqIdx == -1 { return "", "", fmt.Errorf("no '=' in alias definition") @@ -333,7 +328,7 @@ func ParseAliases(aliases string) (map[string]string, error) { } rtn := make(map[string]string) for _, stmt := range file.Stmts { - aliasName, aliasVal, err := parseAliasStmt(stmt) + aliasName, aliasVal, err := parseAliasStmt(stmt, aliases) if err != nil { // fmt.Printf("stmt-err: %v\n", err) continue diff --git a/pkg/comp/comp.go b/pkg/comp/comp.go index 780dddfa..5d555f64 100644 --- a/pkg/comp/comp.go +++ b/pkg/comp/comp.go @@ -8,6 +8,7 @@ import ( "unicode" "github.com/scripthaus-dev/mshell/pkg/packet" + "github.com/scripthaus-dev/mshell/pkg/shexec" "github.com/scripthaus-dev/sh2-server/pkg/sstore" "mvdan.cc/sh/v3/syntax" ) @@ -23,7 +24,13 @@ const ( SimpleCompGenTypeVariable = "variable" ) -type SimpleCompGenFnType = func(ctx context.Context, point SimpleCompPoint, rptr sstore.RemotePtrType, state *packet.ShellState, args []interface{}) +type CompContext struct { + RemotePtr sstore.RemotePtrType + State *packet.ShellState + ForDisplay bool +} + +type SimpleCompGenFnType = func(ctx context.Context, point SimpleCompPoint, compCtx CompContext, args []interface{}) (*CompReturn, error) type SimpleCompPoint struct { Word string @@ -31,12 +38,14 @@ type SimpleCompPoint struct { } type ParsedWord struct { - Offset int - Word string - Prefix string + Offset int + Word *syntax.Word + PartialWord string + Prefix string } type CompPoint struct { + StmtStr string Words []ParsedWord CompWord int CompWordPos int @@ -44,6 +53,38 @@ type CompPoint struct { Suffix string } +func (p *CompPoint) wordAsStr(w ParsedWord) string { + if w.Word != nil { + return p.StmtStr[w.Word.Pos().Offset():w.Word.End().Offset()] + } + return w.PartialWord +} + +func (p *CompPoint) simpleExpandWord(w ParsedWord) string { + ectx := shexec.SimpleExpandContext{} + if w.Word != nil { + return SimpleExpandWord(ectx, w.Word, p.StmtStr) + } + return SimpleExpandPartialWord(ectx, p.PartialWord, false) +} + +func (p *CompPoint) compPrefix() string { + pword := p.Words[p.CompWord] + if p.CompWordPos == 0 { + return "" + } + wordStr := p.wordAsStr(pword) + if p.CompWordPos == len(wordStr) { + return p.simpleExpandWord(pword) + } + // TODO we can do better, if p.Word is not nil, we can look for which WordPart + // our pos is in. we can then do a normal word expand on the previous parts + // and a partial on just the current part. this is an uncommon case though + // and has very little upside (even bash does not expand multipart words correctly) + partialWordStr := wordStr[:p.CompWordPos] + return SimpleExpandPartialWord(shexec.SimpleExpandContext{}, partialWordStr, false) +} + func (p *CompPoint) dump() { if p.Prefix != "" { fmt.Printf("prefix: %s\n", p.Prefix) @@ -55,9 +96,9 @@ func (p *CompPoint) dump() { fmt.Printf("{%s}", w.Prefix) } if idx == p.CompWord { - fmt.Printf("%s\n", strWithCursor(w.Word, p.CompWordPos)) + fmt.Printf("%s\n", strWithCursor(p.wordAsStr(w), p.CompWordPos)) } else { - fmt.Printf("%s\n", w.Word) + fmt.Printf("%s\n", p.wordAsStr(w)) } } if p.Suffix != "" { @@ -66,8 +107,10 @@ func (p *CompPoint) dump() { fmt.Printf("\n") } +// directories will have a trailing "/" type CompEntry struct { - Word string + Word string + IsMetaCmd bool } type CompReturn struct { @@ -154,6 +197,7 @@ func ParseCompPoint(fullCmdStr string, pos int) (*CompPoint, error) { // fmt.Printf("found: ((%s))%s((%s))\n", rtnPoint.Prefix, strWithCursor(stmtStr, stmtPos), rtnPoint.Suffix) // now, find the word that the pos appears in within the stmt above + rtnPoint.StmtStr = stmtStr stmtReader := strings.NewReader(stmtStr) lastWordPos := 0 parser.Words(stmtReader, func(w *syntax.Word) bool { @@ -162,21 +206,22 @@ func ParseCompPoint(fullCmdStr string, pos int) (*CompPoint, error) { if int(w.Pos().Offset()) > lastWordPos { pword.Prefix = stmtStr[lastWordPos:w.Pos().Offset()] } - pword.Word = stmtStr[w.Pos().Offset():w.End().Offset()] + pword.Word = w rtnPoint.Words = append(rtnPoint.Words, pword) lastWordPos = int(w.End().Offset()) return true }) if lastWordPos < len(stmtStr) { pword := ParsedWord{Offset: lastWordPos} - pword.Prefix, pword.Word = splitInitialWhitespace(stmtStr[lastWordPos:]) + pword.Prefix, pword.PartialWord = splitInitialWhitespace(stmtStr[lastWordPos:]) rtnPoint.Words = append(rtnPoint.Words, pword) } if len(rtnPoint.Words) == 0 { rtnPoint.Words = append(rtnPoint.Words, ParsedWord{}) } for idx, w := range rtnPoint.Words { - if stmtPos > w.Offset && stmtPos <= w.Offset+len(w.Prefix)+len(w.Word) { + wordLen := len(rtnPoint.wordAsStr(w)) + if stmtPos > w.Offset && stmtPos <= w.Offset+len(w.Prefix)+wordLen { rtnPoint.CompWord = idx rtnPoint.CompWordPos = stmtPos - w.Offset - len(w.Prefix) if rtnPoint.CompWordPos < 0 { @@ -184,7 +229,7 @@ func ParseCompPoint(fullCmdStr string, pos int) (*CompPoint, error) { } } } - // rtnPoint.dump() + rtnPoint.dump() return &rtnPoint, nil } @@ -192,8 +237,8 @@ func splitCompWord(p *CompPoint) { w := p.Words[p.CompWord] prefixPos := p.CompWordPos + len(w.Prefix) - w1 := ParsedWord{Offset: w.Offset, Prefix: w.Prefix[:prefixPos], Word: ""} - w2 := ParsedWord{Offset: w.Offset + prefixPos, Prefix: w.Prefix[prefixPos:], Word: w.Word} + w1 := ParsedWord{Offset: w.Offset, Prefix: w.Prefix[:prefixPos]} + w2 := ParsedWord{Offset: w.Offset + prefixPos, Prefix: w.Prefix[prefixPos:], Word: w.Word, PartialWord: w.PartialWord} p.CompWord = p.CompWord // the same (w1) p.CompWordPos = 0 // will be at 0 since w1 has a word length of 0 var newWords []ParsedWord diff --git a/pkg/comp/simplecomp.go b/pkg/comp/simplecomp.go new file mode 100644 index 00000000..b34a2e7b --- /dev/null +++ b/pkg/comp/simplecomp.go @@ -0,0 +1,54 @@ +package comp + +import ( + "context" + "fmt" + + "github.com/google/uuid" + "github.com/scripthaus-dev/mshell/pkg/packet" + "github.com/scripthaus-dev/sh2-server/pkg/remote" +) + +func compsToCompReturn(comps []string, hasMore bool) *CompReturn { + var rtn CompReturn + rtn.HasMore = hasMore + for _, comp := range comps { + rtn.Entries = append(rtn.Entries, CompEntry{Word: comp}) + } + return &rtn +} + +func doCompGen(ctx context.Context, compCtx CompContext, prefix string, compType string, forDisplay bool) (*CompReturn, error) { + if !packet.IsValidCompGenType(compType) { + return nil, false, fmt.Errorf("/compgen invalid type '%s'", compType) + } + msh := remote.GetRemoteById(compCtx.RemotePtr.RemoteId) + if msh == nil { + return nil, false, fmt.Errorf("invalid remote '%s', not found", compCtx.RemotePtr) + } + cgPacket := packet.MakeCompGenPacket() + cgPacket.ReqId = uuid.New().String() + cgPacket.CompType = compType + cgPacket.Prefix = prefix + cgPacket.Cwd = compCtx.State.Cwd + resp, err := msh.PacketRpc(ctx, cgPacket) + if err != nil { + return nil, false, err + } + if err = resp.Err(); err != nil { + return nil, false, err + } + comps := getStrArr(resp.Data, "comps") + hasMore := getBool(resp.Data, "hasmore") + return compsToCompReturn(conmps, hasMore), nil +} + +func SimpleCompFile(ctx context.Context, point SimpleCompPoint, compCtx CompContext, args []interface{}) (*CompReturn, error) { + pword := point.Words[p.CompWord] + prefix := "" + crtn, err := doCompGen(ctx, prefix, "file") + if err != nil { + return nil, err + } + return crtn, nil +}