diff --git a/docs-master/keybindings/Keybindings_en.md b/docs-master/keybindings/Keybindings_en.md index 9e2bce45b..2a25d1f62 100644 --- a/docs-master/keybindings/Keybindings_en.md +++ b/docs-master/keybindings/Keybindings_en.md @@ -71,7 +71,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_ | `` - `` | Collapse all files | Collapse all directories in the files tree | | `` = `` | Expand all files | Expand all directories in the file tree | | `` 0 `` | Focus main view | | -| `` / `` | Search the current view by text | | +| `` / `` | Filter the current view by text | | ## Commit summary @@ -160,7 +160,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_ | `` - `` | Collapse all files | Collapse all directories in the files tree | | `` = `` | Expand all files | Expand all directories in the file tree | | `` 0 `` | Focus main view | | -| `` / `` | Search the current view by text | | +| `` / `` | Filter the current view by text | | ## Input prompt diff --git a/docs-master/keybindings/Keybindings_ja.md b/docs-master/keybindings/Keybindings_ja.md index ce9657e4e..899493323 100644 --- a/docs-master/keybindings/Keybindings_ja.md +++ b/docs-master/keybindings/Keybindings_ja.md @@ -119,7 +119,7 @@ _凡例:`<c-b>` はctrl+b、`<a-b>` はalt+b、`B` はshift+bを意味 | `` - `` | すべてのファイルを折りたたむ | ファイルツリー内のすべてのディレクトリを折りたたみます | | `` = `` | すべてのファイルを展開 | ファイルツリー内のすべてのディレクトリを展開します | | `` 0 `` | メインビューにフォーカス | | -| `` / `` | 現在のビューをテキストで検索 | | +| `` / `` | 現在のビューをテキストでフィルタリング | | ## コミット概要 @@ -242,7 +242,7 @@ _凡例:`<c-b>` はctrl+b、`<a-b>` はalt+b、`B` はshift+bを意味 | `` - `` | すべてのファイルを折りたたむ | ファイルツリー内のすべてのディレクトリを折りたたみます | | `` = `` | すべてのファイルを展開 | ファイルツリー内のすべてのディレクトリを展開します | | `` 0 `` | メインビューにフォーカス | | -| `` / `` | 現在のビューをテキストで検索 | | +| `` / `` | 現在のビューをテキストでフィルタリング | | ## メインパネル(ステージング) diff --git a/docs-master/keybindings/Keybindings_ko.md b/docs-master/keybindings/Keybindings_ko.md index 134c0ae04..e4e05b884 100644 --- a/docs-master/keybindings/Keybindings_ko.md +++ b/docs-master/keybindings/Keybindings_ko.md @@ -349,7 +349,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_ | `` - `` | Collapse all files | Collapse all directories in the files tree | | `` = `` | Expand all files | Expand all directories in the file tree | | `` 0 `` | Focus main view | | -| `` / `` | 검색 시작 | | +| `` / `` | Filter the current view by text | | ## 커밋메시지 @@ -405,7 +405,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_ | `` - `` | Collapse all files | Collapse all directories in the files tree | | `` = `` | Expand all files | Expand all directories in the file tree | | `` 0 `` | Focus main view | | -| `` / `` | 검색 시작 | | +| `` / `` | Filter the current view by text | | ## 확인 패널 diff --git a/docs-master/keybindings/Keybindings_nl.md b/docs-master/keybindings/Keybindings_nl.md index ec88b025d..e1d99162c 100644 --- a/docs-master/keybindings/Keybindings_nl.md +++ b/docs-master/keybindings/Keybindings_nl.md @@ -84,7 +84,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_ | `` - `` | Collapse all files | Collapse all directories in the files tree | | `` = `` | Expand all files | Expand all directories in the file tree | | `` 0 `` | Focus main view | | -| `` / `` | Start met zoeken | | +| `` / `` | Filter the current view by text | | ## Bevestigingspaneel @@ -149,7 +149,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_ | `` - `` | Collapse all files | Collapse all directories in the files tree | | `` = `` | Expand all files | Expand all directories in the file tree | | `` 0 `` | Focus main view | | -| `` / `` | Start met zoeken | | +| `` / `` | Filter the current view by text | | ## Commits diff --git a/docs-master/keybindings/Keybindings_pl.md b/docs-master/keybindings/Keybindings_pl.md index bb0907326..28c9f73a8 100644 --- a/docs-master/keybindings/Keybindings_pl.md +++ b/docs-master/keybindings/Keybindings_pl.md @@ -260,7 +260,7 @@ _Legenda: `` oznacza ctrl+b, `` oznacza alt+b, `B` oznacza shift+b_ | `` - `` | Collapse all files | Collapse all directories in the files tree | | `` = `` | Expand all files | Expand all directories in the file tree | | `` 0 `` | Focus main view | | -| `` / `` | Szukaj w bieżącym widoku po tekście | | +| `` / `` | Filtruj bieżący widok po tekście | | ## Pliki commita @@ -280,7 +280,7 @@ _Legenda: `` oznacza ctrl+b, `` oznacza alt+b, `B` oznacza shift+b_ | `` - `` | Collapse all files | Collapse all directories in the files tree | | `` = `` | Expand all files | Expand all directories in the file tree | | `` 0 `` | Focus main view | | -| `` / `` | Szukaj w bieżącym widoku po tekście | | +| `` / `` | Filtruj bieżący widok po tekście | | ## Podsumowanie commita diff --git a/docs-master/keybindings/Keybindings_pt.md b/docs-master/keybindings/Keybindings_pt.md index d55e4ae1a..217d477d4 100644 --- a/docs-master/keybindings/Keybindings_pt.md +++ b/docs-master/keybindings/Keybindings_pt.md @@ -84,7 +84,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_ | `` - `` | Recolher todos os arquivos | Recolher todos os diretórios na árvore de arquivos | | `` = `` | Expandir todos os arquivos | Expandir todos os diretórios na árvore do arquivo | | `` 0 `` | Focus main view | | -| `` / `` | Search the current view by text | | +| `` / `` | Filter the current view by text | | ## Branches locais @@ -153,7 +153,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_ | `` - `` | Recolher todos os arquivos | Recolher todos os diretórios na árvore de arquivos | | `` = `` | Expandir todos os arquivos | Expandir todos os diretórios na árvore do arquivo | | `` 0 `` | Focus main view | | -| `` / `` | Search the current view by text | | +| `` / `` | Filter the current view by text | | ## Commits diff --git a/docs-master/keybindings/Keybindings_ru.md b/docs-master/keybindings/Keybindings_ru.md index 085b4e00b..20c0056b6 100644 --- a/docs-master/keybindings/Keybindings_ru.md +++ b/docs-master/keybindings/Keybindings_ru.md @@ -308,7 +308,7 @@ _Связки клавиш_ | `` - `` | Collapse all files | Collapse all directories in the files tree | | `` = `` | Expand all files | Expand all directories in the file tree | | `` 0 `` | Focus main view | | -| `` / `` | Найти | | +| `` / `` | Filter the current view by text | | ## Статус @@ -399,7 +399,7 @@ _Связки клавиш_ | `` - `` | Collapse all files | Collapse all directories in the files tree | | `` = `` | Expand all files | Expand all directories in the file tree | | `` 0 `` | Focus main view | | -| `` / `` | Найти | | +| `` / `` | Filter the current view by text | | ## Хранилище diff --git a/docs-master/keybindings/Keybindings_zh-CN.md b/docs-master/keybindings/Keybindings_zh-CN.md index c29102c3e..f1b32386a 100644 --- a/docs-master/keybindings/Keybindings_zh-CN.md +++ b/docs-master/keybindings/Keybindings_zh-CN.md @@ -183,7 +183,7 @@ _图例:`` 意味着ctrl+b, `意味着Alt+b, `B` 意味着shift+b_ | `` - `` | 折叠全部文件 | 折叠文件树中的全部目录 | | `` = `` | 展开全部文件 | 展开文件树中的全部目录 | | `` 0 `` | 聚焦主视图 | | -| `` / `` | 开始搜索 | | +| `` / `` | 通过文本过滤当前视图 | | ## 文件 @@ -216,7 +216,7 @@ _图例:`` 意味着ctrl+b, `意味着Alt+b, `B` 意味着shift+b_ | `` - `` | 折叠全部文件 | 折叠文件树中的全部目录 | | `` = `` | 展开全部文件 | 展开文件树中的全部目录 | | `` 0 `` | 聚焦主视图 | | -| `` / `` | 开始搜索 | | +| `` / `` | 通过文本过滤当前视图 | | ## 本地分支 diff --git a/pkg/commands/git_commands/working_tree.go b/pkg/commands/git_commands/working_tree.go index 55159a7f9..664436066 100644 --- a/pkg/commands/git_commands/working_tree.go +++ b/pkg/commands/git_commands/working_tree.go @@ -253,11 +253,14 @@ func (self *WorkingTreeCommands) Exclude(filename string) error { // WorktreeFileDiff returns the diff of a file func (self *WorkingTreeCommands) WorktreeFileDiff(file *models.File, plain bool, cached bool) string { // for now we assume an error means the file was deleted - s, _ := self.WorktreeFileDiffCmdObj(file, plain, cached).RunWithOutput() + s, _ := self.WorktreeFileDiffCmdObj(file, plain, cached, nil).RunWithOutput() return s } -func (self *WorkingTreeCommands) WorktreeFileDiffCmdObj(node models.IFile, plain bool, cached bool) *oscommands.CmdObj { +// WorktreeFileDiffCmdObj returns a command object for diffing a file or directory +// in the working tree. When pathOverrides is non-empty, those paths are used instead of +// the node's path (used to diff only filtered/visible files within a directory). +func (self *WorkingTreeCommands) WorktreeFileDiffCmdObj(node models.IFile, plain bool, cached bool, pathOverrides []string) *oscommands.CmdObj { colorArg := self.pagerConfig.GetColorArg() if plain { colorArg = "never" @@ -270,6 +273,11 @@ func (self *WorkingTreeCommands) WorktreeFileDiffCmdObj(node models.IFile, plain useExtDiff := extDiffCmd != "" && !plain useExtDiffGitConfig := self.pagerConfig.GetUseExternalDiffGitConfig() && !plain + paths := pathOverrides + if len(paths) == 0 { + paths = []string{node.GetPath()} + } + cmdArgs := NewGitCmd("diff"). ConfigIf(useExtDiff, "diff.external="+extDiffCmd). ArgIfElse(useExtDiff || useExtDiffGitConfig, "--ext-diff", "--no-ext-diff"). @@ -282,7 +290,7 @@ func (self *WorkingTreeCommands) WorktreeFileDiffCmdObj(node models.IFile, plain ArgIf(noIndex, "--no-index"). Arg("--"). ArgIf(noIndex, "/dev/null"). - Arg(node.GetPath()). + Arg(paths...). ArgIf(prevPath != "", prevPath). Dir(self.repoPaths.worktreePath). ToArgv() @@ -293,10 +301,10 @@ func (self *WorkingTreeCommands) WorktreeFileDiffCmdObj(node models.IFile, plain // ShowFileDiff get the diff of specified from and to. Typically this will be used for a single commit so it'll be 123abc^..123abc // but when we're in diff mode it could be any 'from' to any 'to'. The reverse flag is also here thanks to diff mode. func (self *WorkingTreeCommands) ShowFileDiff(from string, to string, reverse bool, fileName string, plain bool) (string, error) { - return self.ShowFileDiffCmdObj(from, to, reverse, fileName, plain).RunWithOutput() + return self.ShowFileDiffCmdObj(from, to, reverse, []string{fileName}, plain).RunWithOutput() } -func (self *WorkingTreeCommands) ShowFileDiffCmdObj(from string, to string, reverse bool, fileName string, plain bool) *oscommands.CmdObj { +func (self *WorkingTreeCommands) ShowFileDiffCmdObj(from string, to string, reverse bool, fileNames []string, plain bool) *oscommands.CmdObj { contextSize := self.UserConfig().Git.DiffContextSize colorArg := self.pagerConfig.GetColorArg() @@ -321,7 +329,7 @@ func (self *WorkingTreeCommands) ShowFileDiffCmdObj(from string, to string, reve ArgIf(reverse, "-R"). ArgIf(!plain && self.UserConfig().Git.IgnoreWhitespaceInDiffView, "--ignore-all-space"). Arg("--"). - Arg(fileName). + Arg(fileNames...). Dir(self.repoPaths.worktreePath). ToArgv() diff --git a/pkg/gui/context/commit_files_context.go b/pkg/gui/context/commit_files_context.go index b4a14045c..f819a2eb4 100644 --- a/pkg/gui/context/commit_files_context.go +++ b/pkg/gui/context/commit_files_context.go @@ -3,7 +3,6 @@ package context import ( "fmt" - "github.com/jesseduffield/gocui" "github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/gui/filetree" "github.com/jesseduffield/lazygit/pkg/gui/presentation" @@ -17,13 +16,12 @@ type CommitFilesContext struct { *filetree.CommitFileTreeViewModel *ListContextTrait *DynamicTitleBuilder - *SearchTrait } var ( _ types.IListContext = (*CommitFilesContext)(nil) _ types.DiffableContext = (*CommitFilesContext)(nil) - _ types.ISearchableContext = (*CommitFilesContext)(nil) + _ types.IFilterableContext = (*CommitFilesContext)(nil) ) func NewCommitFilesContext(c *ContextCommon) *CommitFilesContext { @@ -48,7 +46,6 @@ func NewCommitFilesContext(c *ContextCommon) *CommitFilesContext { ctx := &CommitFilesContext{ CommitFileTreeViewModel: viewModel, DynamicTitleBuilder: NewDynamicTitleBuilder(c.Tr.CommitFilesDynamicTitle), - SearchTrait: NewSearchTrait(c), ListContextTrait: &ListContextTrait{ Context: NewSimpleContext( NewBaseContext(NewBaseContextOpts{ @@ -68,9 +65,6 @@ func NewCommitFilesContext(c *ContextCommon) *CommitFilesContext { }, } - ctx.GetView().SetRenderSearchStatus(ctx.SearchTrait.RenderSearchStatus) - ctx.GetView().SetOnSelectItem(ctx.OnSearchSelect) - return ctx } @@ -93,10 +87,6 @@ func (self *CommitFilesContext) GetFromAndToForDiff() (string, string) { return ref.ParentRefName(), ref.RefName() } -func (self *CommitFilesContext) ModelSearchResults(searchStr string, caseSensitive bool) []gocui.SearchPosition { - return nil -} - func (self *CommitFilesContext) ReInit(ref models.Ref, refRange *types.RefRange) { self.SetRef(ref) self.SetRefRange(refRange) diff --git a/pkg/gui/context/working_tree_context.go b/pkg/gui/context/working_tree_context.go index d37306dc8..d82037e44 100644 --- a/pkg/gui/context/working_tree_context.go +++ b/pkg/gui/context/working_tree_context.go @@ -1,7 +1,6 @@ package context import ( - "github.com/jesseduffield/gocui" "github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/gui/filetree" "github.com/jesseduffield/lazygit/pkg/gui/presentation" @@ -13,12 +12,11 @@ import ( type WorkingTreeContext struct { *filetree.FileTreeViewModel *ListContextTrait - *SearchTrait } var ( _ types.IListContext = (*WorkingTreeContext)(nil) - _ types.ISearchableContext = (*WorkingTreeContext)(nil) + _ types.IFilterableContext = (*WorkingTreeContext)(nil) ) func NewWorkingTreeContext(c *ContextCommon) *WorkingTreeContext { @@ -38,7 +36,6 @@ func NewWorkingTreeContext(c *ContextCommon) *WorkingTreeContext { } ctx := &WorkingTreeContext{ - SearchTrait: NewSearchTrait(c), FileTreeViewModel: viewModel, ListContextTrait: &ListContextTrait{ Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{ @@ -56,12 +53,5 @@ func NewWorkingTreeContext(c *ContextCommon) *WorkingTreeContext { }, } - ctx.GetView().SetRenderSearchStatus(ctx.SearchTrait.RenderSearchStatus) - ctx.GetView().SetOnSelectItem(ctx.OnSearchSelect) - return ctx } - -func (self *WorkingTreeContext) ModelSearchResults(searchStr string, caseSensitive bool) []gocui.SearchPosition { - return nil -} diff --git a/pkg/gui/controllers/commits_files_controller.go b/pkg/gui/controllers/commits_files_controller.go index 1412254e7..7d677b756 100644 --- a/pkg/gui/controllers/commits_files_controller.go +++ b/pkg/gui/controllers/commits_files_controller.go @@ -152,7 +152,8 @@ func (self *CommitFilesController) GetOnRenderToMain() func() { from, to := self.context().GetFromAndToForDiff() from, reverse := self.c.Modes().Diffing.GetFromAndReverseArgsForDiff(from) - cmdObj := self.c.Git().WorkingTree.ShowFileDiffCmdObj(from, to, reverse, node.GetPath(), false) + paths := self.pathsForDiff(node) + cmdObj := self.c.Git().WorkingTree.ShowFileDiffCmdObj(from, to, reverse, paths, false) task := types.NewRunPtyTask(cmdObj.GetCmd()) self.c.RenderToMainViews(types.RefreshMainOpts{ @@ -171,7 +172,7 @@ func (self *CommitFilesController) copyDiffToClipboard(path string, toastMessage from, to := self.context().GetFromAndToForDiff() from, reverse := self.c.Modes().Diffing.GetFromAndReverseArgsForDiff(from) - cmdObj := self.c.Git().WorkingTree.ShowFileDiffCmdObj(from, to, reverse, path, true) + cmdObj := self.c.Git().WorkingTree.ShowFileDiffCmdObj(from, to, reverse, []string{path}, true) diff, err := cmdObj.RunWithOutput() if err != nil { return err @@ -571,6 +572,21 @@ func (self *CommitFilesController) GetOnClickFocusedMainView() func(mainViewName } } +// pathsForDiff returns the file paths to use for a diff command. When a text +// filter is active and the node is a directory, only the visible (filtered) +// file paths are returned so the diff reflects what the user sees. +func (self *CommitFilesController) pathsForDiff(node *filetree.CommitFileNode) []string { + if !node.IsFile() && self.context().IsFiltering() { + var paths []string + _ = node.ForEachFile(func(file *models.CommitFile) error { + paths = append(paths, file.Path) + return nil + }) + return paths + } + return []string{node.GetPath()} +} + // NOTE: these functions are identical to those in files_controller.go (except for types) and // could also be cleaned up with some generics func normalisedSelectedCommitFileNodes(selectedNodes []*filetree.CommitFileNode) []*filetree.CommitFileNode { diff --git a/pkg/gui/controllers/files_controller.go b/pkg/gui/controllers/files_controller.go index 5b6cbcef2..8cc2ca5e2 100644 --- a/pkg/gui/controllers/files_controller.go +++ b/pkg/gui/controllers/files_controller.go @@ -294,7 +294,8 @@ func (self *FilesController) GetOnRenderToMain() func() { split := self.c.UserConfig().Gui.SplitDiff == "always" || (node.GetHasUnstagedChanges() && node.GetHasStagedChanges()) mainShowsStaged := !split && node.GetHasStagedChanges() - cmdObj := self.c.Git().WorkingTree.WorktreeFileDiffCmdObj(node, false, mainShowsStaged) + pathOverrides := self.pathOverridesForDiff(node) + cmdObj := self.c.Git().WorkingTree.WorktreeFileDiffCmdObj(node, false, mainShowsStaged, pathOverrides) title := self.c.Tr.UnstagedChanges if mainShowsStaged { title = self.c.Tr.StagedChanges @@ -309,7 +310,7 @@ func (self *FilesController) GetOnRenderToMain() func() { } if split { - cmdObj := self.c.Git().WorkingTree.WorktreeFileDiffCmdObj(node, false, true) + cmdObj := self.c.Git().WorkingTree.WorktreeFileDiffCmdObj(node, false, true, pathOverrides) title := self.c.Tr.StagedChanges if mainShowsStaged { @@ -434,7 +435,19 @@ func (self *FilesController) pressWithLock(selectedNodes []*filetree.FileNode) e } } + // When filtering, expand directory nodes to individual visible file paths + // so that only filtered files are staged/unstaged. toPaths := func(nodes []*filetree.FileNode) []string { + if self.context().IsFiltering() { + var paths []string + for _, node := range nodes { + _ = node.ForEachFile(func(file *models.File) error { + paths = append(paths, file.Path) + return nil + }) + } + return paths + } return lo.Map(nodes, func(node *filetree.FileNode, _ int) string { return node.GetPath() }) @@ -469,22 +482,29 @@ func (self *FilesController) pressWithLock(selectedNodes []*filetree.FileNode) e return err } - // need to partition the paths into tracked and untracked (where we assume directories are tracked). Then we'll run the commands separately. - trackedNodes, untrackedNodes := utils.Partition(selectedNodes, func(node *filetree.FileNode) bool { - // We treat all directories as tracked. I'm not actually sure why we do this but - // it's been the existing behaviour for a while and nobody has complained - return !node.IsFile() || node.GetIsTracked() - }) - - if len(untrackedNodes) > 0 { - if err := self.c.Git().WorkingTree.UnstageUntrackedFiles(toPaths(untrackedNodes)); err != nil { + if self.context().IsFiltering() { + // When filtering, only unstage visible files + if err := self.unstageFilteredFiles(selectedNodes); err != nil { return err } - } + } else { + // need to partition the paths into tracked and untracked (where we assume directories are tracked). Then we'll run the commands separately. + trackedNodes, untrackedNodes := utils.Partition(selectedNodes, func(node *filetree.FileNode) bool { + // We treat all directories as tracked. I'm not actually sure why we do this but + // it's been the existing behaviour for a while and nobody has complained + return !node.IsFile() || node.GetIsTracked() + }) - if len(trackedNodes) > 0 { - if err := self.c.Git().WorkingTree.UnstageTrackedFiles(toPaths(trackedNodes)); err != nil { - return err + if len(untrackedNodes) > 0 { + if err := self.c.Git().WorkingTree.UnstageUntrackedFiles(toPaths(untrackedNodes)); err != nil { + return err + } + } + + if len(trackedNodes) > 0 { + if err := self.c.Git().WorkingTree.UnstageTrackedFiles(toPaths(trackedNodes)); err != nil { + return err + } } } } @@ -503,6 +523,48 @@ func (self *FilesController) press(nodes []*filetree.FileNode) error { return nil } +// pathOverridesForDiff returns file paths to override the node's path in diff +// commands when a text filter is active and the node is a directory. This +// ensures the diff only shows filtered/visible files. +func (self *FilesController) pathOverridesForDiff(node *filetree.FileNode) []string { + if !node.IsFile() && self.context().IsFiltering() { + var paths []string + _ = node.ForEachFile(func(file *models.File) error { + paths = append(paths, file.Path) + return nil + }) + return paths + } + return nil +} + +// unstageFilteredFiles unstages only the visible (filtered) files from the +// given nodes, correctly partitioning by tracked/untracked. +func (self *FilesController) unstageFilteredFiles(nodes []*filetree.FileNode) error { + var trackedPaths, untrackedPaths []string + for _, node := range nodes { + _ = node.ForEachFile(func(file *models.File) error { + if file.Tracked || file.HasStagedChanges { + trackedPaths = append(trackedPaths, file.Path) + } else { + untrackedPaths = append(untrackedPaths, file.Path) + } + return nil + }) + } + if len(untrackedPaths) > 0 { + if err := self.c.Git().WorkingTree.UnstageUntrackedFiles(untrackedPaths); err != nil { + return err + } + } + if len(trackedPaths) > 0 { + if err := self.c.Git().WorkingTree.UnstageTrackedFiles(trackedPaths); err != nil { + return err + } + } + return nil +} + func (self *FilesController) Context() types.Context { return self.context() } @@ -648,9 +710,21 @@ func (self *FilesController) toggleStagedAllWithLock() error { return err } - onlyTrackedFiles := self.context().GetStatusFilter() == filetree.DisplayTracked - if err := self.c.Git().WorkingTree.StageAll(onlyTrackedFiles); err != nil { - return err + if self.context().IsFiltering() { + // When filtering, only stage visible files + var paths []string + _ = root.ForEachFile(func(file *models.File) error { + paths = append(paths, file.Path) + return nil + }) + if err := self.c.Git().WorkingTree.StageFiles(paths, nil); err != nil { + return err + } + } else { + onlyTrackedFiles := self.context().GetStatusFilter() == filetree.DisplayTracked + if err := self.c.Git().WorkingTree.StageAll(onlyTrackedFiles); err != nil { + return err + } } } else { self.c.LogAction(self.c.Tr.Actions.UnstageAllFiles) @@ -659,8 +733,15 @@ func (self *FilesController) toggleStagedAllWithLock() error { return err } - if err := self.c.Git().WorkingTree.UnstageAll(); err != nil { - return err + if self.context().IsFiltering() { + // When filtering, only unstage visible files + if err := self.unstageFilteredFiles([]*filetree.FileNode{root}); err != nil { + return err + } + } else { + if err := self.c.Git().WorkingTree.UnstageAll(); err != nil { + return err + } } } diff --git a/pkg/gui/controllers/submodules_controller.go b/pkg/gui/controllers/submodules_controller.go index a4a1cb796..c0f52bed1 100644 --- a/pkg/gui/controllers/submodules_controller.go +++ b/pkg/gui/controllers/submodules_controller.go @@ -125,7 +125,7 @@ func (self *SubmodulesController) GetOnRenderToMain() func() { if file == nil { task = types.NewRenderStringTask(prefix) } else { - cmdObj := self.c.Git().WorkingTree.WorktreeFileDiffCmdObj(file, false, !file.HasUnstagedChanges && file.HasStagedChanges) + cmdObj := self.c.Git().WorkingTree.WorktreeFileDiffCmdObj(file, false, !file.HasUnstagedChanges && file.HasStagedChanges, nil) task = types.NewRunCommandTaskWithPrefix(cmdObj.GetCmd(), prefix) } } diff --git a/pkg/gui/controllers/switch_to_diff_files_controller.go b/pkg/gui/controllers/switch_to_diff_files_controller.go index 86e927558..cdf943cfe 100644 --- a/pkg/gui/controllers/switch_to_diff_files_controller.go +++ b/pkg/gui/controllers/switch_to_diff_files_controller.go @@ -80,12 +80,12 @@ func (self *SwitchToDiffFilesController) enter() error { } } + commitFilesContext.ClearFilter() commitFilesContext.ReInit(ref, refsRange) commitFilesContext.SetSelection(0) commitFilesContext.SetCanRebase(canRebase) commitFilesContext.SetParentContext(self.context) commitFilesContext.SetWindowName(self.context.GetWindowName()) - commitFilesContext.ClearSearchString() commitFilesContext.GetView().TitlePrefix = self.context.GetView().TitlePrefix self.c.Refresh(types.RefreshOptions{ diff --git a/pkg/gui/filetree/commit_file_tree.go b/pkg/gui/filetree/commit_file_tree.go index 7af83176f..bf6d1251c 100644 --- a/pkg/gui/filetree/commit_file_tree.go +++ b/pkg/gui/filetree/commit_file_tree.go @@ -15,6 +15,8 @@ type ICommitFileTree interface { GetAllItems() []*CommitFileNode GetAllFiles() []*models.CommitFile GetRoot() *CommitFileNode + SetTextFilter(filter string, useFuzzySearch bool) + GetTextFilter() string } type CommitFileTree struct { @@ -23,6 +25,8 @@ type CommitFileTree struct { showTree bool common *common.Common collapsedPaths *CollapsedPaths + textFilter string + useFuzzySearch bool } func (self *CommitFileTree) CollapseAll() { @@ -93,15 +97,34 @@ func (self *CommitFileTree) GetAllFiles() []*models.CommitFile { return self.getFiles() } +func (self *CommitFileTree) getFilesForDisplay() []*models.CommitFile { + files := self.getFiles() + if self.textFilter != "" { + files = filterCommitFilesByText(files, self.textFilter, self.useFuzzySearch) + } + return files +} + func (self *CommitFileTree) SetTree() { + filesForDisplay := self.getFilesForDisplay() showRootItem := self.common.UserConfig().Gui.ShowRootItemInFileTree if self.showTree { - self.tree = BuildTreeFromCommitFiles(self.getFiles(), showRootItem) + self.tree = BuildTreeFromCommitFiles(filesForDisplay, showRootItem) } else { - self.tree = BuildFlatTreeFromCommitFiles(self.getFiles(), showRootItem) + self.tree = BuildFlatTreeFromCommitFiles(filesForDisplay, showRootItem) } } +func (self *CommitFileTree) SetTextFilter(filter string, useFuzzySearch bool) { + self.textFilter = filter + self.useFuzzySearch = useFuzzySearch + self.SetTree() +} + +func (self *CommitFileTree) GetTextFilter() string { + return self.textFilter +} + func (self *CommitFileTree) IsCollapsed(path string) bool { return self.collapsedPaths.IsCollapsed(path) } diff --git a/pkg/gui/filetree/commit_file_tree_view_model.go b/pkg/gui/filetree/commit_file_tree_view_model.go index 02b0fff9a..c2e7e74e4 100644 --- a/pkg/gui/filetree/commit_file_tree_view_model.go +++ b/pkg/gui/filetree/commit_file_tree_view_model.go @@ -8,6 +8,8 @@ import ( "github.com/jesseduffield/lazygit/pkg/common" "github.com/jesseduffield/lazygit/pkg/gui/context/traits" "github.com/jesseduffield/lazygit/pkg/gui/types" + "github.com/jesseduffield/lazygit/pkg/i18n" + "github.com/jesseduffield/lazygit/pkg/utils" "github.com/samber/lo" ) @@ -39,6 +41,8 @@ type CommitFileTreeViewModel struct { // we set this to true when you're viewing the files within the checked-out branch's commits. // If you're viewing the files of some random other branch we can't do any rebase stuff. canRebase bool + + searchHistory *utils.HistoryBuffer[string] } var _ ICommitFileTreeViewModel = &CommitFileTreeViewModel{} @@ -52,6 +56,7 @@ func NewCommitFileTreeViewModel(getFiles func() []*models.CommitFile, common *co ref: nil, refRange: nil, canRebase: false, + searchHistory: utils.NewHistoryBuffer[string](1000), } } @@ -203,3 +208,51 @@ func (self *CommitFileTreeViewModel) SelectPath(filepath string, showRootItem bo self.SetSelection(index) } } + +// IFilterableContext methods + +func (self *CommitFileTreeViewModel) SetFilter(filter string, useFuzzySearch bool) { + self.ICommitFileTree.SetTextFilter(filter, useFuzzySearch) +} + +func (self *CommitFileTreeViewModel) GetFilter() string { + return self.ICommitFileTree.GetTextFilter() +} + +func (self *CommitFileTreeViewModel) ClearFilter() { + selectedNode := self.GetSelected() + var selectedPath string + if selectedNode != nil { + selectedPath = selectedNode.GetInternalPath() + } + + self.ICommitFileTree.SetTextFilter("", false) + + if selectedPath != "" { + self.ExpandToPath(selectedPath) + if idx, found := self.GetIndexForPath(selectedPath); found { + self.SetSelection(idx) + return + } + } + self.ClampSelection() +} + +func (self *CommitFileTreeViewModel) ReApplyFilter(useFuzzySearch bool) { + self.ICommitFileTree.SetTextFilter(self.ICommitFileTree.GetTextFilter(), useFuzzySearch) +} + +func (self *CommitFileTreeViewModel) IsFiltering() bool { + return self.ICommitFileTree.GetTextFilter() != "" +} + +// used for type switch +func (self *CommitFileTreeViewModel) IsFilterableContext() {} + +func (self *CommitFileTreeViewModel) FilterPrefix(tr *i18n.TranslationSet) string { + return tr.FilterPrefix +} + +func (self *CommitFileTreeViewModel) GetSearchHistory() *utils.HistoryBuffer[string] { + return self.searchHistory +} diff --git a/pkg/gui/filetree/file_filter.go b/pkg/gui/filetree/file_filter.go new file mode 100644 index 000000000..cb4916cf7 --- /dev/null +++ b/pkg/gui/filetree/file_filter.go @@ -0,0 +1,48 @@ +package filetree + +import ( + "github.com/jesseduffield/lazygit/pkg/commands/models" + "github.com/jesseduffield/lazygit/pkg/utils" + "github.com/sahilm/fuzzy" + "github.com/samber/lo" +) + +type filePathSource struct { + files []*models.File +} + +func (s *filePathSource) String(i int) string { + return s.files[i].Path +} + +func (s *filePathSource) Len() int { + return len(s.files) +} + +func filterFilesByText(files []*models.File, filter string, useFuzzySearch bool) []*models.File { + source := &filePathSource{files: files} + matches := utils.FindFrom(filter, source, useFuzzySearch) + return lo.Map(matches, func(match fuzzy.Match, _ int) *models.File { + return files[match.Index] + }) +} + +type commitFilePathSource struct { + files []*models.CommitFile +} + +func (s *commitFilePathSource) String(i int) string { + return s.files[i].Path +} + +func (s *commitFilePathSource) Len() int { + return len(s.files) +} + +func filterCommitFilesByText(files []*models.CommitFile, filter string, useFuzzySearch bool) []*models.CommitFile { + source := &commitFilePathSource{files: files} + matches := utils.FindFrom(filter, source, useFuzzySearch) + return lo.Map(matches, func(match fuzzy.Match, _ int) *models.CommitFile { + return files[match.Index] + }) +} diff --git a/pkg/gui/filetree/file_tree.go b/pkg/gui/filetree/file_tree.go index 8190c379a..9840fd8dd 100644 --- a/pkg/gui/filetree/file_tree.go +++ b/pkg/gui/filetree/file_tree.go @@ -48,6 +48,8 @@ type IFileTree interface { GetAllFiles() []*models.File GetStatusFilter() FileTreeDisplayFilter GetRoot() *FileNode + SetTextFilter(filter string, useFuzzySearch bool) + GetTextFilter() string } type FileTree struct { @@ -57,6 +59,8 @@ type FileTree struct { common *common.Common filter FileTreeDisplayFilter collapsedPaths *CollapsedPaths + textFilter string + useFuzzySearch bool } var _ IFileTree = &FileTree{} @@ -80,24 +84,31 @@ func (self *FileTree) ExpandToPath(path string) { } func (self *FileTree) getFilesForDisplay() []*models.File { + var files []*models.File switch self.filter { case DisplayAll: - return self.getFiles() + files = self.getFiles() case DisplayStaged: - return self.FilterFiles(func(file *models.File) bool { return file.HasStagedChanges }) + files = self.FilterFiles(func(file *models.File) bool { return file.HasStagedChanges }) case DisplayUnstaged: - return self.FilterFiles(func(file *models.File) bool { return file.HasUnstagedChanges }) + files = self.FilterFiles(func(file *models.File) bool { return file.HasUnstagedChanges }) case DisplayTracked: // untracked but staged files are technically not tracked by git // but including such files in the filtered mode helps see what files are getting committed - return self.FilterFiles(func(file *models.File) bool { return file.Tracked || file.HasStagedChanges }) + files = self.FilterFiles(func(file *models.File) bool { return file.Tracked || file.HasStagedChanges }) case DisplayUntracked: - return self.FilterFiles(func(file *models.File) bool { return !(file.Tracked || file.HasStagedChanges) }) + files = self.FilterFiles(func(file *models.File) bool { return !(file.Tracked || file.HasStagedChanges) }) case DisplayConflicted: - return self.FilterFiles(func(file *models.File) bool { return file.HasMergeConflicts }) + files = self.FilterFiles(func(file *models.File) bool { return file.HasMergeConflicts }) default: panic(fmt.Sprintf("Unexpected files display filter: %d", self.filter)) } + + if self.textFilter != "" { + files = filterFilesByText(files, self.textFilter, self.useFuzzySearch) + } + + return files } func (self *FileTree) ForceShowUntracked() bool { @@ -213,3 +224,13 @@ func (self *FileTree) CollapsedPaths() *CollapsedPaths { func (self *FileTree) GetStatusFilter() FileTreeDisplayFilter { return self.filter } + +func (self *FileTree) SetTextFilter(filter string, useFuzzySearch bool) { + self.textFilter = filter + self.useFuzzySearch = useFuzzySearch + self.SetTree() +} + +func (self *FileTree) GetTextFilter() string { + return self.textFilter +} diff --git a/pkg/gui/filetree/file_tree_view_model.go b/pkg/gui/filetree/file_tree_view_model.go index 3db39d0a1..741550c19 100644 --- a/pkg/gui/filetree/file_tree_view_model.go +++ b/pkg/gui/filetree/file_tree_view_model.go @@ -8,6 +8,7 @@ import ( "github.com/jesseduffield/lazygit/pkg/common" "github.com/jesseduffield/lazygit/pkg/gui/context/traits" "github.com/jesseduffield/lazygit/pkg/gui/types" + "github.com/jesseduffield/lazygit/pkg/i18n" "github.com/jesseduffield/lazygit/pkg/utils" "github.com/samber/lo" ) @@ -24,6 +25,7 @@ type FileTreeViewModel struct { sync.RWMutex types.IListCursor IFileTree + searchHistory *utils.HistoryBuffer[string] } var _ IFileTreeViewModel = &FileTreeViewModel{} @@ -32,8 +34,9 @@ func NewFileTreeViewModel(getFiles func() []*models.File, common *common.Common, fileTree := NewFileTree(getFiles, common, showTree) listCursor := traits.NewListCursor(fileTree.Len) return &FileTreeViewModel{ - IFileTree: fileTree, - IListCursor: listCursor, + IFileTree: fileTree, + IListCursor: listCursor, + searchHistory: utils.NewHistoryBuffer[string](1000), } } @@ -220,3 +223,51 @@ func (self *FileTreeViewModel) ExpandAll() { self.SetSelectedLineIdx(index) } } + +// IFilterableContext methods + +func (self *FileTreeViewModel) SetFilter(filter string, useFuzzySearch bool) { + self.IFileTree.SetTextFilter(filter, useFuzzySearch) +} + +func (self *FileTreeViewModel) GetFilter() string { + return self.IFileTree.GetTextFilter() +} + +func (self *FileTreeViewModel) ClearFilter() { + selectedNode := self.GetSelected() + var selectedPath string + if selectedNode != nil { + selectedPath = selectedNode.GetInternalPath() + } + + self.IFileTree.SetTextFilter("", false) + + if selectedPath != "" { + self.ExpandToPath(selectedPath) + if idx, found := self.GetIndexForPath(selectedPath); found { + self.SetSelection(idx) + return + } + } + self.ClampSelection() +} + +func (self *FileTreeViewModel) ReApplyFilter(useFuzzySearch bool) { + self.IFileTree.SetTextFilter(self.IFileTree.GetTextFilter(), useFuzzySearch) +} + +func (self *FileTreeViewModel) IsFiltering() bool { + return self.IFileTree.GetTextFilter() != "" +} + +// used for type switch +func (self *FileTreeViewModel) IsFilterableContext() {} + +func (self *FileTreeViewModel) FilterPrefix(tr *i18n.TranslationSet) string { + return tr.FilterPrefix +} + +func (self *FileTreeViewModel) GetSearchHistory() *utils.HistoryBuffer[string] { + return self.searchHistory +} diff --git a/pkg/integration/tests/filter_and_search/filter_commit_files.go b/pkg/integration/tests/filter_and_search/filter_commit_files.go index 953eaf34d..a1a39f1f4 100644 --- a/pkg/integration/tests/filter_and_search/filter_commit_files.go +++ b/pkg/integration/tests/filter_and_search/filter_commit_files.go @@ -8,7 +8,7 @@ import ( var FilterCommitFiles = NewIntegrationTest(NewIntegrationTestArgs{ Description: "Basic commit file filtering by text", ExtraCmdArgs: []string{}, - Skip: true, // skipping until we have implemented file view filtering + Skip: false, SetupConfig: func(config *config.AppConfig) {}, SetupRepo: func(shell *Shell) { shell.CreateDir("folder1") diff --git a/pkg/integration/tests/filter_and_search/filter_commit_files_toggle_directory.go b/pkg/integration/tests/filter_and_search/filter_commit_files_toggle_directory.go new file mode 100644 index 000000000..174c91fc4 --- /dev/null +++ b/pkg/integration/tests/filter_and_search/filter_commit_files_toggle_directory.go @@ -0,0 +1,61 @@ +package filter_and_search + +import ( + "github.com/jesseduffield/lazygit/pkg/config" + . "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +var FilterCommitFilesToggleDirectory = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "Toggle a filtered directory for a custom patch only adds visible files", + ExtraCmdArgs: []string{}, + Skip: false, + SetupConfig: func(config *config.AppConfig) {}, + SetupRepo: func(shell *Shell) { + shell.CreateDir("dir1") + shell.CreateFileAndAdd("dir1/apple-grape", "apple-grape content\n") + shell.CreateFileAndAdd("dir1/apple-orange", "apple-orange content\n") + shell.CreateFileAndAdd("dir1/grape-orange", "grape-orange content\n") + shell.Commit("first commit") + }, + Run: func(t *TestDriver, keys config.KeybindingConfig) { + t.Views().Commits(). + Focus(). + Lines( + Contains("first commit").IsSelected(), + ). + PressEnter() + + t.Views().CommitFiles(). + IsFocused(). + Lines( + Equals("▼ dir1").IsSelected(), + Equals(" A apple-grape"), + Equals(" A apple-orange"), + Equals(" A grape-orange"), + ). + // Filter to show only "apple" files (staying in tree view) + FilterOrSearch("apple"). + Lines( + // first item is always selected after filtering + Equals("▼ dir1").IsSelected(), + Equals(" A apple-grape"), + Equals(" A apple-orange"), + ). + // dir1 is already selected; toggle for patch + PressPrimaryAction(). + Lines( + Equals("▼ dir1").IsSelected(), + Equals(" ● apple-grape"), + Equals(" ● apple-orange"), + ) + + t.Views().Information().Content(Contains("Building patch")) + + // Verify only the filtered files are in the patch (not grape-orange) + t.Views().Secondary().Content( + Contains("apple-grape"). + Contains("apple-orange"). + DoesNotContain("grape-orange"), + ) + }, +}) diff --git a/pkg/integration/tests/filter_and_search/filter_files.go b/pkg/integration/tests/filter_and_search/filter_files.go index 6eae90c18..5a029b146 100644 --- a/pkg/integration/tests/filter_and_search/filter_files.go +++ b/pkg/integration/tests/filter_and_search/filter_files.go @@ -8,7 +8,7 @@ import ( var FilterFiles = NewIntegrationTest(NewIntegrationTestArgs{ Description: "Basic file filtering by text", ExtraCmdArgs: []string{}, - Skip: true, // Skipping until we have implemented file view filtering + Skip: false, SetupConfig: func(config *config.AppConfig) {}, SetupRepo: func(shell *Shell) { shell.CreateDir("folder1") diff --git a/pkg/integration/tests/filter_and_search/filter_files_stage_all.go b/pkg/integration/tests/filter_and_search/filter_files_stage_all.go new file mode 100644 index 000000000..16c981b0c --- /dev/null +++ b/pkg/integration/tests/filter_and_search/filter_files_stage_all.go @@ -0,0 +1,50 @@ +package filter_and_search + +import ( + "github.com/jesseduffield/lazygit/pkg/config" + . "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +var FilterFilesStageAll = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "Toggle all staging with a filter only stages visible files", + ExtraCmdArgs: []string{}, + Skip: false, + SetupConfig: func(config *config.AppConfig) {}, + SetupRepo: func(shell *Shell) { + shell.CreateDir("dir1") + shell.CreateFile("dir1/apple-grape", "apple-grape content\n") + shell.CreateFile("dir1/apple-orange", "apple-orange content\n") + shell.CreateFile("dir1/grape-orange", "grape-orange content\n") + }, + Run: func(t *TestDriver, keys config.KeybindingConfig) { + t.Views().Files(). + Focus(). + Lines( + Equals("▼ dir1").IsSelected(), + Equals(" ?? apple-grape"), + Equals(" ?? apple-orange"), + Equals(" ?? grape-orange"), + ). + // Filter to show only "apple" files + FilterOrSearch("apple"). + Lines( + // first item is always selected after filtering + Equals("▼ dir1").IsSelected(), + Equals(" ?? apple-grape"), + Equals(" ?? apple-orange"), + ). + // Stage all visible files + Press(keys.Files.ToggleStagedAll). + // Clear the filter and verify only apple files are staged + PressEscape() + + t.Views().Files(). + IsFocused(). + Lines( + Equals("▼ dir1").IsSelected(), + Equals(" A apple-grape"), + Equals(" A apple-orange"), + Equals(" ?? grape-orange"), + ) + }, +}) diff --git a/pkg/integration/tests/filter_and_search/filter_files_stage_directory.go b/pkg/integration/tests/filter_and_search/filter_files_stage_directory.go new file mode 100644 index 000000000..3126e5d6f --- /dev/null +++ b/pkg/integration/tests/filter_and_search/filter_files_stage_directory.go @@ -0,0 +1,50 @@ +package filter_and_search + +import ( + "github.com/jesseduffield/lazygit/pkg/config" + . "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +var FilterFilesStageDirectory = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "Staging a filtered directory only stages visible files", + ExtraCmdArgs: []string{}, + Skip: false, + SetupConfig: func(config *config.AppConfig) {}, + SetupRepo: func(shell *Shell) { + shell.CreateDir("dir1") + shell.CreateFile("dir1/apple-grape", "apple-grape content\n") + shell.CreateFile("dir1/apple-orange", "apple-orange content\n") + shell.CreateFile("dir1/grape-orange", "grape-orange content\n") + }, + Run: func(t *TestDriver, keys config.KeybindingConfig) { + t.Views().Files(). + Focus(). + Lines( + Equals("▼ dir1").IsSelected(), + Equals(" ?? apple-grape"), + Equals(" ?? apple-orange"), + Equals(" ?? grape-orange"), + ). + // Filter to show only "apple" files + FilterOrSearch("apple"). + Lines( + // first item is always selected after filtering + Equals("▼ dir1").IsSelected(), + Equals(" ?? apple-grape"), + Equals(" ?? apple-orange"), + ). + // dir1 is already selected; stage it + PressPrimaryAction(). + // Clear the filter to see all files and verify only apple files are staged + PressEscape() + + t.Views().Files(). + IsFocused(). + Lines( + Equals("▼ dir1"), + Equals(" A apple-grape"), + Equals(" A apple-orange"), + Equals(" ?? grape-orange"), + ) + }, +}) diff --git a/pkg/integration/tests/filter_and_search/nested_filter.go b/pkg/integration/tests/filter_and_search/nested_filter.go index 703c7ccf1..6ccb606f2 100644 --- a/pkg/integration/tests/filter_and_search/nested_filter.go +++ b/pkg/integration/tests/filter_and_search/nested_filter.go @@ -70,10 +70,7 @@ var NestedFilter = NewIntegrationTest(NewIntegrationTestArgs{ ). FilterOrSearch("grape"). Lines( - Equals("▼ /"), - Equals(" A apple"), - Equals(" A grape").IsSelected(), - Equals(" A orange"), + Equals("A grape").IsSelected(), ). PressEnter() @@ -91,15 +88,12 @@ var NestedFilter = NewIntegrationTest(NewIntegrationTestArgs{ t.Views().CommitFiles(). IsFocused(). Lines( - Equals("▼ /"), - Equals(" A apple"), - Equals(" A grape").IsSelected(), - Equals(" A orange"), + Equals("A grape").IsSelected(), ). Tap(func() { t.Views().Search().IsVisible().Content(Contains("matches for 'grape'")) }). - // cancel search + // cancel filter PressEscape(). Tap(func() { t.Views().Search().IsInvisible() diff --git a/pkg/integration/tests/filter_and_search/nested_filter_transient.go b/pkg/integration/tests/filter_and_search/nested_filter_transient.go index 8548d68c0..6af0a904e 100644 --- a/pkg/integration/tests/filter_and_search/nested_filter_transient.go +++ b/pkg/integration/tests/filter_and_search/nested_filter_transient.go @@ -75,9 +75,7 @@ var NestedFilterTransient = NewIntegrationTest(NewIntegrationTestArgs{ ). FilterOrSearch("two"). Lines( - Equals("▼ /"), - Equals(" A file-one"), - Equals(" A file-two").IsSelected(), + Equals("A file-two").IsSelected(), ) t.Views().Branches(). @@ -96,7 +94,7 @@ var NestedFilterTransient = NewIntegrationTest(NewIntegrationTestArgs{ t.Views().CommitFiles(). IsFocused(). - // the search on the commit-files context has been cancelled + // the filter on the commit-files context has been cancelled Lines( Equals("▼ /").IsSelected(), Equals(" A file-one"), diff --git a/pkg/integration/tests/patch_building/toggle_directory.go b/pkg/integration/tests/patch_building/toggle_directory.go new file mode 100644 index 000000000..c9161fe69 --- /dev/null +++ b/pkg/integration/tests/patch_building/toggle_directory.go @@ -0,0 +1,64 @@ +package patch_building + +import ( + "github.com/jesseduffield/lazygit/pkg/config" + . "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +var ToggleDirectory = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "Toggle a directory for a custom patch", + ExtraCmdArgs: []string{}, + Skip: false, + SetupConfig: func(config *config.AppConfig) {}, + SetupRepo: func(shell *Shell) { + shell.CreateDir("dir1") + shell.CreateFileAndAdd("dir1/file1", "file1 content\n") + shell.CreateFileAndAdd("dir1/file2", "file2 content\n") + shell.CreateFileAndAdd("dir1/file3", "file3 content\n") + shell.CreateFileAndAdd("other-file", "other content\n") + shell.Commit("first commit") + }, + Run: func(t *TestDriver, keys config.KeybindingConfig) { + t.Views().Commits(). + Focus(). + Lines( + Contains("first commit").IsSelected(), + ). + PressEnter() + + t.Views().CommitFiles(). + IsFocused(). + Lines( + Equals("▼ /").IsSelected(), + Equals(" ▼ dir1"), + Equals(" A file1"), + Equals(" A file2"), + Equals(" A file3"), + Equals(" A other-file"), + ). + NavigateToLine(Contains("dir1")). + PressPrimaryAction(). + Lines( + Equals("▼ /"), + Equals(" ▼ dir1").IsSelected(), + Equals(" ● file1"), + Equals(" ● file2"), + Equals(" ● file3"), + Equals(" A other-file"), + ) + + t.Views().Information().Content(Contains("Building patch")) + + // Toggle the directory again to remove all files from the patch + t.Views().CommitFiles(). + PressPrimaryAction(). + Lines( + Equals("▼ /"), + Equals(" ▼ dir1").IsSelected(), + Equals(" A file1"), + Equals(" A file2"), + Equals(" A file3"), + Equals(" A other-file"), + ) + }, +}) diff --git a/pkg/integration/tests/test_list.go b/pkg/integration/tests/test_list.go index b98de18d8..1fdd4c161 100644 --- a/pkg/integration/tests/test_list.go +++ b/pkg/integration/tests/test_list.go @@ -232,7 +232,10 @@ var tests = []*components.IntegrationTest{ file.StageRangeSelect, filter_and_search.FilterByFileStatus, filter_and_search.FilterCommitFiles, + filter_and_search.FilterCommitFilesToggleDirectory, filter_and_search.FilterFiles, + filter_and_search.FilterFilesStageAll, + filter_and_search.FilterFilesStageDirectory, filter_and_search.FilterFuzzy, filter_and_search.FilterMenu, filter_and_search.FilterMenuByKeybinding, @@ -355,6 +358,7 @@ var tests = []*components.IntegrationTest{ patch_building.SelectAllFiles, patch_building.SpecificSelection, patch_building.StartNewPatch, + patch_building.ToggleDirectory, patch_building.ToggleRange, reflog.Checkout, reflog.CherryPick,