mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-04-25 12:24:47 +02:00
Delete and edit custom commands history items (#3534)
- **PR Description** Allow deleting and editing custom command history items. Deleting is done by hitting `d` on a suggestion; editing is done by hitting `e`, which fills the selected item into the command prompt for further editing. Closes #2528.
This commit is contained in:
commit
866e80529b
@ -14,10 +14,13 @@ 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
|
||||
|
||||
AllowEditSuggestion bool
|
||||
|
||||
// FindSuggestions will take a string that the user has typed into a prompt
|
||||
// and return a slice of suggestions which match that string.
|
||||
|
@ -1,6 +1,8 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
@ -39,6 +41,14 @@ func (self *ConfirmationController) GetKeybindings(opts types.KeybindingsOpts) [
|
||||
Key: opts.GetKey(opts.Config.Universal.TogglePanel),
|
||||
Handler: func() error {
|
||||
if len(self.c.Contexts().Suggestions.State.Suggestions) > 0 {
|
||||
subtitle := ""
|
||||
if self.c.State().GetRepoState().GetCurrentPopupOpts().HandleDeleteSuggestion != nil {
|
||||
// We assume that whenever things are deletable, they
|
||||
// are also editable, so we show both keybindings
|
||||
subtitle = fmt.Sprintf(self.c.Tr.SuggestionsSubtitle,
|
||||
self.c.UserConfig.Keybinding.Universal.Remove, self.c.UserConfig.Keybinding.Universal.Edit)
|
||||
}
|
||||
self.c.Views().Suggestions.Subtitle = subtitle
|
||||
return self.c.ReplaceContext(self.c.Contexts().Suggestions)
|
||||
}
|
||||
return nil
|
||||
|
@ -1,6 +1,7 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers"
|
||||
@ -17,6 +18,7 @@ func (self *CustomCommandAction) Call() error {
|
||||
return self.c.Prompt(types.PromptOpts{
|
||||
Title: self.c.Tr.CustomCommand,
|
||||
FindSuggestionsFunc: self.GetCustomCommandsHistorySuggestionsFunc(),
|
||||
AllowEditSuggestion: true,
|
||||
HandleConfirm: func(command string) error {
|
||||
if self.shouldSaveCommand(command) {
|
||||
self.c.GetAppState().CustomCommandsHistory = utils.Limit(
|
||||
@ -32,13 +34,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`
|
||||
|
@ -159,6 +159,7 @@ func (self *ConfirmationHelper) prepareConfirmationPanel(
|
||||
suggestionsContext.SetSuggestions(opts.FindSuggestionsFunc(""))
|
||||
suggestionsView.Visible = true
|
||||
suggestionsView.Title = fmt.Sprintf(self.c.Tr.SuggestionsTitle, self.c.UserConfig.Keybinding.Universal.TogglePanel)
|
||||
suggestionsView.Subtitle = ""
|
||||
}
|
||||
|
||||
self.ResizeConfirmationPanel()
|
||||
@ -223,6 +224,8 @@ func (self *ConfirmationHelper) CreatePopupPanel(ctx goContext.Context, opts typ
|
||||
return err
|
||||
}
|
||||
|
||||
self.c.Contexts().Suggestions.State.AllowEditSuggestion = opts.AllowEditSuggestion
|
||||
|
||||
self.c.State().GetRepoState().SetCurrentPopupOpts(&opts)
|
||||
|
||||
return self.c.PushContext(self.c.Contexts().Confirmation)
|
||||
@ -270,10 +273,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 +297,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 {
|
||||
|
@ -40,8 +40,32 @@ func (self *SuggestionsController) GetKeybindings(opts types.KeybindingsOpts) []
|
||||
Handler: func() error { return self.context().State.OnClose() },
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.TogglePanel),
|
||||
Handler: func() error { return self.c.ReplaceContext(self.c.Contexts().Confirmation) },
|
||||
Key: opts.GetKey(opts.Config.Universal.TogglePanel),
|
||||
Handler: func() error {
|
||||
self.c.Views().Suggestions.Subtitle = ""
|
||||
return self.c.ReplaceContext(self.c.Contexts().Confirmation)
|
||||
},
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.Remove),
|
||||
Handler: func() error {
|
||||
return self.context().State.OnDeleteSuggestion()
|
||||
},
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.Edit),
|
||||
Handler: func() error {
|
||||
if self.context().State.AllowEditSuggestion {
|
||||
if selectedItem := self.c.Contexts().Suggestions.GetSelected(); selectedItem != nil {
|
||||
self.c.Contexts().Confirmation.GetView().TextArea.Clear()
|
||||
self.c.Contexts().Confirmation.GetView().TextArea.TypeString(selectedItem.Value)
|
||||
self.c.Contexts().Confirmation.GetView().RenderTextArea()
|
||||
self.c.Contexts().Suggestions.RefreshSuggestions()
|
||||
return self.c.ReplaceContext(self.c.Contexts().Confirmation)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -104,13 +104,15 @@ 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,
|
||||
AllowEditSuggestion: opts.AllowEditSuggestion,
|
||||
Mask: opts.Mask,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -165,16 +165,18 @@ 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
|
||||
AllowEditSuggestion bool
|
||||
}
|
||||
|
||||
type ConfirmOpts struct {
|
||||
@ -192,9 +194,11 @@ type PromptOpts struct {
|
||||
InitialContent string
|
||||
FindSuggestionsFunc func(string) []*Suggestion
|
||||
HandleConfirm func(string) error
|
||||
AllowEditSuggestion bool
|
||||
// CAPTURE THIS
|
||||
HandleClose func() error
|
||||
Mask bool
|
||||
HandleClose func() error
|
||||
HandleDeleteSuggestion func(int) error
|
||||
Mask bool
|
||||
}
|
||||
|
||||
type MenuSection struct {
|
||||
|
@ -631,6 +631,7 @@ type TranslationSet struct {
|
||||
SuggestionsCheatsheetTitle string
|
||||
// Unlike the cheatsheet title above, the real suggestions title has a little message saying press tab to focus
|
||||
SuggestionsTitle string
|
||||
SuggestionsSubtitle string
|
||||
ExtrasTitle string
|
||||
PushingTagStatus string
|
||||
PullRequestURLCopiedToClipboard string
|
||||
@ -1593,6 +1594,7 @@ func EnglishTranslationSet() TranslationSet {
|
||||
NavigationTitle: "List panel navigation",
|
||||
SuggestionsCheatsheetTitle: "Suggestions",
|
||||
SuggestionsTitle: "Suggestions (press %s to focus)",
|
||||
SuggestionsSubtitle: "(press %s to delete, %s to edit)",
|
||||
ExtrasTitle: "Command log",
|
||||
PushingTagStatus: "Pushing tag",
|
||||
PullRequestURLCopiedToClipboard: "Pull request URL copied to clipboard",
|
||||
|
@ -82,3 +82,21 @@ 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
|
||||
}
|
||||
|
||||
func (self *PromptDriver) EditSuggestion(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.Edit)
|
||||
return self
|
||||
}
|
||||
|
41
pkg/integration/tests/custom_commands/delete_from_history.go
Normal file
41
pkg/integration/tests/custom_commands/delete_from_history.go
Normal file
@ -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"),
|
||||
)
|
||||
},
|
||||
})
|
31
pkg/integration/tests/custom_commands/edit_history.go
Normal file
31
pkg/integration/tests/custom_commands/edit_history.go
Normal file
@ -0,0 +1,31 @@
|
||||
package custom_commands
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
)
|
||||
|
||||
var EditHistory = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Edit 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) {
|
||||
t.GlobalPress(keys.Universal.ExecuteCustomCommand)
|
||||
t.ExpectPopup().Prompt().
|
||||
Title(Equals("Custom command:")).
|
||||
Type("echo x").
|
||||
Confirm()
|
||||
|
||||
t.GlobalPress(keys.Universal.ExecuteCustomCommand)
|
||||
t.ExpectPopup().Prompt().
|
||||
Title(Equals("Custom command:")).
|
||||
Type("ec").
|
||||
SuggestionLines(
|
||||
Equals("echo x"),
|
||||
).
|
||||
EditSuggestion(Equals("echo x")).
|
||||
InitialText(Equals("echo x"))
|
||||
},
|
||||
})
|
@ -105,6 +105,8 @@ var tests = []*components.IntegrationTest{
|
||||
custom_commands.BasicCmdFromConfig,
|
||||
custom_commands.CheckForConflicts,
|
||||
custom_commands.ComplexCmdAtRuntime,
|
||||
custom_commands.DeleteFromHistory,
|
||||
custom_commands.EditHistory,
|
||||
custom_commands.FormPrompts,
|
||||
custom_commands.History,
|
||||
custom_commands.MenuFromCommand,
|
||||
|
Loading…
x
Reference in New Issue
Block a user