mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-06-13 00:07:59 +02:00
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
This commit is contained in:
parent
2a5763a771
commit
46be280c92
2
go.mod
2
go.mod
@ -11,7 +11,7 @@ require (
|
|||||||
github.com/golang/protobuf v1.3.2 // indirect
|
github.com/golang/protobuf v1.3.2 // indirect
|
||||||
github.com/google/go-cmp v0.3.1 // indirect
|
github.com/google/go-cmp v0.3.1 // indirect
|
||||||
github.com/integrii/flaggy v1.4.0
|
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/pty v1.2.1
|
||||||
github.com/jesseduffield/rollrus v0.0.0-20190701125922-dd028cb0bfd7 // indirect
|
github.com/jesseduffield/rollrus v0.0.0-20190701125922-dd028cb0bfd7 // indirect
|
||||||
github.com/jesseduffield/termbox-go v0.0.0-20200130214842-1d31d1faa3c9 // indirect
|
github.com/jesseduffield/termbox-go v0.0.0-20200130214842-1d31d1faa3c9 // indirect
|
||||||
|
2
go.sum
2
go.sum
@ -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.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 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.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 h1:7xYBiwNH0PpWqC8JmvrPq1a/ksNqyCavzWu9pbBGYWI=
|
||||||
github.com/jesseduffield/pty v1.2.1/go.mod h1:7jlS40+UhOqkZJDIG1B/H21xnuET/+fvbbnHCa8wSIo=
|
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=
|
github.com/jesseduffield/roll v0.0.0-20190629104057-695be2e62b00 h1:+JaOkfBNYQYlGD7dgru8mCwYNEc5tRRI8mThlVANhSM=
|
||||||
|
@ -283,6 +283,9 @@ keybinding:
|
|||||||
nextBlock: '<right>'
|
nextBlock: '<right>'
|
||||||
prevBlock-alt: 'h'
|
prevBlock-alt: 'h'
|
||||||
nextBlock-alt: 'l'
|
nextBlock-alt: 'l'
|
||||||
|
nextMatch: 'n'
|
||||||
|
prevMatch: 'N'
|
||||||
|
startSearch: '/'
|
||||||
optionMenu: 'x'
|
optionMenu: 'x'
|
||||||
optionMenu-alt1: '?'
|
optionMenu-alt1: '?'
|
||||||
select: '<space>'
|
select: '<space>'
|
||||||
|
@ -400,6 +400,7 @@ func (gui *Gui) onBranchesTabClick(tabIndex int) error {
|
|||||||
func (gui *Gui) switchBranchesPanelContext(context string) error {
|
func (gui *Gui) switchBranchesPanelContext(context string) error {
|
||||||
branchesView := gui.getBranchesView()
|
branchesView := gui.getBranchesView()
|
||||||
branchesView.Context = context
|
branchesView.Context = context
|
||||||
|
branchesView.ClearSearch()
|
||||||
|
|
||||||
contextTabIndexMap := map[string]int{
|
contextTabIndexMap := map[string]int{
|
||||||
"local-branches": 0,
|
"local-branches": 0,
|
||||||
@ -444,3 +445,19 @@ func (gui *Gui) handleCreateResetToBranchMenu(g *gocui.Gui, v *gocui.View) error
|
|||||||
|
|
||||||
return gui.createResetMenu(branch.Name)
|
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
|
||||||
|
}
|
||||||
|
@ -212,3 +212,8 @@ func (gui *Gui) enterCommitFile(selectedLineIdx int) error {
|
|||||||
|
|
||||||
return enterTheFile(selectedLineIdx)
|
return enterTheFile(selectedLineIdx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) onCommitFilesPanelSearchSelect(selectedLine int) error {
|
||||||
|
gui.State.Panels.CommitFiles.SelectedLine = selectedLine
|
||||||
|
return gui.handleCommitFileSelect(gui.g, gui.getCommitFilesView())
|
||||||
|
}
|
||||||
|
@ -623,6 +623,7 @@ func (gui *Gui) onCommitsTabClick(tabIndex int) error {
|
|||||||
func (gui *Gui) switchCommitsPanelContext(context string) error {
|
func (gui *Gui) switchCommitsPanelContext(context string) error {
|
||||||
commitsView := gui.getCommitsView()
|
commitsView := gui.getCommitsView()
|
||||||
commitsView.Context = context
|
commitsView.Context = context
|
||||||
|
commitsView.ClearSearch()
|
||||||
|
|
||||||
contextTabIndexMap := map[string]int{
|
contextTabIndexMap := map[string]int{
|
||||||
"branch-commits": 0,
|
"branch-commits": 0,
|
||||||
@ -661,3 +662,16 @@ func (gui *Gui) handleCreateCommitResetMenu(g *gocui.Gui, v *gocui.View) error {
|
|||||||
|
|
||||||
return gui.createResetMenu(commit.Sha)
|
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
|
||||||
|
}
|
||||||
|
@ -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 {
|
func (gui *Gui) handleCreateResetToUpstreamMenu(g *gocui.Gui, v *gocui.View) error {
|
||||||
return gui.createResetMenu("@{upstream}")
|
return gui.createResetMenu("@{upstream}")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) onFilesPanelSearchSelect(selectedLine int) error {
|
||||||
|
gui.State.Panels.Files.SelectedLine = selectedLine
|
||||||
|
return gui.focusAndSelectFile(gui.g, gui.getFilesView())
|
||||||
|
}
|
||||||
|
@ -172,6 +172,12 @@ type panelStates struct {
|
|||||||
Status *statusPanelState
|
Status *statusPanelState
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type searchingState struct {
|
||||||
|
view *gocui.View
|
||||||
|
isSearching bool
|
||||||
|
searchString string
|
||||||
|
}
|
||||||
|
|
||||||
type guiState struct {
|
type guiState struct {
|
||||||
Files []*commands.File
|
Files []*commands.File
|
||||||
Branches []*commands.Branch
|
Branches []*commands.Branch
|
||||||
@ -195,6 +201,7 @@ type guiState struct {
|
|||||||
RetainOriginalDir bool
|
RetainOriginalDir bool
|
||||||
IsRefreshingFiles bool
|
IsRefreshingFiles bool
|
||||||
RefreshingFilesMutex sync.Mutex
|
RefreshingFilesMutex sync.Mutex
|
||||||
|
Searching searchingState
|
||||||
}
|
}
|
||||||
|
|
||||||
// for now the split view will always be on
|
// 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 {
|
if v == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
if v.IsSearching() && newView.Name() != "search" {
|
||||||
|
gui.State.Searching.isSearching = false
|
||||||
|
v.ClearSearch()
|
||||||
|
}
|
||||||
switch v.Name() {
|
switch v.Name() {
|
||||||
case "branches":
|
case "branches":
|
||||||
if v.Context == "local-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)
|
v, err := g.SetView(main, leftSideWidth+panelSpacing, 0, mainPanelRight, mainPanelBottom, gocui.LEFT)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err.Error() != "unknown view" {
|
if err.Error() != "unknown view" {
|
||||||
@ -510,6 +520,9 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
|||||||
v.Wrap = true
|
v.Wrap = true
|
||||||
v.FgColor = textColor
|
v.FgColor = textColor
|
||||||
v.IgnoreCarriageReturns = true
|
v.IgnoreCarriageReturns = true
|
||||||
|
v.SetOnSelectItem(gui.onSelectItemWrapper(func(selectedLine int) error {
|
||||||
|
return nil
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
hiddenViewOffset := 0
|
hiddenViewOffset := 0
|
||||||
@ -542,6 +555,7 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
|||||||
}
|
}
|
||||||
filesView.Highlight = true
|
filesView.Highlight = true
|
||||||
filesView.Title = gui.Tr.SLocalize("FilesTitle")
|
filesView.Title = gui.Tr.SLocalize("FilesTitle")
|
||||||
|
filesView.SetOnSelectItem(gui.onSelectItemWrapper(gui.onFilesPanelSearchSelect))
|
||||||
}
|
}
|
||||||
|
|
||||||
branchesView, err := g.SetViewBeneath("branches", "files", vHeights["branches"])
|
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.Title = gui.Tr.SLocalize("BranchesTitle")
|
||||||
branchesView.Tabs = []string{"Local Branches", "Remotes", "Tags"}
|
branchesView.Tabs = []string{"Local Branches", "Remotes", "Tags"}
|
||||||
branchesView.FgColor = textColor
|
branchesView.FgColor = textColor
|
||||||
|
branchesView.SetOnSelectItem(gui.onSelectItemWrapper(gui.onBranchesPanelSearchSelect))
|
||||||
}
|
}
|
||||||
|
|
||||||
if v, err := g.SetViewBeneath("commitFiles", "branches", vHeights["commits"]); err != nil {
|
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.Title = gui.Tr.SLocalize("CommitFiles")
|
||||||
v.FgColor = textColor
|
v.FgColor = textColor
|
||||||
|
v.SetOnSelectItem(gui.onSelectItemWrapper(gui.onCommitFilesPanelSearchSelect))
|
||||||
}
|
}
|
||||||
|
|
||||||
commitsView, err := g.SetViewBeneath("commits", "branches", vHeights["commits"])
|
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.Title = gui.Tr.SLocalize("CommitsTitle")
|
||||||
commitsView.Tabs = []string{"Commits", "Reflog"}
|
commitsView.Tabs = []string{"Commits", "Reflog"}
|
||||||
commitsView.FgColor = textColor
|
commitsView.FgColor = textColor
|
||||||
|
commitsView.SetOnSelectItem(gui.onSelectItemWrapper(gui.onCommitsPanelSearchSelect))
|
||||||
}
|
}
|
||||||
|
|
||||||
stashView, err := g.SetViewBeneath("stash", "commits", vHeights["stash"])
|
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.Title = gui.Tr.SLocalize("StashTitle")
|
||||||
stashView.FgColor = textColor
|
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 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
|
return err
|
||||||
}
|
}
|
||||||
v.Frame = false
|
v.Frame = false
|
||||||
v.FgColor = theme.GetGocuiColor(userConfig.GetStringSlice("gui.theme.optionsTextColor"))
|
v.FgColor = theme.OptionsColor
|
||||||
}
|
}
|
||||||
|
|
||||||
if gui.getCommitMessageView() == nil {
|
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 appStatusView, err := g.SetView("appStatus", -1, height-2, width, height, 0); err != nil {
|
||||||
if err.Error() != "unknown view" {
|
if err.Error() != "unknown view" {
|
||||||
return err
|
return err
|
||||||
@ -826,6 +873,12 @@ func (gui *Gui) Run() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer g.Close()
|
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{})
|
gui.stopChan = make(chan struct{})
|
||||||
|
|
||||||
g.ASCII = runtime.GOOS == "windows" && runewidth.IsEastAsian()
|
g.ASCII = runtime.GOOS == "windows" && runewidth.IsEastAsian()
|
||||||
|
@ -1398,6 +1398,18 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
|
|||||||
Modifier: gocui.ModNone,
|
Modifier: gocui.ModNone,
|
||||||
Handler: gui.handleCommitFilesClick,
|
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"} {
|
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: 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.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: 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},
|
||||||
}...)
|
}...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
89
pkg/gui/searching.go
Normal file
89
pkg/gui/searching.go
Normal file
@ -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
|
||||||
|
}
|
@ -122,3 +122,8 @@ func (gui *Gui) handleStashSave(stashFunc func(message string) error) error {
|
|||||||
return gui.refreshFiles()
|
return gui.refreshFiles()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) onStashPanelSearchSelect(selectedLine int) error {
|
||||||
|
gui.State.Panels.Stash.SelectedLine = selectedLine
|
||||||
|
return gui.handleStashEntrySelect(gui.g, gui.getStashView())
|
||||||
|
}
|
||||||
|
@ -135,6 +135,8 @@ func (gui *Gui) newLineFocused(g *gocui.Gui, v *gocui.View) error {
|
|||||||
}
|
}
|
||||||
v.Highlight = false
|
v.Highlight = false
|
||||||
return nil
|
return nil
|
||||||
|
case "search":
|
||||||
|
return nil
|
||||||
default:
|
default:
|
||||||
panic(gui.Tr.SLocalize("NoViewMachingNewLineFocusedSwitchStatement"))
|
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
|
// 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 {
|
func (gui *Gui) focusPoint(cx int, cy int, lineCount int, v *gocui.View) error {
|
||||||
if cy < 0 || cy > lineCount {
|
v.FocusPoint(cx, cy)
|
||||||
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)
|
|
||||||
}
|
|
||||||
return nil
|
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 {
|
if err := v.SetOrigin(0, 0); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := v.SetCursor(0, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return gui.setViewContent(gui.g, v, s)
|
return gui.setViewContent(gui.g, v, s)
|
||||||
})
|
})
|
||||||
return nil
|
return nil
|
||||||
@ -333,6 +313,11 @@ func (gui *Gui) getMenuView() *gocui.View {
|
|||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) getSearchView() *gocui.View {
|
||||||
|
v, _ := gui.g.View("search")
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
func (gui *Gui) trimmedContent(v *gocui.View) string {
|
func (gui *Gui) trimmedContent(v *gocui.View) string {
|
||||||
return strings.TrimSpace(v.Buffer())
|
return strings.TrimSpace(v.Buffer())
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,10 @@ var (
|
|||||||
|
|
||||||
// SelectedLineBgColor is the background color for the selected line
|
// SelectedLineBgColor is the background color for the selected line
|
||||||
SelectedLineBgColor color.Attribute
|
SelectedLineBgColor color.Attribute
|
||||||
|
|
||||||
|
OptionsFgColor color.Attribute
|
||||||
|
|
||||||
|
OptionsColor gocui.Attribute
|
||||||
)
|
)
|
||||||
|
|
||||||
// UpdateTheme updates all theme variables
|
// UpdateTheme updates all theme variables
|
||||||
@ -30,6 +34,8 @@ func UpdateTheme(userConfig *viper.Viper) {
|
|||||||
ActiveBorderColor = GetGocuiColor(userConfig.GetStringSlice("gui.theme.activeBorderColor"))
|
ActiveBorderColor = GetGocuiColor(userConfig.GetStringSlice("gui.theme.activeBorderColor"))
|
||||||
InactiveBorderColor = GetGocuiColor(userConfig.GetStringSlice("gui.theme.inactiveBorderColor"))
|
InactiveBorderColor = GetGocuiColor(userConfig.GetStringSlice("gui.theme.inactiveBorderColor"))
|
||||||
SelectedLineBgColor = GetBgColor(userConfig.GetStringSlice("gui.theme.selectedLineBgColor"))
|
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")
|
isLightTheme := userConfig.GetBool("gui.theme.lightTheme")
|
||||||
if isLightTheme {
|
if isLightTheme {
|
||||||
|
14
vendor/github.com/jesseduffield/gocui/escape.go
generated
vendored
14
vendor/github.com/jesseduffield/gocui/escape.go
generated
vendored
@ -6,7 +6,6 @@ package gocui
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/go-errors/errors"
|
"github.com/go-errors/errors"
|
||||||
)
|
)
|
||||||
@ -17,7 +16,6 @@ type escapeInterpreter struct {
|
|||||||
csiParam []string
|
csiParam []string
|
||||||
curFgColor, curBgColor Attribute
|
curFgColor, curBgColor Attribute
|
||||||
mode OutputMode
|
mode OutputMode
|
||||||
mutex sync.Mutex
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type escapeState int
|
type escapeState int
|
||||||
@ -37,9 +35,6 @@ var (
|
|||||||
|
|
||||||
// runes in case of error will output the non-parsed runes as a string.
|
// runes in case of error will output the non-parsed runes as a string.
|
||||||
func (ei *escapeInterpreter) runes() []rune {
|
func (ei *escapeInterpreter) runes() []rune {
|
||||||
ei.mutex.Lock()
|
|
||||||
defer ei.mutex.Unlock()
|
|
||||||
|
|
||||||
switch ei.state {
|
switch ei.state {
|
||||||
case stateNone:
|
case stateNone:
|
||||||
return []rune{0x1b}
|
return []rune{0x1b}
|
||||||
@ -72,9 +67,6 @@ func newEscapeInterpreter(mode OutputMode) *escapeInterpreter {
|
|||||||
|
|
||||||
// reset sets the escapeInterpreter in initial state.
|
// reset sets the escapeInterpreter in initial state.
|
||||||
func (ei *escapeInterpreter) reset() {
|
func (ei *escapeInterpreter) reset() {
|
||||||
ei.mutex.Lock()
|
|
||||||
defer ei.mutex.Unlock()
|
|
||||||
|
|
||||||
ei.state = stateNone
|
ei.state = stateNone
|
||||||
ei.curFgColor = ColorDefault
|
ei.curFgColor = ColorDefault
|
||||||
ei.curBgColor = ColorDefault
|
ei.curBgColor = ColorDefault
|
||||||
@ -85,9 +77,6 @@ func (ei *escapeInterpreter) reset() {
|
|||||||
// of an escape sequence, and as such should not be printed verbatim. Otherwise,
|
// of an escape sequence, and as such should not be printed verbatim. Otherwise,
|
||||||
// it's not an escape sequence.
|
// it's not an escape sequence.
|
||||||
func (ei *escapeInterpreter) parseOne(ch rune) (isEscape bool, err error) {
|
func (ei *escapeInterpreter) parseOne(ch rune) (isEscape bool, err error) {
|
||||||
ei.mutex.Lock()
|
|
||||||
defer ei.mutex.Unlock()
|
|
||||||
|
|
||||||
// Sanity checks
|
// Sanity checks
|
||||||
if len(ei.csiParam) > 20 {
|
if len(ei.csiParam) > 20 {
|
||||||
return false, errCSITooLong
|
return false, errCSITooLong
|
||||||
@ -191,9 +180,6 @@ func (ei *escapeInterpreter) outputNormal() error {
|
|||||||
// 0x11 - 0xe8: 216 different colors
|
// 0x11 - 0xe8: 216 different colors
|
||||||
// 0xe9 - 0x1ff: 24 different shades of grey
|
// 0xe9 - 0x1ff: 24 different shades of grey
|
||||||
func (ei *escapeInterpreter) output256() error {
|
func (ei *escapeInterpreter) output256() error {
|
||||||
ei.mutex.Lock()
|
|
||||||
defer ei.mutex.Unlock()
|
|
||||||
|
|
||||||
if len(ei.csiParam) < 3 {
|
if len(ei.csiParam) < 3 {
|
||||||
return ei.outputNormal()
|
return ei.outputNormal()
|
||||||
}
|
}
|
||||||
|
28
vendor/github.com/jesseduffield/gocui/gui.go
generated
vendored
28
vendor/github.com/jesseduffield/gocui/gui.go
generated
vendored
@ -94,6 +94,12 @@ type Gui struct {
|
|||||||
// tickingMutex ensures we don't have two loops ticking. The point of 'ticking'
|
// tickingMutex ensures we don't have two loops ticking. The point of 'ticking'
|
||||||
// is to refresh the gui rapidly so that loader characters can be animated.
|
// is to refresh the gui rapidly so that loader characters can be animated.
|
||||||
tickingMutex sync.Mutex
|
tickingMutex sync.Mutex
|
||||||
|
|
||||||
|
OnSearchEscape func() error
|
||||||
|
// these keys must either be of type Key of rune
|
||||||
|
SearchEscapeKey interface{}
|
||||||
|
NextSearchMatchKey interface{}
|
||||||
|
PrevSearchMatchKey interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewGui returns a new Gui object with a given output mode.
|
// NewGui returns a new Gui object with a given output mode.
|
||||||
@ -124,6 +130,11 @@ func NewGui(mode OutputMode, supportOverlaps bool) (*Gui, error) {
|
|||||||
// view edges
|
// view edges
|
||||||
g.SupportOverlaps = supportOverlaps
|
g.SupportOverlaps = supportOverlaps
|
||||||
|
|
||||||
|
// default keys for when searching strings in a view
|
||||||
|
g.SearchEscapeKey = KeyEsc
|
||||||
|
g.NextSearchMatchKey = 'n'
|
||||||
|
g.PrevSearchMatchKey = 'N'
|
||||||
|
|
||||||
return g, nil
|
return g, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -803,6 +814,23 @@ func (g *Gui) execKeybindings(v *View, ev *termbox.Event) (matched bool, err err
|
|||||||
var globalKb *keybinding
|
var globalKb *keybinding
|
||||||
var matchingParentViewKb *keybinding
|
var matchingParentViewKb *keybinding
|
||||||
|
|
||||||
|
// if we're searching, and we've hit n/N/Esc, we ignore the default keybinding
|
||||||
|
if v.IsSearching() && Modifier(ev.Mod) == ModNone {
|
||||||
|
if eventMatchesKey(ev, g.NextSearchMatchKey) {
|
||||||
|
return true, v.gotoNextMatch()
|
||||||
|
} else if eventMatchesKey(ev, g.PrevSearchMatchKey) {
|
||||||
|
return true, v.gotoPreviousMatch()
|
||||||
|
} else if eventMatchesKey(ev, g.SearchEscapeKey) {
|
||||||
|
v.searcher.clearSearch()
|
||||||
|
if g.OnSearchEscape != nil {
|
||||||
|
if err := g.OnSearchEscape(); err != nil {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for _, kb := range g.keybindings {
|
for _, kb := range g.keybindings {
|
||||||
if kb.handler == nil {
|
if kb.handler == nil {
|
||||||
continue
|
continue
|
||||||
|
14
vendor/github.com/jesseduffield/gocui/keybinding.go
generated
vendored
14
vendor/github.com/jesseduffield/gocui/keybinding.go
generated
vendored
@ -29,6 +29,20 @@ func newKeybinding(viewname string, contexts []string, key Key, ch rune, mod Mod
|
|||||||
return kb
|
return kb
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func eventMatchesKey(ev *termbox.Event, key interface{}) bool {
|
||||||
|
// assuming ModNone for now
|
||||||
|
if Modifier(ev.Mod) != ModNone {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
k, ch, err := getKey(key)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return k == Key(ev.Key) && ch == ev.Ch
|
||||||
|
}
|
||||||
|
|
||||||
// matchKeypress returns if the keybinding matches the keypress.
|
// matchKeypress returns if the keybinding matches the keypress.
|
||||||
func (kb *keybinding) matchKeypress(key Key, ch rune, mod Modifier) bool {
|
func (kb *keybinding) matchKeypress(key Key, ch rune, mod Modifier) bool {
|
||||||
return kb.key == key && kb.ch == ch && kb.mod == mod
|
return kb.key == key && kb.ch == ch && kb.mod == mod
|
||||||
|
178
vendor/github.com/jesseduffield/gocui/view.go
generated
vendored
178
vendor/github.com/jesseduffield/gocui/view.go
generated
vendored
@ -105,6 +105,137 @@ type View struct {
|
|||||||
ParentView *View
|
ParentView *View
|
||||||
|
|
||||||
Context string // this is for assigning keybindings to a view only in certain contexts
|
Context string // this is for assigning keybindings to a view only in certain contexts
|
||||||
|
|
||||||
|
searcher *searcher
|
||||||
|
}
|
||||||
|
|
||||||
|
type searcher struct {
|
||||||
|
searchString string
|
||||||
|
searchPositions []cellPos
|
||||||
|
currentSearchIndex int
|
||||||
|
onSelectItem func(int, int, int) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) SetOnSelectItem(onSelectItem func(int, int, int) error) {
|
||||||
|
v.searcher.onSelectItem = onSelectItem
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) gotoNextMatch() error {
|
||||||
|
if len(v.searcher.searchPositions) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if v.searcher.currentSearchIndex == len(v.searcher.searchPositions)-1 {
|
||||||
|
v.searcher.currentSearchIndex = 0
|
||||||
|
} else {
|
||||||
|
v.searcher.currentSearchIndex++
|
||||||
|
}
|
||||||
|
return v.SelectSearchResult(v.searcher.currentSearchIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) gotoPreviousMatch() error {
|
||||||
|
if len(v.searcher.searchPositions) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if v.searcher.currentSearchIndex == 0 {
|
||||||
|
if len(v.searcher.searchPositions) > 0 {
|
||||||
|
v.searcher.currentSearchIndex = len(v.searcher.searchPositions) - 1
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
v.searcher.currentSearchIndex--
|
||||||
|
}
|
||||||
|
return v.SelectSearchResult(v.searcher.currentSearchIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) SelectSearchResult(index int) error {
|
||||||
|
y := v.searcher.searchPositions[index].y
|
||||||
|
v.FocusPoint(0, y)
|
||||||
|
if v.searcher.onSelectItem != nil {
|
||||||
|
return v.searcher.onSelectItem(y, index, len(v.searcher.searchPositions))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) Search(str string) error {
|
||||||
|
v.writeMutex.Lock()
|
||||||
|
defer v.writeMutex.Unlock()
|
||||||
|
|
||||||
|
v.searcher.search(str)
|
||||||
|
v.updateSearchPositions()
|
||||||
|
if len(v.searcher.searchPositions) > 0 {
|
||||||
|
// get the first result past the current cursor
|
||||||
|
currentIndex := 0
|
||||||
|
adjustedY := v.oy + v.cy
|
||||||
|
adjustedX := v.ox + v.cx
|
||||||
|
for i, pos := range v.searcher.searchPositions {
|
||||||
|
if pos.y > adjustedY || (pos.y == adjustedY && pos.x > adjustedX) {
|
||||||
|
currentIndex = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v.searcher.currentSearchIndex = currentIndex
|
||||||
|
return v.SelectSearchResult(currentIndex)
|
||||||
|
} else {
|
||||||
|
return v.searcher.onSelectItem(-1, -1, 0)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) ClearSearch() {
|
||||||
|
v.searcher.clearSearch()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) IsSearching() bool {
|
||||||
|
return v.searcher.searchString != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) FocusPoint(cx int, cy int) {
|
||||||
|
lineCount := len(v.lines)
|
||||||
|
if cy < 0 || cy > lineCount {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, 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.cx = cx
|
||||||
|
v.cy = cy
|
||||||
|
v.oy = 0
|
||||||
|
} else if cy < v.oy {
|
||||||
|
v.cx = cx
|
||||||
|
v.cy = 0
|
||||||
|
v.oy = cy
|
||||||
|
} else if cy > v.oy+ly {
|
||||||
|
v.cx = cx
|
||||||
|
v.cy = ly
|
||||||
|
v.oy = cy - ly
|
||||||
|
} else {
|
||||||
|
v.cx = cx
|
||||||
|
v.cy = cy - v.oy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *searcher) search(str string) {
|
||||||
|
s.searchString = str
|
||||||
|
s.searchPositions = []cellPos{}
|
||||||
|
s.currentSearchIndex = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *searcher) clearSearch() {
|
||||||
|
s.searchString = ""
|
||||||
|
s.searchPositions = []cellPos{}
|
||||||
|
s.currentSearchIndex = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
type cellPos struct {
|
||||||
|
x int
|
||||||
|
y int
|
||||||
}
|
}
|
||||||
|
|
||||||
type viewLine struct {
|
type viewLine struct {
|
||||||
@ -140,6 +271,7 @@ func newView(name string, x0, y0, x1, y1 int, mode OutputMode) *View {
|
|||||||
Editor: DefaultEditor,
|
Editor: DefaultEditor,
|
||||||
tainted: true,
|
tainted: true,
|
||||||
ei: newEscapeInterpreter(mode),
|
ei: newEscapeInterpreter(mode),
|
||||||
|
searcher: &searcher{},
|
||||||
}
|
}
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
@ -331,8 +463,35 @@ func (v *View) Rewind() {
|
|||||||
v.readOffset = 0
|
v.readOffset = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (v *View) updateSearchPositions() {
|
||||||
|
if v.searcher.searchString != "" {
|
||||||
|
v.searcher.searchPositions = []cellPos{}
|
||||||
|
for y, line := range v.lines {
|
||||||
|
lineLoop:
|
||||||
|
for x, _ := range line {
|
||||||
|
if line[x].chr == rune(v.searcher.searchString[0]) {
|
||||||
|
for offset := 1; offset < len(v.searcher.searchString); offset++ {
|
||||||
|
if len(line)-1 < x+offset {
|
||||||
|
continue lineLoop
|
||||||
|
}
|
||||||
|
if line[x+offset].chr != rune(v.searcher.searchString[offset]) {
|
||||||
|
continue lineLoop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v.searcher.searchPositions = append(v.searcher.searchPositions, cellPos{x: x, y: y})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// draw re-draws the view's contents.
|
// draw re-draws the view's contents.
|
||||||
func (v *View) draw() error {
|
func (v *View) draw() error {
|
||||||
|
v.writeMutex.Lock()
|
||||||
|
defer v.writeMutex.Unlock()
|
||||||
|
|
||||||
|
v.updateSearchPositions()
|
||||||
maxX, maxY := v.Size()
|
maxX, maxY := v.Size()
|
||||||
|
|
||||||
if v.Wrap {
|
if v.Wrap {
|
||||||
@ -392,6 +551,13 @@ func (v *View) draw() error {
|
|||||||
if bgColor == ColorDefault {
|
if bgColor == ColorDefault {
|
||||||
bgColor = v.BgColor
|
bgColor = v.BgColor
|
||||||
}
|
}
|
||||||
|
if matched, selected := v.isPatternMatchedRune(x, y); matched {
|
||||||
|
if selected {
|
||||||
|
bgColor = ColorCyan
|
||||||
|
} else {
|
||||||
|
bgColor = ColorYellow
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err := v.setRune(x, y, c.chr, fgColor, bgColor); err != nil {
|
if err := v.setRune(x, y, c.chr, fgColor, bgColor); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -403,6 +569,18 @@ func (v *View) draw() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (v *View) isPatternMatchedRune(x, y int) (bool, bool) {
|
||||||
|
searchStringLength := len(v.searcher.searchString)
|
||||||
|
for i, pos := range v.searcher.searchPositions {
|
||||||
|
adjustedY := y + v.oy
|
||||||
|
adjustedX := x + v.ox
|
||||||
|
if adjustedY == pos.y && adjustedX >= pos.x && adjustedX < pos.x+searchStringLength {
|
||||||
|
return true, i == v.searcher.currentSearchIndex
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, false
|
||||||
|
}
|
||||||
|
|
||||||
// realPosition returns the position in the internal buffer corresponding to the
|
// realPosition returns the position in the internal buffer corresponding to the
|
||||||
// point (x, y) of the view.
|
// point (x, y) of the view.
|
||||||
func (v *View) realPosition(vx, vy int) (x, y int, err error) {
|
func (v *View) realPosition(vx, vy int) (x, y int, err error) {
|
||||||
|
2
vendor/modules.txt
vendored
2
vendor/modules.txt
vendored
@ -32,7 +32,7 @@ github.com/hashicorp/hcl/json/token
|
|||||||
github.com/integrii/flaggy
|
github.com/integrii/flaggy
|
||||||
# github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99
|
# github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99
|
||||||
github.com/jbenet/go-context/io
|
github.com/jbenet/go-context/io
|
||||||
# github.com/jesseduffield/gocui v0.3.1-0.20200201013258-57fdcf23edc5
|
# github.com/jesseduffield/gocui v0.3.1-0.20200223105115-3e1f0f7c3efe
|
||||||
github.com/jesseduffield/gocui
|
github.com/jesseduffield/gocui
|
||||||
# github.com/jesseduffield/pty v1.2.1
|
# github.com/jesseduffield/pty v1.2.1
|
||||||
github.com/jesseduffield/pty
|
github.com/jesseduffield/pty
|
||||||
|
Loading…
x
Reference in New Issue
Block a user