From 9593c2a72060c320bcbb02157064988f48223f3f Mon Sep 17 00:00:00 2001 From: Dmitry Maluka Date: Sat, 8 Jul 2023 23:00:22 +0200 Subject: [PATCH] Add HistorySearchUp and HistorySearchDown actions (#1829) Add HistorySearchUp and HistorySearchDown actions which are similar to HistoryUp and HistoryDown but search for the prev/next history item whose beginning matches the currently entered text in the infobuffer (more precisely, the text before cursor). Also fixed the following issue: if we scrolled to an older history item and then edit the infobuffer, this older item gets modified. We should not edit old history entries. So in this case set HistoryNum to the last (newly added) item and modify the last item. --- internal/action/infopane.go | 30 +++++++++++++++++----- internal/info/history.go | 50 +++++++++++++++++++++++++++++++++++++ internal/info/infobuffer.go | 5 ++++ 3 files changed, 79 insertions(+), 6 deletions(-) diff --git a/internal/action/infopane.go b/internal/action/infopane.go index 7fad9ad6..e1342ad9 100644 --- a/internal/action/infopane.go +++ b/internal/action/infopane.go @@ -108,7 +108,11 @@ func (h *InfoPane) HandleEvent(event tcell.Event) { if done && h.HasPrompt && !hasYN { resp := string(h.LineBytes(0)) hist := h.History[h.PromptType] - hist[h.HistoryNum] = resp + if resp != hist[h.HistoryNum] { + h.HistoryNum = len(hist) - 1 + hist[h.HistoryNum] = resp + h.HistorySearch = false + } if h.EventCallback != nil { h.EventCallback(resp) } @@ -155,6 +159,18 @@ func (h *InfoPane) HistoryDown() { h.DownHistory(h.History[h.PromptType]) } +// HistorySearchUp fetches the previous history item beginning with the text +// in the infobuffer before cursor +func (h *InfoPane) HistorySearchUp() { + h.SearchUpHistory(h.History[h.PromptType]) +} + +// HistorySearchDown fetches the next history item beginning with the text +// in the infobuffer before cursor +func (h *InfoPane) HistorySearchDown() { + h.SearchDownHistory(h.History[h.PromptType]) +} + // Autocomplete begins autocompletion func (h *InfoPane) CommandComplete() { b := h.Buf @@ -198,9 +214,11 @@ func (h *InfoPane) AbortCommand() { // InfoKeyActions contains the list of all possible key actions the infopane could execute var InfoKeyActions = map[string]InfoKeyAction{ - "HistoryUp": (*InfoPane).HistoryUp, - "HistoryDown": (*InfoPane).HistoryDown, - "CommandComplete": (*InfoPane).CommandComplete, - "ExecuteCommand": (*InfoPane).ExecuteCommand, - "AbortCommand": (*InfoPane).AbortCommand, + "HistoryUp": (*InfoPane).HistoryUp, + "HistoryDown": (*InfoPane).HistoryDown, + "HistorySearchUp": (*InfoPane).HistorySearchUp, + "HistorySearchDown": (*InfoPane).HistorySearchDown, + "CommandComplete": (*InfoPane).CommandComplete, + "ExecuteCommand": (*InfoPane).ExecuteCommand, + "AbortCommand": (*InfoPane).AbortCommand, } diff --git a/internal/info/history.go b/internal/info/history.go index 722a22f7..a09a58cf 100644 --- a/internal/info/history.go +++ b/internal/info/history.go @@ -4,8 +4,10 @@ import ( "encoding/gob" "os" "path/filepath" + "strings" "github.com/zyedidia/micro/v2/internal/config" + "github.com/zyedidia/micro/v2/internal/util" ) // LoadHistory attempts to load user history from configDir/buffers/history @@ -102,3 +104,51 @@ func (i *InfoBuf) DownHistory(history []string) { i.Buffer.GetActiveCursor().GotoLoc(i.End()) } } + +// SearchUpHistory fetches the previous item in the history +// beginning with the text in the infobuffer before cursor +func (i *InfoBuf) SearchUpHistory(history []string) { + if i.HistoryNum > 0 && i.HasPrompt && !i.HasYN { + i.searchHistory(history, false) + } +} + +// SearchDownHistory fetches the next item in the history +// beginning with the text in the infobuffer before cursor +func (i *InfoBuf) SearchDownHistory(history []string) { + if i.HistoryNum < len(history)-1 && i.HasPrompt && !i.HasYN { + i.searchHistory(history, true) + } +} + +func (i *InfoBuf) searchHistory(history []string, down bool) { + line := string(i.LineBytes(0)) + c := i.Buffer.GetActiveCursor() + + if !i.HistorySearch || !strings.HasPrefix(line, i.HistorySearchPrefix) { + i.HistorySearch = true + i.HistorySearchPrefix = util.SliceStartStr(line, c.X) + } + + found := -1 + if down { + for j := i.HistoryNum + 1; j < len(history); j++ { + if strings.HasPrefix(history[j], i.HistorySearchPrefix) { + found = j + break + } + } + } else { + for j := i.HistoryNum - 1; j >= 0; j-- { + if strings.HasPrefix(history[j], i.HistorySearchPrefix) { + found = j + break + } + } + } + if found != -1 { + i.HistoryNum = found + i.Replace(i.Start(), i.End(), history[found]) + c.GotoLoc(i.End()) + } +} diff --git a/internal/info/infobuffer.go b/internal/info/infobuffer.go index e88701a4..8f8d7251 100644 --- a/internal/info/infobuffer.go +++ b/internal/info/infobuffer.go @@ -25,6 +25,10 @@ type InfoBuf struct { // It's a map of history type -> history array History map[string][]string HistoryNum int + // HistorySearch indicates whether we are searching for history items + // beginning with HistorySearchPrefix + HistorySearch bool + HistorySearchPrefix string // Is the current message a message from the gutter HasGutter bool @@ -102,6 +106,7 @@ func (i *InfoBuf) Prompt(prompt string, msg string, ptype string, eventcb func(s i.History[ptype] = append(i.History[ptype], "") } i.HistoryNum = len(i.History[ptype]) - 1 + i.HistorySearch = false i.PromptType = ptype i.Msg = prompt