Some work
This commit is contained in:
99
helpers/levenshtein.go
Normal file
99
helpers/levenshtein.go
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
/*
|
||||||
|
Copyright © Brian Buller <brian@bullercodeworks.com>
|
||||||
|
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
package helpers
|
||||||
|
|
||||||
|
import "unicode/utf8"
|
||||||
|
|
||||||
|
const levMinLngthThreshold = 32
|
||||||
|
|
||||||
|
func ComputeLevDistance(a, b string) int {
|
||||||
|
if len(a) == 0 {
|
||||||
|
return utf8.RuneCountInString(b)
|
||||||
|
}
|
||||||
|
if len(b) == 0 {
|
||||||
|
return utf8.RuneCountInString(a)
|
||||||
|
}
|
||||||
|
|
||||||
|
if a == b {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
s1 := []rune(a)
|
||||||
|
s2 := []rune(b)
|
||||||
|
|
||||||
|
if len(s1) > len(s2) {
|
||||||
|
s1, s2 = s2, s1
|
||||||
|
}
|
||||||
|
|
||||||
|
s1, s2 = trimLongestCommonSuffix(s1, s2)
|
||||||
|
s1, s2 = trimLongestCommonPrefix(s1, s2)
|
||||||
|
|
||||||
|
lenS1 := len(s1)
|
||||||
|
lenS2 := len(s2)
|
||||||
|
|
||||||
|
// Init the row
|
||||||
|
var x []uint16
|
||||||
|
if lenS1+1 > levMinLngthThreshold {
|
||||||
|
x = make([]uint16, lenS1+1)
|
||||||
|
} else {
|
||||||
|
x = make([]uint16, levMinLngthThreshold)
|
||||||
|
x = x[:lenS1+1]
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 1; i < len(x); i++ {
|
||||||
|
x[i] = uint16(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = x[lenS1]
|
||||||
|
y := x[1:]
|
||||||
|
y = y[:lenS1]
|
||||||
|
for i := 0; i < lenS2; i++ {
|
||||||
|
prev := uint16(i + 1)
|
||||||
|
for j := 0; j < lenS1; j++ {
|
||||||
|
current := x[j]
|
||||||
|
if s2[i] != s1[j] {
|
||||||
|
current = min(x[j], prev, y[j]) + 1
|
||||||
|
}
|
||||||
|
x[j] = prev
|
||||||
|
prev = current
|
||||||
|
}
|
||||||
|
x[lenS1] = prev
|
||||||
|
}
|
||||||
|
return int(x[lenS1])
|
||||||
|
}
|
||||||
|
|
||||||
|
func trimLongestCommonSuffix(a, b []rune) ([]rune, []rune) {
|
||||||
|
m := min(len(a), len(b))
|
||||||
|
a2 := a[len(a)-m:]
|
||||||
|
b2 := b[len(b)-m:]
|
||||||
|
i := len(a2)
|
||||||
|
b2 = b2[:i]
|
||||||
|
for ; i > 0 && a2[i-1] == b2[i-1]; i-- {
|
||||||
|
}
|
||||||
|
return a[:len(a)-len(a2)+i], b[:len(b)-len(b2)+i]
|
||||||
|
}
|
||||||
|
|
||||||
|
func trimLongestCommonPrefix(a, b []rune) ([]rune, []rune) {
|
||||||
|
var i int
|
||||||
|
for m := min(len(a), len(b)); i < m && a[i] == b[i]; i++ {
|
||||||
|
}
|
||||||
|
return a[i:], b[i:]
|
||||||
|
}
|
||||||
143
wdgt_cli.go
143
wdgt_cli.go
@@ -23,7 +23,6 @@ package widgets
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
wh "git.bullercodeworks.com/brian/tcell-widgets/helpers"
|
wh "git.bullercodeworks.com/brian/tcell-widgets/helpers"
|
||||||
@@ -53,13 +52,12 @@ type Cli struct {
|
|||||||
history []string
|
history []string
|
||||||
historyPosition int
|
historyPosition int
|
||||||
|
|
||||||
commands []string
|
commands []*CliCommand
|
||||||
commandMap map[string]cliCommand
|
|
||||||
commandGuessMap map[string]func(args ...string) string
|
|
||||||
|
|
||||||
keyMap KeyMap
|
keyMap KeyMap
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Fix Command/SubCommand finding
|
||||||
var _ Widget = (*Cli)(nil)
|
var _ Widget = (*Cli)(nil)
|
||||||
|
|
||||||
func NewCli(id string, s tcell.Style) *Cli {
|
func NewCli(id string, s tcell.Style) *Cli {
|
||||||
@@ -146,6 +144,7 @@ func (w *Cli) Draw(screen tcell.Screen) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
x, y := w.x+1, w.y+1+w.h-3
|
x, y := w.x+1, w.y+1+w.h-3
|
||||||
|
if !w.minimized {
|
||||||
for i := 0; i < w.h-2; i++ {
|
for i := 0; i < w.h-2; i++ {
|
||||||
if len(w.log) > (i + w.logPosition) {
|
if len(w.log) > (i + w.logPosition) {
|
||||||
idx := len(w.log) - (i + w.logPosition) - 1
|
idx := len(w.log) - (i + w.logPosition) - 1
|
||||||
@@ -162,6 +161,7 @@ func (w *Cli) Draw(screen tcell.Screen) {
|
|||||||
y--
|
y--
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
y = w.y + w.h - 1
|
y = w.y + w.h - 1
|
||||||
cursor := " "
|
cursor := " "
|
||||||
pre := "> "
|
pre := "> "
|
||||||
@@ -258,26 +258,29 @@ func (w *Cli) initKeyMap() {
|
|||||||
tcell.KeyTab: func(ev *tcell.EventKey) bool {
|
tcell.KeyTab: func(ev *tcell.EventKey) bool {
|
||||||
// Auto-complete
|
// Auto-complete
|
||||||
guess := w.findBestGuess()
|
guess := w.findBestGuess()
|
||||||
if guess != "" {
|
if guess != nil {
|
||||||
w.value = guess
|
wrk := strings.Split(w.value, " ")
|
||||||
|
wrk = append(wrk[:len(wrk)-1], guess.cmd)
|
||||||
|
w.value = strings.Join(wrk, " ")
|
||||||
w.cursor = len(w.value)
|
w.cursor = len(w.value)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
},
|
},
|
||||||
tcell.KeyEnter: func(ev *tcell.EventKey) bool {
|
tcell.KeyEnter: func(ev *tcell.EventKey) bool {
|
||||||
cmds := strings.Split(w.value, " ")
|
args := strings.Split(w.value, " ")
|
||||||
if v, ok := w.commandMap[cmds[0]]; ok {
|
|
||||||
w.history = append(w.history, w.value)
|
|
||||||
w.historyPosition = -1
|
w.historyPosition = -1
|
||||||
w.value = ""
|
w.value = ""
|
||||||
w.cursor = 0
|
w.cursor = 0
|
||||||
if len(cmds) > 1 {
|
v := w.value
|
||||||
return v(cmds[1:]...)
|
if len(args) > 0 {
|
||||||
} else {
|
v = args[0]
|
||||||
return v()
|
|
||||||
}
|
}
|
||||||
|
if wrk := w.GetCommandFor(v); wrk != nil {
|
||||||
|
w.history = append(w.history, w.value)
|
||||||
|
return wrk.Run(args...)
|
||||||
}
|
}
|
||||||
|
w.history = append(w.history, fmt.Sprintf("%s: command not found", w.value))
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
tcell.KeyPgUp: func(ev *tcell.EventKey) bool {
|
tcell.KeyPgUp: func(ev *tcell.EventKey) bool {
|
||||||
@@ -309,33 +312,56 @@ func (w *Cli) SetValue(val string) {
|
|||||||
w.cursor = len(val)
|
w.cursor = len(val)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Cli) AddComamnd(cmd string, do cliCommand) {
|
func (w *Cli) GetCommandFor(txt string) *CliCommand {
|
||||||
|
for i := range w.commands {
|
||||||
|
if w.commands[i].cmd == txt {
|
||||||
|
return w.commands[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Cli) HasCommand(cmd *CliCommand) bool {
|
||||||
|
for i := range w.commands {
|
||||||
|
if w.commands[i].cmd == cmd.cmd {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Cli) AddCommand(cmd *CliCommand) {
|
||||||
|
if !w.HasCommand(cmd) {
|
||||||
w.commands = append(w.commands, cmd)
|
w.commands = append(w.commands, cmd)
|
||||||
sort.Strings(w.commands)
|
}
|
||||||
w.commandMap[cmd] = do
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Cli) RemoveCommand(cmd string) {
|
func (w *Cli) RemoveCommand(cmd string) {
|
||||||
var idx int
|
var idx int
|
||||||
for idx = range w.commands {
|
for idx = range w.commands {
|
||||||
if w.commands[idx] == cmd {
|
if w.commands[idx].cmd == cmd {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
w.commands = append(w.commands[:idx], w.commands[idx+1:]...)
|
w.commands = append(w.commands[:idx], w.commands[idx+1:]...)
|
||||||
delete(w.commandMap, cmd)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Cli) findBestGuess() string {
|
func (w *Cli) findBestGuess() *CliCommand {
|
||||||
if w.value == "" {
|
if w.value == "" {
|
||||||
return ""
|
return nil
|
||||||
|
}
|
||||||
|
args := strings.Split(w.value, " ")
|
||||||
|
if len(args) > 1 {
|
||||||
|
if wrk := w.GetCommandFor(args[0]); wrk != nil {
|
||||||
|
return wrk.findBestGuess(args[1:]...)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for _, v := range w.commands {
|
for _, v := range w.commands {
|
||||||
if strings.HasPrefix(v, w.value) {
|
if strings.HasPrefix(v.cmd, w.value) {
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ""
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Cli) Log(txt string, args ...any) {
|
func (w *Cli) Log(txt string, args ...any) {
|
||||||
@@ -352,10 +378,71 @@ func (w *Cli) Clear() {
|
|||||||
func (w *Cli) SetMinimized(m bool) { w.minimized = m }
|
func (w *Cli) SetMinimized(m bool) { w.minimized = m }
|
||||||
func (w *Cli) IsMinimized() bool { return w.minimized }
|
func (w *Cli) IsMinimized() bool { return w.minimized }
|
||||||
|
|
||||||
type cliCommand func(args ...string) bool
|
type CliCommand struct {
|
||||||
|
cmd string
|
||||||
// TODO
|
do func(args ...string) bool
|
||||||
func (c *cliCommand) findBestGuess(args ...string) string {
|
subCommands []*CliCommand
|
||||||
_ = args
|
}
|
||||||
return ""
|
|
||||||
|
func NewCliCommand(nm string, do func(args ...string) bool, subs ...*CliCommand) *CliCommand {
|
||||||
|
c := CliCommand{
|
||||||
|
cmd: nm,
|
||||||
|
do: do,
|
||||||
|
}
|
||||||
|
for i := range subs {
|
||||||
|
c.AddCommand(subs[i])
|
||||||
|
}
|
||||||
|
return &c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CliCommand) Run(args ...string) bool {
|
||||||
|
if len(c.subCommands) > 0 {
|
||||||
|
wrk := c.findBestGuess(args...)
|
||||||
|
if wrk != nil {
|
||||||
|
return wrk.Run(args...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if c.do != nil {
|
||||||
|
return c.do(args...)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CliCommand) AddCommand(cc *CliCommand) error {
|
||||||
|
if c.HasCommand(cc.cmd) {
|
||||||
|
return fmt.Errorf("command already exists: %s", cc.cmd)
|
||||||
|
}
|
||||||
|
c.subCommands = append(c.subCommands, cc)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CliCommand) HasCommand(cmd string) bool {
|
||||||
|
for i := range c.subCommands {
|
||||||
|
if c.subCommands[i].cmd == cmd {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CliCommand) GetCommandFor(cmd string) *CliCommand {
|
||||||
|
for i := range c.subCommands {
|
||||||
|
if c.subCommands[i].cmd == cmd {
|
||||||
|
return c.subCommands[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CliCommand) findBestGuess(args ...string) *CliCommand {
|
||||||
|
if len(args) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Find the best guess
|
||||||
|
for _, v := range c.subCommands {
|
||||||
|
if strings.HasPrefix(args[0], v.cmd) {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -212,7 +212,12 @@ func (w *SimpleList) MoveDown() bool {
|
|||||||
}
|
}
|
||||||
func (w *SimpleList) SetTitle(ttl string) { w.title = ttl }
|
func (w *SimpleList) SetTitle(ttl string) { w.title = ttl }
|
||||||
func (w *SimpleList) SetList(l []string) { w.list = l }
|
func (w *SimpleList) SetList(l []string) { w.list = l }
|
||||||
func (w *SimpleList) Clear() { w.list = []string{} }
|
func (w *SimpleList) Clear() {
|
||||||
|
w.list = []string{}
|
||||||
|
for k := range w.itemsStyle {
|
||||||
|
delete(w.itemsStyle, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
func (w *SimpleList) Add(l string) { w.list = append(w.list, l) }
|
func (w *SimpleList) Add(l string) { w.list = append(w.list, l) }
|
||||||
func (w *SimpleList) Remove(l string) {
|
func (w *SimpleList) Remove(l string) {
|
||||||
var idx int
|
var idx int
|
||||||
@@ -243,6 +248,14 @@ func (w *SimpleList) SetItem(idx int, txt string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
func (w *SimpleList) SelectedIndex() int { return w.cursor }
|
func (w *SimpleList) SelectedIndex() int { return w.cursor }
|
||||||
|
func (w *SimpleList) SetSelectedIndex(i int) {
|
||||||
|
if i < 0 {
|
||||||
|
i = 0
|
||||||
|
} else if i >= len(w.list) {
|
||||||
|
i = len(w.list) - 1
|
||||||
|
}
|
||||||
|
w.cursor = i
|
||||||
|
}
|
||||||
func (w *SimpleList) ClearBorder() { w.border = []rune{} }
|
func (w *SimpleList) ClearBorder() { w.border = []rune{} }
|
||||||
func (w *SimpleList) SetOnSelect(s func(int, string) bool) { w.onSelect = s }
|
func (w *SimpleList) SetOnSelect(s func(int, string) bool) { w.onSelect = s }
|
||||||
func (w *SimpleList) SetVimMode(b bool) { w.vimMode = b }
|
func (w *SimpleList) SetVimMode(b bool) { w.vimMode = b }
|
||||||
|
|||||||
Reference in New Issue
Block a user