diff --git a/app.go b/app.go index 973970b..232d2e7 100644 --- a/app.go +++ b/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 } diff --git a/comp.go b/comp.go index 788dc3a..cddd609 100644 --- a/comp.go +++ b/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 } diff --git a/eval.go b/eval.go index 5f17039..9c98120 100644 --- a/eval.go +++ b/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 diff --git a/ui.go b/ui.go index 13aa7d5..0d672df 100644 --- a/ui.go +++ b/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) +}