show completion menu for multiple match
This commit is contained in:
parent
66ce56eb6e
commit
19561f6531
2
app.go
2
app.go
@ -51,7 +51,7 @@ func (app *App) handleInp() {
|
||||
|
||||
return
|
||||
}
|
||||
e := app.ui.getExpr()
|
||||
e := app.ui.getExpr(app.nav)
|
||||
if e == nil {
|
||||
continue
|
||||
}
|
||||
|
108
comp.go
108
comp.go
@ -36,29 +36,26 @@ func matchLongest(s1, s2 string) string {
|
||||
return s1[:i]
|
||||
}
|
||||
|
||||
func matchWord(s string, words []string) string {
|
||||
var match string
|
||||
|
||||
func matchWord(s string, words []string) (matches []string, longest string) {
|
||||
for _, w := range words {
|
||||
if strings.HasPrefix(w, s) {
|
||||
if match != "" {
|
||||
match = matchLongest(match, w)
|
||||
matches = append(matches, w)
|
||||
if longest != "" {
|
||||
longest = matchLongest(longest, w)
|
||||
} else {
|
||||
match = w + " "
|
||||
longest = w + " "
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if match != "" {
|
||||
return match
|
||||
if longest == "" {
|
||||
longest = s
|
||||
}
|
||||
|
||||
return s
|
||||
return
|
||||
}
|
||||
|
||||
func matchExec(s string) string {
|
||||
var match string
|
||||
|
||||
func matchExec(s string) (matches []string, longest string) {
|
||||
paths := strings.Split(envPath, ":")
|
||||
|
||||
for _, p := range paths {
|
||||
@ -81,25 +78,25 @@ func matchExec(s string) string {
|
||||
if !f.Mode().IsRegular() || f.Mode()&0111 == 0 {
|
||||
continue
|
||||
}
|
||||
if match != "" {
|
||||
match = matchLongest(match, f.Name())
|
||||
|
||||
matches = append(matches, f.Name())
|
||||
if longest != "" {
|
||||
longest = matchLongest(longest, f.Name())
|
||||
} else {
|
||||
match = f.Name() + " "
|
||||
longest = f.Name() + " "
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if match != "" {
|
||||
return match
|
||||
if longest == "" {
|
||||
longest = s
|
||||
}
|
||||
|
||||
return s
|
||||
return
|
||||
}
|
||||
|
||||
func matchFile(s string) string {
|
||||
var match string
|
||||
|
||||
func matchFile(s string) (matches []string, longest string) {
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
log.Printf("getting current directory: %s", err)
|
||||
@ -112,81 +109,78 @@ func matchFile(s string) string {
|
||||
|
||||
for _, f := range fi {
|
||||
if strings.HasPrefix(f.Name(), s) {
|
||||
if match != "" {
|
||||
match = matchLongest(match, f.Name())
|
||||
matches = append(matches, f.Name())
|
||||
if longest != "" {
|
||||
longest = matchLongest(longest, f.Name())
|
||||
} else {
|
||||
match = f.Name() + " "
|
||||
longest = f.Name() + " "
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if match != "" {
|
||||
return match
|
||||
if longest == "" {
|
||||
longest = s
|
||||
}
|
||||
|
||||
return s
|
||||
return
|
||||
}
|
||||
|
||||
func compCmd(acc []rune) []rune {
|
||||
func compCmd(acc []rune) (matches []string, longestAcc []rune) {
|
||||
if len(acc) == 0 || acc[len(acc)-1] == ' ' {
|
||||
return acc
|
||||
return matches, acc
|
||||
}
|
||||
|
||||
s := string(acc)
|
||||
f := strings.Fields(s)
|
||||
|
||||
var longest string
|
||||
|
||||
switch len(f) {
|
||||
case 0: // do nothing
|
||||
case 0:
|
||||
longestAcc = acc
|
||||
case 1:
|
||||
words := gCmdWords
|
||||
for c, _ := range gOpts.cmds {
|
||||
words = append(words, c)
|
||||
}
|
||||
return []rune(matchWord(s, words))
|
||||
matches, longest = matchWord(s, words)
|
||||
longestAcc = []rune(longest)
|
||||
default:
|
||||
switch f[0] {
|
||||
case "set":
|
||||
opt := matchWord(f[1], gOptWords)
|
||||
ret := []rune(f[0])
|
||||
ret = append(ret, ' ')
|
||||
ret = append(ret, []rune(opt)...)
|
||||
return ret
|
||||
case "map", "cmd": // do nothing
|
||||
matches, longest = matchWord(f[1], gOptWords)
|
||||
longestAcc = append(acc[:len(acc)-len(f[len(f)-1])], []rune(longest)...)
|
||||
case "map", "cmd":
|
||||
longestAcc = acc
|
||||
default:
|
||||
ret := []rune(f[0])
|
||||
ret = append(ret, ' ')
|
||||
for i := 1; i < len(f); i++ {
|
||||
name := matchFile(f[i])
|
||||
ret = append(ret, []rune(name)...)
|
||||
}
|
||||
return ret
|
||||
matches, longest = matchFile(f[len(f)-1])
|
||||
longestAcc = append(acc[:len(acc)-len(f[len(f)-1])], []rune(longest)...)
|
||||
}
|
||||
}
|
||||
|
||||
return acc
|
||||
return
|
||||
}
|
||||
|
||||
func compShell(acc []rune) []rune {
|
||||
func compShell(acc []rune) (matches []string, longestAcc []rune) {
|
||||
if len(acc) == 0 || acc[len(acc)-1] == ' ' {
|
||||
return acc
|
||||
return matches, acc
|
||||
}
|
||||
|
||||
s := string(acc)
|
||||
f := strings.Fields(s)
|
||||
|
||||
var longest string
|
||||
|
||||
switch len(f) {
|
||||
case 0: // do nothing
|
||||
case 0:
|
||||
longestAcc = acc
|
||||
case 1:
|
||||
return []rune(matchExec(s))
|
||||
matches, longest = matchExec(s)
|
||||
longestAcc = []rune(longest)
|
||||
default:
|
||||
ret := []rune(f[0])
|
||||
ret = append(ret, ' ')
|
||||
for i := 1; i < len(f); i++ {
|
||||
name := matchFile(f[i])
|
||||
ret = append(ret, []rune(name)...)
|
||||
}
|
||||
return ret
|
||||
matches, longest = matchFile(f[len(f)-1])
|
||||
longestAcc = append(acc[:len(acc)-len(f[len(f)-1])], []rune(longest)...)
|
||||
}
|
||||
|
||||
return acc
|
||||
return
|
||||
}
|
||||
|
12
eval.go
12
eval.go
@ -206,7 +206,7 @@ func (e *CallExpr) eval(app *App, args []string) {
|
||||
}
|
||||
app.ui.echoFileInfo(app.nav)
|
||||
case "read":
|
||||
s := app.ui.prompt(":")
|
||||
s := app.ui.prompt(app.nav, ":")
|
||||
if len(s) == 0 {
|
||||
app.ui.echoFileInfo(app.nav)
|
||||
return
|
||||
@ -221,24 +221,24 @@ func (e *CallExpr) eval(app *App, args []string) {
|
||||
log.Print(p.err)
|
||||
}
|
||||
case "read-shell":
|
||||
s := app.ui.prompt("$")
|
||||
s := app.ui.prompt(app.nav, "$")
|
||||
log.Printf("shell: %s", s)
|
||||
app.runShell(s, nil, false, false)
|
||||
case "read-shell-wait":
|
||||
s := app.ui.prompt("!")
|
||||
s := app.ui.prompt(app.nav, "!")
|
||||
log.Printf("shell-wait: %s", s)
|
||||
app.runShell(s, nil, true, false)
|
||||
case "read-shell-async":
|
||||
s := app.ui.prompt("&")
|
||||
s := app.ui.prompt(app.nav, "&")
|
||||
log.Printf("shell-async: %s", s)
|
||||
app.runShell(s, nil, false, true)
|
||||
case "search":
|
||||
s := app.ui.prompt("/")
|
||||
s := app.ui.prompt(app.nav, "/")
|
||||
log.Printf("search: %s", s)
|
||||
app.ui.message = "sorry, search is not implemented yet!"
|
||||
// TODO: implement
|
||||
case "search-back":
|
||||
s := app.ui.prompt("?")
|
||||
s := app.ui.prompt(app.nav, "?")
|
||||
log.Printf("search-back: %s", s)
|
||||
app.ui.message = "sorry, search-back is not implemented yet!"
|
||||
// TODO: implement
|
||||
|
68
ui.go
68
ui.go
@ -346,7 +346,7 @@ func findBinds(keys map[string]Expr, prefix string) (binds map[string]Expr, ok b
|
||||
return
|
||||
}
|
||||
|
||||
func (ui *UI) getExpr() Expr {
|
||||
func (ui *UI) getExpr(nav *Nav) Expr {
|
||||
r := &CallExpr{"redraw", nil}
|
||||
|
||||
var acc []rune
|
||||
@ -400,12 +400,14 @@ func (ui *UI) getExpr() Expr {
|
||||
if ok {
|
||||
return gOpts.keys[string(acc)]
|
||||
}
|
||||
ui.draw(nav)
|
||||
ui.listBinds(binds)
|
||||
default:
|
||||
if ok {
|
||||
// TODO: use a delay
|
||||
return gOpts.keys[string(acc)]
|
||||
}
|
||||
ui.draw(nav)
|
||||
ui.listBinds(binds)
|
||||
}
|
||||
case termbox.EventResize:
|
||||
@ -416,7 +418,7 @@ func (ui *UI) getExpr() Expr {
|
||||
}
|
||||
}
|
||||
|
||||
func (ui *UI) prompt(pref string) string {
|
||||
func (ui *UI) prompt(nav *Nav, pref string) string {
|
||||
fg, bg := termbox.ColorDefault, termbox.ColorDefault
|
||||
|
||||
win := ui.msgwin
|
||||
@ -448,10 +450,15 @@ func (ui *UI) prompt(pref string) string {
|
||||
termbox.Flush()
|
||||
return string(acc)
|
||||
case termbox.KeyTab:
|
||||
var matches []string
|
||||
if pref == ":" {
|
||||
acc = compCmd(acc)
|
||||
matches, acc = compCmd(acc)
|
||||
} else {
|
||||
acc = compShell(acc)
|
||||
matches, acc = compShell(acc)
|
||||
}
|
||||
ui.draw(nav)
|
||||
if len(matches) > 1 {
|
||||
ui.listMatches(matches)
|
||||
}
|
||||
case termbox.KeyEsc:
|
||||
return ""
|
||||
@ -492,17 +499,7 @@ func (ui *UI) sync() {
|
||||
}
|
||||
}
|
||||
|
||||
func (ui *UI) listBinds(binds map[string]Expr) {
|
||||
t := new(tabwriter.Writer)
|
||||
b := new(bytes.Buffer)
|
||||
|
||||
t.Init(b, 0, 8, 0, '\t', 0)
|
||||
fmt.Fprintln(t, "keys\tcommand")
|
||||
for key, expr := range binds {
|
||||
fmt.Fprintf(t, "%s\t%v\n", key, expr)
|
||||
}
|
||||
t.Flush()
|
||||
|
||||
func (ui *UI) showMenu(b *bytes.Buffer) {
|
||||
lines := strings.Split(b.String(), "\n")
|
||||
|
||||
lines = lines[:len(lines)-1]
|
||||
@ -512,8 +509,47 @@ func (ui *UI) listBinds(binds map[string]Expr) {
|
||||
|
||||
ui.menuwin.printl(0, 0, termbox.AttrBold, termbox.AttrBold, lines[0])
|
||||
for i, line := range lines[1:] {
|
||||
ui.menuwin.printl(0, i+1, termbox.ColorDefault, termbox.ColorDefault, line)
|
||||
ui.menuwin.printl(0, i+1, termbox.ColorDefault, termbox.ColorDefault, "")
|
||||
ui.menuwin.print(0, i+1, termbox.ColorDefault, termbox.ColorDefault, line)
|
||||
}
|
||||
|
||||
termbox.Flush()
|
||||
}
|
||||
|
||||
func (ui *UI) listBinds(binds map[string]Expr) {
|
||||
t := new(tabwriter.Writer)
|
||||
b := new(bytes.Buffer)
|
||||
|
||||
t.Init(b, 0, gOpts.tabstop, 2, '\t', 0)
|
||||
fmt.Fprintln(t, "keys\tcommand")
|
||||
for key, expr := range binds {
|
||||
fmt.Fprintf(t, "%s\t%v\n", key, expr)
|
||||
}
|
||||
t.Flush()
|
||||
|
||||
ui.showMenu(b)
|
||||
}
|
||||
|
||||
func (ui *UI) listMatches(matches []string) {
|
||||
b := new(bytes.Buffer)
|
||||
|
||||
wtot, _ := termbox.Size()
|
||||
|
||||
wcol := 0
|
||||
for _, m := range matches {
|
||||
wcol = max(wcol, len(m))
|
||||
}
|
||||
wcol += gOpts.tabstop - wcol%gOpts.tabstop
|
||||
|
||||
ncol := wtot / wcol
|
||||
|
||||
b.WriteString("possible matches\n")
|
||||
for i := 0; i < len(matches); i++ {
|
||||
for j := 0; j < ncol && i < len(matches); i, j = i+1, j+1 {
|
||||
b.WriteString(fmt.Sprintf("%s%*s", matches[i], wcol-len(matches[i]), ""))
|
||||
}
|
||||
b.WriteByte('\n')
|
||||
}
|
||||
|
||||
ui.showMenu(b)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user