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 /vendor | |
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 'vendor')
-rw-r--r-- | vendor/github.com/jesseduffield/gocui/escape.go | 14 | ||||
-rw-r--r-- | vendor/github.com/jesseduffield/gocui/gui.go | 28 | ||||
-rw-r--r-- | vendor/github.com/jesseduffield/gocui/keybinding.go | 14 | ||||
-rw-r--r-- | vendor/github.com/jesseduffield/gocui/view.go | 196 | ||||
-rw-r--r-- | vendor/modules.txt | 2 |
5 files changed, 230 insertions, 24 deletions
diff --git a/vendor/github.com/jesseduffield/gocui/escape.go b/vendor/github.com/jesseduffield/gocui/escape.go index c09003e31..10972bff2 100644 --- a/vendor/github.com/jesseduffield/gocui/escape.go +++ b/vendor/github.com/jesseduffield/gocui/escape.go @@ -6,7 +6,6 @@ package gocui import ( "strconv" - "sync" "github.com/go-errors/errors" ) @@ -17,7 +16,6 @@ type escapeInterpreter struct { csiParam []string curFgColor, curBgColor Attribute mode OutputMode - mutex sync.Mutex } type escapeState int @@ -37,9 +35,6 @@ var ( // runes in case of error will output the non-parsed runes as a string. func (ei *escapeInterpreter) runes() []rune { - ei.mutex.Lock() - defer ei.mutex.Unlock() - switch ei.state { case stateNone: return []rune{0x1b} @@ -72,9 +67,6 @@ func newEscapeInterpreter(mode OutputMode) *escapeInterpreter { // reset sets the escapeInterpreter in initial state. func (ei *escapeInterpreter) reset() { - ei.mutex.Lock() - defer ei.mutex.Unlock() - ei.state = stateNone ei.curFgColor = 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, // it's not an escape sequence. func (ei *escapeInterpreter) parseOne(ch rune) (isEscape bool, err error) { - ei.mutex.Lock() - defer ei.mutex.Unlock() - // Sanity checks if len(ei.csiParam) > 20 { return false, errCSITooLong @@ -191,9 +180,6 @@ func (ei *escapeInterpreter) outputNormal() error { // 0x11 - 0xe8: 216 different colors // 0xe9 - 0x1ff: 24 different shades of grey func (ei *escapeInterpreter) output256() error { - ei.mutex.Lock() - defer ei.mutex.Unlock() - if len(ei.csiParam) < 3 { return ei.outputNormal() } diff --git a/vendor/github.com/jesseduffield/gocui/gui.go b/vendor/github.com/jesseduffield/gocui/gui.go index c4ac34780..93f7606b0 100644 --- a/vendor/github.com/jesseduffield/gocui/gui.go +++ b/vendor/github.com/jesseduffield/gocui/gui.go @@ -94,6 +94,12 @@ type Gui struct { // 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. 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. @@ -124,6 +130,11 @@ func NewGui(mode OutputMode, supportOverlaps bool) (*Gui, error) { // view edges g.SupportOverlaps = supportOverlaps + // default keys for when searching strings in a view + g.SearchEscapeKey = KeyEsc + g.NextSearchMatchKey = 'n' + g.PrevSearchMatchKey = 'N' + return g, nil } @@ -803,6 +814,23 @@ func (g *Gui) execKeybindings(v *View, ev *termbox.Event) (matched bool, err err var globalKb *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 { if kb.handler == nil { continue diff --git a/vendor/github.com/jesseduffield/gocui/keybinding.go b/vendor/github.com/jesseduffield/gocui/keybinding.go index d3b8904c8..6e4040e10 100644 --- a/vendor/github.com/jesseduffield/gocui/keybinding.go +++ b/vendor/github.com/jesseduffield/gocui/keybinding.go @@ -29,6 +29,20 @@ func newKeybinding(viewname string, contexts []string, key Key, ch rune, mod Mod 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. func (kb *keybinding) matchKeypress(key Key, ch rune, mod Modifier) bool { return kb.key == key && kb.ch == ch && kb.mod == mod diff --git a/vendor/github.com/jesseduffield/gocui/view.go b/vendor/github.com/jesseduffield/gocui/view.go index ee3657eb3..1277e3c51 100644 --- a/vendor/github.com/jesseduffield/gocui/view.go +++ b/vendor/github.com/jesseduffield/gocui/view.go @@ -105,6 +105,137 @@ type View struct { ParentView *View 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 { @@ -131,15 +262,16 @@ func (l lineType) String() string { // newView returns a new View object. func newView(name string, x0, y0, x1, y1 int, mode OutputMode) *View { v := &View{ - name: name, - x0: x0, - y0: y0, - x1: x1, - y1: y1, - Frame: true, - Editor: DefaultEditor, - tainted: true, - ei: newEscapeInterpreter(mode), + name: name, + x0: x0, + y0: y0, + x1: x1, + y1: y1, + Frame: true, + Editor: DefaultEditor, + tainted: true, + ei: newEscapeInterpreter(mode), + searcher: &searcher{}, } return v } @@ -331,8 +463,35 @@ func (v *View) Rewind() { 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. func (v *View) draw() error { + v.writeMutex.Lock() + defer v.writeMutex.Unlock() + + v.updateSearchPositions() maxX, maxY := v.Size() if v.Wrap { @@ -392,6 +551,13 @@ func (v *View) draw() error { if bgColor == ColorDefault { 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 { return err @@ -403,6 +569,18 @@ func (v *View) draw() error { 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 // point (x, y) of the view. func (v *View) realPosition(vx, vy int) (x, y int, err error) { diff --git a/vendor/modules.txt b/vendor/modules.txt index bebe13c4d..b90da203a 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -32,7 +32,7 @@ github.com/hashicorp/hcl/json/token github.com/integrii/flaggy # github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 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/pty v1.2.1 github.com/jesseduffield/pty |