mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-05-11 12:25:47 +02:00
show suggestions when typing in an origin
This commit is contained in:
parent
b6a5e9d615
commit
629494144f
6 changed files with 139 additions and 99 deletions
|
@ -7,8 +7,6 @@ import (
|
|||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
|
@ -220,7 +218,7 @@ func (gui *Gui) handleCheckoutRef(ref string, options handleCheckoutRefOptions)
|
|||
func (gui *Gui) handleCheckoutByName() error {
|
||||
return gui.prompt(promptOpts{
|
||||
title: gui.Tr.BranchName + ":",
|
||||
findSuggestionsFunc: gui.findBranchNameSuggestions,
|
||||
findSuggestionsFunc: gui.getBranchNameSuggestionsFunc(),
|
||||
handleConfirm: func(response string) error {
|
||||
return gui.handleCheckoutRef(response, handleCheckoutRefOptions{
|
||||
span: "Checkout branch",
|
||||
|
@ -535,32 +533,6 @@ func (gui *Gui) handleNewBranchOffCurrentItem() error {
|
|||
})
|
||||
}
|
||||
|
||||
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) findBranchNameSuggestions(input string) []*types.Suggestion {
|
||||
branchNames := gui.getBranchNames()
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// sanitizedBranchName will remove all spaces in favor of a dash "-" to meet
|
||||
// git's branch naming requirement.
|
||||
func sanitizedBranchName(input string) string {
|
||||
|
|
|
@ -1,14 +1,5 @@
|
|||
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{
|
||||
|
@ -49,60 +40,3 @@ func (gui *Gui) setFiltering(path string) error {
|
|||
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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ func (gui *Gui) handleCreateFilteringMenuPanel() error {
|
|||
displayString: gui.Tr.LcFilterPathOption,
|
||||
onPress: func() error {
|
||||
return gui.prompt(promptOpts{
|
||||
findSuggestionsFunc: gui.getFindSuggestionsForFilterPath(),
|
||||
findSuggestionsFunc: gui.getFilePathSuggestionsFunc(),
|
||||
title: gui.Tr.LcEnterFileName,
|
||||
handleConfirm: func(response string) error {
|
||||
return gui.setFiltering(strings.TrimSpace(response))
|
||||
|
|
133
pkg/gui/find_suggestions.go
Normal file
133
pkg/gui/find_suggestions.go
Normal file
|
@ -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
|
||||
}
|
||||
}
|
|
@ -28,7 +28,7 @@ func (gui *Gui) createPullRequestMenu(selectedBranch *models.Branch, checkedOutB
|
|||
onPress: func() error {
|
||||
return gui.prompt(promptOpts{
|
||||
title: branch.Name + " →",
|
||||
findSuggestionsFunc: gui.findBranchNameSuggestions,
|
||||
findSuggestionsFunc: gui.getBranchNameSuggestionsFunc(),
|
||||
handleConfirm: func(targetBranchName string) error {
|
||||
return gui.createPullRequest(branch.Name, targetBranchName)
|
||||
}},
|
||||
|
|
|
@ -121,8 +121,9 @@ func (gui *Gui) handlePushTag(tag *models.Tag) error {
|
|||
)
|
||||
|
||||
return gui.prompt(promptOpts{
|
||||
title: title,
|
||||
initialContent: "origin",
|
||||
title: title,
|
||||
initialContent: "origin",
|
||||
findSuggestionsFunc: gui.getRemoteSuggestionsFunc(),
|
||||
handleConfirm: func(response string) error {
|
||||
return gui.WithWaitingStatus(gui.Tr.PushingTagStatus, func() error {
|
||||
err := gui.GitCommand.WithSpan(gui.Tr.Spans.PushTag).PushTag(response, tag.Name, gui.promptUserForCredential)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue