diff --git a/go.mod b/go.mod index df0ad8f6..64c9d1b4 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/aquasecurity/libbpfgo v0.1.2-0.20210708203834-4928d36fafac github.com/cosiner/argv v0.1.0 github.com/creack/pty v1.1.9 + github.com/derekparker/trie v0.0.0-20200317170641-1fdf38b7b0e9 github.com/google/go-dap v0.5.0 github.com/hashicorp/golang-lru v0.5.4 github.com/mattn/go-colorable v0.0.9 diff --git a/go.sum b/go.sum index 09b09d2d..3e6abe01 100644 --- a/go.sum +++ b/go.sum @@ -44,6 +44,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/derekparker/trie v0.0.0-20200317170641-1fdf38b7b0e9 h1:G765iDCq7bP5opdrPkXk+4V3yfkgV9iGFuheWZ/X/zY= +github.com/derekparker/trie v0.0.0-20200317170641-1fdf38b7b0e9/go.mod h1:D6ICZm05D9VN1n/8iOtBxLpXtoGp6HDFUJ1RNVieOSE= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= diff --git a/pkg/terminal/command.go b/pkg/terminal/command.go index 2736e469..87f9ed5f 100644 --- a/pkg/terminal/command.go +++ b/pkg/terminal/command.go @@ -590,10 +590,10 @@ func (c *Commands) Register(cmdstr string, cf cmdfunc, helpMsg string) { // Find will look up the command function for the given command input. // If it cannot find the command it will default to noCmdAvailable(). // If the command is an empty string it will replay the last command. -func (c *Commands) Find(cmdstr string, prefix cmdPrefix) cmdfunc { +func (c *Commands) Find(cmdstr string, prefix cmdPrefix) command { // If use last command, if there was one. if cmdstr == "" { - return nullCommand + return command{aliases: []string{"nullcmd"}, cmdFn: nullCommand} } for _, v := range c.cmds { @@ -601,11 +601,11 @@ func (c *Commands) Find(cmdstr string, prefix cmdPrefix) cmdfunc { if prefix != noPrefix && v.allowedPrefixes&prefix == 0 { continue } - return v.cmdFn + return v } } - return noCmdAvailable + return command{aliases: []string{"nocmd"}, cmdFn: noCmdAvailable} } // CallWithContext takes a command and a context that command should be executed in. @@ -616,7 +616,7 @@ func (c *Commands) CallWithContext(cmdstr string, t *Term, ctx callContext) erro if len(vals) > 1 { args = strings.TrimSpace(vals[1]) } - return c.Find(cmdname, ctx.Prefix)(t, ctx, args) + return c.Find(cmdname, ctx.Prefix).cmdFn(t, ctx, args) } // Call takes a command to execute. diff --git a/pkg/terminal/command_test.go b/pkg/terminal/command_test.go index a80b6203..93b80da4 100644 --- a/pkg/terminal/command_test.go +++ b/pkg/terminal/command_test.go @@ -178,7 +178,7 @@ func withTestTerminalBuildFlags(name string, t testing.TB, buildFlags test.Build func TestCommandDefault(t *testing.T) { var ( cmds = Commands{} - cmd = cmds.Find("non-existant-command", noPrefix) + cmd = cmds.Find("non-existant-command", noPrefix).cmdFn ) err := cmd(nil, callContext{}, "") @@ -194,7 +194,7 @@ func TestCommandDefault(t *testing.T) { func TestCommandReplayWithoutPreviousCommand(t *testing.T) { var ( cmds = DebugCommands(nil) - cmd = cmds.Find("", noPrefix) + cmd = cmds.Find("", noPrefix).cmdFn err = cmd(nil, callContext{}, "") ) @@ -206,7 +206,7 @@ func TestCommandReplayWithoutPreviousCommand(t *testing.T) { func TestCommandThread(t *testing.T) { var ( cmds = DebugCommands(nil) - cmd = cmds.Find("thread", noPrefix) + cmd = cmds.Find("thread", noPrefix).cmdFn ) err := cmd(nil, callContext{}, "") diff --git a/pkg/terminal/terminal.go b/pkg/terminal/terminal.go index d484337d..0cfa677d 100644 --- a/pkg/terminal/terminal.go +++ b/pkg/terminal/terminal.go @@ -10,6 +10,7 @@ import ( "sync" "syscall" + "github.com/derekparker/trie" "github.com/peterh/liner" "github.com/go-delve/delve/pkg/config" @@ -210,21 +211,32 @@ func (t *Term) Run() (int, error) { signal.Notify(ch, syscall.SIGINT) go t.sigintGuard(ch, multiClient) - t.line.SetCompleter(func(line string) (c []string) { - if strings.HasPrefix(line, "break ") || strings.HasPrefix(line, "b ") { - filter := line[strings.Index(line, " ")+1:] - funcs, _ := t.client.ListFunctions(filter) - for _, f := range funcs { - c = append(c, "break "+f) - } - return + fns := trie.New() + cmds := trie.New() + funcs, _ := t.client.ListFunctions("") + for _, fn := range funcs { + fns.Add(fn, nil) + } + for _, cmd := range t.cmds.cmds { + for _, alias := range cmd.aliases { + cmds.Add(alias, nil) } - for _, cmd := range t.cmds.cmds { - for _, alias := range cmd.aliases { - if strings.HasPrefix(alias, strings.ToLower(line)) { - c = append(c, alias) + } + + t.line.SetCompleter(func(line string) (c []string) { + cmd := t.cmds.Find(strings.Split(line, " ")[0], noPrefix) + switch cmd.aliases[0] { + case "break", "trace", "continue": + if spc := strings.LastIndex(line, " "); spc > 0 { + prefix := line[:spc] + " " + funcs := fns.FuzzySearch(line[spc+1:]) + for _, f := range funcs { + c = append(c, prefix+f) } } + case "nullcmd", "nocmd": + commands := cmds.FuzzySearch(strings.ToLower(line)) + c = append(c, commands...) } return }) diff --git a/vendor/github.com/derekparker/trie/.deepsource.toml b/vendor/github.com/derekparker/trie/.deepsource.toml new file mode 100644 index 00000000..3af3d2df --- /dev/null +++ b/vendor/github.com/derekparker/trie/.deepsource.toml @@ -0,0 +1,13 @@ +version = 1 + +test_patterns = ["*_test.go"] + +exclude_patterns = ["vendor/*"] + +[[analyzers]] +name = "go" +enabled = true + + [analyzers.meta] + import_path = "github.com/derekparker/trie" + dependencies_vendored = true \ No newline at end of file diff --git a/vendor/github.com/derekparker/trie/.gitignore b/vendor/github.com/derekparker/trie/.gitignore new file mode 100644 index 00000000..9ed3b07c --- /dev/null +++ b/vendor/github.com/derekparker/trie/.gitignore @@ -0,0 +1 @@ +*.test diff --git a/vendor/github.com/derekparker/trie/LICENSE b/vendor/github.com/derekparker/trie/LICENSE new file mode 100644 index 00000000..5ee9d2f1 --- /dev/null +++ b/vendor/github.com/derekparker/trie/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2014 Derek Parker + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/derekparker/trie/README.md b/vendor/github.com/derekparker/trie/README.md new file mode 100644 index 00000000..03e765d0 --- /dev/null +++ b/vendor/github.com/derekparker/trie/README.md @@ -0,0 +1,62 @@ +[![GoDoc](https://godoc.org/github.com/derekparker/trie?status.svg)](https://godoc.org/github.com/derekparker/trie) + +# Trie +Data structure and relevant algorithms for extremely fast prefix/fuzzy string searching. + +## Usage + +Create a Trie with: + +```Go +t := trie.New() +``` + +Add Keys with: + +```Go +// Add can take in meta information which can be stored with the key. +// i.e. you could store any information you would like to associate with +// this particular key. +t.Add("foobar", 1) +``` + +Find a key with: + +```Go +node, ok := t.Find("foobar") +meta := node.Meta() +// use meta with meta.(type) +``` + +Remove Keys with: + +```Go +t.Remove("foobar") +``` + +Prefix search with: + +```Go +t.PrefixSearch("foo") +``` + +Fast test for valid prefix: +```Go +t.HasKeysWithPrefix("foo") +``` + +Fuzzy search with: + +```Go +t.FuzzySearch("fb") +``` + +## Contributing +Fork this repo and run tests with: + + go test + +Create a feature branch, write your tests and code and submit a pull request. + +## License +MIT diff --git a/vendor/github.com/derekparker/trie/trie.go b/vendor/github.com/derekparker/trie/trie.go new file mode 100644 index 00000000..2837c648 --- /dev/null +++ b/vendor/github.com/derekparker/trie/trie.go @@ -0,0 +1,306 @@ +// Implementation of an R-Way Trie data structure. +// +// A Trie has a root Node which is the base of the tree. +// Each subsequent Node has a letter and children, which are +// nodes that have letter values associated with them. +package trie + +import ( + "sort" + "sync" +) + +type Node struct { + val rune + path string + term bool + depth int + meta interface{} + mask uint64 + parent *Node + children map[rune]*Node + termCount int +} + +type Trie struct { + mu sync.Mutex + root *Node + size int +} + +type ByKeys []string + +func (a ByKeys) Len() int { return len(a) } +func (a ByKeys) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a ByKeys) Less(i, j int) bool { return len(a[i]) < len(a[j]) } + +const nul = 0x0 + +// Creates a new Trie with an initialized root Node. +func New() *Trie { + return &Trie{ + root: &Node{children: make(map[rune]*Node), depth: 0}, + size: 0, + } +} + +// Returns the root node for the Trie. +func (t *Trie) Root() *Node { + return t.root +} + +// Adds the key to the Trie, including meta data. Meta data +// is stored as `interface{}` and must be type cast by +// the caller. +func (t *Trie) Add(key string, meta interface{}) *Node { + t.mu.Lock() + + t.size++ + runes := []rune(key) + bitmask := maskruneslice(runes) + node := t.root + node.mask |= bitmask + node.termCount++ + for i := range runes { + r := runes[i] + bitmask = maskruneslice(runes[i:]) + if n, ok := node.children[r]; ok { + node = n + node.mask |= bitmask + } else { + node = node.NewChild(r, "", bitmask, nil, false) + } + node.termCount++ + } + node = node.NewChild(nul, key, 0, meta, true) + t.mu.Unlock() + + return node +} + +// Finds and returns meta data associated +// with `key`. +func (t *Trie) Find(key string) (*Node, bool) { + node := findNode(t.Root(), []rune(key)) + if node == nil { + return nil, false + } + + node, ok := node.Children()[nul] + if !ok || !node.term { + return nil, false + } + + return node, true +} + +func (t *Trie) HasKeysWithPrefix(key string) bool { + node := findNode(t.Root(), []rune(key)) + return node != nil +} + +// Removes a key from the trie, ensuring that +// all bitmasks up to root are appropriately recalculated. +func (t *Trie) Remove(key string) { + var ( + i int + rs = []rune(key) + node = findNode(t.Root(), []rune(key)) + ) + t.mu.Lock() + + t.size-- + for n := node.Parent(); n != nil; n = n.Parent() { + i++ + if len(n.Children()) > 1 { + r := rs[len(rs)-i] + n.RemoveChild(r) + break + } + } + t.mu.Unlock() +} + +// Returns all the keys currently stored in the trie. +func (t *Trie) Keys() []string { + if t.size == 0 { + return []string{} + } + + return t.PrefixSearch("") +} + +// Performs a fuzzy search against the keys in the trie. +func (t Trie) FuzzySearch(pre string) []string { + keys := fuzzycollect(t.Root(), []rune(pre)) + sort.Sort(ByKeys(keys)) + return keys +} + +// Performs a prefix search against the keys in the trie. +func (t Trie) PrefixSearch(pre string) []string { + node := findNode(t.Root(), []rune(pre)) + if node == nil { + return nil + } + + return collect(node) +} + +// Creates and returns a pointer to a new child for the node. +func (parent *Node) NewChild(val rune, path string, bitmask uint64, meta interface{}, term bool) *Node { + node := &Node{ + val: val, + path: path, + mask: bitmask, + term: term, + meta: meta, + parent: parent, + children: make(map[rune]*Node), + depth: parent.depth + 1, + } + parent.children[node.val] = node + parent.mask |= bitmask + return node +} + +func (n *Node) RemoveChild(r rune) { + delete(n.children, r) + for nd := n.parent; nd != nil; nd = nd.parent { + nd.mask ^= nd.mask + nd.mask |= uint64(1) << uint64(nd.val-'a') + for _, c := range nd.children { + nd.mask |= c.mask + } + } +} + +// Returns the parent of this node. +func (n Node) Parent() *Node { + return n.parent +} + +// Returns the meta information of this node. +func (n Node) Meta() interface{} { + return n.meta +} + +// Returns the children of this node. +func (n Node) Children() map[rune]*Node { + return n.children +} + +func (n Node) Terminating() bool { + return n.term +} + +func (n Node) Val() rune { + return n.val +} + +func (n Node) Depth() int { + return n.depth +} + +// Returns a uint64 representing the current +// mask of this node. +func (n Node) Mask() uint64 { + return n.mask +} + +func findNode(node *Node, runes []rune) *Node { + if node == nil { + return nil + } + + if len(runes) == 0 { + return node + } + + n, ok := node.Children()[runes[0]] + if !ok { + return nil + } + + var nrunes []rune + if len(runes) > 1 { + nrunes = runes[1:] + } else { + nrunes = runes[0:0] + } + + return findNode(n, nrunes) +} + +func maskruneslice(rs []rune) uint64 { + var m uint64 + for _, r := range rs { + m |= uint64(1) << uint64(r-'a') + } + return m +} + +func collect(node *Node) []string { + var ( + n *Node + i int + ) + keys := make([]string, 0, node.termCount) + nodes := make([]*Node, 1, len(node.children)) + nodes[0] = node + for l := len(nodes); l != 0; l = len(nodes) { + i = l - 1 + n = nodes[i] + nodes = nodes[:i] + for _, c := range n.children { + nodes = append(nodes, c) + } + if n.term { + word := n.path + keys = append(keys, word) + } + } + return keys +} + +type potentialSubtree struct { + idx int + node *Node +} + +func fuzzycollect(node *Node, partial []rune) []string { + if len(partial) == 0 { + return collect(node) + } + + var ( + m uint64 + i int + p potentialSubtree + keys []string + ) + + potential := []potentialSubtree{potentialSubtree{node: node, idx: 0}} + for l := len(potential); l > 0; l = len(potential) { + i = l - 1 + p = potential[i] + potential = potential[:i] + m = maskruneslice(partial[p.idx:]) + if (p.node.mask & m) != m { + continue + } + + if p.node.val == partial[p.idx] { + p.idx++ + if p.idx == len(partial) { + keys = append(keys, collect(p.node)...) + continue + } + } + + for _, c := range p.node.children { + potential = append(potential, potentialSubtree{node: c, idx: p.idx}) + } + } + return keys +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 8bdd2269..18db4fba 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -10,6 +10,8 @@ github.com/cpuguy83/go-md2man/v2/md2man # github.com/creack/pty v1.1.9 ## explicit github.com/creack/pty +# github.com/derekparker/trie v0.0.0-20200317170641-1fdf38b7b0e9 +github.com/derekparker/trie # github.com/google/go-dap v0.5.0 ## explicit github.com/google/go-dap