Compare commits

..

3 Commits

8 changed files with 358 additions and 167 deletions

View File

@ -102,10 +102,12 @@ FileBufferPanel :: struct {
GrepPanel :: struct {
query_arena: mem.Arena,
query_region: mem.Arena_Temp_Memory,
buffer: int,
selected_result: int,
search_query: string,
query_results: []GrepQueryResult,
glyphs: GlyphBuffer,
}
GrepQueryResult :: struct {

View File

@ -43,11 +43,6 @@ Selection :: struct {
end: Cursor,
}
Glyph :: struct {
codepoint: u8,
color: theme.PaletteColor,
}
FileBuffer :: struct {
allocator: mem.Allocator,
@ -63,9 +58,7 @@ FileBuffer :: struct {
added_content: [dynamic]u8,
content_slices: [dynamic][]u8,
glyph_buffer_width: int,
glyph_buffer_height: int,
glyph_buffer: [dynamic]Glyph,
glyphs: GlyphBuffer,
input_buffer: [dynamic]u8,
}
@ -527,9 +520,12 @@ move_cursor_down :: proc(buffer: ^FileBuffer, amount: int = 1, cursor: Maybe(^Cu
break;
}
}
if it.hit_end {
return
}
line_length := file_buffer_line_length(buffer, it.cursor.index);
if it.cursor.col < line_length && it.cursor.col < current_col {
if it.cursor.col < line_length-1 && it.cursor.col < current_col {
for _ in iterate_file_buffer(&it) {
if it.cursor.col >= line_length-1 || it.cursor.col >= current_col {
break;
@ -705,10 +701,7 @@ new_virtual_file_buffer :: proc(allocator: mem.Allocator) -> FileBuffer {
added_content = make([dynamic]u8, 0, 1024*1024),
content_slices = make([dynamic][]u8, 0, 1024*1024),
glyph_buffer_width = width,
glyph_buffer_height = height,
glyph_buffer = make([dynamic]Glyph, width*height, width*height),
glyphs = make_glyph_buffer(width, height),
input_buffer = make([dynamic]u8, 0, 1024),
};
@ -760,10 +753,7 @@ new_file_buffer :: proc(allocator: mem.Allocator, file_path: string, base_dir: s
added_content = make([dynamic]u8, 0, 1024*1024),
content_slices = make([dynamic][]u8, 0, 1024*1024),
glyph_buffer_width = width,
glyph_buffer_height = height,
glyph_buffer = make([dynamic]Glyph, width*height, width*height),
glyphs = make_glyph_buffer(width, height),
input_buffer = make([dynamic]u8, 0, 1024),
};
@ -809,11 +799,12 @@ next_buffer :: proc(state: ^State, prev_buffer: ^int) -> int {
return index;
}
// TODO: replace this with arena for the file buffer
free_file_buffer :: proc(buffer: ^FileBuffer) {
delete(buffer.original_content);
delete(buffer.added_content);
delete(buffer.content_slices);
delete(buffer.glyph_buffer);
delete(buffer.glyphs.buffer);
delete(buffer.input_buffer);
}
@ -827,9 +818,9 @@ color_character :: proc(buffer: ^FileBuffer, start: Cursor, end: Cursor, palette
start.line -= buffer.top_line;
}
if end.line >= buffer.top_line + buffer.glyph_buffer_height {
end.line = buffer.glyph_buffer_height - 1;
end.col = buffer.glyph_buffer_width - 1;
if end.line >= buffer.top_line + buffer.glyphs.height {
end.line = buffer.glyphs.height - 1;
end.col = buffer.glyphs.width - 1;
} else {
end.line -= buffer.top_line;
}
@ -839,72 +830,19 @@ color_character :: proc(buffer: ^FileBuffer, start: Cursor, end: Cursor, palette
end_col := end.col;
if j > start.line && j < end.line {
start_col = 0;
end_col = buffer.glyph_buffer_width;
end_col = buffer.glyphs.width;
} else if j < end.line {
end_col = buffer.glyph_buffer_width;
end_col = buffer.glyphs.width;
} else if j > start.line && j == end.line {
start_col = 0;
}
for i in start_col..<math.min(end_col+1, buffer.glyph_buffer_width) {
buffer.glyph_buffer[i + j * buffer.glyph_buffer_width].color = palette_index;
for i in start_col..<math.min(end_col+1, buffer.glyphs.width) {
buffer.glyphs.buffer[i + j * buffer.glyphs.width].color = palette_index;
}
}
}
update_glyph_buffer :: proc(buffer: ^FileBuffer) {
for &glyph in buffer.glyph_buffer {
glyph = Glyph{};
}
begin := buffer.top_line;
rendered_col: int;
rendered_line: int;
it := new_file_buffer_iter(buffer);
for character in iterate_file_buffer(&it) {
if character == '\r' { continue; }
screen_line := rendered_line - begin;
// don't render past the screen
if rendered_line >= begin && screen_line >= buffer.glyph_buffer_height { break; }
// render INSERT mode text into glyph buffer
if len(buffer.input_buffer) > 0 && rendered_line == buffer.cursor.line && rendered_col >= buffer.cursor.col && rendered_col < buffer.cursor.col + len(buffer.input_buffer) {
for k in 0..<len(buffer.input_buffer) {
screen_line = rendered_line - begin;
if buffer.input_buffer[k] == '\n' {
rendered_col = 0;
rendered_line += 1;
continue;
}
if rendered_line >= begin && rendered_col < buffer.glyph_buffer_width {
buffer.glyph_buffer[rendered_col + screen_line * buffer.glyph_buffer_width].color = .Foreground;
buffer.glyph_buffer[rendered_col + screen_line * buffer.glyph_buffer_width].codepoint = buffer.input_buffer[k];
rendered_col += 1;
}
}
}
screen_line = rendered_line - begin;
if character == '\n' {
rendered_col = 0;
rendered_line += 1;
continue;
}
if rendered_line >= begin && rendered_col < buffer.glyph_buffer_width {
buffer.glyph_buffer[rendered_col + screen_line * buffer.glyph_buffer_width] = Glyph { codepoint = character, color = .Foreground };
}
rendered_col += 1;
}
}
draw_file_buffer :: proc(state: ^State, buffer: ^FileBuffer, x: int, y: int, show_line_numbers: bool = true) {
update_glyph_buffer(buffer);
@ -961,7 +899,8 @@ draw_file_buffer :: proc(state: ^State, buffer: ^FileBuffer, x: int, y: int, sho
draw_rect(state, cursor_x, cursor_y, state.source_font_width, state.source_font_height, .Blue);
}
for j in 0..<buffer.glyph_buffer_height {
// TODO: replace with glyph_buffer.draw_glyph_buffer
for j in 0..<buffer.glyphs.height {
text_y := y + state.source_font_height * j;
if show_line_numbers {
@ -969,9 +908,9 @@ draw_file_buffer :: proc(state: ^State, buffer: ^FileBuffer, x: int, y: int, sho
}
line_length := 0;
for i in 0..<buffer.glyph_buffer_width {
for i in 0..<buffer.glyphs.width {
text_x := x + padding + i * state.source_font_width;
glyph := buffer.glyph_buffer[i + j * buffer.glyph_buffer_width];
glyph := buffer.glyphs.buffer[i + j * buffer.glyphs.width];
if glyph.codepoint == 0 { break; }
line_length += 1;
@ -1015,14 +954,14 @@ update_file_buffer_scroll :: proc(buffer: ^FileBuffer, cursor: Maybe(^Cursor) =
cursor = &buffer.cursor;
}
if cursor.?.line > (buffer.top_line + buffer.glyph_buffer_height - 5) {
buffer.top_line = math.max(cursor.?.line - buffer.glyph_buffer_height + 5, 0);
if cursor.?.line > (buffer.top_line + buffer.glyphs.height - 5) {
buffer.top_line = math.max(cursor.?.line - buffer.glyphs.height + 5, 0);
} else if cursor.?.line < (buffer.top_line + 5) {
buffer.top_line = math.max(cursor.?.line - 5, 0);
}
// if buffer.cursor.line > (buffer.top_line + buffer.glyph_buffer_height - 5) {
// buffer.top_line = math.max(buffer.cursor.line - buffer.glyph_buffer_height + 5, 0);
// if buffer.cursor.line > (buffer.top_line + buffer.glyphs.height - 5) {
// buffer.top_line = math.max(buffer.cursor.line - buffer.glyphs.height + 5, 0);
// } else if buffer.cursor.line < (buffer.top_line + 5) {
// buffer.top_line = math.max(buffer.cursor.line - 5, 0);
// }

139
src/core/glyph_buffer.odin Normal file
View File

@ -0,0 +1,139 @@
package core
import "core:fmt"
import "../theme"
GlyphBuffer :: struct {
buffer: []Glyph,
width: int,
height: int,
}
Glyph :: struct {
codepoint: u8,
color: theme.PaletteColor,
}
make_glyph_buffer :: proc(width, height: int, allocator := context.allocator) -> GlyphBuffer {
context.allocator = allocator
return GlyphBuffer {
width = width,
height = height,
buffer = make([]Glyph, width*height)
}
}
update_glyph_buffer_from_file_buffer :: proc(buffer: ^FileBuffer) {
for &glyph in buffer.glyphs.buffer {
glyph = Glyph{};
}
begin := buffer.top_line;
rendered_col: int;
rendered_line: int;
it := new_file_buffer_iter(buffer);
for character in iterate_file_buffer(&it) {
if character == '\r' { continue; }
screen_line := rendered_line - begin;
// don't render past the screen
if rendered_line >= begin && screen_line >= buffer.glyphs.height { break; }
// render INSERT mode text into glyph buffer
if len(buffer.input_buffer) > 0 && rendered_line == buffer.cursor.line && rendered_col >= buffer.cursor.col && rendered_col < buffer.cursor.col + len(buffer.input_buffer) {
for k in 0..<len(buffer.input_buffer) {
screen_line = rendered_line - begin;
if buffer.input_buffer[k] == '\n' {
rendered_col = 0;
rendered_line += 1;
continue;
}
if rendered_line >= begin && rendered_col < buffer.glyphs.width {
buffer.glyphs.buffer[rendered_col + screen_line * buffer.glyphs.width].color = .Foreground;
buffer.glyphs.buffer[rendered_col + screen_line * buffer.glyphs.width].codepoint = buffer.input_buffer[k];
rendered_col += 1;
}
}
}
screen_line = rendered_line - begin;
if character == '\n' {
rendered_col = 0;
rendered_line += 1;
continue;
}
if rendered_line >= begin && rendered_col < buffer.glyphs.width {
buffer.glyphs.buffer[rendered_col + screen_line * buffer.glyphs.width] = Glyph { codepoint = character, color = .Foreground };
}
rendered_col += 1;
}
}
update_glyph_buffer_from_bytes :: proc(glyphs: ^GlyphBuffer, data: []u8, top_line: int) {
for &glyph in glyphs.buffer {
glyph = Glyph{};
}
begin := top_line;
rendered_col: int;
rendered_line: int;
for character in data {
if character == '\r' { continue; }
screen_line := rendered_line - begin;
// don't render past the screen
if rendered_line >= begin && screen_line >= glyphs.height { break; }
screen_line = rendered_line - begin;
if character == '\n' {
rendered_col = 0;
rendered_line += 1;
continue;
}
if rendered_line >= begin && rendered_col < glyphs.width {
glyphs.buffer[rendered_col + screen_line * glyphs.width] = Glyph { codepoint = character, color = .Foreground };
}
rendered_col += 1;
}
}
update_glyph_buffer :: proc{update_glyph_buffer_from_file_buffer, update_glyph_buffer_from_bytes}
draw_glyph_buffer :: proc(state: ^State, glyphs: ^GlyphBuffer, x: int, y: int, top_line: int, show_line_numbers: bool = true) {
padding := 0;
if show_line_numbers {
padding = state.source_font_width * 5;
}
for j in 0..<glyphs.height {
text_y := y + state.source_font_height * j;
if show_line_numbers {
draw_text(state, fmt.tprintf("%d", top_line + j + 1), x, text_y);
}
line_length := 0;
for i in 0..<glyphs.width {
text_x := x + padding + i * state.source_font_width;
glyph := glyphs.buffer[i + j * glyphs.width];
if glyph.codepoint == 0 { break; }
line_length += 1;
draw_codepoint(state, rune(glyph.codepoint), text_x, text_y, glyph.color);
}
}
}

View File

@ -1,5 +1,6 @@
package input
import "base:runtime"
import "core:log"
import "vendor:sdl2"
@ -220,11 +221,16 @@ register_default_text_input_actions :: proc(input_map: ^core.InputActions) {
// TODO: add shift+o to insert newline above current one
core.register_key_action(input_map, .O, proc(state: ^State) {
core.move_cursor_end_of_line(core.current_buffer(state), false);
core.insert_content(core.current_buffer(state), []u8{'\n'});
state.mode = .Insert;
if buffer := core.current_buffer(state); buffer != nil {
core.move_cursor_end_of_line(buffer, false);
runtime.clear(&buffer.input_buffer)
sdl2.StartTextInput();
append(&buffer.input_buffer, '\n')
state.mode = .Insert;
sdl2.StartTextInput();
}
}, "insert mode on newline");
// Copy-Paste

View File

@ -54,8 +54,8 @@ ui_font_height :: proc() -> i32 {
draw :: proc(state: ^State) {
if buffer := core.current_buffer(state); buffer != nil {
buffer.glyph_buffer_height = math.min(256, int((state.screen_height - state.source_font_height*2) / state.source_font_height)) + 1;
buffer.glyph_buffer_width = math.min(256, int((state.screen_width - state.source_font_width) / state.source_font_width));
buffer.glyphs.height = math.min(256, int((state.screen_height - state.source_font_height*2) / state.source_font_height)) + 1;
buffer.glyphs.width = math.min(256, int((state.screen_width - state.source_font_width) / state.source_font_width));
}
render_color := theme.get_palette_color(.Background);
@ -168,8 +168,8 @@ ui_file_buffer :: proc(s: ^ui.State, buffer: ^FileBuffer) {
draw_func := proc(state: ^State, e: ui.UI_Element, user_data: rawptr) {
buffer := transmute(^FileBuffer)user_data;
if buffer != nil {
buffer.glyph_buffer_width = e.layout.size.x / state.source_font_width;
buffer.glyph_buffer_height = e.layout.size.y / state.source_font_height + 1;
buffer.glyphs.width = e.layout.size.x / state.source_font_width;
buffer.glyphs.height = e.layout.size.y / state.source_font_height + 1;
core.draw_file_buffer(state, buffer, e.layout.pos.x, e.layout.pos.y);
}

View File

@ -120,7 +120,9 @@ close :: proc(state: ^core.State, panel_id: int) {
util.delete(&state.panels, panel_id)
// TODO: keep track of the last active panel instead of focusing back to the first one
state.current_panel = util.get_first_active_index(&state.panels).?
if first_active, ok := util.get_first_active_index(&state.panels).?; ok {
state.current_panel = first_active
}
core.reset_input_map(state)
}
@ -152,8 +154,8 @@ render_file_buffer :: proc(state: ^core.State, s: ^ui.State, buffer: ^core.FileB
draw_func := proc(state: ^core.State, e: ui.UI_Element, user_data: rawptr) {
buffer := transmute(^core.FileBuffer)user_data;
if buffer != nil {
buffer.glyph_buffer_width = e.layout.size.x / state.source_font_width;
buffer.glyph_buffer_height = e.layout.size.y / state.source_font_height + 1;
buffer.glyphs.width = e.layout.size.x / state.source_font_width;
buffer.glyphs.height = e.layout.size.y / state.source_font_height + 1;
core.draw_file_buffer(state, buffer, e.layout.pos.x, e.layout.pos.y);
}
@ -205,8 +207,8 @@ render_raw_buffer :: proc(state: ^core.State, s: ^ui.State, buffer: ^core.FileBu
draw_func := proc(state: ^core.State, e: ui.UI_Element, user_data: rawptr) {
buffer := transmute(^core.FileBuffer)user_data;
if buffer != nil {
buffer.glyph_buffer_width = e.layout.size.x / state.source_font_width;
buffer.glyph_buffer_height = e.layout.size.y / state.source_font_height + 1;
buffer.glyphs.width = e.layout.size.x / state.source_font_width;
buffer.glyphs.height = e.layout.size.y / state.source_font_height + 1;
core.draw_file_buffer(state, buffer, e.layout.pos.x, e.layout.pos.y, false);
}
@ -219,6 +221,24 @@ render_raw_buffer :: proc(state: ^core.State, s: ^ui.State, buffer: ^core.FileBu
}
render_glyph_buffer :: proc(state: ^core.State, s: ^ui.State, glyphs: ^core.GlyphBuffer) {
draw_func := proc(state: ^core.State, e: ui.UI_Element, user_data: rawptr) {
glyphs := transmute(^core.GlyphBuffer)user_data;
if glyphs != nil {
glyphs.width = e.layout.size.x / state.source_font_width;
glyphs.height = e.layout.size.y / state.source_font_height + 1;
core.draw_glyph_buffer(state, glyphs, e.layout.pos.x, e.layout.pos.y, 0, true);
}
};
ui.open_element(s, ui.UI_Element_Kind_Custom{fn = draw_func, user_data = transmute(rawptr)glyphs}, {
kind = {ui.Grow{}, ui.Grow{}}
})
ui.close_element(s)
}
make_file_buffer_panel :: proc(buffer_index: int) -> core.Panel {
input_map := core.new_input_map()
@ -256,15 +276,19 @@ make_file_buffer_panel :: proc(buffer_index: int) -> core.Panel {
make_grep_panel :: proc(state: ^core.State) -> core.Panel {
query_arena: mem.Arena
mem.arena_init(&query_arena, make([]u8, 1024*1024, state.ctx.allocator))
mem.arena_init(&query_arena, make([]u8, 1024*1024*2, state.ctx.allocator))
glyphs := core.make_glyph_buffer(256,256, allocator = mem.arena_allocator(&query_arena))
input_map := core.new_input_map()
grep_input_buffer := core.new_virtual_file_buffer(context.allocator)
runtime.append(&state.buffers, grep_input_buffer)
run_query :: proc(panel_state: ^core.GrepPanel, query: string, directory: string) {
mem.arena_free_all(&panel_state.query_arena)
panel_state.query_results = nil
if panel_state.query_region.arena != nil {
mem.end_arena_temp_memory(panel_state.query_region)
}
panel_state.query_region = mem.begin_arena_temp_memory(&panel_state.query_arena)
context.allocator = mem.arena_allocator(&panel_state.query_arena)
@ -275,6 +299,13 @@ make_grep_panel :: proc(state: ^core.State) -> core.Panel {
panel_state.query_results = rs_grep_as_results(&rs_results)
free_grep_results(rs_results)
panel_state.selected_result = 0
core.update_glyph_buffer_from_bytes(
&panel_state.glyphs,
transmute([]u8)panel_state.query_results[panel_state.selected_result].file_context,
panel_state.query_results[panel_state.selected_result].line,
)
}
core.register_key_action(&input_map.mode[.Normal], .ENTER, proc(state: ^core.State) {
@ -308,6 +339,12 @@ make_grep_panel :: proc(state: ^core.State) -> core.Panel {
if panel_state, ok := &current_panel.panel_state.(core.GrepPanel); ok {
// TODO: bounds checking
panel_state.selected_result -= 1
core.update_glyph_buffer_from_bytes(
&panel_state.glyphs,
transmute([]u8)panel_state.query_results[panel_state.selected_result].file_context,
panel_state.query_results[panel_state.selected_result].line,
)
}
}
}, "move selection up");
@ -318,6 +355,12 @@ make_grep_panel :: proc(state: ^core.State) -> core.Panel {
if panel_state, ok := &current_panel.panel_state.(core.GrepPanel); ok {
// TODO: bounds checking
panel_state.selected_result += 1
core.update_glyph_buffer_from_bytes(
&panel_state.glyphs,
transmute([]u8)panel_state.query_results[panel_state.selected_result].file_context,
panel_state.query_results[panel_state.selected_result].line,
)
}
}
}, "move selection down");
@ -338,6 +381,7 @@ make_grep_panel :: proc(state: ^core.State) -> core.Panel {
query_arena = query_arena,
buffer = len(state.buffers)-1,
query_results = nil,
glyphs = glyphs,
},
input_map = input_map,
buffer_proc = proc(state: ^core.State, panel_state: ^core.PanelState) -> (buffer: ^core.FileBuffer, ok: bool) {
@ -357,75 +401,74 @@ make_grep_panel :: proc(state: ^core.State) -> core.Panel {
}
},
render_proc = proc(state: ^core.State, panel_state: ^core.PanelState) -> (ok: bool) {
panel_state := panel_state.(core.GrepPanel) or_return;
if panel_state, ok := &panel_state.(core.GrepPanel); ok {
s := transmute(^ui.State)state.ui
s := transmute(^ui.State)state.ui
ui.open_element(s, nil, {
dir = .TopToBottom,
kind = {ui.Grow{}, ui.Grow{}}
})
{
// query results and file contents side-by-side
ui.open_element(s, nil, {
dir = .LeftToRight,
dir = .TopToBottom,
kind = {ui.Grow{}, ui.Grow{}}
})
{
if panel_state.query_results != nil {
// query results
ui.open_element(s, nil, {
dir = .TopToBottom,
kind = {ui.Grow{}, ui.Grow{}}
})
{
for result, i in panel_state.query_results {
ui.open_element(s, nil, {
dir = .LeftToRight,
kind = {ui.Fit{}, ui.Fit{}},
})
{
defer ui.close_element(s)
// query results and file contents side-by-side
ui.open_element(s, nil, {
dir = .LeftToRight,
kind = {ui.Grow{}, ui.Grow{}}
})
{
if panel_state.query_results != nil {
// query results
ui.open_element(s, nil, {
dir = .TopToBottom,
kind = {ui.Grow{}, ui.Grow{}}
})
{
for result, i in panel_state.query_results {
ui.open_element(s, nil, {
dir = .LeftToRight,
kind = {ui.Fit{}, ui.Fit{}},
})
{
defer ui.close_element(s)
ui.open_element(s, fmt.tprintf("%v:%v: ", result.line, result.col), {})
ui.close_element(s)
ui.open_element(s, fmt.tprintf("%v:%v: ", result.line, result.col), {})
ui.close_element(s)
// TODO: when styling is implemented, make this look better
if panel_state.selected_result == i {
ui.open_element(s, fmt.tprintf("%s <--", result.file_path), {})
ui.close_element(s)
} else {
ui.open_element(s, result.file_path, {})
ui.close_element(s)
// TODO: when styling is implemented, make this look better
if panel_state.selected_result == i {
ui.open_element(s, fmt.tprintf("%s <--", result.file_path), {})
ui.close_element(s)
} else {
ui.open_element(s, result.file_path, {})
ui.close_element(s)
}
}
}
}
}
ui.close_element(s)
ui.close_element(s)
// file contents
selected_result := &panel_state.query_results[panel_state.selected_result]
ui.open_element(s, selected_result.file_context, {
kind = {ui.Grow{}, ui.Grow{}}
})
ui.close_element(s)
// file contents
selected_result := &panel_state.query_results[panel_state.selected_result]
render_glyph_buffer(state, s, &panel_state.glyphs)
}
}
ui.close_element(s)
// text input
ui.open_element(s, nil, {
kind = {ui.Grow{}, ui.Exact(state.source_font_height)}
})
{
defer ui.close_element(s)
render_raw_buffer(state, s, &state.buffers[panel_state.buffer])
}
}
ui.close_element(s)
// text input
ui.open_element(s, nil, {
kind = {ui.Grow{}, ui.Exact(state.source_font_height)}
})
{
defer ui.close_element(s)
render_raw_buffer(state, s, &state.buffers[panel_state.buffer])
}
return true
}
ui.close_element(s)
return true
return false
}
}
}

View File

@ -49,7 +49,8 @@ impl Match {
let column = value.bytes_range_in_buffer().len() as u64;
Ok(Self {
text: line,
// TODO: only return N-lines of context instead of the entire freakin' buffer
text: value.buffer().to_vec(),
path: path.unwrap_or_default(),
line_number: value.line_number(),
column,

View File

@ -322,23 +322,48 @@ delete_across_slices :: proc(t: ^testing.T) {
testing.expectf(t, contents == expected_text, "got '%v', expected '%v'", contents, expected_text)
}
// @(test)
// insert_line_under_current :: proc(t: ^testing.T) {
// e := new_test_editor()
// setup_empty_buffer(&e)
@(test)
move_down_next_line_has_shorter_length :: proc(t: ^testing.T) {
e := new_test_editor()
setup_empty_buffer(&e)
// buffer := &e.buffers[0]
is_ctrl_pressed := false
// inputted_text := "Hello, world!\nThis is a new line"
// expected_text := fmt.aprintf("%v\n", inputted_text)
// run_text_insertion(&e, inputted_text)
buffer := &e.buffers[0]
// expect_line_col(t, buffer.cursor, 1, 17)
// expect_cursor_index(t, buffer.cursor, 0, 31)
run_text_insertion(&e, "012345678\n0")
// contents := buffer_to_string(core.current_buffer(&e))
// testing.expectf(t, contents == expected_text, "got '%v', expected '%v'", contents, expected_text)
// }
// Move up to the first line
run_input_multiple(&e, press_key(.K), 1)
// Move to the end of the line
run_inputs(&e, []ArtificialInput{ press_key(.G), press_key(.L)})
// Move down to the second line
run_input_multiple(&e, press_key(.J), 1)
expect_line_col(t, buffer.cursor, 1, 0)
expect_cursor_index(t, buffer.cursor, 0, 10)
}
@(test)
move_down_on_last_line :: proc(t: ^testing.T) {
e := new_test_editor()
setup_empty_buffer(&e)
is_ctrl_pressed := false
buffer := &e.buffers[0]
run_text_insertion(&e, "012345678")
// Try to move down
run_input_multiple(&e, press_key(.J), 1)
// Cursor should stay where it is
expect_line_col(t, buffer.cursor, 0, 8)
expect_cursor_index(t, buffer.cursor, 0, 8)
}
@(test)
move_left_at_beginning_of_file :: proc(t: ^testing.T) {
@ -478,6 +503,42 @@ move_to_beginning_of_line_from_start :: proc(t: ^testing.T) {
expect_cursor_index(t, buffer.cursor, 0, 0)
}
@(test)
insert_line_under_current :: proc(t: ^testing.T) {
e := new_test_editor()
setup_empty_buffer(&e)
buffer := &e.buffers[0]
initial_text := "Hello, world!\nThis is a new line"
run_text_insertion(&e, initial_text)
expected_text := "Hello, world!\nThis is the second line\nThis is a new line\n"
// ------------- ----------------------
// -------------------------
// 0 1 3
// Move cursor up onto the end of "Hello, world!"
run_input_multiple(&e, press_key(.K), 1)
// Insert line below and enter insert mode
run_input_multiple(&e, press_key(.O), 1)
// Technically the cursor is still on the first line, because the `input_buffer`
// has been modified but not the actual contents of the filebuffer
expect_line_col(t, buffer.cursor, 0, 13)
expect_cursor_index(t, buffer.cursor, 0, 13)
run_text_insertion(&e, "This is the second line")
expect_line_col(t, buffer.cursor, 1, 22)
expect_cursor_index(t, buffer.cursor, 1, 23)
contents := buffer_to_string(core.current_buffer(&e))
testing.expectf(t, contents == expected_text, "got '%v', expected '%v'", contents, expected_text)
}
run_editor_frame :: proc(state: ^core.State, input: ArtificialInput, is_ctrl_pressed: ^bool) {
log.infof("running input: %v", input)