pkg/terminal: Allow fuzzy searching tab completions (#2633)
This patch implements fuzzy searching for tab completions in the terminal client. Under the hood it is using a trie data structure (https://en.wikipedia.org/wiki/Trie) to perform very fast prefix / fuzzy searches.
This commit is contained in:
parent
985eca462c
commit
43d50202f3
1
go.mod
1
go.mod
@ -6,6 +6,7 @@ require (
|
|||||||
github.com/aquasecurity/libbpfgo v0.1.2-0.20210708203834-4928d36fafac
|
github.com/aquasecurity/libbpfgo v0.1.2-0.20210708203834-4928d36fafac
|
||||||
github.com/cosiner/argv v0.1.0
|
github.com/cosiner/argv v0.1.0
|
||||||
github.com/creack/pty v1.1.9
|
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/google/go-dap v0.5.0
|
||||||
github.com/hashicorp/golang-lru v0.5.4
|
github.com/hashicorp/golang-lru v0.5.4
|
||||||
github.com/mattn/go-colorable v0.0.9
|
github.com/mattn/go-colorable v0.0.9
|
||||||
|
2
go.sum
2
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.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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
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/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/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=
|
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||||
|
@ -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.
|
// Find will look up the command function for the given command input.
|
||||||
// If it cannot find the command it will default to noCmdAvailable().
|
// If it cannot find the command it will default to noCmdAvailable().
|
||||||
// If the command is an empty string it will replay the last command.
|
// 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 <enter> use last command, if there was one.
|
// If <enter> use last command, if there was one.
|
||||||
if cmdstr == "" {
|
if cmdstr == "" {
|
||||||
return nullCommand
|
return command{aliases: []string{"nullcmd"}, cmdFn: nullCommand}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, v := range c.cmds {
|
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 {
|
if prefix != noPrefix && v.allowedPrefixes&prefix == 0 {
|
||||||
continue
|
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.
|
// 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 {
|
if len(vals) > 1 {
|
||||||
args = strings.TrimSpace(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.
|
// Call takes a command to execute.
|
||||||
|
@ -178,7 +178,7 @@ func withTestTerminalBuildFlags(name string, t testing.TB, buildFlags test.Build
|
|||||||
func TestCommandDefault(t *testing.T) {
|
func TestCommandDefault(t *testing.T) {
|
||||||
var (
|
var (
|
||||||
cmds = Commands{}
|
cmds = Commands{}
|
||||||
cmd = cmds.Find("non-existant-command", noPrefix)
|
cmd = cmds.Find("non-existant-command", noPrefix).cmdFn
|
||||||
)
|
)
|
||||||
|
|
||||||
err := cmd(nil, callContext{}, "")
|
err := cmd(nil, callContext{}, "")
|
||||||
@ -194,7 +194,7 @@ func TestCommandDefault(t *testing.T) {
|
|||||||
func TestCommandReplayWithoutPreviousCommand(t *testing.T) {
|
func TestCommandReplayWithoutPreviousCommand(t *testing.T) {
|
||||||
var (
|
var (
|
||||||
cmds = DebugCommands(nil)
|
cmds = DebugCommands(nil)
|
||||||
cmd = cmds.Find("", noPrefix)
|
cmd = cmds.Find("", noPrefix).cmdFn
|
||||||
err = cmd(nil, callContext{}, "")
|
err = cmd(nil, callContext{}, "")
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -206,7 +206,7 @@ func TestCommandReplayWithoutPreviousCommand(t *testing.T) {
|
|||||||
func TestCommandThread(t *testing.T) {
|
func TestCommandThread(t *testing.T) {
|
||||||
var (
|
var (
|
||||||
cmds = DebugCommands(nil)
|
cmds = DebugCommands(nil)
|
||||||
cmd = cmds.Find("thread", noPrefix)
|
cmd = cmds.Find("thread", noPrefix).cmdFn
|
||||||
)
|
)
|
||||||
|
|
||||||
err := cmd(nil, callContext{}, "")
|
err := cmd(nil, callContext{}, "")
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/derekparker/trie"
|
||||||
"github.com/peterh/liner"
|
"github.com/peterh/liner"
|
||||||
|
|
||||||
"github.com/go-delve/delve/pkg/config"
|
"github.com/go-delve/delve/pkg/config"
|
||||||
@ -210,21 +211,32 @@ func (t *Term) Run() (int, error) {
|
|||||||
signal.Notify(ch, syscall.SIGINT)
|
signal.Notify(ch, syscall.SIGINT)
|
||||||
go t.sigintGuard(ch, multiClient)
|
go t.sigintGuard(ch, multiClient)
|
||||||
|
|
||||||
t.line.SetCompleter(func(line string) (c []string) {
|
fns := trie.New()
|
||||||
if strings.HasPrefix(line, "break ") || strings.HasPrefix(line, "b ") {
|
cmds := trie.New()
|
||||||
filter := line[strings.Index(line, " ")+1:]
|
funcs, _ := t.client.ListFunctions("")
|
||||||
funcs, _ := t.client.ListFunctions(filter)
|
for _, fn := range funcs {
|
||||||
for _, f := range funcs {
|
fns.Add(fn, nil)
|
||||||
c = append(c, "break "+f)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
for _, cmd := range t.cmds.cmds {
|
for _, cmd := range t.cmds.cmds {
|
||||||
for _, alias := range cmd.aliases {
|
for _, alias := range cmd.aliases {
|
||||||
if strings.HasPrefix(alias, strings.ToLower(line)) {
|
cmds.Add(alias, nil)
|
||||||
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
|
return
|
||||||
})
|
})
|
||||||
|
13
vendor/github.com/derekparker/trie/.deepsource.toml
generated
vendored
Normal file
13
vendor/github.com/derekparker/trie/.deepsource.toml
generated
vendored
Normal file
@ -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
|
1
vendor/github.com/derekparker/trie/.gitignore
generated
vendored
Normal file
1
vendor/github.com/derekparker/trie/.gitignore
generated
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
*.test
|
20
vendor/github.com/derekparker/trie/LICENSE
generated
vendored
Normal file
20
vendor/github.com/derekparker/trie/LICENSE
generated
vendored
Normal file
@ -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.
|
62
vendor/github.com/derekparker/trie/README.md
generated
vendored
Normal file
62
vendor/github.com/derekparker/trie/README.md
generated
vendored
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
[](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
|
306
vendor/github.com/derekparker/trie/trie.go
generated
vendored
Normal file
306
vendor/github.com/derekparker/trie/trie.go
generated
vendored
Normal file
@ -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
|
||||||
|
}
|
2
vendor/modules.txt
vendored
2
vendor/modules.txt
vendored
@ -10,6 +10,8 @@ github.com/cpuguy83/go-md2man/v2/md2man
|
|||||||
# github.com/creack/pty v1.1.9
|
# github.com/creack/pty v1.1.9
|
||||||
## explicit
|
## explicit
|
||||||
github.com/creack/pty
|
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
|
# github.com/google/go-dap v0.5.0
|
||||||
## explicit
|
## explicit
|
||||||
github.com/google/go-dap
|
github.com/google/go-dap
|
||||||
|
Loading…
Reference in New Issue
Block a user