Skip to content

Commit

Permalink
Feat/implement basic searching (#40)
Browse files Browse the repository at this point in the history
* define search object with methods

* feat: acquire matching find count for now

* find selection per each line

* feat: handle editing search field and draw cursor etc.,

* refactor: store finds don't return them

* feat: next find pos returns current find position

* test: ensure current find progresses on next invocation

* test: ensure we go back to the start on elapse

* fix: add matches line and start and end

* docs: define todo

* scroll through current line finds before lines

* docs: remove unneeded comment

* add more to clear from search

* test: define check for current searching scrolling bug

* test: explicitly check resolved finds

* fix: pass tests which show find selection scrolls correctly

* refactor: adjust render behaviour switching based on current mode

* test: ensure that next line find is selected correctly

* test: ensure find selection loops back to first

* fix: define explicit status label and colour for search mode
  • Loading branch information
tauraamui authored Oct 29, 2023
1 parent 2ffdd23 commit 7e14882
Show file tree
Hide file tree
Showing 3 changed files with 225 additions and 7 deletions.
3 changes: 3 additions & 0 deletions src/mode.v
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ enum Mode as u8 {
visual
insert
command
search
}

fn (mode Mode) draw(mut ctx tui.Context, x int, y int) int {
Expand All @@ -44,6 +45,7 @@ fn (mode Mode) color() Color {
.visual { status_lilac }
.insert { status_orange }
.command { status_cyan }
.search { status_purple }
}
}

Expand All @@ -53,6 +55,7 @@ fn (mode Mode) str() string {
.visual { "VISUAL" }
.insert { "INSERT" }
.command { "COMMAND" }
.search { "SEARCH" }
}
}

153 changes: 146 additions & 7 deletions src/view.v
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import term.ui as tui
import log
import datatypes
import strconv
import regex

