show completion menu for multiple match

This commit is contained in:
Gokcehan 2016-08-21 18:41:03 +03:00
parent 66ce56eb6e
commit 19561f6531
4 changed files with 110 additions and 80 deletions

2
app.go
View File

@ -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
View File

@ -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
View File

@ -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
View File

@ -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)
}