mirror of
https://github.com/wavetermdev/waveterm.git
synced 2025-01-24 22:01:33 +01:00
checkpoint on compgen
This commit is contained in:
parent
848f7164a3
commit
055dc7c8ac
@ -20,10 +20,12 @@ import (
|
|||||||
"github.com/scripthaus-dev/mshell/pkg/base"
|
"github.com/scripthaus-dev/mshell/pkg/base"
|
||||||
"github.com/scripthaus-dev/mshell/pkg/packet"
|
"github.com/scripthaus-dev/mshell/pkg/packet"
|
||||||
"github.com/scripthaus-dev/mshell/pkg/shexec"
|
"github.com/scripthaus-dev/mshell/pkg/shexec"
|
||||||
|
"github.com/scripthaus-dev/sh2-server/pkg/comp"
|
||||||
"github.com/scripthaus-dev/sh2-server/pkg/remote"
|
"github.com/scripthaus-dev/sh2-server/pkg/remote"
|
||||||
"github.com/scripthaus-dev/sh2-server/pkg/scbase"
|
"github.com/scripthaus-dev/sh2-server/pkg/scbase"
|
||||||
"github.com/scripthaus-dev/sh2-server/pkg/scpacket"
|
"github.com/scripthaus-dev/sh2-server/pkg/scpacket"
|
||||||
"github.com/scripthaus-dev/sh2-server/pkg/sstore"
|
"github.com/scripthaus-dev/sh2-server/pkg/sstore"
|
||||||
|
"github.com/scripthaus-dev/sh2-server/pkg/utilfn"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -32,6 +34,11 @@ const (
|
|||||||
HistoryTypeGlobal = "global"
|
HistoryTypeGlobal = "global"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
comp.RegisterSimpleCompFn("meta", simpleCompMeta)
|
||||||
|
comp.RegisterSimpleCompFn("command+meta", simpleCompCommandMeta)
|
||||||
|
}
|
||||||
|
|
||||||
const DefaultUserId = "sawka"
|
const DefaultUserId = "sawka"
|
||||||
const MaxNameLen = 50
|
const MaxNameLen = 50
|
||||||
const MaxRemoteAliasLen = 50
|
const MaxRemoteAliasLen = 50
|
||||||
@ -1043,50 +1050,6 @@ func updateHistoryContext(ctx context.Context, line *sstore.LineType, cmd *sstor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getStrArr(v interface{}, field string) []string {
|
|
||||||
if v == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
m, ok := v.(map[string]interface{})
|
|
||||||
if !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
fieldVal := m[field]
|
|
||||||
if fieldVal == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
iarr, ok := fieldVal.([]interface{})
|
|
||||||
if !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
var sarr []string
|
|
||||||
for _, iv := range iarr {
|
|
||||||
if sv, ok := iv.(string); ok {
|
|
||||||
sarr = append(sarr, sv)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return sarr
|
|
||||||
}
|
|
||||||
|
|
||||||
func getBool(v interface{}, field string) bool {
|
|
||||||
if v == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
m, ok := v.(map[string]interface{})
|
|
||||||
if !ok {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
fieldVal := m[field]
|
|
||||||
if fieldVal == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
bval, ok := fieldVal.(bool)
|
|
||||||
if !ok {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return bval
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeInfoFromComps(compType string, comps []string, hasMore bool) sstore.UpdatePacket {
|
func makeInfoFromComps(compType string, comps []string, hasMore bool) sstore.UpdatePacket {
|
||||||
sort.Slice(comps, func(i int, j int) bool {
|
sort.Slice(comps, func(i int, j int) bool {
|
||||||
c1 := comps[i]
|
c1 := comps[i]
|
||||||
@ -1156,6 +1119,23 @@ func longestPrefix(root string, comps []string) string {
|
|||||||
return lcp
|
return lcp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func simpleCompMeta(ctx context.Context, prefix string, compCtx comp.CompContext, args []interface{}) (*comp.CompReturn, error) {
|
||||||
|
compsCmd, _ := comp.DoSimpleComp(ctx, "command", prefix, compCtx, nil)
|
||||||
|
compsMeta, _ := simpleCompCommandMeta(ctx, prefix, compCtx, nil)
|
||||||
|
return comp.CombineCompReturn(compsCmd, compsMeta), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func simpleCompCommandMeta(ctx context.Context, prefix string, compCtx comp.CompContext, args []interface{}) (*comp.CompReturn, error) {
|
||||||
|
rtn := comp.CompReturn{}
|
||||||
|
validCommands := getValidCommands()
|
||||||
|
for _, cmd := range validCommands {
|
||||||
|
if strings.HasPrefix(cmd, prefix) {
|
||||||
|
rtn.Entries = append(rtn.Entries, comp.CompEntry{Word: cmd, IsMetaCmd: true})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &rtn, nil
|
||||||
|
}
|
||||||
|
|
||||||
func doMetaCompGen(ctx context.Context, pk *scpacket.FeCommandPacketType, prefix string, forDisplay bool) ([]string, bool, error) {
|
func doMetaCompGen(ctx context.Context, pk *scpacket.FeCommandPacketType, prefix string, forDisplay bool) ([]string, bool, error) {
|
||||||
ids, err := resolveUiIds(ctx, pk, 0) // best effort
|
ids, err := resolveUiIds(ctx, pk, 0) // best effort
|
||||||
var comps []string
|
var comps []string
|
||||||
@ -1202,8 +1182,8 @@ func doCompGen(ctx context.Context, pk *scpacket.FeCommandPacketType, prefix str
|
|||||||
if err = resp.Err(); err != nil {
|
if err = resp.Err(); err != nil {
|
||||||
return nil, false, err
|
return nil, false, err
|
||||||
}
|
}
|
||||||
comps := getStrArr(resp.Data, "comps")
|
comps := utilfn.GetStrArr(resp.Data, "comps")
|
||||||
hasMore := getBool(resp.Data, "hasmore")
|
hasMore := utilfn.GetBool(resp.Data, "hasmore")
|
||||||
return comps, hasMore, nil
|
return comps, hasMore, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1743,7 +1723,7 @@ func displayStateUpdateDiff(buf *bytes.Buffer, oldState packet.ShellState, newSt
|
|||||||
if newVal.IsExport() {
|
if newVal.IsExport() {
|
||||||
exportStr = "export "
|
exportStr = "export "
|
||||||
}
|
}
|
||||||
buf.WriteString(fmt.Sprintf("%s%s=%s\n", exportStr, key, ShellQuote(newVal.Value, false, 50)))
|
buf.WriteString(fmt.Sprintf("%s%s=%s\n", exportStr, key, utilfn.ShellQuote(newVal.Value, false, 50)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for key, _ := range oldEnvMap {
|
for key, _ := range oldEnvMap {
|
||||||
|
@ -6,9 +6,9 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/alessio/shellescape"
|
|
||||||
"github.com/scripthaus-dev/mshell/pkg/shexec"
|
"github.com/scripthaus-dev/mshell/pkg/shexec"
|
||||||
"github.com/scripthaus-dev/sh2-server/pkg/scpacket"
|
"github.com/scripthaus-dev/sh2-server/pkg/scpacket"
|
||||||
|
"github.com/scripthaus-dev/sh2-server/pkg/utilfn"
|
||||||
"mvdan.cc/sh/v3/expand"
|
"mvdan.cc/sh/v3/expand"
|
||||||
"mvdan.cc/sh/v3/syntax"
|
"mvdan.cc/sh/v3/syntax"
|
||||||
)
|
)
|
||||||
@ -116,31 +116,6 @@ func onlyRawArgs(metaCmd string, metaSubCmd string) bool {
|
|||||||
return metaCmd == "run" || metaCmd == "comment"
|
return metaCmd == "run" || metaCmd == "comment"
|
||||||
}
|
}
|
||||||
|
|
||||||
// minimum maxlen=6
|
|
||||||
func ShellQuote(val string, forceQuote bool, maxLen int) string {
|
|
||||||
if maxLen < 6 {
|
|
||||||
maxLen = 6
|
|
||||||
}
|
|
||||||
rtn := shellescape.Quote(val)
|
|
||||||
if strings.HasPrefix(rtn, "\"") || strings.HasPrefix(rtn, "'") {
|
|
||||||
if len(rtn) > maxLen {
|
|
||||||
return rtn[0:maxLen-4] + "..." + rtn[0:1]
|
|
||||||
}
|
|
||||||
return rtn
|
|
||||||
}
|
|
||||||
if forceQuote {
|
|
||||||
if len(rtn) > maxLen-2 {
|
|
||||||
return "\"" + rtn[0:maxLen-5] + "...\""
|
|
||||||
}
|
|
||||||
return "\"" + rtn + "\""
|
|
||||||
} else {
|
|
||||||
if len(rtn) > maxLen {
|
|
||||||
return rtn[0:maxLen-3] + "..."
|
|
||||||
}
|
|
||||||
return rtn
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func setBracketArgs(argMap map[string]string, bracketStr string) error {
|
func setBracketArgs(argMap map[string]string, bracketStr string) error {
|
||||||
bracketStr = strings.TrimSpace(bracketStr)
|
bracketStr = strings.TrimSpace(bracketStr)
|
||||||
if bracketStr == "" {
|
if bracketStr == "" {
|
||||||
@ -161,7 +136,7 @@ func setBracketArgs(argMap map[string]string, bracketStr string) error {
|
|||||||
varVal = litStr[eqIdx+1:]
|
varVal = litStr[eqIdx+1:]
|
||||||
}
|
}
|
||||||
if !shexec.IsValidBashIdentifier(varName) {
|
if !shexec.IsValidBashIdentifier(varName) {
|
||||||
wordErr = fmt.Errorf("invalid identifier %s in bracket args", ShellQuote(varName, true, 20))
|
wordErr = fmt.Errorf("invalid identifier %s in bracket args", utilfn.ShellQuote(varName, true, 20))
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if varVal == "" {
|
if varVal == "" {
|
||||||
|
169
pkg/comp/comp.go
169
pkg/comp/comp.go
@ -2,17 +2,23 @@
|
|||||||
package comp
|
package comp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
"github.com/scripthaus-dev/mshell/pkg/packet"
|
"github.com/scripthaus-dev/mshell/pkg/packet"
|
||||||
"github.com/scripthaus-dev/mshell/pkg/shexec"
|
"github.com/scripthaus-dev/mshell/pkg/shexec"
|
||||||
"github.com/scripthaus-dev/sh2-server/pkg/sstore"
|
"github.com/scripthaus-dev/sh2-server/pkg/sstore"
|
||||||
|
"github.com/scripthaus-dev/sh2-server/pkg/utilfn"
|
||||||
"mvdan.cc/sh/v3/syntax"
|
"mvdan.cc/sh/v3/syntax"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const MaxCompQuoteLen = 5000
|
||||||
|
|
||||||
const (
|
const (
|
||||||
SimpleCompGenTypeFile = "file"
|
SimpleCompGenTypeFile = "file"
|
||||||
SimpleCompGenTypeDir = "dir"
|
SimpleCompGenTypeDir = "dir"
|
||||||
@ -24,19 +30,31 @@ const (
|
|||||||
SimpleCompGenTypeVariable = "variable"
|
SimpleCompGenTypeVariable = "variable"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
QuoteTypeLiteral = ""
|
||||||
|
QuoteTypeDQ = "\""
|
||||||
|
QuoteTypeANSI = "$'"
|
||||||
|
QuoteTypeSQ = "'"
|
||||||
|
)
|
||||||
|
|
||||||
type CompContext struct {
|
type CompContext struct {
|
||||||
RemotePtr sstore.RemotePtrType
|
RemotePtr sstore.RemotePtrType
|
||||||
State *packet.ShellState
|
State *packet.ShellState
|
||||||
ForDisplay bool
|
ForDisplay bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type SimpleCompGenFnType = func(ctx context.Context, point SimpleCompPoint, compCtx CompContext, args []interface{}) (*CompReturn, error)
|
|
||||||
|
|
||||||
type SimpleCompPoint struct {
|
type SimpleCompPoint struct {
|
||||||
Word string
|
Word string
|
||||||
Pos int
|
Pos int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type fullCompPrefix struct {
|
||||||
|
RawStr string
|
||||||
|
RawPos int
|
||||||
|
CompPrefix string
|
||||||
|
QuoteTypePref string
|
||||||
|
}
|
||||||
|
|
||||||
type ParsedWord struct {
|
type ParsedWord struct {
|
||||||
Offset int
|
Offset int
|
||||||
Word *syntax.Word
|
Word *syntax.Word
|
||||||
@ -53,6 +71,75 @@ type CompPoint struct {
|
|||||||
Suffix string
|
Suffix string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// directories will have a trailing "/"
|
||||||
|
type CompEntry struct {
|
||||||
|
Word string
|
||||||
|
IsMetaCmd bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type CompReturn struct {
|
||||||
|
Entries []CompEntry
|
||||||
|
HasMore bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func compQuoteDQString(s string, close bool) string {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
buf.WriteByte('"')
|
||||||
|
for _, ch := range s {
|
||||||
|
if ch == '"' || ch == '\\' || ch == '$' || ch == '`' {
|
||||||
|
buf.WriteByte('\\')
|
||||||
|
buf.WriteRune(ch)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
buf.WriteRune(ch)
|
||||||
|
}
|
||||||
|
if close {
|
||||||
|
buf.WriteByte('"')
|
||||||
|
}
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func compQuoteString(s string, quoteType string, close bool) string {
|
||||||
|
if quoteType != QuoteTypeANSI {
|
||||||
|
for _, ch := range s {
|
||||||
|
if ch > unicode.MaxASCII || !unicode.IsPrint(ch) || ch == '!' {
|
||||||
|
quoteType = QuoteTypeANSI
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if ch == '\'' {
|
||||||
|
if quoteType == QuoteTypeSQ || quoteType == QuoteTypeLiteral {
|
||||||
|
quoteType = QuoteTypeANSI
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if quoteType == QuoteTypeANSI {
|
||||||
|
rtn := strconv.QuoteToASCII(s)
|
||||||
|
rtn = "$'" + strings.ReplaceAll(rtn[1:len(rtn)-1], "'", "\\'")
|
||||||
|
if close {
|
||||||
|
rtn = rtn + "'"
|
||||||
|
}
|
||||||
|
return rtn
|
||||||
|
}
|
||||||
|
if quoteType == QuoteTypeLiteral {
|
||||||
|
rtn := utilfn.ShellQuote(s, false, MaxCompQuoteLen)
|
||||||
|
if len(rtn) > 0 && rtn[0] == '\'' && !close {
|
||||||
|
rtn = rtn[0 : len(rtn)-1]
|
||||||
|
}
|
||||||
|
return rtn
|
||||||
|
}
|
||||||
|
if quoteType == QuoteTypeSQ {
|
||||||
|
rtn := utilfn.ShellQuote(s, true, MaxCompQuoteLen)
|
||||||
|
if !close {
|
||||||
|
rtn = rtn[0 : len(rtn)-1]
|
||||||
|
}
|
||||||
|
return rtn
|
||||||
|
}
|
||||||
|
// QuoteTypeDQ
|
||||||
|
return compQuoteDQString(s, close)
|
||||||
|
}
|
||||||
|
|
||||||
func (p *CompPoint) wordAsStr(w ParsedWord) string {
|
func (p *CompPoint) wordAsStr(w ParsedWord) string {
|
||||||
if w.Word != nil {
|
if w.Word != nil {
|
||||||
return p.StmtStr[w.Word.Pos().Offset():w.Word.End().Offset()]
|
return p.StmtStr[w.Word.Pos().Offset():w.Word.End().Offset()]
|
||||||
@ -63,16 +150,29 @@ func (p *CompPoint) wordAsStr(w ParsedWord) string {
|
|||||||
func (p *CompPoint) simpleExpandWord(w ParsedWord) string {
|
func (p *CompPoint) simpleExpandWord(w ParsedWord) string {
|
||||||
ectx := shexec.SimpleExpandContext{}
|
ectx := shexec.SimpleExpandContext{}
|
||||||
if w.Word != nil {
|
if w.Word != nil {
|
||||||
return SimpleExpandWord(ectx, w.Word, p.StmtStr)
|
return shexec.SimpleExpandWord(ectx, w.Word, p.StmtStr)
|
||||||
}
|
}
|
||||||
return SimpleExpandPartialWord(ectx, p.PartialWord, false)
|
return shexec.SimpleExpandPartialWord(ectx, w.PartialWord, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *CompPoint) compPrefix() string {
|
func getQuoteTypePref(str string) string {
|
||||||
pword := p.Words[p.CompWord]
|
if strings.HasPrefix(str, QuoteTypeANSI) {
|
||||||
|
return QuoteTypeANSI
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(str, QuoteTypeDQ) {
|
||||||
|
return QuoteTypeDQ
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(str, QuoteTypeSQ) {
|
||||||
|
return QuoteTypeSQ
|
||||||
|
}
|
||||||
|
return QuoteTypeLiteral
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *CompPoint) getCompPrefix() string {
|
||||||
if p.CompWordPos == 0 {
|
if p.CompWordPos == 0 {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
pword := p.Words[p.CompWord]
|
||||||
wordStr := p.wordAsStr(pword)
|
wordStr := p.wordAsStr(pword)
|
||||||
if p.CompWordPos == len(wordStr) {
|
if p.CompWordPos == len(wordStr) {
|
||||||
return p.simpleExpandWord(pword)
|
return p.simpleExpandWord(pword)
|
||||||
@ -82,7 +182,21 @@ func (p *CompPoint) compPrefix() string {
|
|||||||
// and a partial on just the current part. this is an uncommon case though
|
// 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)
|
// and has very little upside (even bash does not expand multipart words correctly)
|
||||||
partialWordStr := wordStr[:p.CompWordPos]
|
partialWordStr := wordStr[:p.CompWordPos]
|
||||||
return SimpleExpandPartialWord(shexec.SimpleExpandContext{}, partialWordStr, false)
|
return shexec.SimpleExpandPartialWord(shexec.SimpleExpandContext{}, partialWordStr, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *CompPoint) extendWord(newWord string, newWordComplete bool) (string, int) {
|
||||||
|
pword := p.Words[p.CompWord]
|
||||||
|
wordStr := p.wordAsStr(pword)
|
||||||
|
quotePref := getQuoteTypePref(wordStr)
|
||||||
|
needsClose := newWordComplete && (len(wordStr) == p.CompWordPos)
|
||||||
|
wordSuffix := wordStr[p.CompWordPos:]
|
||||||
|
newQuotedStr := compQuoteString(newWord, quotePref, needsClose)
|
||||||
|
if needsClose && wordSuffix == "" {
|
||||||
|
newQuotedStr = newQuotedStr + " "
|
||||||
|
}
|
||||||
|
newPos := len(newQuotedStr)
|
||||||
|
return newQuotedStr + wordSuffix, newPos
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *CompPoint) dump() {
|
func (p *CompPoint) dump() {
|
||||||
@ -107,17 +221,6 @@ func (p *CompPoint) dump() {
|
|||||||
fmt.Printf("\n")
|
fmt.Printf("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
// directories will have a trailing "/"
|
|
||||||
type CompEntry struct {
|
|
||||||
Word string
|
|
||||||
IsMetaCmd bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type CompReturn struct {
|
|
||||||
Entries []CompEntry
|
|
||||||
HasMore bool
|
|
||||||
}
|
|
||||||
|
|
||||||
var SimpleCompGenFns map[string]SimpleCompGenFnType
|
var SimpleCompGenFns map[string]SimpleCompGenFnType
|
||||||
|
|
||||||
func strWithCursor(str string, pos int) string {
|
func strWithCursor(str string, pos int) string {
|
||||||
@ -229,7 +332,6 @@ func ParseCompPoint(fullCmdStr string, pos int) (*CompPoint, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rtnPoint.dump()
|
|
||||||
return &rtnPoint, nil
|
return &rtnPoint, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -253,3 +355,32 @@ func splitCompWord(p *CompPoint) {
|
|||||||
func DoCompGen(ctx context.Context, point CompPoint, rptr sstore.RemotePtrType, state *packet.ShellState) (*CompReturn, error) {
|
func DoCompGen(ctx context.Context, point CompPoint, rptr sstore.RemotePtrType, state *packet.ShellState) (*CompReturn, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SortCompReturnEntries(c *CompReturn) {
|
||||||
|
sort.Slice(c.Entries, func(i int, j int) bool {
|
||||||
|
e1 := c.Entries[i]
|
||||||
|
e2 := c.Entries[j]
|
||||||
|
if e1.Word < e2.Word {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if e1.Word == e2.Word && e1.IsMetaCmd && !e2.IsMetaCmd {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func CombineCompReturn(c1 *CompReturn, c2 *CompReturn) *CompReturn {
|
||||||
|
if c1 == nil {
|
||||||
|
return c2
|
||||||
|
}
|
||||||
|
if c2 == nil {
|
||||||
|
return c1
|
||||||
|
}
|
||||||
|
var rtn CompReturn
|
||||||
|
rtn.HasMore = c1.HasMore || c2.HasMore
|
||||||
|
rtn.Entries = append([]CompEntry{}, c1.Entries...)
|
||||||
|
rtn.Entries = append(rtn.Entries, c2.Entries...)
|
||||||
|
SortCompReturnEntries(&rtn)
|
||||||
|
return &rtn
|
||||||
|
}
|
||||||
|
@ -3,12 +3,47 @@ package comp
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/scripthaus-dev/mshell/pkg/packet"
|
"github.com/scripthaus-dev/mshell/pkg/packet"
|
||||||
"github.com/scripthaus-dev/sh2-server/pkg/remote"
|
"github.com/scripthaus-dev/sh2-server/pkg/remote"
|
||||||
|
"github.com/scripthaus-dev/sh2-server/pkg/utilfn"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var globalLock = &sync.Mutex{}
|
||||||
|
var simpleCompMap = map[string]SimpleCompGenFnType{
|
||||||
|
"file": simpleCompFile,
|
||||||
|
"directory": simpleCompDir,
|
||||||
|
"variable": simpleCompVar,
|
||||||
|
"command": simpleCompCommand,
|
||||||
|
}
|
||||||
|
|
||||||
|
type SimpleCompGenFnType = func(ctx context.Context, prefix string, compCtx CompContext, args []interface{}) (*CompReturn, error)
|
||||||
|
|
||||||
|
func RegisterSimpleCompFn(compType string, fn SimpleCompGenFnType) {
|
||||||
|
globalLock.Lock()
|
||||||
|
defer globalLock.Unlock()
|
||||||
|
if _, ok := simpleCompMap[compType]; ok {
|
||||||
|
panic(fmt.Sprintf("simpleCompFn %q already registered", compType))
|
||||||
|
}
|
||||||
|
simpleCompMap[compType] = fn
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSimpleCompFn(compType string) SimpleCompGenFnType {
|
||||||
|
globalLock.Lock()
|
||||||
|
defer globalLock.Unlock()
|
||||||
|
return simpleCompMap[compType]
|
||||||
|
}
|
||||||
|
|
||||||
|
func DoSimpleComp(ctx context.Context, compType string, prefix string, compCtx CompContext, args []interface{}) (*CompReturn, error) {
|
||||||
|
compFn := getSimpleCompFn(compType)
|
||||||
|
if compFn == nil {
|
||||||
|
return nil, fmt.Errorf("no simple comp fn for %q", compType)
|
||||||
|
}
|
||||||
|
return compFn(ctx, prefix, compCtx, args)
|
||||||
|
}
|
||||||
|
|
||||||
func compsToCompReturn(comps []string, hasMore bool) *CompReturn {
|
func compsToCompReturn(comps []string, hasMore bool) *CompReturn {
|
||||||
var rtn CompReturn
|
var rtn CompReturn
|
||||||
rtn.HasMore = hasMore
|
rtn.HasMore = hasMore
|
||||||
@ -18,13 +53,13 @@ func compsToCompReturn(comps []string, hasMore bool) *CompReturn {
|
|||||||
return &rtn
|
return &rtn
|
||||||
}
|
}
|
||||||
|
|
||||||
func doCompGen(ctx context.Context, compCtx CompContext, prefix string, compType string, forDisplay bool) (*CompReturn, error) {
|
func doCompGen(ctx context.Context, prefix string, compType string, compCtx CompContext) (*CompReturn, error) {
|
||||||
if !packet.IsValidCompGenType(compType) {
|
if !packet.IsValidCompGenType(compType) {
|
||||||
return nil, false, fmt.Errorf("/compgen invalid type '%s'", compType)
|
return nil, fmt.Errorf("/compgen invalid type '%s'", compType)
|
||||||
}
|
}
|
||||||
msh := remote.GetRemoteById(compCtx.RemotePtr.RemoteId)
|
msh := remote.GetRemoteById(compCtx.RemotePtr.RemoteId)
|
||||||
if msh == nil {
|
if msh == nil {
|
||||||
return nil, false, fmt.Errorf("invalid remote '%s', not found", compCtx.RemotePtr)
|
return nil, fmt.Errorf("invalid remote '%s', not found", compCtx.RemotePtr)
|
||||||
}
|
}
|
||||||
cgPacket := packet.MakeCompGenPacket()
|
cgPacket := packet.MakeCompGenPacket()
|
||||||
cgPacket.ReqId = uuid.New().String()
|
cgPacket.ReqId = uuid.New().String()
|
||||||
@ -32,23 +67,29 @@ func doCompGen(ctx context.Context, compCtx CompContext, prefix string, compType
|
|||||||
cgPacket.Prefix = prefix
|
cgPacket.Prefix = prefix
|
||||||
cgPacket.Cwd = compCtx.State.Cwd
|
cgPacket.Cwd = compCtx.State.Cwd
|
||||||
resp, err := msh.PacketRpc(ctx, cgPacket)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return crtn, nil
|
if err = resp.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
comps := utilfn.GetStrArr(resp.Data, "comps")
|
||||||
|
hasMore := utilfn.GetBool(resp.Data, "hasmore")
|
||||||
|
return compsToCompReturn(comps, hasMore), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func simpleCompFile(ctx context.Context, prefix string, compCtx CompContext, args []interface{}) (*CompReturn, error) {
|
||||||
|
return doCompGen(ctx, prefix, "file", compCtx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func simpleCompDir(ctx context.Context, prefix string, compCtx CompContext, args []interface{}) (*CompReturn, error) {
|
||||||
|
return doCompGen(ctx, prefix, "directory", compCtx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func simpleCompVar(ctx context.Context, prefix string, compCtx CompContext, args []interface{}) (*CompReturn, error) {
|
||||||
|
return doCompGen(ctx, prefix, "variable", compCtx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func simpleCompCommand(ctx context.Context, prefix string, compCtx CompContext, args []interface{}) (*CompReturn, error) {
|
||||||
|
return doCompGen(ctx, prefix, "command", compCtx)
|
||||||
}
|
}
|
||||||
|
76
pkg/utilfn/utilfn.go
Normal file
76
pkg/utilfn/utilfn.go
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
package utilfn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/alessio/shellescape"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetStrArr(v interface{}, field string) []string {
|
||||||
|
if v == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
m, ok := v.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
fieldVal := m[field]
|
||||||
|
if fieldVal == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
iarr, ok := fieldVal.([]interface{})
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var sarr []string
|
||||||
|
for _, iv := range iarr {
|
||||||
|
if sv, ok := iv.(string); ok {
|
||||||
|
sarr = append(sarr, sv)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sarr
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetBool(v interface{}, field string) bool {
|
||||||
|
if v == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
m, ok := v.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
fieldVal := m[field]
|
||||||
|
if fieldVal == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
bval, ok := fieldVal.(bool)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return bval
|
||||||
|
}
|
||||||
|
|
||||||
|
// minimum maxlen=6
|
||||||
|
func ShellQuote(val string, forceQuote bool, maxLen int) string {
|
||||||
|
if maxLen < 6 {
|
||||||
|
maxLen = 6
|
||||||
|
}
|
||||||
|
rtn := shellescape.Quote(val)
|
||||||
|
if strings.HasPrefix(rtn, "\"") || strings.HasPrefix(rtn, "'") {
|
||||||
|
if len(rtn) > maxLen {
|
||||||
|
return rtn[0:maxLen-4] + "..." + rtn[0:1]
|
||||||
|
}
|
||||||
|
return rtn
|
||||||
|
}
|
||||||
|
if forceQuote {
|
||||||
|
if len(rtn) > maxLen-2 {
|
||||||
|
return "\"" + rtn[0:maxLen-5] + "...\""
|
||||||
|
}
|
||||||
|
return "\"" + rtn + "\""
|
||||||
|
} else {
|
||||||
|
if len(rtn) > maxLen {
|
||||||
|
return rtn[0:maxLen-3] + "..."
|
||||||
|
}
|
||||||
|
return rtn
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user