diff --git a/app.go b/app.go index b77d1463..b0211282 100644 --- a/app.go +++ b/app.go @@ -20,16 +20,19 @@ type cmdItem struct { } type app struct { - ui *ui - nav *nav - ticker *time.Ticker - quitChan chan struct{} - cmd *exec.Cmd - cmdIn io.WriteCloser - cmdOutBuf []byte - cmdHistory []cmdItem - cmdHistoryBeg int - cmdHistoryInd int + ui *ui + nav *nav + ticker *time.Ticker + quitChan chan struct{} + cmd *exec.Cmd + cmdIn io.WriteCloser + cmdOutBuf []byte + cmdHistory []cmdItem + cmdHistoryBeg int + cmdHistoryInd int + menuCompActive bool + menuComps []string + menuCompInd int } func newApp(ui *ui, nav *nav) *app { diff --git a/eval.go b/eval.go index 07bab874..eb934e0b 100644 --- a/eval.go +++ b/eval.go @@ -512,10 +512,52 @@ func splitKeys(s string) (keys []string) { return } +func doComplete(app *app) (matches []string) { + switch app.ui.cmdPrefix { + case ":": + matches, app.ui.cmdAccLeft = completeCmd(app.ui.cmdAccLeft) + case "/", "?": + matches, app.ui.cmdAccLeft = completeFile(app.ui.cmdAccLeft) + case "$", "%", "!", "&": + matches, app.ui.cmdAccLeft = completeShell(app.ui.cmdAccLeft) + } + return +} + +func menuComplete(app *app, dir int) { + if !app.menuCompActive { + app.ui.cmdTmp = app.ui.cmdAccLeft + app.menuComps = doComplete(app) + if len(app.menuComps) > 1 { + app.menuCompInd = -1 + app.menuCompActive = true + } + } else { + app.menuCompInd += dir + if app.menuCompInd == len(app.menuComps) { + app.menuCompInd = 0 + } else if app.menuCompInd < 0 { + app.menuCompInd = len(app.menuComps) - 1 + } + + comp := app.menuComps[app.menuCompInd] + toks := tokenize(string(app.ui.cmdTmp)) + last := toks[len(toks)-1] + + if app.ui.cmdPrefix != "/" && app.ui.cmdPrefix != "?" { + comp = escape(comp) + _, last = filepath.Split(last) + } + + ind := len(app.ui.cmdTmp) - len([]rune(last)) + app.ui.cmdAccLeft = append(app.ui.cmdTmp[:ind], []rune(comp)...) + } + app.ui.menuBuf = listMatches(app.ui.screen, app.menuComps, app.menuCompInd) +} + func update(app *app) { - // Remove the current menu buffer app.ui.menuBuf = nil - app.ui.menuSelected = -2 + app.menuCompActive = false switch { case gOpts.incsearch && app.ui.cmdPrefix == "/": @@ -602,15 +644,13 @@ func resetIncCmd(app *app) { func normal(app *app) { resetIncCmd(app) - app.ui.menuBuf = nil - app.ui.menuSelected = -2 + app.cmdHistoryInd = 0 + app.menuCompActive = false + app.ui.menuBuf = nil app.ui.cmdAccLeft = nil app.ui.cmdAccRight = nil - app.ui.cmdTmp = nil app.ui.cmdPrefix = "" - - app.cmdHistoryInd = 0 } func insert(app *app, arg string) { @@ -811,6 +851,8 @@ func insert(app *app, arg string) { } } default: + app.ui.menuBuf = nil + app.menuCompActive = false app.ui.cmdAccLeft = append(app.ui.cmdAccLeft, []rune(arg)...) } } @@ -1603,14 +1645,6 @@ func (e *callExpr) eval(app *app, args []string) { if len(e.args) == 0 { return } - - // Reset the completion menu as in bash/vim - // and update the pertinent variables - if app.ui.menuBuf != nil { - app.ui.menuBuf = nil - app.ui.menuSelected = -2 - } - insert(app, e.args[0]) case "cmd-escape": if app.ui.cmdPrefix == ">" { @@ -1618,109 +1652,15 @@ func (e *callExpr) eval(app *app, args []string) { } normal(app) case "cmd-complete": - var matches []string - switch app.ui.cmdPrefix { - case ":": - matches, app.ui.cmdAccLeft = completeCmd(app.ui.cmdAccLeft) - case "/", "?": - matches, app.ui.cmdAccLeft = completeFile(app.ui.cmdAccLeft) - case "$", "%", "!", "&": - matches, app.ui.cmdAccLeft = completeShell(app.ui.cmdAccLeft) - default: - return - } - - app.ui.draw(app.nav) - - // If there are no multiple matches - // we do not need to show the list of matches - if len(matches) <= 1 { - app.ui.menuBuf = nil - return - } - - if b, err := listMatches(app.ui.screen, matches); err != nil { - app.ui.echoerrf("cmd-complete: %s", err) - } else { - app.ui.menuBuf = b - } + matches := doComplete(app) + app.ui.menuBuf = listMatches(app.ui.screen, matches, -1) case "cmd-menu-complete": - // note that, as we will store the current selected value - // in cmdAccLeft, we can not call the complete function with its - // value, as it is already a final completion result - if app.ui.menuBuf == nil { - app.ui.cmdTmp = app.ui.cmdAccLeft - } - - var matches []string - switch app.ui.cmdPrefix { - case ":": - matches, app.ui.cmdAccLeft = completeCmd(app.ui.cmdTmp) - case "/", "?": - matches, app.ui.cmdAccLeft = completeFile(app.ui.cmdTmp) - case "$", "%", "!", "&": - matches, app.ui.cmdAccLeft = completeShell(app.ui.cmdTmp) - default: - return - } - - app.ui.draw(app.nav) - - if len(matches) > 1 { - // Check if the tab-selection was inited - if app.ui.menuSelected == -2 { - app.ui.menuSelected = -1 - } else { - app.ui.menuSelected++ - app.ui.menuSelected %= len(matches) - } - - if err := listMatchesMenu(app.ui, matches); err != nil { - app.ui.echoerrf("cmd-menu-complete: %s", err) - } - } else { - app.ui.menuBuf = nil - app.ui.menuSelected = -2 - } + menuComplete(app, 1) case "cmd-menu-complete-back": - if app.ui.menuBuf == nil { - app.ui.cmdTmp = app.ui.cmdAccLeft - } - - var matches []string - switch app.ui.cmdPrefix { - case ":": - matches, app.ui.cmdAccLeft = completeCmd(app.ui.cmdTmp) - case "/", "?": - matches, app.ui.cmdAccLeft = completeFile(app.ui.cmdTmp) - case "$", "%", "!", "&": - matches, app.ui.cmdAccLeft = completeShell(app.ui.cmdTmp) - default: - return - } - - app.ui.draw(app.nav) - - if len(matches) > 1 { - // Check if the tab-selection was inited - if app.ui.menuSelected == -2 { - app.ui.menuSelected = -1 - } else if app.ui.menuSelected <= 0 { - app.ui.menuSelected = len(matches) - 1 - } else { - app.ui.menuSelected-- - } - - if err := listMatchesMenu(app.ui, matches); err != nil { - app.ui.echoerrf("cmd-menu-complete-back: %s", err) - } - } else { - app.ui.menuBuf = nil - app.ui.menuSelected = -2 - } + menuComplete(app, -1) case "cmd-menu-accept": app.ui.menuBuf = nil - app.ui.menuSelected = -2 + app.menuCompActive = false case "cmd-enter": s := string(append(app.ui.cmdAccLeft, app.ui.cmdAccRight...)) if len(s) == 0 && app.ui.cmdPrefix != "filter: " { @@ -1728,11 +1668,10 @@ func (e *callExpr) eval(app *app, args []string) { } app.ui.menuBuf = nil - app.ui.menuSelected = -2 + app.menuCompActive = false app.ui.cmdAccLeft = nil app.ui.cmdAccRight = nil - app.ui.cmdTmp = nil switch app.ui.cmdPrefix { case ":": diff --git a/ui.go b/ui.go index 83c6dd19..4818c48d 100644 --- a/ui.go +++ b/ui.go @@ -477,30 +477,29 @@ func (win *win) printDir(screen tcell.Screen, dir *dir, selections map[string]in } type ui struct { - screen tcell.Screen - polling bool - wins []*win - promptWin *win - msgWin *win - menuWin *win - msg string - regPrev *reg - dirPrev *dir - exprChan chan expr - keyChan chan string - tevChan chan tcell.Event - evChan chan tcell.Event - menuBuf *bytes.Buffer - menuSelected int - cmdPrefix string - cmdAccLeft []rune - cmdAccRight []rune - cmdYankBuf []rune - cmdTmp []rune - keyAcc []rune - keyCount []rune - styles styleMap - icons iconMap + screen tcell.Screen + polling bool + wins []*win + promptWin *win + msgWin *win + menuWin *win + msg string + regPrev *reg + dirPrev *dir + exprChan chan expr + keyChan chan string + tevChan chan tcell.Event + evChan chan tcell.Event + menuBuf *bytes.Buffer + cmdPrefix string + cmdAccLeft []rune + cmdAccRight []rune + cmdYankBuf []rune + cmdTmp []rune + keyAcc []rune + keyCount []rune + styles styleMap + icons iconMap } func getWidths(wtot int) []int { @@ -551,19 +550,18 @@ func newUI(screen tcell.Screen) *ui { wtot, htot := screen.Size() ui := &ui{ - screen: screen, - polling: true, - wins: getWins(screen), - promptWin: newWin(wtot, 1, 0, 0), - msgWin: newWin(wtot, 1, 0, htot-1), - menuWin: newWin(wtot, 1, 0, htot-2), - exprChan: make(chan expr, 1000), - keyChan: make(chan string, 1000), - tevChan: make(chan tcell.Event, 1000), - evChan: make(chan tcell.Event, 1000), - styles: parseStyles(), - icons: parseIcons(), - menuSelected: -2, + screen: screen, + polling: true, + wins: getWins(screen), + promptWin: newWin(wtot, 1, 0, 0), + msgWin: newWin(wtot, 1, 0, htot-1), + menuWin: newWin(wtot, 1, 0, htot-2), + exprChan: make(chan expr, 1000), + keyChan: make(chan string, 1000), + tevChan: make(chan tcell.Event, 1000), + evChan: make(chan tcell.Event, 1000), + styles: parseStyles(), + icons: parseIcons(), } go ui.pollEvents() @@ -1226,95 +1224,35 @@ func anyKey() { os.Stdin.Read(b) } -func listMatches(screen tcell.Screen, matches []string) (*bytes.Buffer, error) { - b := new(bytes.Buffer) - - wtot, _ := screen.Size() - wcol := 0 - - for _, m := range matches { - wcol = max(wcol, len(m)) - } - - wcol += gOpts.tabstop - wcol%gOpts.tabstop - ncol := wtot / wcol - - if _, err := b.WriteString("possible matches\n"); err != nil { - return b, err - } - - for i := 0; i < len(matches); { - for j := 0; j < ncol && i < len(matches); i, j = i+1, j+1 { - target := matches[i] - if _, err := b.WriteString(fmt.Sprintf("%s%*s", target, wcol-len(target), "")); err != nil { - return nil, err - } - } - - if err := b.WriteByte('\n'); err != nil { - return nil, err - } +func listMatches(screen tcell.Screen, matches []string, selectedInd int) *bytes.Buffer { + if len(matches) < 2 { + return nil } - - return b, nil -} - -func listMatchesMenu(ui *ui, matches []string) error { b := new(bytes.Buffer) - wtot, _ := ui.screen.Size() - + wtot, _ := screen.Size() wcol := 0 for _, m := range matches { wcol = max(wcol, len(m)) } wcol += gOpts.tabstop - wcol%gOpts.tabstop - ncol := wtot / wcol - n, err := b.WriteString("possible matches\n") - if err != nil { - return err - } - - bytesWrote := n + b.WriteString("possible matches\n") for i := 0; i < len(matches); { for j := 0; j < ncol && i < len(matches); i, j = i+1, j+1 { target := matches[i] - // Handle menu tab match only if wanted - if ui.menuSelected == i { - comp := []rune(escape(target)) - toks := tokenize(string(ui.cmdAccLeft)) - _, last := filepath.Split(toks[len(toks)-1]) - - if last == "" { - ui.cmdAccLeft = append(ui.cmdAccLeft, comp...) - } else { - ui.cmdAccLeft = append(ui.cmdAccLeft[:len(ui.cmdAccLeft)-len([]rune(last))], comp...) - } - + if selectedInd == i { target = fmt.Sprintf("\033[7m%s\033[0m%*s", target, wcol-len(target), "") } else { target = fmt.Sprintf("%s%*s", target, wcol-len(target), "") } - - n, err := b.WriteString(target) - if err != nil { - return err - } - - bytesWrote += n + b.WriteString(target) } - - if err := b.WriteByte('\n'); err != nil { - return err - } - - bytesWrote += 1 + b.WriteByte('\n') } - ui.menuBuf = b - return nil + return b }