struct Cursor {
mut:
Expand Down Expand Up @@ -62,6 +63,7 @@ const (
status_orange = Color { 237, 207, 123 }
status_lilac = Color { 194, 110, 230 }
status_cyan = Color { 138, 222, 237 }
status_purple = Color { 130, 144, 250 }

rune_digits = [`0`, `1`, `2`, `3`, `4`, `5`, `6`, `7`, `8`, `9`]

Expand Down Expand Up @@ -100,6 +102,7 @@ mut:
buffer Buffer
cursor Cursor
cmd_buf CmdBuffer
search Search
repeat_amount string
x int
width int
Expand All @@ -116,6 +119,109 @@ mut:
y_lines []string
}

struct FindCursor {
mut:
line int
match_index int
}

struct Match {
start int
end int
line int
}

struct Search {
mut:
to_find string
cursor_x int
finds map[int][]int
current_find FindCursor
}

fn (mut search Search) draw(mut ctx tui.Context, draw_cursor bool) {
ctx.draw_text(1, ctx.window_height, search.to_find)
ctx.set_bg_color(r: 230, g: 230, b: 230)
ctx.draw_point(search.cursor_x+1, ctx.window_height)
ctx.reset_bg_color()
}

fn (mut search Search) prepare_for_input() {
search.to_find = "/"
search.cursor_x = 1
}

fn (mut search Search) put_char(c string) {
first := search.to_find[..search.cursor_x]
last := search.to_find[search.cursor_x..]
search.to_find = "${first}${c}${last}"
search.cursor_x += 1
}

fn (mut search Search) left() {
search.cursor_x -= 1
if search.cursor_x <= 1 { search.cursor_x = 1 }
}

fn (mut search Search) right() {
search.cursor_x += 1
if search.cursor_x > search.to_find.len { search.cursor_x = search.to_find.len }
}

fn (mut search Search) backspace() {
if search.cursor_x == 1 { return }
first := search.to_find[..search.cursor_x-1]
last := search.to_find[search.cursor_x..]
search.to_find = "${first}${last}"
search.cursor_x -= 1
if search.cursor_x < 1 { search.cursor_x = 1 }
}

fn (mut search Search) find(lines []string) {
search.current_find = FindCursor{}
mut finds := map[int][]int{}
mut re := regex.regex_opt(search.to_find.replace_once("/", "")) or { return }
for i, line in lines {
found := re.find_all(line)
if found.len == 0 { continue }
finds[i] = found
}
search.finds = finds.move()
}

fn (mut search Search) next_find_pos() ?Match {
if search.finds.len == 0 { return none }

line_number := search.finds.keys()[search.current_find.line]
line_matches := search.finds[line_number]
start := line_matches[search.current_find.match_index]
end := line_matches[search.current_find.match_index + 1]

search.current_find.match_index += 2
if search.current_find.match_index + 1 >= line_matches.len {
search.current_find.match_index = 0
search.current_find.line += 1
if search.current_find.line >= search.finds.keys().len {
search.current_find.line = 0
}
}

return Match{ start, end, line_number }
}

fn (mut search Search) clear() {
search.to_find = ""
search.cursor_x = 0
search.finds.clear()
search.current_find = FindCursor{}
}

struct Find {
mut:
start int
end int
}

struct Buffer {
mut:
lines []string
Expand Down Expand Up @@ -276,6 +382,7 @@ fn (app &App) new_view() View {
res.load_syntaxes()
res.load_config()
res.set_current_syntax_idx(".v")
res.cursor.selection_start = Pos{ -1, -1 }
return res
}

Expand Down Expand Up @@ -327,6 +434,7 @@ fn (mut view View) draw(mut ctx tui.Context) {

draw_status_line(mut ctx, Status{ view.mode, view.cursor.pos.x, view.cursor.pos.y, os.base(view.path) })
view.cmd_buf.draw(mut ctx, view.mode == .command)
if view.mode == .search { view.search.draw(mut ctx, view.mode == .search) }

ctx.draw_text(ctx.window_width-view.repeat_amount.len, ctx.window_height, view.repeat_amount)
if view.mode == .insert {
Expand Down Expand Up @@ -358,13 +466,16 @@ fn (mut view View) draw_document(mut ctx tui.Context) {
view.draw_text_line_number(mut ctx, y)

document_space_y := view.from + y
if view.mode == .visual {
within_selection = view.cursor.line_is_within_selection(document_space_y)
if within_selection { ctx.set_bg_color(r: color.r, g: color.g, b: color.b) }
} else {
within_selection = false
if y == cursor_screen_space_y {
ctx.set_bg_color(r: 53, g: 53, b: 53)
match view.mode {
.visual {
within_selection = view.cursor.line_is_within_selection(document_space_y)
if within_selection { ctx.set_bg_color(r: color.r, g: color.g, b: color.b) }
}
else {
within_selection = false
if y == cursor_screen_space_y {
ctx.set_bg_color(r: 53, g: 53, b: 53)
}
}
}
view.draw_text_line(mut ctx, y, line, within_selection)
Expand Down Expand Up @@ -639,6 +750,7 @@ fn (mut view View) on_key_down(e &tui.Event) {
.colon { view.cmd() }
.left_square_bracket { view.left_square_bracket() }
.right_square_bracket { view.right_square_bracket() }
.slash { view.search() }
.escape { view.escape() }
.enter {
// TODO(tauraamui) -> what even is this, remove??
Expand Down Expand Up @@ -692,11 +804,32 @@ fn (mut view View) on_key_down(e &tui.Event) {
}
}
}
.search {
match e.code {
.escape { view.escape() }
.space { view.search.put_char(" ") }
48...57, 97...122 { // 0-9a-zA-Z
view.search.put_char(e.ascii.ascii_str())
}
.left { view.search.left() }
.right { view.search.right() }
.up {}
.down {}
.backspace { view.search.backspace() }
.enter { view.search.find(view.buffer.lines) }
.tab { pos := view.search.next_find_pos() or { return }; view.jump_cursor_to(pos.line) }
else {
view.search.put_char(e.ascii.ascii_str())
}
}
}
.insert {
match e.code {
.escape { view.escape() }
.enter { view.enter() }
.backspace { view.backspace() }
.up {}
.down {}
.left { view.left() }
.right { view.right() }
.tab { view.insert_text('\t') }
Expand Down Expand Up @@ -775,6 +908,7 @@ fn (mut view View) escape() {
view.cursor.pos.x -= 1
view.clamp_cursor_x_pos()
view.cmd_buf.clear()
view.search.clear()

// if current line only contains whitespace prefix clear the line
line := view.buffer.lines[view.cursor.pos.y]
Expand Down Expand Up @@ -847,6 +981,11 @@ fn (mut view View) exec_cmd() bool {
}
}

fn (mut view View) search() {
view.mode = .search
view.search.prepare_for_input()
}

fn (mut view View) h() {
view.cursor.pos.x -= 1
view.clamp_cursor_x_pos()
Expand Down
76 changes: 76 additions & 0 deletions src/view_test.v
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,82 @@ fn test_right_arrow_at_end_of_sentence_in_insert_mode() {
assert fake_view.cursor.pos.x == 20
}

fn test_search_is_toggled() {
mut fake_view := View{ log: unsafe { nil }, mode: .normal }

fake_view.search()

assert fake_view.mode == .search
}

fn test_search_within_for_single_line() {
mut fake_search := Search{ to_find: "/efg" }
fake_search.find(["abcdefg"])
assert fake_search.finds[0] == [4, 7]
result := fake_search.next_find_pos() or { panic("") }
assert result.start == 4
assert result.end == 7
assert result.line == 0
}

fn test_search_within_for_multiple_lines() {
mut fake_search := Search{ to_find: "/redpanda" }
fake_search.find([
"This is a fake document that doesn't talk about anything.",
"It might mention animals like bats, redpandas and goats, but that's all.",
"Trees are where redpandas hang out, literally."
])
assert fake_search.finds[0] == []
assert fake_search.finds[1] == [36, 44]
assert fake_search.finds[2] == [16, 24]

first_result := fake_search.next_find_pos() or { panic("") }
assert first_result.start == 36
assert first_result.end == 44
assert first_result.line == 1

second_result := fake_search.next_find_pos() or { panic("") }
assert second_result.start == 16
assert second_result.end == 24
assert second_result.line == 2

scrolled_back_around_result := fake_search.next_find_pos() or { panic("") }
assert scrolled_back_around_result.start == 36
assert scrolled_back_around_result.end == 44
assert scrolled_back_around_result.line == 1
}

fn test_search_within_for_multiple_lines_multiple_matches_per_line() {
mut fake_search := Search{ to_find: "/redpanda" }
fake_search.find([
"This is a fake document about redpandas, it mentions redpandas multiple times.",
"Any animal like redpandas might be referred to more than once, who knows?"
])

assert fake_search.finds[0] == [30, 38, 53, 61]
assert fake_search.finds[1] == [16, 24]

first_result := fake_search.next_find_pos() or { panic("") }
assert first_result.start == 30
assert first_result.end == 38
assert first_result.line == 0

second_result := fake_search.next_find_pos() or { panic("") }
assert second_result.start == 53
assert second_result.end == 61
assert second_result.line == 0

third_result := fake_search.next_find_pos() or { panic("") }
assert third_result.start == 16
assert third_result.end == 24
assert third_result.line == 1

looped_back_first_result := fake_search.next_find_pos() or { panic("") }
assert looped_back_first_result.start == 30
assert looped_back_first_result.end == 38
assert looped_back_first_result.line == 0
}

fn test_calc_w_move_amount_simple_sentence_line() {
// manually set the documents contents
fake_line := "this is a line to test with"
Expand Down

0 comments on commit 7e14882

Please sign in to comment.