Skip to content

Commit

Permalink
feat: search based on location + tests
Browse files Browse the repository at this point in the history
  • Loading branch information
cfoust committed Oct 27, 2023
1 parent ff092f1 commit a0d3d5c
Show file tree
Hide file tree
Showing 6 changed files with 203 additions and 144 deletions.
46 changes: 43 additions & 3 deletions pkg/mux/screen/replay/replay_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,49 @@ func createTest(events []sessions.Event) (*Replay, func(msgs ...interface{})) {
}

func TestSearch(t *testing.T) {
r, i := createTest(createTestSession())
i(ActionBeginning, ActionSearchForward, "test", "enter")
require.Equal(t, 2, len(r.matches))
s := sessions.NewSimulator().
Add(
// TODO(cfoust): 10/27/23 why does R=2 lead to fewer matches?
geom.Size{R: 10, C: 10},
"foo",
"bar blah", // 2
"foo",
"foo",
"foo",
"foo",
"bar", // 7
"foo",
"bar", // 9
"foo",
)

r, i := createTest(s.Events())
i(ActionBeginning, ActionSearchForward, "bar", "enter")
require.Equal(t, 3, len(r.matches))
require.Equal(t, 2, r.location.Index)
i(ActionSearchAgain)
require.Equal(t, 7, r.location.Index)
i(ActionSearchReverse)
require.Equal(t, 2, r.location.Index)
// Loop back from beginning
i(ActionSearchReverse)
require.Equal(t, 9, r.location.Index)
// Loop over end
i(ActionSearchAgain)
require.Equal(t, 2, r.location.Index)

// Ensure that the -1 rewriting works
r.gotoIndex(2, -1)
i(ActionSearchReverse)
require.Equal(t, 2, r.location.Index)

// Now search backward
i(ActionEnd, ActionSearchBackward, "bar", "enter")
require.Equal(t, 9, r.location.Index)
i(ActionSearchAgain)
require.Equal(t, 7, r.location.Index)
i(ActionSearchReverse)
require.Equal(t, 9, r.location.Index)
}

func TestIndex(t *testing.T) {
Expand Down
96 changes: 3 additions & 93 deletions pkg/mux/screen/replay/screen.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ type Replay struct {
isForward bool
isWaiting bool
searchInput textinput.Model
matchIndex int
matches []search.SearchResult
}

Expand Down Expand Up @@ -156,93 +155,11 @@ func (r *Replay) Update(msg tea.Msg) (taro.Model, tea.Cmd) {
},
)
case SearchResultEvent:
r.isWaiting = false

// TODO(cfoust): 10/13/23 handle error

matches := msg.results
if len(matches) == 0 {
r.matches = matches
return r, nil
}

origin := msg.origin

if msg.isForward {
startIndex := 0
for i, match := range matches {
begin := match.Begin
if begin.Index >= origin.Index && begin.Offset > origin.Offset {
startIndex = i
break
}
}

matches = append(matches[startIndex:], matches[:startIndex]...)
} else {
reverse(matches)

startIndex := 0
for i, match := range matches {
begin := match.Begin
if begin.Index <= origin.Index && begin.Offset <= origin.Offset {
startIndex = i
break
}
}

matches = append(matches[startIndex:], matches[:startIndex]...)
}

r.matches = matches

if len(r.matches) > 0 {
r.gotoMatch(0)
}

return r, nil
return r.handleSearchResult(msg)
}

if r.isSearching {
switch msg := msg.(type) {
case ActionEvent:
switch msg.Type {
case ActionQuit:
r.isSearching = false
return r, nil
}
case taro.KeyMsg:
switch msg.Type {
case taro.KeyEnter:
value := r.searchInput.Value()

r.searchInput.Reset()
r.isWaiting = true
r.isSearching = false
r.matches = make([]search.SearchResult, 0)

location := r.location
isForward := r.isForward
events := r.events

return r, func() tea.Msg {
res, err := search.Search(events, value)
return SearchResultEvent{
isForward: isForward,
origin: location,
results: res,
err: err,
}
}
}
}
var cmd tea.Cmd
inputMsg := msg
if key, ok := msg.(taro.KeyMsg); ok {
inputMsg = key.ToTea()
}
r.searchInput, cmd = r.searchInput.Update(inputMsg)
return r, cmd
return r.handleSearchInput(msg)
}

// These events do not stop playback
Expand Down Expand Up @@ -308,14 +225,7 @@ func (r *Replay) Update(msg tea.Msg) (taro.Model, tea.Cmd) {
r.gotoIndex(-1, -1)
}
case ActionSearchAgain, ActionSearchReverse:
delta := 1
if msg.Type == ActionSearchReverse {
delta = -1
}

if !r.isSelectionMode {
r.gotoMatchDelta(delta)
}
r.searchAgain(msg.Type != ActionSearchReverse)
case ActionSearchForward, ActionSearchBackward:
r.isSearching = true
r.isForward = msg.Type == ActionSearchForward
Expand Down
146 changes: 146 additions & 0 deletions pkg/mux/screen/replay/search.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package replay

