summaryrefslogtreecommitdiffstats
path: root/pkg/gui/filtering.go
blob: 17fd44e23825cd37f4233718519ec4996108903e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
package gui

import (
	"os"

	"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"
)

func (gui *Gui) validateNotInFilterMode() (bool, error) {
	if gui.State.Modes.Filtering.Active() {
		err := gui.ask(askOpts{
			title:         gui.Tr.MustExitFilterModeTitle,
			prompt:        gui.Tr.MustExitFilterModePrompt,
			handleConfirm: gui.exitFilterMode,
		})

		return false, err
	}
	return true, nil
}

func (gui *Gui) exitFilterMode() error {
	return gui.clearFiltering()
}

func (gui *Gui) clearFiltering() error {
	gui.State.Modes.Filtering.Reset()
	if gui.State.ScreenMode == SCREEN_HALF {
		gui.State.ScreenMode = SCREEN_NORMAL
	}

	return gui.refreshSidePanels(refreshOptions{scope: []RefreshableView{COMMITS}})
}

func (gui *Gui) setFiltering(path string) error {
	gui.State.Modes.Filtering.SetPath(path)
	if gui.State.ScreenMode == SCREEN_NORMAL {
		gui.State.ScreenMode = SCREEN_HALF
	}

	if err := gui.pushContext(gui.State.Contexts.BranchCommits); err != nil {
		return err
	}

	return gui.refreshSidePanels(refreshOptions{scope: []RefreshableView{COMMITS}, then: func() {
		gui.State.Contexts.BranchCommits.GetPanelState().SetSelectedLineIdx(0)
	}})
}

// 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) getFindSuggestionsForFilterPath() 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
	}
}