Skip to content

Commit

Permalink
support searching in side panels
Browse files Browse the repository at this point in the history
For now we're just doing side panels, because it will take more work
to support this in the various main panel contexts
  • Loading branch information
jesseduffield committed Feb 24, 2020
1 parent 2a5763a commit 46be280
Show file tree
Hide file tree
Showing 18 changed files with 456 additions and 53 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ require (
github.com/golang/protobuf v1.3.2 // indirect
github.com/google/go-cmp v0.3.1 // indirect
github.com/integrii/flaggy v1.4.0
github.com/jesseduffield/gocui v0.3.1-0.20200201013258-57fdcf23edc5
github.com/jesseduffield/gocui v0.3.1-0.20200223105115-3e1f0f7c3efe
github.com/jesseduffield/pty v1.2.1
github.com/jesseduffield/rollrus v0.0.0-20190701125922-dd028cb0bfd7 // indirect
github.com/jesseduffield/termbox-go v0.0.0-20200130214842-1d31d1faa3c9 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ github.com/jesseduffield/gocui v0.3.1-0.20200131131454-a319843434ac h1:vp7I0RpFq
github.com/jesseduffield/gocui v0.3.1-0.20200131131454-a319843434ac/go.mod h1:2RtZznzYKt8RLRwvFiSkXjU0Ei8WwHdubgnlaYH47dw=
github.com/jesseduffield/gocui v0.3.1-0.20200201013258-57fdcf23edc5 h1:tE0w3tuL/bj1o5VMhjjE0ep6i7Fva+RYjKcMFcniJEY=
github.com/jesseduffield/gocui v0.3.1-0.20200201013258-57fdcf23edc5/go.mod h1:2RtZznzYKt8RLRwvFiSkXjU0Ei8WwHdubgnlaYH47dw=
github.com/jesseduffield/gocui v0.3.1-0.20200223105115-3e1f0f7c3efe h1:UQyebauOcBzbGq32kTXwEyuJaqp3BkI8JoCrGs2jijU=
github.com/jesseduffield/gocui v0.3.1-0.20200223105115-3e1f0f7c3efe/go.mod h1:2RtZznzYKt8RLRwvFiSkXjU0Ei8WwHdubgnlaYH47dw=
github.com/jesseduffield/pty v1.2.1 h1:7xYBiwNH0PpWqC8JmvrPq1a/ksNqyCavzWu9pbBGYWI=
github.com/jesseduffield/pty v1.2.1/go.mod h1:7jlS40+UhOqkZJDIG1B/H21xnuET/+fvbbnHCa8wSIo=
github.com/jesseduffield/roll v0.0.0-20190629104057-695be2e62b00 h1:+JaOkfBNYQYlGD7dgru8mCwYNEc5tRRI8mThlVANhSM=
Expand Down
3 changes: 3 additions & 0 deletions pkg/config/app_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,9 @@ keybinding:
nextBlock: '<right>'
prevBlock-alt: 'h'
nextBlock-alt: 'l'
nextMatch: 'n'
prevMatch: 'N'
startSearch: '/'
optionMenu: 'x'
optionMenu-alt1: '?'
select: '<space>'
Expand Down
17 changes: 17 additions & 0 deletions pkg/gui/branches_panel.go
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,7 @@ func (gui *Gui) onBranchesTabClick(tabIndex int) error {
func (gui *Gui) switchBranchesPanelContext(context string) error {
branchesView := gui.getBranchesView()
branchesView.Context = context
branchesView.ClearSearch()

contextTabIndexMap := map[string]int{
"local-branches": 0,
Expand Down Expand Up @@ -444,3 +445,19 @@ func (gui *Gui) handleCreateResetToBranchMenu(g *gocui.Gui, v *gocui.View) error

return gui.createResetMenu(branch.Name)
}

func (gui *Gui) onBranchesPanelSearchSelect(selectedLine int) error {
branchesView := gui.getBranchesView()
switch branchesView.Context {
case "local-branches":
gui.State.Panels.Branches.SelectedLine = selectedLine
return gui.handleBranchSelect(gui.g, branchesView)
case "remotes":
gui.State.Panels.Remotes.SelectedLine = selectedLine
return gui.handleRemoteSelect(gui.g, branchesView)
case "remote-branches":
gui.State.Panels.RemoteBranches.SelectedLine = selectedLine
return gui.handleRemoteBranchSelect(gui.g, branchesView)
}
return nil
}
5 changes: 5 additions & 0 deletions pkg/gui/commit_files_panel.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,3 +212,8 @@ func (gui *Gui) enterCommitFile(selectedLineIdx int) error {

return enterTheFile(selectedLineIdx)
}

func (gui *Gui) onCommitFilesPanelSearchSelect(selectedLine int) error {
gui.State.Panels.CommitFiles.SelectedLine = selectedLine
return gui.handleCommitFileSelect(gui.g, gui.getCommitFilesView())
}
14 changes: 14 additions & 0 deletions pkg/gui/commits_panel.go
Original file line number Diff line number Diff line change
Expand Up @@ -623,6 +623,7 @@ func (gui *Gui) onCommitsTabClick(tabIndex int) error {
func (gui *Gui) switchCommitsPanelContext(context string) error {
commitsView := gui.getCommitsView()
commitsView.Context = context
commitsView.ClearSearch()

contextTabIndexMap := map[string]int{
"branch-commits": 0,
Expand Down Expand Up @@ -661,3 +662,16 @@ func (gui *Gui) handleCreateCommitResetMenu(g *gocui.Gui, v *gocui.View) error {

return gui.createResetMenu(commit.Sha)
}

func (gui *Gui) onCommitsPanelSearchSelect(selectedLine int) error {
commitsView := gui.getCommitsView()
switch commitsView.Context {
case "branch-commits":
gui.State.Panels.Commits.SelectedLine = selectedLine
return gui.handleCommitSelect(gui.g, commitsView)
case "reflog-commits":
gui.State.Panels.ReflogCommits.SelectedLine = selectedLine
return gui.handleReflogCommitSelect(gui.g, commitsView)
}
return nil
}
5 changes: 5 additions & 0 deletions pkg/gui/files_panel.go
Original file line number Diff line number Diff line change
Expand Up @@ -574,3 +574,8 @@ func (gui *Gui) handleStashChanges(g *gocui.Gui, v *gocui.View) error {
func (gui *Gui) handleCreateResetToUpstreamMenu(g *gocui.Gui, v *gocui.View) error {
return gui.createResetMenu("@{upstream}")
}

func (gui *Gui) onFilesPanelSearchSelect(selectedLine int) error {
gui.State.Panels.Files.SelectedLine = selectedLine
return gui.focusAndSelectFile(gui.g, gui.getFilesView())
}
57 changes: 55 additions & 2 deletions pkg/gui/gui.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,12 @@ type panelStates struct {
Status *statusPanelState
}

type searchingState struct {
view *gocui.View
isSearching bool
searchString string
}

type guiState struct {
Files []*commands.File
Branches []*commands.Branch
Expand All @@ -195,6 +201,7 @@ type guiState struct {
RetainOriginalDir bool
IsRefreshingFiles bool
RefreshingFilesMutex sync.Mutex
Searching searchingState
}

// for now the split view will always be on
Expand Down Expand Up @@ -338,6 +345,10 @@ func (gui *Gui) onFocusLost(v *gocui.View, newView *gocui.View) error {
if v == nil {
return nil
}
if v.IsSearching() && newView.Name() != "search" {
gui.State.Searching.isSearching = false
v.ClearSearch()
}
switch v.Name() {
case "branches":
if v.Context == "local-branches" {
Expand Down Expand Up @@ -500,7 +511,6 @@ func (gui *Gui) layout(g *gocui.Gui) error {
}
}

userConfig := gui.Config.GetUserConfig()
v, err := g.SetView(main, leftSideWidth+panelSpacing, 0, mainPanelRight, mainPanelBottom, gocui.LEFT)
if err != nil {
if err.Error() != "unknown view" {
Expand All @@ -510,6 +520,9 @@ func (gui *Gui) layout(g *gocui.Gui) error {
v.Wrap = true
v.FgColor = textColor
v.IgnoreCarriageReturns = true
v.SetOnSelectItem(gui.onSelectItemWrapper(func(selectedLine int) error {
return nil
}))
}

hiddenViewOffset := 0
Expand Down Expand Up @@ -542,6 +555,7 @@ func (gui *Gui) layout(g *gocui.Gui) error {
}
filesView.Highlight = true
filesView.Title = gui.Tr.SLocalize("FilesTitle")
filesView.SetOnSelectItem(gui.onSelectItemWrapper(gui.onFilesPanelSearchSelect))
}

branchesView, err := g.SetViewBeneath("branches", "files", vHeights["branches"])
Expand All @@ -552,6 +566,7 @@ func (gui *Gui) layout(g *gocui.Gui) error {
branchesView.Title = gui.Tr.SLocalize("BranchesTitle")
branchesView.Tabs = []string{"Local Branches", "Remotes", "Tags"}
branchesView.FgColor = textColor
branchesView.SetOnSelectItem(gui.onSelectItemWrapper(gui.onBranchesPanelSearchSelect))
}

if v, err := g.SetViewBeneath("commitFiles", "branches", vHeights["commits"]); err != nil {
Expand All @@ -560,6 +575,7 @@ func (gui *Gui) layout(g *gocui.Gui) error {
}
v.Title = gui.Tr.SLocalize("CommitFiles")
v.FgColor = textColor
v.SetOnSelectItem(gui.onSelectItemWrapper(gui.onCommitFilesPanelSearchSelect))
}

commitsView, err := g.SetViewBeneath("commits", "branches", vHeights["commits"])
Expand All @@ -570,6 +586,7 @@ func (gui *Gui) layout(g *gocui.Gui) error {
commitsView.Title = gui.Tr.SLocalize("CommitsTitle")
commitsView.Tabs = []string{"Commits", "Reflog"}
commitsView.FgColor = textColor
commitsView.SetOnSelectItem(gui.onSelectItemWrapper(gui.onCommitsPanelSearchSelect))
}

stashView, err := g.SetViewBeneath("stash", "commits", vHeights["stash"])
Expand All @@ -579,14 +596,15 @@ func (gui *Gui) layout(g *gocui.Gui) error {
}
stashView.Title = gui.Tr.SLocalize("StashTitle")
stashView.FgColor = textColor
stashView.SetOnSelectItem(gui.onSelectItemWrapper(gui.onStashPanelSearchSelect))
}

if v, err := g.SetView("options", appStatusOptionsBoundary-1, height-2, optionsVersionBoundary-1, height, 0); err != nil {
if err.Error() != "unknown view" {
return err
}
v.Frame = false
v.FgColor = theme.GetGocuiColor(userConfig.GetStringSlice("gui.theme.optionsTextColor"))
v.FgColor = theme.OptionsColor
}

if gui.getCommitMessageView() == nil {
Expand Down Expand Up @@ -619,6 +637,35 @@ func (gui *Gui) layout(g *gocui.Gui) error {
}
}

searchViewOffset := hiddenViewOffset
if gui.State.Searching.isSearching {
searchViewOffset = 0
}

// this view takes up one character. Its only purpose is to show the slash when searching
searchPrefix := "search: "
if searchPrefixView, err := g.SetView("searchPrefix", appStatusOptionsBoundary-1+searchViewOffset, height-2+searchViewOffset, len(searchPrefix)+searchViewOffset, height+searchViewOffset, 0); err != nil {
if err.Error() != "unknown view" {
return err
}

searchPrefixView.BgColor = gocui.ColorDefault
searchPrefixView.FgColor = gocui.ColorGreen
searchPrefixView.Frame = false
gui.setViewContent(gui.g, searchPrefixView, searchPrefix)
}

if searchView, err := g.SetView("search", appStatusOptionsBoundary-1+searchViewOffset+len(searchPrefix), height-2+searchViewOffset, optionsVersionBoundary+searchViewOffset, height+searchViewOffset, 0); err != nil {
if err.Error() != "unknown view" {
return err
}

searchView.BgColor = gocui.ColorDefault
searchView.FgColor = gocui.ColorGreen
searchView.Frame = false
searchView.Editable = true
}

if appStatusView, err := g.SetView("appStatus", -1, height-2, width, height, 0); err != nil {
if err.Error() != "unknown view" {
return err
Expand Down Expand Up @@ -826,6 +873,12 @@ func (gui *Gui) Run() error {
return err
}
defer g.Close()

g.OnSearchEscape = gui.onSearchEscape
g.SearchEscapeKey = gui.getKey("universal.return")
g.NextSearchMatchKey = gui.getKey("universal.nextMatch")
g.PrevSearchMatchKey = gui.getKey("universal.prevMatch")

gui.stopChan = make(chan struct{})

g.ASCII = runtime.GOOS == "windows" && runewidth.IsEastAsian()
Expand Down
13 changes: 13 additions & 0 deletions pkg/gui/keybindings.go
Original file line number Diff line number Diff line change
Expand Up @@ -1398,6 +1398,18 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
Modifier: gocui.ModNone,
Handler: gui.handleCommitFilesClick,
},
{
ViewName: "search",
Key: gocui.KeyEnter,
Modifier: gocui.ModNone,
Handler: gui.handleSearch,
},
{
ViewName: "search",
Key: gui.getKey("universal.return"),
Modifier: gocui.ModNone,
Handler: gui.handleSearchEscape,
},
}

for _, viewName := range []string{"status", "branches", "files", "commits", "commitFiles", "stash", "menu"} {
Expand All @@ -1424,6 +1436,7 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
{ViewName: listView.viewName, Contexts: []string{listView.context}, Key: gui.getKey("universal.nextItem"), Modifier: gocui.ModNone, Handler: listView.handleNextLine},
{ViewName: listView.viewName, Contexts: []string{listView.context}, Key: gocui.MouseWheelDown, Modifier: gocui.ModNone, Handler: listView.handleNextLine},
{ViewName: listView.viewName, Contexts: []string{listView.context}, Key: gocui.MouseLeft, Modifier: gocui.ModNone, Handler: listView.handleClick},
{ViewName: listView.viewName, Contexts: []string{listView.context}, Key: gui.getKey("universal.startSearch"), Modifier: gocui.ModNone, Handler: gui.handleOpenSearch},
}...)
}

Expand Down
89 changes: 89 additions & 0 deletions pkg/gui/searching.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package gui

import (
"fmt"

"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/theme"
"github.com/jesseduffield/lazygit/pkg/utils"
)

func (gui *Gui) handleOpenSearch(g *gocui.Gui, v *gocui.View) error {
gui.State.Searching.isSearching = true
gui.State.Searching.view = v
gui.renderString(gui.g, "search", "")
gui.switchFocus(gui.g, v, gui.getSearchView())

return nil
}

func (gui *Gui) handleSearch(g *gocui.Gui, v *gocui.View) error {
gui.State.Searching.searchString = gui.getSearchView().Buffer()
gui.switchFocus(gui.g, nil, gui.State.Searching.view)
if err := gui.State.Searching.view.Search(gui.State.Searching.searchString); err != nil {
return err
}

return nil
}

func (gui *Gui) onSelectItemWrapper(innerFunc func(int) error) func(int, int, int) error {
return func(y int, index int, total int) error {
if total == 0 {
gui.renderString(
gui.g,
"search",
fmt.Sprintf(
"no matches for '%s' %s",
gui.State.Searching.searchString,
utils.ColoredString(
fmt.Sprintf("%s: exit search mode", gui.getKeyDisplay("universal.return")),
theme.OptionsFgColor,
),
),
)
return nil
}
gui.renderString(
gui.g,
"search",
fmt.Sprintf(
"matches for '%s' (%d of %d) %s",
gui.State.Searching.searchString,
index+1,
total,
utils.ColoredString(
fmt.Sprintf(
"%s: next match, %s: previous match, %s: exit search mode",
gui.getKeyDisplay("universal.nextMatch"),
gui.getKeyDisplay("universal.prevMatch"),
gui.getKeyDisplay("universal.return"),
),
theme.OptionsFgColor,
),
),
)
if err := innerFunc(y); err != nil {
return err
}
return nil
}
}

func (gui *Gui) onSearchEscape() error {
gui.State.Searching.isSearching = false
gui.State.Searching.view = nil
return nil
}

func (gui *Gui) handleSearchEscape(g *gocui.Gui, v *gocui.View) error {
if err := gui.switchFocus(gui.g, nil, gui.State.Searching.view); err != nil {
return err
}

if err := gui.onSearchEscape(); err != nil {
return err
}

return nil
}
5 changes: 5 additions & 0 deletions pkg/gui/stash_panel.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,3 +122,8 @@ func (gui *Gui) handleStashSave(stashFunc func(message string) error) error {
return gui.refreshFiles()
})
}

func (gui *Gui) onStashPanelSearchSelect(selectedLine int) error {
gui.State.Panels.Stash.SelectedLine = selectedLine
return gui.handleStashEntrySelect(gui.g, gui.getStashView())
}
Loading

0 comments on commit 46be280

Please sign in to comment.