import (
"github.com/cfoust/cy/pkg/geom"
P "github.com/cfoust/cy/pkg/io/protocol"
"github.com/cfoust/cy/pkg/sessions/search"
"github.com/cfoust/cy/pkg/taro"

tea "github.com/charmbracelet/bubbletea"
)

func (r *Replay) gotoMatch(index int) {
if len(r.matches) == 0 {
return
}

index = geom.Clamp(index, 0, len(r.matches)-1)
match := r.matches[index].Begin
r.gotoIndex(match.Index, match.Offset)
}

func (r *Replay) searchAgain(isForward bool) {
if r.isSelectionMode {
return
}

matches := r.matches
if len(matches) == 0 {
return
}

if !r.isForward {
isForward = !isForward
}

location := r.location

firstMatch := matches[0].Begin
lastMatch := matches[len(matches) - 1].Begin

if !isForward && (location.Before(firstMatch) || location.Equal(firstMatch)) {
location.Index = len(r.events) - 1
location.Offset = -1
}

// In order for the comparison to work, we have to turn our special -1
// offset into a real value
if location.Offset == -1 {
event := r.events[location.Index]
if output, ok := event.Message.(P.OutputMessage); ok {
location.Offset = len(output.Data) - 1
}
}

if isForward && (location.After(lastMatch) || location.Equal(lastMatch)) {
location.Index = 0
location.Offset = -1
}

var initialIndex int
var other search.Address
if isForward {
for i, match := range matches {
other = match.Begin
if location.After(other) || location.Equal(other) {
continue
}
initialIndex = i
break
}
} else {
for i := len(matches) - 1; i >= 0; i-- {
other = matches[i].Begin
if location.Before(other) || location.Equal(other) {
continue
}
initialIndex = i
break
}
}

r.gotoMatch(initialIndex)
}

func (r *Replay) handleSearchResult(msg SearchResultEvent) (taro.Model, tea.Cmd) {
r.isWaiting = false

// TODO(cfoust): 10/13/23 handle error

matches := msg.results
if len(matches) == 0 {
r.matches = matches
return r, nil
}

// TODO(cfoust): 10/27/23 y tho
reverse(matches)

r.matches = matches
r.location = msg.origin
r.isForward = msg.isForward
r.searchAgain(true)
return r, nil
}

func (r *Replay) handleSearchInput(msg tea.Msg) (taro.Model, tea.Cmd) {
switch msg := msg.(type) {
case ActionEvent:
switch msg.Type {
case ActionQuit:
r.isSearching = false
return r, nil
}
case taro.KeyMsg:
switch msg.Type {
case taro.KeyEnter:
value := r.searchInput.Value()

r.searchInput.Reset()
r.isWaiting = true
r.isSearching = false
r.matches = make([]search.SearchResult, 0)

location := r.location
isForward := r.isForward
events := r.events

return r, func() tea.Msg {
res, err := search.Search(events, value)
return SearchResultEvent{
isForward: isForward,
origin: location,
results: res,
err: err,
}
}
}
}
var cmd tea.Cmd
inputMsg := msg
if key, ok := msg.(taro.KeyMsg); ok {
inputMsg = key.ToTea()
}
r.searchInput, cmd = r.searchInput.Update(inputMsg)
return r, cmd
}
22 changes: 0 additions & 22 deletions pkg/mux/screen/replay/time.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,28 +88,6 @@ func (r *Replay) gotoIndex(index, indexByte int) {
r.setIndex(index, indexByte, true)
}

func (r *Replay) gotoMatch(index int) {
if len(r.matches) == 0 {
return
}

index = geom.Clamp(index, 0, len(r.matches)-1)
r.matchIndex = index
match := r.matches[index].Begin
r.gotoIndex(match.Index, match.Offset)
}

func (r *Replay) gotoMatchDelta(delta int) {
numMatches := len(r.matches)
if numMatches == 0 {
return
}

// Python-esque modulo behavior
index := (((r.matchIndex + delta) % numMatches) + numMatches) % numMatches
r.gotoMatch(index)
}

func (r *Replay) scheduleUpdate() (taro.Model, tea.Cmd) {
since := time.Now()
return r, func() tea.Msg {
Expand Down
6 changes: 3 additions & 3 deletions pkg/mux/screen/replay/view.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ func (r *Replay) drawMatches(state *tty.State) {
}

location := r.location
for i, match := range matches {
for _, match := range matches {
// This match is not on the screen
if location.Compare(match.Begin) < 0 || location.Compare(match.End) >= 0 {
if location.Before(match.Begin) || location.After(match.End) {
continue
}

isSelected := i == r.matchIndex && location.Compare(match.Begin) == 0
isSelected := location.Equal(match.Begin)
from := r.termToViewport(match.From)
to := r.termToViewport(match.To)
if !r.isInViewport(from) || !r.isInViewport(to) {
Expand Down
Loading

0 comments on commit a0d3d5c

Please sign in to comment.