From 6300987ae94c2da5cc29498e41a2ed18ff00f842 Mon Sep 17 00:00:00 2001 From: Tw Date: Mon, 12 Aug 2024 20:23:24 +0800 Subject: [PATCH] extend lastcmd mode to support all the previous cmds Signed-off-by: Tw --- pkg/cli/modes/lastcmd.go | 44 +++++++++++++++++--------- pkg/cli/modes/lastcmd_test.go | 59 +++++++++++++++++++++++++---------- pkg/cli/tk/listbox.go | 16 ++++++++++ 3 files changed, 87 insertions(+), 32 deletions(-) diff --git a/pkg/cli/modes/lastcmd.go b/pkg/cli/modes/lastcmd.go index 614295e49..e1f02fd88 100644 --- a/pkg/cli/modes/lastcmd.go +++ b/pkg/cli/modes/lastcmd.go @@ -45,20 +45,26 @@ func NewLastcmd(app cli.App, cfg LastcmdSpec) (Lastcmd, error) { } c := cfg.Store.Cursor("") c.Prev() - cmd, err := c.Get() - if err != nil { - return nil, fmt.Errorf("db error: %v", err) - } - wordifier := cfg.Wordifier - if wordifier == nil { - wordifier = strings.Fields - } - cmdText := cmd.Text - words := wordifier(cmdText) - entries := make([]lastcmdEntry, len(words)+1) - entries[0] = lastcmdEntry{content: cmdText} - for i, word := range words { - entries[i+1] = lastcmdEntry{strconv.Itoa(i), strconv.Itoa(i - len(words)), word} + + get_items := func(c histutil.Cursor) []lastcmdEntry { + cmd, err := c.Get() + if err != nil { + return []lastcmdEntry{} + } + + wordifier := cfg.Wordifier + if wordifier == nil { + wordifier = strings.Fields + } + cmdText := cmd.Text + words := wordifier(cmdText) + entries := make([]lastcmdEntry, len(words)+1) + entries[0] = lastcmdEntry{content: cmdText} + for i, word := range words { + entries[i+1] = lastcmdEntry{strconv.Itoa(i), strconv.Itoa(i - len(words)), word} + } + + return entries } accept := func(text string) { @@ -74,9 +80,17 @@ func NewLastcmd(app cli.App, cfg LastcmdSpec) (Lastcmd, error) { OnAccept: func(it tk.Items, i int) { accept(it.(lastcmdItems).entries[i].content) }, + OnUnderFlow: func(lb tk.ListBox) { + c.Prev() + lb.Reset(filterLastcmdItems(get_items(c), ""), 0) + }, + OnOverFlow: func(lb tk.ListBox) { + c.Next() + lb.Reset(filterLastcmdItems(get_items(c), ""), 0) + }, }, OnFilter: func(w tk.ComboBox, p string) { - items := filterLastcmdItems(entries, p) + items := filterLastcmdItems(get_items(c), p) if len(items.entries) == 1 { accept(items.entries[0].content) } else { diff --git a/pkg/cli/modes/lastcmd_test.go b/pkg/cli/modes/lastcmd_test.go index 8aeb17d55..1bfaf79a7 100644 --- a/pkg/cli/modes/lastcmd_test.go +++ b/pkg/cli/modes/lastcmd_test.go @@ -31,23 +31,6 @@ func TestNewLastcmd_FocusedWidgetNotCodeArea(t *testing.T) { }) } -func TestNewLastcmd_StoreError(t *testing.T) { - f := Setup() - defer f.Stop() - - db := histutil.NewFaultyInMemoryDB() - store, err := histutil.NewDBStore(db) - if err != nil { - panic(err) - } - db.SetOneOffError(errMock) - - _, err = NewLastcmd(f.App, LastcmdSpec{Store: store}) - if err.Error() != "db error: mock error" { - t.Error("expect db error") - } -} - func TestLastcmd(t *testing.T) { f := Setup() defer f.Stop() @@ -115,3 +98,45 @@ func startLastcmd(app cli.App, spec LastcmdSpec) { w, err := NewLastcmd(app, spec) startMode(app, w, err) } + +func TestLastcmdOverFlow(t *testing.T) { + f := Setup() + defer f.Stop() + + st := histutil.NewMemStore("foo", "bar") + startLastcmd(f.App, LastcmdSpec{ + Store: st, + }) + + // Test UI. + f.TestTTY(t, + "\n", // empty code area + " LASTCMD ", Styles, + "********* ", term.DotHere, "\n", + " bar \n", Styles, + "++++++++++++++++++++++++++++++++++++++++++++++++++", + " 0 bar", + ) + + // Test underflow. + f.TTY.Inject(term.K(ui.Up, ui.Alt)) + f.TestTTY(t, + "\n", // empty code area + " LASTCMD ", Styles, + "********* ", term.DotHere, "\n", + " foo \n", Styles, + "++++++++++++++++++++++++++++++++++++++++++++++++++", + " 0 foo", + ) + + // Test overflow. + f.TTY.Inject(term.K(ui.Down, ui.Alt)) + f.TestTTY(t, + "\n", // empty code area + " LASTCMD ", Styles, + "********* ", term.DotHere, "\n", + " bar \n", Styles, + "++++++++++++++++++++++++++++++++++++++++++++++++++", + " 0 bar", + ) +} diff --git a/pkg/cli/tk/listbox.go b/pkg/cli/tk/listbox.go index 13124d4d0..2f235edb7 100644 --- a/pkg/cli/tk/listbox.go +++ b/pkg/cli/tk/listbox.go @@ -35,6 +35,10 @@ type ListBoxSpec struct { OnSelect func(it Items, i int) // A function called on the accept event. OnAccept func(it Items, i int) + // A function called when selecting before the beginning of the items. + OnUnderFlow func(ListBox) + // A function called when selecting after the ending of the items. + OnOverFlow func(ListBox) // Whether the listbox should be rendered in a horizontal layout. Note that // in the horizontal layout, items must have only one line. Horizontal bool @@ -65,6 +69,12 @@ func NewListBox(spec ListBoxSpec) ListBox { if spec.OnAccept == nil { spec.OnAccept = func(Items, int) {} } + if spec.OnUnderFlow == nil { + spec.OnUnderFlow = func(ListBox) {} + } + if spec.OnOverFlow == nil { + spec.OnOverFlow = func(ListBox) {} + } if spec.OnSelect == nil { spec.OnSelect = func(Items, int) {} } else { @@ -284,6 +294,12 @@ func (w *listBox) Handle(event term.Event) bool { case term.K(ui.Enter): w.Accept() return true + case term.K(ui.Up, ui.Alt): + w.OnUnderFlow(w) + return true + case term.K(ui.Down, ui.Alt): + w.OnOverFlow(w) + return true } return false }