diff options
author | Jesse Duffield <jessedduffield@gmail.com> | 2020-02-23 21:53:30 +1100 |
---|---|---|
committer | Jesse Duffield <jessedduffield@gmail.com> | 2020-02-24 22:18:04 +1100 |
commit | 46be280c921994ba783b8c53232d118f0547ad14 (patch) | |
tree | 0f2e637414a54e2dd034081f0fce888346e31655 /pkg | |
parent | 2a5763a77190a579ae3cb57926a0d907964fcccb (diff) |
support searching in side panels
For now we're just doing side panels, because it will take more work
to support this in the various main panel contexts
Diffstat (limited to 'pkg')
-rw-r--r-- | pkg/config/app_config.go | 3 | ||||
-rw-r--r-- | pkg/gui/branches_panel.go | 17 | ||||
-rw-r--r-- | pkg/gui/commit_files_panel.go | 5 | ||||
-rw-r--r-- | pkg/gui/commits_panel.go | 14 | ||||
-rw-r--r-- | pkg/gui/files_panel.go | 5 | ||||
-rw-r--r-- | pkg/gui/gui.go | 57 | ||||
-rw-r--r-- | pkg/gui/keybindings.go | 13 | ||||
-rw-r--r-- | pkg/gui/searching.go | 89 | ||||
-rw-r--r-- | pkg/gui/stash_panel.go | 5 | ||||
-rw-r--r-- | pkg/gui/view_helpers.go | 37 | ||||
-rw-r--r-- | pkg/theme/theme.go | 6 |
11 files changed, 223 insertions, 28 deletions
diff --git a/pkg/config/app_config.go b/pkg/config/app_config.go index 6db7322c7..a0de1fb4e 100644 --- a/pkg/config/app_config.go +++ b/pkg/config/app_config.go @@ -283,6 +283,9 @@ keybinding: nextBlock: '<right>' prevBlock-alt: 'h' nextBlock-alt: 'l' + nextMatch: 'n' + prevMatch: 'N' + startSearch: '/' optionMenu: 'x' optionMenu-alt1: '?' select: '<space>' diff --git a/pkg/gui/branches_panel.go b/pkg/gui/branches_panel.go index a683a2342..251b112d8 100644 --- a/pkg/gui/branches_panel.go +++ b/pkg/gui/branches_panel.go @@ -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, @@ -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 +} diff --git a/pkg/gui/commit_files_panel.go b/pkg/gui/commit_files_panel.go index b4a08625a..fbfeeb11c 100644 --- a/pkg/gui/commit_files_panel.go +++ b/pkg/gui/commit_files_panel.go @@ -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()) +} diff --git a/pkg/gui/commits_panel.go b/pkg/gui/commits_panel.go index 150a7ae16..714f886b6 100644 --- a/pkg/gui/commits_panel.go +++ b/pkg/gui/commits_panel.go @@ -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, @@ -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 +} diff --git a/pkg/gui/files_panel.go b/pkg/gui/files_panel.go index 1ca3538a2..9e81009ea 100644 --- a/pkg/gui/files_panel.go +++ b/pkg/gui/files_panel.go @@ -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()) +} diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go index 929c4ba88..015e3ebfe 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -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 @@ -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 @@ -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" { @@ -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" { @@ -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 @@ -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"]) @@ -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 { @@ -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"]) @@ -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"]) @@ -579,6 +596,7 @@ 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 { @@ -586,7 +604,7 @@ func (gui *Gui) layout(g *gocui.Gui) error { return err } v.Frame = false - v.FgColor = theme.GetGocuiColor(userConfig.GetStringSlice("gui.theme.optionsTextColor")) + v.FgColor = theme.OptionsColor } if gui.getCommitMessageView() == nil { @@ -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 @@ -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() diff --git a/pkg/gui/keybindings.go b/pkg/gui/keybindings.go index 3286c702e..bd050557a 100644 --- a/pkg/gui/keybindings.go +++ b/pkg/gui/keybindings.go @@ -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"} { @@ -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}, }...) } diff --git a/pkg/gui/searching.go b/pkg/gui/searching.go new file mode 100644 index 000000000..6e8cd2f64 --- /dev/null +++ b/pkg/gui/searching.go @@ -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 +} diff --git a/pkg/gui/stash_panel.go b/pkg/gui/stash_panel.go index 23ecaba60..9ca72dee0 100644 --- a/pkg/gui/stash_panel.go +++ b/pkg/gui/stash_panel.go @@ -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()) +} diff --git a/pkg/gui/view_helpers.go b/pkg/gui/view_helpers.go index bf25dbdbb..5452f1754 100644 --- a/pkg/gui/view_helpers.go +++ b/pkg/gui/view_helpers.go @@ -135,6 +135,8 @@ func (gui *Gui) newLineFocused(g *gocui.Gui, v *gocui.View) error { } v.Highlight = false return nil + case "search": + return nil default: panic(gui.Tr.SLocalize("NoViewMachingNewLineFocusedSwitchStatement")) } @@ -218,32 +220,7 @@ func (gui *Gui) resetOrigin(v *gocui.View) error { // if the cursor down past the last item, move it to the last line func (gui *Gui) focusPoint(cx int, cy int, lineCount int, v *gocui.View) error { - if cy < 0 || cy > lineCount { - return nil - } - ox, oy := v.Origin() - _, height := v.Size() - - ly := height - 1 - if ly == -1 { - ly = 0 - } - - // if line is above origin, move origin and set cursor to zero - // if line is below origin + height, move origin and set cursor to max - // otherwise set cursor to value - origin - if ly > lineCount { - _ = v.SetCursor(cx, cy) - _ = v.SetOrigin(ox, 0) - } else if cy < oy { - _ = v.SetCursor(cx, 0) - _ = v.SetOrigin(ox, cy) - } else if cy > oy+ly { - _ = v.SetCursor(cx, ly) - _ = v.SetOrigin(ox, cy-ly) - } else { - _ = v.SetCursor(cx, cy-oy) - } + v.FocusPoint(cx, cy) return nil } @@ -268,6 +245,9 @@ func (gui *Gui) renderString(g *gocui.Gui, viewName, s string) error { if err := v.SetOrigin(0, 0); err != nil { return err } + if err := v.SetCursor(0, 0); err != nil { + return err + } return gui.setViewContent(gui.g, v, s) }) return nil @@ -333,6 +313,11 @@ func (gui *Gui) getMenuView() *gocui.View { return v } +func (gui *Gui) getSearchView() *gocui.View { + v, _ := gui.g.View("search") + return v +} + func (gui *Gui) trimmedContent(v *gocui.View) string { return strings.TrimSpace(v.Buffer()) } diff --git a/pkg/theme/theme.go b/pkg/theme/theme.go index c5e49accd..04b49a555 100644 --- a/pkg/theme/theme.go +++ b/pkg/theme/theme.go @@ -23,6 +23,10 @@ var ( // SelectedLineBgColor is the background color for the selected line SelectedLineBgColor color.Attribute + + OptionsFgColor color.Attribute + + OptionsColor gocui.Attribute ) // UpdateTheme updates all theme variables @@ -30,6 +34,8 @@ func UpdateTheme(userConfig *viper.Viper) { ActiveBorderColor = GetGocuiColor(userConfig.GetStringSlice("gui.theme.activeBorderColor")) InactiveBorderColor = GetGocuiColor(userConfig.GetStringSlice("gui.theme.inactiveBorderColor")) SelectedLineBgColor = GetBgColor(userConfig.GetStringSlice("gui.theme.selectedLineBgColor")) + OptionsColor = GetGocuiColor(userConfig.GetStringSlice("gui.theme.optionsTextColor")) + OptionsFgColor = GetFgColor(userConfig.GetStringSlice("gui.theme.optionsTextColor")) isLightTheme := userConfig.GetBool("gui.theme.lightTheme") if isLightTheme { |