From da3e0f7147a3400374d643f6d746b0911fc5eaaa Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Tue, 30 Jan 2024 12:04:02 +0100 Subject: [PATCH] Support deleting items from the custom commands history In the custom commands panel you can now tab to the suggestions and hit 'd' to delete items from there. Useful if you mistyped a command and don't want it to appear in your history any more. --- pkg/gui/context/suggestions_context.go | 9 ++-- pkg/gui/controllers/custom_command_action.go | 26 +++++++++++- .../helpers/confirmation_helper.go | 11 +++++ pkg/gui/controllers/suggestions_controller.go | 6 +++ pkg/gui/popup/popup_handler.go | 15 +++---- pkg/gui/types/common.go | 20 +++++---- pkg/integration/components/prompt_driver.go | 9 ++++ .../custom_commands/delete_from_history.go | 41 +++++++++++++++++++ pkg/integration/tests/test_list.go | 1 + 9 files changed, 116 insertions(+), 22 deletions(-) create mode 100644 pkg/integration/tests/custom_commands/delete_from_history.go diff --git a/pkg/gui/context/suggestions_context.go b/pkg/gui/context/suggestions_context.go index 59908fe5e..7b657121a 100644 --- a/pkg/gui/context/suggestions_context.go +++ b/pkg/gui/context/suggestions_context.go @@ -14,10 +14,11 @@ type SuggestionsContext struct { } type SuggestionsContextState struct { - Suggestions []*types.Suggestion - OnConfirm func() error - OnClose func() error - AsyncHandler *tasks.AsyncHandler + Suggestions []*types.Suggestion + OnConfirm func() error + OnClose func() error + OnDeleteSuggestion func() error + AsyncHandler *tasks.AsyncHandler // FindSuggestions will take a string that the user has typed into a prompt // and return a slice of suggestions which match that string. diff --git a/pkg/gui/controllers/custom_command_action.go b/pkg/gui/controllers/custom_command_action.go index f4de3218e..3225867d9 100644 --- a/pkg/gui/controllers/custom_command_action.go +++ b/pkg/gui/controllers/custom_command_action.go @@ -1,6 +1,7 @@ package controllers import ( + "slices" "strings" "github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers" @@ -32,13 +33,34 @@ func (self *CustomCommandAction) Call() error { self.c.OS().Cmd.NewShell(command), ) }, + HandleDeleteSuggestion: func(index int) error { + // index is the index in the _filtered_ list of suggestions, so we + // need to map it back to the full list. There's no really good way + // to do this, but fortunately we keep the items in the + // CustomCommandsHistory unique, which allows us to simply search + // for it by string. + item := self.c.Contexts().Suggestions.GetItems()[index].Value + fullIndex := lo.IndexOf(self.c.GetAppState().CustomCommandsHistory, item) + if fullIndex == -1 { + // Should never happen, but better be safe + return nil + } + + self.c.GetAppState().CustomCommandsHistory = slices.Delete( + self.c.GetAppState().CustomCommandsHistory, fullIndex, fullIndex+1) + self.c.SaveAppStateAndLogError() + self.c.Contexts().Suggestions.RefreshSuggestions() + return nil + }, }) } func (self *CustomCommandAction) GetCustomCommandsHistorySuggestionsFunc() func(string) []*types.Suggestion { - history := self.c.GetAppState().CustomCommandsHistory + return func(input string) []*types.Suggestion { + history := self.c.GetAppState().CustomCommandsHistory - return helpers.FilterFunc(history, self.c.UserConfig.Gui.UseFuzzySearch()) + return helpers.FilterFunc(history, self.c.UserConfig.Gui.UseFuzzySearch())(input) + } } // this mimics the shell functionality `ignorespace` diff --git a/pkg/gui/controllers/helpers/confirmation_helper.go b/pkg/gui/controllers/helpers/confirmation_helper.go index 8c1265c15..0e5e54109 100644 --- a/pkg/gui/controllers/helpers/confirmation_helper.go +++ b/pkg/gui/controllers/helpers/confirmation_helper.go @@ -270,10 +270,20 @@ func (self *ConfirmationHelper) setKeyBindings(cancel goContext.CancelFunc, opts onClose := self.wrappedConfirmationFunction(cancel, opts.HandleClose) + onDeleteSuggestion := func() error { + if opts.HandleDeleteSuggestion == nil { + return nil + } + + idx := self.c.Contexts().Suggestions.GetSelectedLineIdx() + return opts.HandleDeleteSuggestion(idx) + } + self.c.Contexts().Confirmation.State.OnConfirm = onConfirm self.c.Contexts().Confirmation.State.OnClose = onClose self.c.Contexts().Suggestions.State.OnConfirm = onSuggestionConfirm self.c.Contexts().Suggestions.State.OnClose = onClose + self.c.Contexts().Suggestions.State.OnDeleteSuggestion = onDeleteSuggestion return nil } @@ -284,6 +294,7 @@ func (self *ConfirmationHelper) clearConfirmationViewKeyBindings() { self.c.Contexts().Confirmation.State.OnClose = noop self.c.Contexts().Suggestions.State.OnConfirm = noop self.c.Contexts().Suggestions.State.OnClose = noop + self.c.Contexts().Suggestions.State.OnDeleteSuggestion = noop } func (self *ConfirmationHelper) getSelectedSuggestionValue() string { diff --git a/pkg/gui/controllers/suggestions_controller.go b/pkg/gui/controllers/suggestions_controller.go index d6e6151ff..4491cfe35 100644 --- a/pkg/gui/controllers/suggestions_controller.go +++ b/pkg/gui/controllers/suggestions_controller.go @@ -43,6 +43,12 @@ func (self *SuggestionsController) GetKeybindings(opts types.KeybindingsOpts) [] Key: opts.GetKey(opts.Config.Universal.TogglePanel), Handler: func() error { return self.c.ReplaceContext(self.c.Contexts().Confirmation) }, }, + { + Key: opts.GetKey(opts.Config.Universal.Remove), + Handler: func() error { + return self.context().State.OnDeleteSuggestion() + }, + }, } return bindings diff --git a/pkg/gui/popup/popup_handler.go b/pkg/gui/popup/popup_handler.go index 1ca9cc3ee..ac003e943 100644 --- a/pkg/gui/popup/popup_handler.go +++ b/pkg/gui/popup/popup_handler.go @@ -104,13 +104,14 @@ func (self *PopupHandler) Confirm(opts types.ConfirmOpts) error { func (self *PopupHandler) Prompt(opts types.PromptOpts) error { return self.createPopupPanelFn(context.Background(), types.CreatePopupPanelOpts{ - Title: opts.Title, - Prompt: opts.InitialContent, - Editable: true, - HandleConfirmPrompt: opts.HandleConfirm, - HandleClose: opts.HandleClose, - FindSuggestionsFunc: opts.FindSuggestionsFunc, - Mask: opts.Mask, + Title: opts.Title, + Prompt: opts.InitialContent, + Editable: true, + HandleConfirmPrompt: opts.HandleConfirm, + HandleClose: opts.HandleClose, + HandleDeleteSuggestion: opts.HandleDeleteSuggestion, + FindSuggestionsFunc: opts.FindSuggestionsFunc, + Mask: opts.Mask, }) } diff --git a/pkg/gui/types/common.go b/pkg/gui/types/common.go index 44f07c1e3..39378484b 100644 --- a/pkg/gui/types/common.go +++ b/pkg/gui/types/common.go @@ -165,13 +165,14 @@ type CreateMenuOptions struct { } type CreatePopupPanelOpts struct { - HasLoader bool - Editable bool - Title string - Prompt string - HandleConfirm func() error - HandleConfirmPrompt func(string) error - HandleClose func() error + HasLoader bool + Editable bool + Title string + Prompt string + HandleConfirm func() error + HandleConfirmPrompt func(string) error + HandleClose func() error + HandleDeleteSuggestion func(int) error FindSuggestionsFunc func(string) []*Suggestion Mask bool @@ -193,8 +194,9 @@ type PromptOpts struct { FindSuggestionsFunc func(string) []*Suggestion HandleConfirm func(string) error // CAPTURE THIS - HandleClose func() error - Mask bool + HandleClose func() error + HandleDeleteSuggestion func(int) error + Mask bool } type MenuSection struct { diff --git a/pkg/integration/components/prompt_driver.go b/pkg/integration/components/prompt_driver.go index 023c2f438..d1cce878c 100644 --- a/pkg/integration/components/prompt_driver.go +++ b/pkg/integration/components/prompt_driver.go @@ -82,3 +82,12 @@ func (self *PromptDriver) ConfirmSuggestion(matcher *TextMatcher) { NavigateToLine(matcher). PressEnter() } + +func (self *PromptDriver) DeleteSuggestion(matcher *TextMatcher) *PromptDriver { + self.t.press(self.t.keys.Universal.TogglePanel) + self.t.Views().Suggestions(). + IsFocused(). + NavigateToLine(matcher) + self.t.press(self.t.keys.Universal.Remove) + return self +} diff --git a/pkg/integration/tests/custom_commands/delete_from_history.go b/pkg/integration/tests/custom_commands/delete_from_history.go new file mode 100644 index 000000000..e90e6c3c6 --- /dev/null +++ b/pkg/integration/tests/custom_commands/delete_from_history.go @@ -0,0 +1,41 @@ +package custom_commands + +import ( + "github.com/jesseduffield/lazygit/pkg/config" + . "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +var DeleteFromHistory = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "Delete an entry from the custom commands history", + ExtraCmdArgs: []string{}, + Skip: false, + SetupRepo: func(shell *Shell) {}, + SetupConfig: func(cfg *config.AppConfig) {}, + Run: func(t *TestDriver, keys config.KeybindingConfig) { + createCustomCommand := func(command string) { + t.GlobalPress(keys.Universal.ExecuteCustomCommand) + t.ExpectPopup().Prompt(). + Title(Equals("Custom command:")). + Type(command). + Confirm() + } + + createCustomCommand("echo 1") + createCustomCommand("echo 2") + createCustomCommand("echo 3") + + t.GlobalPress(keys.Universal.ExecuteCustomCommand) + t.ExpectPopup().Prompt(). + Title(Equals("Custom command:")). + SuggestionLines( + Contains("3"), + Contains("2"), + Contains("1"), + ). + DeleteSuggestion(Contains("2")). + SuggestionLines( + Contains("3"), + Contains("1"), + ) + }, +}) diff --git a/pkg/integration/tests/test_list.go b/pkg/integration/tests/test_list.go index 2548702e9..eae6a5f7b 100644 --- a/pkg/integration/tests/test_list.go +++ b/pkg/integration/tests/test_list.go @@ -105,6 +105,7 @@ var tests = []*components.IntegrationTest{ custom_commands.BasicCmdFromConfig, custom_commands.CheckForConflicts, custom_commands.ComplexCmdAtRuntime, + custom_commands.DeleteFromHistory, custom_commands.FormPrompts, custom_commands.History, custom_commands.MenuFromCommand,