From e29cae4f7ffc635e0acd9439a7545e481b7f3fab Mon Sep 17 00:00:00 2001 From: Zachary Yedidia Date: Thu, 3 Jan 2019 17:07:28 -0500 Subject: [PATCH] Infobar history --- cmd/micro/action/actions.go | 6 +- cmd/micro/action/command.go | 61 +++++++++---------- .../infocomplete.go} | 30 ++++----- cmd/micro/action/infohandler.go | 23 ++++--- cmd/micro/info/history.go | 18 ++++++ cmd/micro/info/infobuffer.go | 25 +++++++- 6 files changed, 100 insertions(+), 63 deletions(-) rename cmd/micro/{info/autocomplete.go => action/infocomplete.go} (94%) diff --git a/cmd/micro/action/actions.go b/cmd/micro/action/actions.go index a1b40191..4aed393f 100644 --- a/cmd/micro/action/actions.go +++ b/cmd/micro/action/actions.go @@ -560,7 +560,7 @@ func (h *BufHandler) SaveAs() bool { // Find opens a prompt and searches forward for the input func (h *BufHandler) Find() bool { - InfoBar.Prompt("Find: ", "", func(resp string) { + InfoBar.Prompt("Find: ", "", "Find", func(resp string) { // Event callback match, found, _ := h.Buf.FindNext(resp, h.Cursor.Loc, true) if found { @@ -802,7 +802,7 @@ func (h *BufHandler) SelectAll() bool { // OpenFile opens a new file in the buffer func (h *BufHandler) OpenFile() bool { - InfoBar.Prompt("> ", "open ", nil, func(resp string, canceled bool) { + InfoBar.Prompt("> ", "open ", "Open", nil, func(resp string, canceled bool) { if !canceled { HandleCommand(resp) } @@ -966,7 +966,7 @@ func (h *BufHandler) ShellMode() bool { // CommandMode lets the user enter a command func (h *BufHandler) CommandMode() bool { - InfoBar.Prompt("> ", "", nil, func(resp string, canceled bool) { + InfoBar.Prompt("> ", "", "Command", nil, func(resp string, canceled bool) { if !canceled { HandleCommand(resp) } diff --git a/cmd/micro/action/command.go b/cmd/micro/action/command.go index 644fe675..6ca60156 100644 --- a/cmd/micro/action/command.go +++ b/cmd/micro/action/command.go @@ -3,7 +3,6 @@ package action import ( "os" - "github.com/zyedidia/micro/cmd/micro/info" "github.com/zyedidia/micro/cmd/micro/shellwords" "github.com/zyedidia/micro/cmd/micro/util" ) @@ -11,13 +10,13 @@ import ( // A Command contains an action (a function to call) as well as information about how to autocomplete the command type Command struct { action func([]string) - completions []info.Completion + completions []Completion } // A StrCommand is similar to a command but keeps the name of the action type StrCommand struct { action string - completions []info.Completion + completions []Completion } var commands map[string]Command @@ -71,7 +70,7 @@ func parseCommands(userCommands map[string]StrCommand) { // MakeCommand is a function to easily create new commands // This can be called by plugins in Lua so that plugins can define their own commands -func MakeCommand(name, function string, completions ...info.Completion) { +func MakeCommand(name, function string, completions ...Completion) { action := commandActions[function] // if _, ok := commandActions[function]; !ok { // If the user seems to be binding a function that doesn't exist @@ -85,32 +84,32 @@ func MakeCommand(name, function string, completions ...info.Completion) { // DefaultCommands returns a map containing micro's default commands func DefaultCommands() map[string]StrCommand { return map[string]StrCommand{ - "set": {"Set", []info.Completion{info.OptionCompletion, info.OptionValueCompletion}}, - "setlocal": {"SetLocal", []info.Completion{info.OptionCompletion, info.OptionValueCompletion}}, - "show": {"Show", []info.Completion{info.OptionCompletion, info.NoCompletion}}, - "showkey": {"ShowKey", []info.Completion{info.NoCompletion}}, - "bind": {"Bind", []info.Completion{info.NoCompletion}}, - "run": {"Run", []info.Completion{info.NoCompletion}}, - "quit": {"Quit", []info.Completion{info.NoCompletion}}, - "save": {"Save", []info.Completion{info.NoCompletion}}, - "replace": {"Replace", []info.Completion{info.NoCompletion}}, - "replaceall": {"ReplaceAll", []info.Completion{info.NoCompletion}}, - "vsplit": {"VSplit", []info.Completion{info.FileCompletion, info.NoCompletion}}, - "hsplit": {"HSplit", []info.Completion{info.FileCompletion, info.NoCompletion}}, - "tab": {"Tab", []info.Completion{info.FileCompletion, info.NoCompletion}}, - "help": {"Help", []info.Completion{info.HelpCompletion, info.NoCompletion}}, - "eval": {"Eval", []info.Completion{info.NoCompletion}}, - "log": {"ToggleLog", []info.Completion{info.NoCompletion}}, - "plugin": {"Plugin", []info.Completion{info.PluginCmdCompletion, info.PluginNameCompletion}}, - "reload": {"Reload", []info.Completion{info.NoCompletion}}, - "cd": {"Cd", []info.Completion{info.FileCompletion}}, - "pwd": {"Pwd", []info.Completion{info.NoCompletion}}, - "open": {"Open", []info.Completion{info.FileCompletion}}, - "tabswitch": {"TabSwitch", []info.Completion{info.NoCompletion}}, - "term": {"Term", []info.Completion{info.NoCompletion}}, - "memusage": {"MemUsage", []info.Completion{info.NoCompletion}}, - "retab": {"Retab", []info.Completion{info.NoCompletion}}, - "raw": {"Raw", []info.Completion{info.NoCompletion}}, + "set": {"Set", []Completion{OptionCompletion, OptionValueCompletion}}, + "setlocal": {"SetLocal", []Completion{OptionCompletion, OptionValueCompletion}}, + "show": {"Show", []Completion{OptionCompletion, NoCompletion}}, + "showkey": {"ShowKey", []Completion{NoCompletion}}, + "bind": {"Bind", []Completion{NoCompletion}}, + "run": {"Run", []Completion{NoCompletion}}, + "quit": {"Quit", []Completion{NoCompletion}}, + "save": {"Save", []Completion{NoCompletion}}, + "replace": {"Replace", []Completion{NoCompletion}}, + "replaceall": {"ReplaceAll", []Completion{NoCompletion}}, + "vsplit": {"VSplit", []Completion{FileCompletion, NoCompletion}}, + "hsplit": {"HSplit", []Completion{FileCompletion, NoCompletion}}, + "tab": {"Tab", []Completion{FileCompletion, NoCompletion}}, + "help": {"Help", []Completion{HelpCompletion, NoCompletion}}, + "eval": {"Eval", []Completion{NoCompletion}}, + "log": {"ToggleLog", []Completion{NoCompletion}}, + "plugin": {"Plugin", []Completion{PluginCmdCompletion, PluginNameCompletion}}, + "reload": {"Reload", []Completion{NoCompletion}}, + "cd": {"Cd", []Completion{FileCompletion}}, + "pwd": {"Pwd", []Completion{NoCompletion}}, + "open": {"Open", []Completion{FileCompletion}}, + "tabswitch": {"TabSwitch", []Completion{NoCompletion}}, + "term": {"Term", []Completion{NoCompletion}}, + "memusage": {"MemUsage", []Completion{NoCompletion}}, + "retab": {"Retab", []Completion{NoCompletion}}, + "raw": {"Raw", []Completion{NoCompletion}}, } } @@ -119,7 +118,7 @@ func DefaultCommands() map[string]StrCommand { // enter func CommandEditAction(prompt string) BufKeyAction { return func(h *BufHandler) bool { - InfoBar.Prompt("> ", prompt, nil, func(resp string, canceled bool) { + InfoBar.Prompt("> ", prompt, "Command", nil, func(resp string, canceled bool) { if !canceled { HandleCommand(resp) } diff --git a/cmd/micro/info/autocomplete.go b/cmd/micro/action/infocomplete.go similarity index 94% rename from cmd/micro/info/autocomplete.go rename to cmd/micro/action/infocomplete.go index 59d5056e..f25a3a9b 100644 --- a/cmd/micro/info/autocomplete.go +++ b/cmd/micro/action/infocomplete.go @@ -1,4 +1,4 @@ -package info +package action import ( "io/ioutil" @@ -76,20 +76,20 @@ func FileComplete(input string) (string, []string) { } // CommandComplete autocompletes commands -// func CommandComplete(input string) (string, []string) { -// var suggestions []string -// for cmd := range commands { -// if strings.HasPrefix(cmd, input) { -// suggestions = append(suggestions, cmd) -// } -// } -// -// var chosen string -// if len(suggestions) == 1 { -// chosen = suggestions[0] -// } -// return chosen, suggestions -// } +func CommandComplete(input string) (string, []string) { + var suggestions []string + for cmd := range commands { + if strings.HasPrefix(cmd, input) { + suggestions = append(suggestions, cmd) + } + } + + var chosen string + if len(suggestions) == 1 { + chosen = suggestions[0] + } + return chosen, suggestions +} // HelpComplete autocompletes help topics func HelpComplete(input string) (string, []string) { diff --git a/cmd/micro/action/infohandler.go b/cmd/micro/action/infohandler.go index c50247de..b2941ec7 100644 --- a/cmd/micro/action/infohandler.go +++ b/cmd/micro/action/infohandler.go @@ -35,6 +35,15 @@ func (h *InfoHandler) HandleEvent(event tcell.Event) { done := h.DoKeyEvent(ke) if !done && e.Key() == tcell.KeyRune { h.DoRuneInsert(e.Rune()) + done = true + } + if done && h.HasPrompt { + resp := strings.TrimSpace(string(h.LineBytes(0))) + hist := h.History[h.PromptType] + hist[h.HistoryNum] = resp + if h.EventCallback != nil { + h.EventCallback(resp) + } } case *tcell.EventMouse: h.BufHandler.HandleEvent(event) @@ -61,19 +70,9 @@ func (h *InfoHandler) DoKeyEvent(e KeyEvent) bool { done = action(h.BufHandler) } } - if done && h.EventCallback != nil { - h.EventCallback(strings.TrimSpace(string(h.LineBytes(0)))) - } return done } -func (h *InfoHandler) DoRuneInsert(r rune) { - h.BufHandler.DoRuneInsert(r) - if h.EventCallback != nil { - h.EventCallback(strings.TrimSpace(string(h.LineBytes(0)))) - } -} - // InfoNones is a list of actions that should have no effect when executed // by an infohandler var InfoNones = []string{ @@ -136,10 +135,10 @@ var InfoOverrides = map[string]InfoKeyAction{ } func (h *InfoHandler) CursorUp() { - // TODO: history + h.UpHistory(h.History[h.PromptType]) } func (h *InfoHandler) CursorDown() { - // TODO: history + h.DownHistory(h.History[h.PromptType]) } func (h *InfoHandler) InsertTab() { // TODO: autocomplete diff --git a/cmd/micro/info/history.go b/cmd/micro/info/history.go index aaa006db..18e6bbba 100644 --- a/cmd/micro/info/history.go +++ b/cmd/micro/info/history.go @@ -59,3 +59,21 @@ func (i *InfoBuf) SaveHistory() { } } } + +// UpHistory fetches the previous item in the history +func (i *InfoBuf) UpHistory(history []string) { + if i.HistoryNum > 0 { + i.HistoryNum-- + i.Replace(i.Start(), i.End(), history[i.HistoryNum]) + i.Buffer.GetActiveCursor().GotoLoc(i.End()) + } +} + +// DownHistory fetches the next item in the history +func (i *InfoBuf) DownHistory(history []string) { + if i.HistoryNum < len(history)-1 { + i.HistoryNum++ + i.Replace(i.Start(), i.End(), history[i.HistoryNum]) + i.Buffer.GetActiveCursor().GotoLoc(i.End()) + } +} diff --git a/cmd/micro/info/infobuffer.go b/cmd/micro/info/infobuffer.go index 21b1aee5..91604e43 100644 --- a/cmd/micro/info/infobuffer.go +++ b/cmd/micro/info/infobuffer.go @@ -16,6 +16,8 @@ type InfoBuf struct { HasMessage bool HasError bool + PromptType string + Msg string // This map stores the history for all the different kinds of uses Prompt has @@ -36,10 +38,16 @@ func NewBuffer() *InfoBuf { ib.History = make(map[string][]string) ib.Buffer = buffer.NewBufferFromString("", "infobar", buffer.BTInfo) + ib.LoadHistory() return ib } +// Close performs any cleanup necessary when shutting down the infobuffer +func (i *InfoBuf) Close() { + i.SaveHistory() +} + // Message sends a message to the user func (i *InfoBuf) Message(msg ...interface{}) { // only display a new message if there isn't an active prompt @@ -68,12 +76,20 @@ func (i *InfoBuf) Error(msg ...interface{}) { // and callbacks executed when the user executes an event and when the user finishes the prompt // The eventcb passes the current user response as the argument and donecb passes the user's message // and a boolean indicating if the prompt was canceled -func (i *InfoBuf) Prompt(prompt string, msg string, eventcb func(string), donecb func(string, bool)) { +func (i *InfoBuf) Prompt(prompt string, msg string, ptype string, eventcb func(string), donecb func(string, bool)) { // If we get another prompt mid-prompt we cancel the one getting overwritten if i.HasPrompt { i.DonePrompt(true) } + if _, ok := i.History[ptype]; !ok { + i.History[ptype] = []string{""} + } else { + i.History[ptype] = append(i.History[ptype], "") + } + i.HistoryNum = len(i.History[ptype]) - 1 + + i.PromptType = ptype i.Msg = prompt i.HasPrompt = true i.HasMessage, i.HasError = false, false @@ -88,8 +104,13 @@ func (i *InfoBuf) DonePrompt(canceled bool) { if i.PromptCallback != nil { if canceled { i.PromptCallback("", true) + h := i.History[i.PromptType] + i.History[i.PromptType] = h[:len(h)-1] } else { - i.PromptCallback(strings.TrimSpace(string(i.LineBytes(0))), false) + resp := strings.TrimSpace(string(i.LineBytes(0))) + i.PromptCallback(resp, false) + h := i.History[i.PromptType] + h[len(h)-1] = resp } } i.PromptCallback = nil