diff --git a/src/mode.v b/src/mode.v index f9875d7b..b92ff752 100644 --- a/src/mode.v +++ b/src/mode.v @@ -24,6 +24,7 @@ enum Mode as u8 { search leader pending_delete + replace } fn (mode Mode) draw(mut ctx tui.Context, x int, y int) int { @@ -51,6 +52,7 @@ fn (mode Mode) color() Color { .search { status_purple } .leader { status_purple } .pending_delete { status_green } + .replace { status_green } } } @@ -63,6 +65,7 @@ fn (mode Mode) str() string { .search { "SEARCH" } .leader { "LEADER" } .pending_delete { "NORMAL" } + .replace { "NORMAL" } } } diff --git a/src/view.v b/src/view.v index a205d2b4..81df64e9 100644 --- a/src/view.v +++ b/src/view.v @@ -506,7 +506,7 @@ fn (mut view View) draw(mut ctx tui.Context) { if view.mode == .insert { set_cursor_to_vertical_bar(mut ctx) } else { set_cursor_to_block(mut ctx) } - if view.d_count == 1 { set_cursor_to_underline(mut ctx) } + if view.d_count == 1 || view.mode == .replace { set_cursor_to_underline(mut ctx) } ctx.set_cursor_position(view.x+1+offset, cursor_screen_space_y+1) } @@ -833,6 +833,7 @@ fn (mut view View) on_key_down(e &tui.Event, mut root Root) { .o { if e.modifiers == .shift { view.shift_o() } else { view.o() } } .a { if e.modifiers == .shift { view.shift_a() } else { view.a() } } .p { view.p() } + .r { view.r() } .x { view.x() } .up { view.k() } .right { view.l() } @@ -948,6 +949,22 @@ fn (mut view View) on_key_down(e &tui.Event, mut root Root) { else {} } } + .replace { + match e.code { + .escape { view.escape_replace() } + .enter { view.escape_replace() } + .backspace {} + .up {} + .down {} + .left {} + .right {} + .tab {} + else { + view.replace_char(e.ascii) + view.escape_replace() + } + } + } } } @@ -1060,6 +1077,10 @@ fn (mut view View) escape() { view.buffer.update_undo_history() } +fn (mut view View) escape_replace() { + view.mode = .normal +} + fn (mut view View) jump_cursor_to(position int) { defer { view.clamp_cursor_within_document_bounds() @@ -1168,6 +1189,10 @@ fn (mut view View) v() { view.cursor.selection_start = view.cursor.pos } +fn (mut view View) r() { + view.mode = .replace +} + fn (mut view View) visual_y() { start := view.cursor.selection_start_y() mut end := view.cursor.selection_end_y() @@ -1536,6 +1561,16 @@ fn (mut view View) right_square_bracket() { } } +fn (mut view View) replace_char(c u8) { + if c < 32 { + return + } + line := view.buffer.lines[view.cursor.pos.y].runes() + start := line[..view.cursor.pos.x] + end := line[view.cursor.pos.x+1..] + view.buffer.lines[view.cursor.pos.y] = "${start.string()}${c.ascii_str()}${end.string()}" +} + fn get_clean_words(line string) []string { mut res := []string{} mut i := 0 diff --git a/src/view_test.v b/src/view_test.v index 7cd97ecf..0d48e9a8 100644 --- a/src/view_test.v +++ b/src/view_test.v @@ -17,6 +17,7 @@ module main import arrays import lib.clipboard import lib.workspace +import term.ui as tui const example_file = "module history\n\nimport datatypes\nimport lib.diff { Op }\n\npub struct History {\nmut:\n\tundos datatypes.Stack[Op] // will actually be type diff.Op\n\tredos datatypes.Stack[Op]\n}" @@ -1085,6 +1086,114 @@ fn test_shift_a_enters_insert_mode_at_the_end_of_current_line() { assert fake_view.mode == .insert } +fn test_r_replaces_character_in_middle_of_line() { + clip := clipboard.new() + mut editor := Editor{ clipboard: mut clip, file_finder_modal: unsafe { nil } } + mut fake_view := View{ log: unsafe { nil }, mode: .normal, clipboard: mut clip } + + fake_view.buffer.lines = ["some random line", "another line of text", "one last line"] + fake_view.cursor.pos.y = 2 + fake_view.cursor.pos.x = 4 + fake_view.r() + + assert fake_view.mode == .replace + + event := &tui.Event{code: tui.KeyCode.p, ascii: 112} + fake_view.on_key_down(event, mut editor) + + assert fake_view.mode == .normal + assert fake_view.buffer.lines[fake_view.cursor.pos.y] == "one past line" + assert fake_view.cursor.pos.x == 4 + assert fake_view.cursor.pos.y == 2 + +} + +fn test_r_replaces_character_with_special_character() { + clip := clipboard.new() + mut editor := Editor{ clipboard: mut clip, file_finder_modal: unsafe { nil } } + mut fake_view := View{ log: unsafe { nil }, mode: .normal, clipboard: mut clip } + + fake_view.buffer.lines = ["some random line", "another line of text", "one last line"] + fake_view.cursor.pos.y = 2 + fake_view.cursor.pos.x = 8 + fake_view.r() + + assert fake_view.mode == .replace + + event := &tui.Event{code: tui.KeyCode.exclamation, ascii: 33} + fake_view.on_key_down(event, mut editor) + + assert fake_view.mode == .normal + assert fake_view.buffer.lines[fake_view.cursor.pos.y] == "one last!line" + assert fake_view.cursor.pos.x == 8 + assert fake_view.cursor.pos.y == 2 + +} + +fn test_r_replaces_character_with_space() { + clip := clipboard.new() + mut editor := Editor{ clipboard: mut clip, file_finder_modal: unsafe { nil } } + mut fake_view := View{ log: unsafe { nil }, mode: .normal, clipboard: mut clip } + + fake_view.buffer.lines = ["some random line", "another line of text", "one last line"] + fake_view.cursor.pos.y = 2 + fake_view.cursor.pos.x = 4 + fake_view.r() + + assert fake_view.mode == .replace + + event := &tui.Event{code: tui.KeyCode.space, ascii: 32} + fake_view.on_key_down(event, mut editor) + + assert fake_view.mode == .normal + assert fake_view.buffer.lines[fake_view.cursor.pos.y] == "one ast line" + assert fake_view.cursor.pos.x == 4 + assert fake_view.cursor.pos.y == 2 + +} + +fn test_r_doesnt_change_anything_when_escape_is_used() { + clip := clipboard.new() + mut editor := Editor{ clipboard: mut clip, file_finder_modal: unsafe { nil } } + mut fake_view := View{ log: unsafe { nil }, mode: .normal, clipboard: mut clip } + + fake_view.buffer.lines = ["some random line", "another line of text", "one last line"] + fake_view.cursor.pos.y = 2 + fake_view.cursor.pos.x = 4 + fake_view.r() + + assert fake_view.mode == .replace + + event := &tui.Event{code: tui.KeyCode.escape, ascii: 27} + fake_view.on_key_down(event, mut editor) + + assert fake_view.mode == .normal + assert fake_view.cursor.pos.x == 4 + assert fake_view.cursor.pos.y == 2 + assert fake_view.buffer.lines[fake_view.cursor.pos.y] == "one last line" +} + +fn test_r_doesnt_change_anything_when_enter_is_used() { + clip := clipboard.new() + mut editor := Editor{ clipboard: mut clip, file_finder_modal: unsafe { nil } } + mut fake_view := View{ log: unsafe { nil }, mode: .normal, clipboard: mut clip } + + fake_view.buffer.lines = ["some random line", "another line of text", "one last line"] + fake_view.cursor.pos.y = 1 + fake_view.cursor.pos.x = 7 + fake_view.r() + + assert fake_view.mode == .replace + + event := &tui.Event{code: tui.KeyCode.enter, ascii: 10} + fake_view.on_key_down(event, mut editor) + + assert fake_view.mode == .normal + assert fake_view.cursor.pos.x == 7 + assert fake_view.cursor.pos.y == 1 + assert fake_view.buffer.lines[fake_view.cursor.pos.y] == "another line of text" +} + fn test_shift_o_adds_line_above_cursor() { mut clip := clipboard.new() mut fake_view := View{ log: unsafe { nil }, mode: .normal, clipboard: mut clip }