mirror of
https://github.com/shazow/ssh-chat.git
synced 2025-04-26 13:22:18 +03:00
Add NameTrie for predictable, correct name autocomplete
This commit is contained in:
parent
55c1def24d
commit
56cd155e4f
@ -232,3 +232,8 @@ func (r *Room) NamesPrefix(prefix string) []string {
|
|||||||
}
|
}
|
||||||
return names
|
return names
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Room) CompleteName(prefix string) string {
|
||||||
|
name, _ := r.Members.CompleteName(prefix)
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
8
host.go
8
host.go
@ -192,13 +192,7 @@ func (h *Host) Serve() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *Host) completeName(partial string) string {
|
func (h *Host) completeName(partial string) string {
|
||||||
names := h.NamesPrefix(partial)
|
return h.CompleteName(partial)
|
||||||
if len(names) == 0 {
|
|
||||||
// Didn't find anything
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
return names[len(names)-1]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Host) completeCommand(partial string) string {
|
func (h *Host) completeCommand(partial string) string {
|
||||||
|
83
set/name_trie.go
Normal file
83
set/name_trie.go
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
package set
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NameTrie struct {
|
||||||
|
children map[rune]*NameTrie
|
||||||
|
terminates bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func createNameTrie() *NameTrie {
|
||||||
|
return &NameTrie{make(map[rune]*NameTrie), false}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tree *NameTrie) Exists(name string) bool {
|
||||||
|
nameSlice := []rune(name)
|
||||||
|
node, ok := tree.traverse(nameSlice, false)
|
||||||
|
return ok && node.terminates
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tree *NameTrie) Insert(name string) {
|
||||||
|
nameSlice := []rune(name)
|
||||||
|
node, ok := tree.traverse(nameSlice, true)
|
||||||
|
if ok {
|
||||||
|
node.terminates = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tree *NameTrie) Delete(name string) {
|
||||||
|
nameSlice := []rune(name)
|
||||||
|
node, ok := tree.traverse(nameSlice, false)
|
||||||
|
if ok {
|
||||||
|
node.terminates = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tree *NameTrie) ClosestName(prefix string) (name string, ok bool) {
|
||||||
|
fmt.Println(tree, prefix)
|
||||||
|
nameslice := []rune(prefix)
|
||||||
|
node, ok := tree.traverse(nameslice, false)
|
||||||
|
if !ok {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
return node.closestName(prefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (node *NameTrie) closestName(prefix string) (name string, ok bool) {
|
||||||
|
if node.terminates {
|
||||||
|
return prefix, true
|
||||||
|
}
|
||||||
|
keys := []string{}
|
||||||
|
for suffix := range(node.children){keys = append(keys, string(suffix))}
|
||||||
|
sort.Strings(keys)
|
||||||
|
fmt.Println(keys)
|
||||||
|
for i := range keys {
|
||||||
|
child := node.children[[]rune(keys[i])[0]]
|
||||||
|
name, ok := child.closestName(prefix + keys[i])
|
||||||
|
if ok{
|
||||||
|
return name, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tree *NameTrie) traverse(remainder []rune, create bool) (nextTree *NameTrie, ok bool) {
|
||||||
|
nextRune := remainder[0]
|
||||||
|
nextTree, ok = tree.children[nextRune]
|
||||||
|
if !ok {
|
||||||
|
if create {
|
||||||
|
tree.children[nextRune] = createNameTrie()
|
||||||
|
nextTree = tree.children[nextRune]
|
||||||
|
} else {
|
||||||
|
return createNameTrie(), false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(remainder) < 2 {
|
||||||
|
return nextTree, true
|
||||||
|
} else {
|
||||||
|
return nextTree.traverse(remainder[1:], create)
|
||||||
|
}
|
||||||
|
}
|
@ -21,6 +21,7 @@ type Set struct {
|
|||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
lookup map[string]Item
|
lookup map[string]Item
|
||||||
normalize func(string) string
|
normalize func(string) string
|
||||||
|
names *NameTrie
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new set with case-insensitive keys
|
// New creates a new set with case-insensitive keys
|
||||||
@ -28,6 +29,7 @@ func New() *Set {
|
|||||||
return &Set{
|
return &Set{
|
||||||
lookup: map[string]Item{},
|
lookup: map[string]Item{},
|
||||||
normalize: normalize,
|
normalize: normalize,
|
||||||
|
names: createNameTrie(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,6 +38,7 @@ func (s *Set) Clear() int {
|
|||||||
s.Lock()
|
s.Lock()
|
||||||
n := len(s.lookup)
|
n := len(s.lookup)
|
||||||
s.lookup = map[string]Item{}
|
s.lookup = map[string]Item{}
|
||||||
|
s.names = createNameTrie()
|
||||||
s.Unlock()
|
s.Unlock()
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
@ -102,6 +105,7 @@ func (s *Set) Add(item Item) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
s.lookup[key] = item
|
s.lookup[key] = item
|
||||||
|
s.names.Insert(key)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,6 +121,7 @@ func (s *Set) Remove(key string) error {
|
|||||||
return ErrMissing
|
return ErrMissing
|
||||||
}
|
}
|
||||||
delete(s.lookup, key)
|
delete(s.lookup, key)
|
||||||
|
s.names.Delete(key)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -187,6 +192,10 @@ func (s *Set) ListPrefix(prefix string) []Item {
|
|||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Set) CompleteName(name string) (string, bool) {
|
||||||
|
return s.names.ClosestName(name)
|
||||||
|
}
|
||||||
|
|
||||||
func normalize(key string) string {
|
func normalize(key string) string {
|
||||||
return strings.ToLower(key)
|
return strings.ToLower(key)
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user