diff --git a/docs-master/keybindings/Keybindings_en.md b/docs-master/keybindings/Keybindings_en.md index 06ff049a0..7e845652e 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 74873ed2e..0f6eb9caa 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 7627822b2..f2b2e456b 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 ceec40904..04a9e5d72 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 5bb91ec79..2dc5fc95f 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 ed34b0ae8..256dd293b 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 d01ea24e2..bbc064e3b 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 b533f6495..ec157414e 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/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 e015f9887..ab297cede 100644 --- a/pkg/gui/controllers/commits_files_controller.go +++ b/pkg/gui/controllers/commits_files_controller.go @@ -400,12 +400,15 @@ func (self *CommitFilesController) toggleForPatch(selectedNodes []*filetree.Comm selectedNodes = normalisedSelectedCommitFileNodes(selectedNodes) + // Collect all files to operate on. For directory nodes, this + // includes all files under the directory, not just the ones + // visible after filtering. + filesToProcess := self.collectFilesForNodes(selectedNodes) + // Find if any file in the selection is unselected or partially added - adding := lo.SomeBy(selectedNodes, func(node *filetree.CommitFileNode) bool { - return node.SomeFile(func(file *models.CommitFile) bool { - fileStatus := self.c.Git().Patch.PatchBuilder.GetFileStatus(file.Path, self.context().GetRef().RefName()) - return fileStatus == patch.PART || fileStatus == patch.UNSELECTED - }) + adding := lo.SomeBy(filesToProcess, func(file *models.CommitFile) bool { + fileStatus := self.c.Git().Patch.PatchBuilder.GetFileStatus(file.Path, self.context().GetRef().RefName()) + return fileStatus == patch.PART || fileStatus == patch.UNSELECTED }) patchOperationFunction := self.c.Git().Patch.PatchBuilder.RemoveFile @@ -414,11 +417,8 @@ func (self *CommitFilesController) toggleForPatch(selectedNodes []*filetree.Comm patchOperationFunction = self.c.Git().Patch.PatchBuilder.AddFileWhole } - for _, node := range selectedNodes { - err := node.ForEachFile(func(file *models.CommitFile) error { - return patchOperationFunction(file.Path) - }) - if err != nil { + for _, file := range filesToProcess { + if err := patchOperationFunction(file.Path); err != nil { return err } } @@ -570,6 +570,30 @@ func isDescendentOfSelectedCommitFileNodes(node *filetree.CommitFileNode, select return false } +// collectFilesForNodes returns all commit files that should be operated on +// for the given nodes. For directory nodes, this includes all files under +// the directory from the full unfiltered list, so that toggling a directory +// for a patch always affects all files regardless of any active text filter. +func (self *CommitFilesController) collectFilesForNodes(nodes []*filetree.CommitFileNode) []*models.CommitFile { + allCommitFiles := self.context().GetAllFiles() + result := []*models.CommitFile{} + + for _, node := range nodes { + if node.IsFile() { + result = append(result, node.File) + } else { + dirPath := node.GetPath() + for _, file := range allCommitFiles { + if dirPath == "" || strings.HasPrefix(file.Path, dirPath+"/") { + result = append(result, file) + } + } + } + } + + return result +} + func (self *CommitFilesController) isInTreeMode() *types.DisabledReason { if !self.context().CommitFileTreeViewModel.InTreeMode() { return &types.DisabledReason{Text: self.c.Tr.DisabledInFlatView} diff --git a/pkg/gui/controllers/helpers/search_helper.go b/pkg/gui/controllers/helpers/search_helper.go index f15c6dda7..9b3dcec64 100644 --- a/pkg/gui/controllers/helpers/search_helper.go +++ b/pkg/gui/controllers/helpers/search_helper.go @@ -33,6 +33,8 @@ func NewSearchHelper( func (self *SearchHelper) OpenFilterPrompt(context types.IFilterableContext) error { state := self.searchState() + state.PrevSearchIndex = -1 + state.Context = context self.searchPrefixView().SetContent(context.FilterPrefix(self.c.Tr)) @@ -238,7 +240,7 @@ func (self *SearchHelper) ReApplyFilter(context types.Context) { filterableContext, ok := context.(types.IFilterableContext) if ok { state := self.searchState() - if context == state.Context { + if context == state.Context && self.c.Context().Current().GetKey() == self.c.Contexts().Search.GetKey() { filterableContext.SetSelection(0) filterableContext.GetView().SetOriginY(0) } 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_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/nested_filter.go b/pkg/integration/tests/filter_and_search/nested_filter.go index 703c7ccf1..3e42ce32f 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"), + Contains("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"), + Contains("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..3cc2bdbbb 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(), + Contains("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"),