diff options
Diffstat (limited to 'pkg/gui/find_suggestions.go')
-rw-r--r-- | pkg/gui/find_suggestions.go | 133 |
1 files changed, 133 insertions, 0 deletions
diff --git a/pkg/gui/find_suggestions.go b/pkg/gui/find_suggestions.go new file mode 100644 index 000000000..b5ee0ecf2 --- /dev/null +++ b/pkg/gui/find_suggestions.go @@ -0,0 +1,133 @@ +package gui + +import ( + "os" + + "github.com/jesseduffield/lazygit/pkg/gui/presentation" + "github.com/jesseduffield/lazygit/pkg/gui/types" + "github.com/jesseduffield/lazygit/pkg/utils" + "github.com/jesseduffield/minimal/gitignore" + "gopkg.in/ozeidan/fuzzy-patricia.v3/patricia" +) + +// Thinking out loud: I'm typically a staunch advocate of organising code by feature rather than type, +// because colocating code that relates to the same feature means far less effort +// to get all the context you need to work on any particular feature. But the one +// major benefit of grouping by type is that it makes it makes it less likely that +// somebody will re-implement the same logic twice, because they can quickly see +// if a certain method has been used for some use case, given that as a starting point +// they know about the type. In that vein, I'm including all our functions for +// finding suggestions in this file, so that it's easy to see if a function already +// exists for fetching a particular model. + +func (gui *Gui) getRemoteNames() []string { + result := make([]string, len(gui.State.Remotes)) + for i, remote := range gui.State.Remotes { + result[i] = remote.Name + } + return result +} + +func matchesToSuggestions(matches []string) []*types.Suggestion { + suggestions := make([]*types.Suggestion, len(matches)) + for i, match := range matches { + suggestions[i] = &types.Suggestion{ + Value: match, + Label: match, + } + } + return suggestions +} + +func (gui *Gui) getRemoteSuggestionsFunc() func(string) []*types.Suggestion { + remoteNames := gui.getRemoteNames() + + return func(input string) []*types.Suggestion { + return matchesToSuggestions( + utils.FuzzySearch(input, remoteNames), + ) + } +} + +func (gui *Gui) getBranchNames() []string { + result := make([]string, len(gui.State.Branches)) + for i, branch := range gui.State.Branches { + result[i] = branch.Name + } + return result +} + +func (gui *Gui) getBranchNameSuggestionsFunc() func(string) []*types.Suggestion { + branchNames := gui.getBranchNames() + + return func(input string) []*types.Suggestion { + matchingBranchNames := utils.FuzzySearch(sanitizedBranchName(input), branchNames) + + suggestions := make([]*types.Suggestion, len(matchingBranchNames)) + for i, branchName := range matchingBranchNames { + suggestions[i] = &types.Suggestion{ + Value: branchName, + Label: presentation.GetBranchTextStyle(branchName).Sprint(branchName), + } + } + + return suggestions + } +} + +// here we asynchronously fetch the latest set of paths in the repo and store in +// gui.State.FilesTrie. On the main thread we'll be doing a fuzzy search via +// gui.State.FilesTrie. So if we've looked for a file previously, we'll start with +// the old trie and eventually it'll be swapped out for the new one. +func (gui *Gui) getFilePathSuggestionsFunc() func(string) []*types.Suggestion { + _ = gui.WithWaitingStatus(gui.Tr.LcLoadingFileSuggestions, func() error { + trie := patricia.NewTrie() + // load every non-gitignored file in the repo + ignore, err := gitignore.FromGit() + if err != nil { + return err + } + + err = ignore.Walk(".", + func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + trie.Insert(patricia.Prefix(path), path) + return nil + }) + // cache the trie for future use + gui.State.FilesTrie = trie + + // refresh the selections view + gui.suggestionsAsyncHandler.Do(func() func() { + // assuming here that the confirmation view is what we're typing into. + // This assumption may prove false over time + suggestions := gui.findSuggestions(gui.Views.Confirmation.TextArea.GetContent()) + return func() { gui.setSuggestions(suggestions) } + }) + + return err + }) + + return func(input string) []*types.Suggestion { + matchingNames := []string{} + _ = gui.State.FilesTrie.VisitFuzzy(patricia.Prefix(input), true, func(prefix patricia.Prefix, item patricia.Item, skipped int) error { + matchingNames = append(matchingNames, item.(string)) + return nil + }) + + // doing another fuzzy search for good measure + matchingNames = utils.FuzzySearch(input, matchingNames) + + suggestions := make([]*types.Suggestion, len(matchingNames)) + for i, name := range matchingNames { + suggestions[i] = &types.Suggestion{ + Value: name, + Label: name, + } + } + + return suggestions + } +} |