Compare commits
5 Commits
381062518e
...
a70da837fa
Author | SHA1 | Date |
---|---|---|
|
a70da837fa | |
|
ad5e4f85fd | |
|
dab095e88d | |
|
c9991fb0ad | |
|
e3d41ccad6 |
|
@ -137,8 +137,6 @@ current_buffer :: proc(state: ^State) -> ^FileBuffer {
|
|||
}
|
||||
|
||||
yank_whole_line :: proc(state: ^State, buffer: ^FileBuffer) {
|
||||
context.allocator = buffer.allocator
|
||||
|
||||
if state.yank_register.data != nil {
|
||||
delete(state.yank_register.data)
|
||||
state.yank_register.data = nil
|
||||
|
@ -171,7 +169,12 @@ yank_selection :: proc(state: ^State, buffer: ^FileBuffer) {
|
|||
length := selection_length(buffer, selection)
|
||||
|
||||
state.yank_register.whole_line = false
|
||||
state.yank_register.data = make([]u8, length)
|
||||
|
||||
err: runtime.Allocator_Error
|
||||
state.yank_register.data, err = make([]u8, length)
|
||||
if err != nil {
|
||||
log.error("failed to allocate memory for yank register")
|
||||
}
|
||||
|
||||
it := new_file_buffer_iter_with_cursor(buffer, selection.start)
|
||||
|
||||
|
@ -185,7 +188,7 @@ yank_selection :: proc(state: ^State, buffer: ^FileBuffer) {
|
|||
}
|
||||
|
||||
paste_register :: proc(state: ^State, register: Register, buffer: ^FileBuffer) {
|
||||
insert_content(buffer, register.data)
|
||||
insert_content(buffer, register.data, reparse_buffer = true)
|
||||
move_cursor_left(buffer)
|
||||
}
|
||||
|
||||
|
|
|
@ -55,8 +55,6 @@ FileBuffer :: struct {
|
|||
|
||||
history: FileHistory,
|
||||
glyphs: GlyphBuffer,
|
||||
|
||||
input_buffer: [dynamic]u8,
|
||||
}
|
||||
|
||||
BufferFlagSet :: bit_set[BufferFlags]
|
||||
|
@ -712,7 +710,6 @@ new_virtual_file_buffer :: proc(allocator := context.allocator) -> FileBuffer {
|
|||
history = make_history(),
|
||||
|
||||
glyphs = make_glyph_buffer(width, height),
|
||||
input_buffer = make([dynamic]u8, 0, 1024),
|
||||
};
|
||||
|
||||
return buffer;
|
||||
|
@ -776,7 +773,6 @@ new_file_buffer :: proc(allocator: mem.Allocator, file_path: string, base_dir: s
|
|||
history = make_history(content),
|
||||
|
||||
glyphs = make_glyph_buffer(width, height),
|
||||
input_buffer = make([dynamic]u8, 0, 1024),
|
||||
};
|
||||
|
||||
ts.parse_buffer(&buffer.tree, tree_sitter_file_buffer_input(&buffer))
|
||||
|
@ -834,7 +830,6 @@ free_file_buffer :: proc(buffer: ^FileBuffer) {
|
|||
ts.delete_state(&buffer.tree)
|
||||
free_history(&buffer.history)
|
||||
delete(buffer.glyphs.buffer)
|
||||
delete(buffer.input_buffer)
|
||||
}
|
||||
|
||||
color_character :: proc(buffer: ^FileBuffer, start: Cursor, end: Cursor, palette_index: theme.PaletteColor) {
|
||||
|
@ -906,26 +901,6 @@ draw_file_buffer :: proc(state: ^State, buffer: ^FileBuffer, x, y, w, h: int, sh
|
|||
draw_rect(state, start_sel_x, start_sel_y, state.source_font_width, state.source_font_height, .Green);
|
||||
draw_rect(state, end_sel_x, end_sel_y, state.source_font_width, state.source_font_height, .Blue);
|
||||
} else if state.mode == .Insert {
|
||||
draw_rect(state, cursor_x, cursor_y, state.source_font_width, state.source_font_height, .Green);
|
||||
|
||||
num_line_break := 0;
|
||||
line_length := 0;
|
||||
for c in buffer.input_buffer {
|
||||
if c == '\n' {
|
||||
num_line_break += 1;
|
||||
line_length = 0;
|
||||
} else {
|
||||
line_length += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if num_line_break > 0 {
|
||||
cursor_x = x + padding + line_length * state.source_font_width;
|
||||
cursor_y = cursor_y + num_line_break * state.source_font_height;
|
||||
} else {
|
||||
cursor_x += line_length * state.source_font_width;
|
||||
}
|
||||
|
||||
draw_rect(state, cursor_x, cursor_y, state.source_font_width, state.source_font_height, .Blue);
|
||||
}
|
||||
}
|
||||
|
@ -1007,56 +982,101 @@ scroll_file_buffer :: proc(buffer: ^FileBuffer, dir: ScrollDir, cursor: Maybe(^C
|
|||
}
|
||||
}
|
||||
|
||||
insert_content :: proc(buffer: ^FileBuffer, to_be_inserted: []u8, append_to_end: bool = false) {
|
||||
insert_content :: proc(buffer: ^FileBuffer, to_be_inserted: []u8, reparse_buffer: bool = false) {
|
||||
if len(to_be_inserted) == 0 {
|
||||
return;
|
||||
}
|
||||
buffer.flags += { .UnsavedChanges }
|
||||
|
||||
index := buffer.history.cursor.index if !append_to_end else new_piece_table_index_from_end(buffer_piece_table(buffer))
|
||||
index := buffer.history.cursor.index
|
||||
|
||||
insert_text(buffer_piece_table(buffer), to_be_inserted, buffer.history.cursor.index)
|
||||
|
||||
if !append_to_end {
|
||||
update_file_buffer_index_from_cursor(buffer);
|
||||
move_cursor_right(buffer, false, amt = len(to_be_inserted) - 1);
|
||||
}
|
||||
update_file_buffer_index_from_cursor(buffer);
|
||||
move_cursor_right(buffer, false, amt = len(to_be_inserted));
|
||||
|
||||
ts.parse_buffer(&buffer.tree, tree_sitter_file_buffer_input(buffer))
|
||||
if reparse_buffer {
|
||||
ts.parse_buffer(&buffer.tree, tree_sitter_file_buffer_input(buffer))
|
||||
}
|
||||
}
|
||||
|
||||
delete_content_from_buffer_cursor :: proc(buffer: ^FileBuffer, amount: int) {
|
||||
if amount <= len(buffer.input_buffer) {
|
||||
runtime.resize(&buffer.input_buffer, len(buffer.input_buffer)-amount);
|
||||
} else {
|
||||
buffer.flags += { .UnsavedChanges }
|
||||
delete_content_from_buffer_cursor :: proc(buffer: ^FileBuffer, amount: int, reparse_buffer: bool = false) {
|
||||
buffer.flags += { .UnsavedChanges }
|
||||
|
||||
amount := amount - len(buffer.input_buffer);
|
||||
runtime.clear(&buffer.input_buffer);
|
||||
// Calculate proper line/col values
|
||||
it := new_file_buffer_iter_with_cursor(buffer, buffer.history.cursor);
|
||||
iterate_file_buffer_reverse(&it)
|
||||
|
||||
// Calculate proper line/col values
|
||||
it := new_file_buffer_iter_with_cursor(buffer, buffer.history.cursor);
|
||||
iterate_file_buffer_reverse(&it)
|
||||
delete_text(buffer_piece_table(buffer), &buffer.history.cursor.index)
|
||||
|
||||
delete_text(buffer_piece_table(buffer), &buffer.history.cursor.index)
|
||||
buffer.history.cursor.line = it.cursor.line
|
||||
buffer.history.cursor.col = it.cursor.col
|
||||
|
||||
buffer.history.cursor.line = it.cursor.line
|
||||
buffer.history.cursor.col = it.cursor.col
|
||||
if reparse_buffer {
|
||||
ts.parse_buffer(&buffer.tree, tree_sitter_file_buffer_input(buffer))
|
||||
}
|
||||
|
||||
ts.parse_buffer(&buffer.tree, tree_sitter_file_buffer_input(buffer))
|
||||
}
|
||||
|
||||
delete_content_from_selection :: proc(buffer: ^FileBuffer, selection: ^Selection) {
|
||||
delete_content_from_selection :: proc(buffer: ^FileBuffer, selection: ^Selection, reparse_buffer: bool = false) {
|
||||
buffer.flags += { .UnsavedChanges }
|
||||
|
||||
selection^ = swap_selections(selection^)
|
||||
delete_text_in_span(buffer_piece_table(buffer), &selection.start.index, &selection.end.index)
|
||||
|
||||
buffer.history.cursor.line = selection.start.line
|
||||
buffer.history.cursor.col = selection.start.col
|
||||
|
||||
buffer.history.cursor.index = selection.start.index
|
||||
|
||||
ts.parse_buffer(&buffer.tree, tree_sitter_file_buffer_input(buffer))
|
||||
if get_character_at_piece_table_index(buffer_piece_table(buffer), selection.start.index) == '\n' {
|
||||
move_cursor_left(buffer)
|
||||
}
|
||||
|
||||
if reparse_buffer {
|
||||
ts.parse_buffer(&buffer.tree, tree_sitter_file_buffer_input(buffer))
|
||||
}
|
||||
}
|
||||
|
||||
delete_content :: proc{delete_content_from_buffer_cursor, delete_content_from_selection};
|
||||
|
||||
get_buffer_indent :: proc(buffer: ^FileBuffer, cursor: Maybe(Cursor) = nil) -> int {
|
||||
cursor := cursor;
|
||||
|
||||
if cursor == nil {
|
||||
cursor = buffer.history.cursor;
|
||||
}
|
||||
|
||||
ptr_cursor := &cursor.?
|
||||
|
||||
move_cursor_start_of_line(buffer, ptr_cursor)
|
||||
|
||||
it := new_file_buffer_iter_with_cursor(buffer, ptr_cursor^);
|
||||
iterate_file_buffer_until(&it, until_non_whitespace)
|
||||
|
||||
return it.cursor.col
|
||||
}
|
||||
|
||||
buffer_to_string :: proc(buffer: ^FileBuffer, allocator := context.allocator) -> string {
|
||||
context.allocator = allocator
|
||||
|
||||
length := 0
|
||||
for chunk in buffer_piece_table(buffer).chunks {
|
||||
length += len(chunk)
|
||||
}
|
||||
|
||||
buffer_contents := make([]u8, length)
|
||||
|
||||
offset := 0
|
||||
for chunk in buffer_piece_table(buffer).chunks {
|
||||
for c in chunk {
|
||||
buffer_contents[offset] = c
|
||||
offset += 1
|
||||
}
|
||||
}
|
||||
|
||||
return string(buffer_contents[:len(buffer_contents)-1])
|
||||
}
|
||||
|
||||
buffer_append_new_line :: proc(buffer: ^FileBuffer) {
|
||||
|
||||
}
|
|
@ -61,25 +61,26 @@ update_glyph_buffer_from_file_buffer :: proc(buffer: ^FileBuffer, width, height:
|
|||
// 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.history.cursor.line && rendered_col >= buffer.history.cursor.col && rendered_col < buffer.history.cursor.col + len(buffer.input_buffer) {
|
||||
for k in 0..<len(buffer.input_buffer) {
|
||||
screen_line = rendered_line - begin;
|
||||
// NOTE: `input_buffer` doesn't exist anymore, but this is a nice reference for just inserting text within the glyph buffer
|
||||
//
|
||||
// if len(buffer.input_buffer) > 0 && rendered_line == buffer.history.cursor.line && rendered_col >= buffer.history.cursor.col && rendered_col < buffer.history.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 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];
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
// rendered_col += 1;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
screen_line = rendered_line - begin;
|
||||
|
||||
|
|
|
@ -32,10 +32,11 @@ new_logger :: proc(buffer: ^FileBuffer) -> runtime.Logger {
|
|||
logger_proc :: proc(data: rawptr, level: runtime.Logger_Level, text: string, options: runtime.Logger_Options, location := #caller_location) {
|
||||
buffer := cast(^FileBuffer)data;
|
||||
|
||||
if .Level in options {
|
||||
insert_content(buffer, transmute([]u8)(Level_Header[level]), true);
|
||||
}
|
||||
// FIXME
|
||||
// if .Level in options {
|
||||
// insert_content(buffer, transmute([]u8)(Level_Header[level]), true);
|
||||
// }
|
||||
|
||||
insert_content(buffer, transmute([]u8)(text), true);
|
||||
insert_content(buffer, {'\n'}, true);
|
||||
// insert_content(buffer, transmute([]u8)(text), true);
|
||||
// insert_content(buffer, {'\n'}, true);
|
||||
}
|
||||
|
|
|
@ -3,6 +3,8 @@ package core
|
|||
PieceTable :: struct {
|
||||
original_content: []u8,
|
||||
added_content: [dynamic]u8,
|
||||
|
||||
// TODO: don't actually reference `added_content` and `original_content` via pointers, since they can be re-allocated
|
||||
chunks: [dynamic][]u8,
|
||||
}
|
||||
|
||||
|
@ -158,7 +160,19 @@ insert_text :: proc(t: ^PieceTable, to_be_inserted: []u8, index: PieceTableIndex
|
|||
if index.char_index == 0 {
|
||||
// insertion happening in beginning of content slice
|
||||
|
||||
inject_at(&t.chunks, index.chunk_index, inserted_slice);
|
||||
if len(t.chunks) > 1 && index.chunk_index > 0 {
|
||||
last_chunk_index := len(t.chunks[index.chunk_index-1])-1
|
||||
|
||||
if (&t.chunks[index.chunk_index-1][last_chunk_index]) == (&t.added_content[len(t.added_content)-1 - length]) {
|
||||
start := len(t.added_content)-1 - last_chunk_index - length
|
||||
|
||||
t.chunks[index.chunk_index-1] = t.added_content[start:]
|
||||
} else {
|
||||
inject_at(&t.chunks, index.chunk_index, inserted_slice);
|
||||
}
|
||||
} else {
|
||||
inject_at(&t.chunks, index.chunk_index, inserted_slice);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// insertion is happening in middle of content slice
|
||||
|
|
|
@ -25,24 +25,6 @@ FileBuffer :: core.FileBuffer;
|
|||
|
||||
state := core.State {};
|
||||
|
||||
do_normal_mode :: proc(state: ^State, buffer: ^FileBuffer) {
|
||||
}
|
||||
|
||||
do_insert_mode :: proc(state: ^State, buffer: ^FileBuffer) {
|
||||
key := 0;
|
||||
|
||||
for key > 0 {
|
||||
if key >= 32 && key <= 125 && len(buffer.input_buffer) < 1024-1 {
|
||||
append(&buffer.input_buffer, u8(key));
|
||||
}
|
||||
|
||||
key = 0;
|
||||
}
|
||||
}
|
||||
|
||||
do_visual_mode :: proc(state: ^State, buffer: ^FileBuffer) {
|
||||
}
|
||||
|
||||
ui_font_width :: proc() -> i32 {
|
||||
return i32(state.source_font_width);
|
||||
}
|
||||
|
@ -286,6 +268,7 @@ main :: proc() {
|
|||
log.error("SDL failed to initialize:", sdl2.GetError());
|
||||
return;
|
||||
}
|
||||
defer sdl2.Quit()
|
||||
|
||||
if ttf.Init() < 0 {
|
||||
log.error("SDL_TTF failed to initialize:", ttf.GetError());
|
||||
|
@ -443,22 +426,49 @@ main :: proc() {
|
|||
case .ESCAPE: {
|
||||
state.mode = .Normal;
|
||||
|
||||
core.insert_content(buffer, buffer.input_buffer[:]);
|
||||
runtime.clear(&buffer.input_buffer);
|
||||
// core.insert_content(buffer, buffer.input_buffer[:]);
|
||||
// runtime.clear(&buffer.input_buffer);
|
||||
core.move_cursor_left(buffer)
|
||||
|
||||
sdl2.StopTextInput();
|
||||
|
||||
ts.parse_buffer(&buffer.tree, core.tree_sitter_file_buffer_input(buffer))
|
||||
}
|
||||
case .TAB: {
|
||||
// TODO: change this to insert a tab character
|
||||
for _ in 0..<4 {
|
||||
append(&buffer.input_buffer, ' ');
|
||||
// for _ in 0..<4 {
|
||||
// append(&buffer.input_buffer, ' ');
|
||||
// }
|
||||
core.insert_content(buffer, transmute([]u8)string(" "))
|
||||
|
||||
if current_panel, ok := state.current_panel.?; ok {
|
||||
if panel, ok := util.get(&state.panels, current_panel).?; ok && panel.on_buffer_input != nil {
|
||||
panel->on_buffer_input(&state)
|
||||
}
|
||||
}
|
||||
}
|
||||
case .BACKSPACE: {
|
||||
core.delete_content(buffer, 1);
|
||||
|
||||
if current_panel, ok := state.current_panel.?; ok {
|
||||
if panel, ok := util.get(&state.panels, current_panel).?; ok && panel.on_buffer_input != nil {
|
||||
panel->on_buffer_input(&state)
|
||||
}
|
||||
}
|
||||
}
|
||||
case .ENTER: {
|
||||
append(&buffer.input_buffer, '\n');
|
||||
indent := core.get_buffer_indent(buffer)
|
||||
core.insert_content(buffer, []u8{'\n'})
|
||||
|
||||
for i in 0..<indent {
|
||||
core.insert_content(buffer, []u8{' '})
|
||||
}
|
||||
|
||||
if current_panel, ok := state.current_panel.?; ok {
|
||||
if panel, ok := util.get(&state.panels, current_panel).?; ok && panel.on_buffer_input != nil {
|
||||
panel->on_buffer_input(&state)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -470,8 +480,9 @@ main :: proc() {
|
|||
break;
|
||||
}
|
||||
|
||||
if char >= 32 && char <= 125 && len(buffer.input_buffer) < 1024-1 {
|
||||
append(&buffer.input_buffer, u8(char));
|
||||
if char >= 32 && char <= 125 {
|
||||
// append(&buffer.input_buffer, u8(char));
|
||||
core.insert_content(buffer, []u8{char})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -488,20 +499,6 @@ main :: proc() {
|
|||
|
||||
draw(&state);
|
||||
|
||||
switch state.mode {
|
||||
case .Normal:
|
||||
buffer := core.current_buffer(&state);
|
||||
do_normal_mode(&state, buffer);
|
||||
case .Insert:
|
||||
buffer := core.current_buffer(&state);
|
||||
do_insert_mode(&state, buffer);
|
||||
case .Visual:
|
||||
buffer := core.current_buffer(&state);
|
||||
do_visual_mode(&state, buffer);
|
||||
}
|
||||
|
||||
runtime.free_all(context.temp_allocator);
|
||||
}
|
||||
|
||||
sdl2.Quit();
|
||||
}
|
||||
|
|
|
@ -188,6 +188,25 @@ file_buffer_go_actions :: proc(input_map: ^core.InputActions) {
|
|||
}, "move to end of line");
|
||||
}
|
||||
|
||||
file_buffer_delete_actions :: proc(input_map: ^core.InputActions) {
|
||||
core.register_key_action(input_map, .D, proc(state: ^core.State, user_data: rawptr) {
|
||||
buffer := &(&(transmute(^core.Panel)user_data).type.(core.FileBufferPanel)).buffer
|
||||
|
||||
core.push_new_snapshot(&buffer.history)
|
||||
|
||||
buffer.selection = core.new_selection(buffer.history.cursor);
|
||||
sel_cur := &(buffer.selection.?);
|
||||
|
||||
core.move_cursor_start_of_line(buffer, cursor = &sel_cur.start);
|
||||
core.move_cursor_end_of_line(buffer, cursor = &sel_cur.end, stop_at_end = false);
|
||||
|
||||
core.delete_content_from_selection(buffer, sel_cur, reparse_buffer = true)
|
||||
|
||||
buffer.selection = nil;
|
||||
core.reset_input_map(state)
|
||||
}, "delete whole line");
|
||||
}
|
||||
|
||||
file_buffer_input_actions :: proc(input_map: ^core.InputActions) {
|
||||
// Cursor Movement
|
||||
{
|
||||
|
@ -273,6 +292,10 @@ file_buffer_input_actions :: proc(input_map: ^core.InputActions) {
|
|||
file_buffer_go_actions(&go_actions);
|
||||
core.register_key_action(input_map, .G, go_actions, "Go commands");
|
||||
|
||||
delete_actions := core.new_input_actions(show_help = true)
|
||||
file_buffer_delete_actions(&delete_actions);
|
||||
core.register_key_action(input_map, .D, delete_actions, "Delete commands");
|
||||
|
||||
core.register_key_action(input_map, .V, proc(state: ^core.State, user_data: rawptr) {
|
||||
buffer := &(&(transmute(^core.Panel)user_data).type.(core.FileBufferPanel)).buffer
|
||||
|
||||
|
@ -428,7 +451,7 @@ file_buffer_visual_actions :: proc(input_map: ^core.InputActions) {
|
|||
if state.yank_register.whole_line {
|
||||
core.insert_content(buffer, []u8{'\n'});
|
||||
core.paste_register(state, state.yank_register, buffer)
|
||||
core.insert_content(buffer, []u8{'\n'});
|
||||
core.insert_content(buffer, []u8{'\n'}, reparse_buffer = true);
|
||||
} else {
|
||||
core.paste_register(state, state.yank_register, buffer)
|
||||
}
|
||||
|
@ -479,10 +502,23 @@ file_buffer_text_input_actions :: proc(input_map: ^core.InputActions) {
|
|||
core.push_new_snapshot(&buffer.history)
|
||||
|
||||
if buffer := buffer; buffer != nil {
|
||||
core.move_cursor_end_of_line(buffer, false);
|
||||
runtime.clear(&buffer.input_buffer)
|
||||
core.move_cursor_end_of_line(buffer);
|
||||
|
||||
char := core.get_character_at_piece_table_index(core.buffer_piece_table(buffer), buffer.history.cursor.index)
|
||||
indent := core.get_buffer_indent(buffer)
|
||||
if char == '{' {
|
||||
// TODO: update tab to be configurable
|
||||
indent += 4
|
||||
}
|
||||
|
||||
append(&buffer.input_buffer, '\n')
|
||||
if char != '\n' {
|
||||
core.move_cursor_right(buffer, stop_at_end = false)
|
||||
}
|
||||
|
||||
core.insert_content(buffer, []u8{'\n'})
|
||||
for i in 0..<indent {
|
||||
core.insert_content(buffer, []u8{' '})
|
||||
}
|
||||
|
||||
state.mode = .Insert;
|
||||
|
||||
|
@ -511,9 +547,8 @@ file_buffer_text_input_actions :: proc(input_map: ^core.InputActions) {
|
|||
core.push_new_snapshot(&buffer.history)
|
||||
|
||||
if state.yank_register.whole_line {
|
||||
core.move_cursor_end_of_line(buffer, false);
|
||||
core.move_cursor_end_of_line(buffer, stop_at_end = false);
|
||||
core.insert_content(buffer, []u8{'\n'});
|
||||
core.move_cursor_right(buffer, false);
|
||||
} else {
|
||||
core.move_cursor_right(buffer)
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ open_grep_panel :: proc(state: ^core.State) {
|
|||
}
|
||||
|
||||
make_grep_panel :: proc() -> core.Panel {
|
||||
run_query :: proc(panel_state: ^core.GrepPanel, query: string, directory: string) {
|
||||
run_query :: proc(panel_state: ^core.GrepPanel, buffer: ^core.FileBuffer, directory: string) {
|
||||
if panel_state.query_region.arena != nil {
|
||||
mem.end_arena_temp_memory(panel_state.query_region)
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ make_grep_panel :: proc() -> core.Panel {
|
|||
context.allocator = mem.arena_allocator(&panel_state.query_arena)
|
||||
|
||||
rs_results := grep(
|
||||
strings.clone_to_cstring(query),
|
||||
strings.clone_to_cstring(core.buffer_to_string(buffer)),
|
||||
strings.clone_to_cstring(directory)
|
||||
);
|
||||
|
||||
|
@ -142,7 +142,7 @@ make_grep_panel :: proc() -> core.Panel {
|
|||
},
|
||||
on_buffer_input = proc(panel: ^core.Panel, state: ^core.State) {
|
||||
if panel_state, ok := &panel.type.(core.GrepPanel); ok {
|
||||
run_query(panel_state, string(panel_state.buffer.input_buffer[:]), state.directory)
|
||||
run_query(panel_state, &panel_state.buffer, state.directory)
|
||||
}
|
||||
},
|
||||
render = proc(panel: ^core.Panel, state: ^core.State) -> (ok: bool) {
|
||||
|
@ -183,7 +183,7 @@ make_grep_panel :: proc() -> core.Panel {
|
|||
max_results := container_height / 16
|
||||
|
||||
for result, i in panel_state.query_results {
|
||||
if i > max_results {
|
||||
if i >= max_results {
|
||||
break
|
||||
}
|
||||
|
||||
|
@ -204,8 +204,14 @@ make_grep_panel :: proc() -> core.Panel {
|
|||
style.background_color = .Background2
|
||||
}
|
||||
|
||||
ui.open_element(s, result.file_path[len(state.directory):], {}, style)
|
||||
ui.close_element(s)
|
||||
if len(result.file_path) > 0 {
|
||||
ui.open_element(s, result.file_path[len(state.directory):], {}, style)
|
||||
ui.close_element(s)
|
||||
} else {
|
||||
style.background_color = .BrightRed
|
||||
ui.open_element(s, "BAD FILE DIRECTORY", {}, style)
|
||||
ui.close_element(s)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -270,7 +270,6 @@ insert_before_slice :: proc(t: ^testing.T) {
|
|||
run_text_insertion(&e, " rich")
|
||||
|
||||
expect_line_col(t, buffer.history.cursor, 0, 20)
|
||||
expect_cursor_index(t, buffer.history.cursor, 2, 4)
|
||||
|
||||
contents := buffer_to_string(core.current_buffer(&e))
|
||||
defer delete(contents)
|
||||
|
@ -346,8 +345,7 @@ delete_in_slice :: proc(t: ^testing.T) {
|
|||
run_input_multiple(&e, press_key(.BACKSPACE), 3)
|
||||
run_input_multiple(&e, press_key(.ESCAPE), 1)
|
||||
|
||||
expect_line_col(t, buffer.history.cursor, 0, 17)
|
||||
expect_cursor_index(t, buffer.history.cursor, 3, 0)
|
||||
expect_line_col(t, buffer.history.cursor, 0, 16)
|
||||
|
||||
contents := buffer_to_string(core.current_buffer(&e))
|
||||
defer delete(contents)
|
||||
|
@ -387,15 +385,14 @@ delete_across_slices :: proc(t: ^testing.T) {
|
|||
run_input_multiple(&e, press_key(.ESCAPE), 1)
|
||||
|
||||
// Move right, passed the 'h' on to the space before 'world!'
|
||||
run_input_multiple(&e, press_key(.L), 1)
|
||||
run_input_multiple(&e, press_key(.L), 2)
|
||||
|
||||
// Remove the ' h', which consists of two content slices
|
||||
run_input_multiple(&e, press_key(.I), 1)
|
||||
run_input_multiple(&e, press_key(.BACKSPACE), 2)
|
||||
run_input_multiple(&e, press_key(.ESCAPE), 1)
|
||||
|
||||
expect_line_col(t, buffer.history.cursor, 0, 16)
|
||||
expect_cursor_index(t, buffer.history.cursor, 2, 0)
|
||||
expect_line_col(t, buffer.history.cursor, 0, 15)
|
||||
|
||||
contents := buffer_to_string(core.current_buffer(&e))
|
||||
defer delete(contents)
|
||||
|
@ -632,14 +629,12 @@ append_end_of_line :: proc(t: ^testing.T) {
|
|||
run_input_multiple(&e, press_key(.A), 1)
|
||||
run_input_multiple(&e, press_key(.ESCAPE), 1)
|
||||
|
||||
expect_line_col(t, buffer.history.cursor, 0, 5)
|
||||
expect_cursor_index(t, buffer.history.cursor, 1, 0)
|
||||
expect_line_col(t, buffer.history.cursor, 0, 4)
|
||||
|
||||
run_input_multiple(&e, press_key(.A), 1)
|
||||
run_input_multiple(&e, press_key(.ESCAPE), 1)
|
||||
|
||||
expect_line_col(t, buffer.history.cursor, 0, 5)
|
||||
expect_cursor_index(t, buffer.history.cursor, 1, 0)
|
||||
expect_line_col(t, buffer.history.cursor, 0, 4)
|
||||
}
|
||||
|
||||
@(test)
|
||||
|
@ -667,15 +662,11 @@ insert_line_under_current :: proc(t: ^testing.T) {
|
|||
// 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.history.cursor, 0, 13)
|
||||
expect_cursor_index(t, buffer.history.cursor, 0, 13)
|
||||
expect_line_col(t, buffer.history.cursor, 1, 0)
|
||||
|
||||
run_text_insertion(&e, "This is the second line")
|
||||
|
||||
expect_line_col(t, buffer.history.cursor, 1, 22)
|
||||
expect_cursor_index(t, buffer.history.cursor, 1, 23)
|
||||
|
||||
contents := buffer_to_string(core.current_buffer(&e))
|
||||
defer delete(contents)
|
||||
|
@ -716,6 +707,41 @@ yank_and_paste_whole_line :: proc(t: ^testing.T) {
|
|||
testing.expectf(t, contents == expected_text, "got '%v', expected '%v'", contents, expected_text)
|
||||
}
|
||||
|
||||
@(test)
|
||||
select_and_delete_half_of_line_backwards :: proc(t: ^testing.T) {
|
||||
e := new_test_editor()
|
||||
setup_empty_buffer(&e)
|
||||
defer {
|
||||
panels.close(&e, 0)
|
||||
delete_editor(&e)
|
||||
}
|
||||
|
||||
buffer := core.current_buffer(&e)
|
||||
|
||||
initial_text := "Hello, world!\nThis is a new line"
|
||||
run_text_insertion(&e, initial_text)
|
||||
|
||||
expected_text := "Hello\nThis is a new line\n"
|
||||
|
||||
// Move up to "Hello, world!"
|
||||
run_input_multiple(&e, press_key(.K), 1)
|
||||
|
||||
// Move to end of line
|
||||
run_inputs(&e, []ArtificialInput{ press_key(.G), press_key(.L)})
|
||||
|
||||
// Move to the end of 'Hello' and delete selection
|
||||
run_input_multiple(&e, press_key(.V), 1)
|
||||
run_input_multiple(&e, press_key(.H), 7)
|
||||
run_input_multiple(&e, press_key(.D), 1)
|
||||
|
||||
expect_line_col(t, buffer.history.cursor, 0, 4)
|
||||
|
||||
contents := buffer_to_string(core.current_buffer(&e))
|
||||
defer delete(contents)
|
||||
|
||||
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) {
|
||||
{
|
||||
run_key_action := proc(state: ^core.State, control_key_pressed: bool, key: core.Key) -> bool {
|
||||
|
@ -777,21 +803,40 @@ run_editor_frame :: proc(state: ^core.State, input: ArtificialInput, is_ctrl_pre
|
|||
#partial switch key.key {
|
||||
case .ESCAPE: {
|
||||
state.mode = .Normal;
|
||||
|
||||
core.insert_content(buffer, buffer.input_buffer[:]);
|
||||
runtime.clear(&buffer.input_buffer);
|
||||
core.move_cursor_left(buffer)
|
||||
}
|
||||
case .TAB: {
|
||||
// TODO: change this to insert a tab character
|
||||
for _ in 0..<4 {
|
||||
append(&buffer.input_buffer, ' ');
|
||||
core.insert_content(buffer, transmute([]u8)string(" "))
|
||||
|
||||
if current_panel, ok := state.current_panel.?; ok {
|
||||
if panel, ok := util.get(&state.panels, current_panel).?; ok && panel.on_buffer_input != nil {
|
||||
panel->on_buffer_input(state)
|
||||
}
|
||||
}
|
||||
}
|
||||
case .BACKSPACE: {
|
||||
core.delete_content(buffer, 1);
|
||||
|
||||
if current_panel, ok := state.current_panel.?; ok {
|
||||
if panel, ok := util.get(&state.panels, current_panel).?; ok && panel.on_buffer_input != nil {
|
||||
panel->on_buffer_input(state)
|
||||
}
|
||||
}
|
||||
}
|
||||
case .ENTER: {
|
||||
append(&buffer.input_buffer, '\n');
|
||||
indent := core.get_buffer_indent(buffer)
|
||||
core.insert_content(buffer, []u8{'\n'})
|
||||
|
||||
for i in 0..<indent {
|
||||
core.insert_content(buffer, []u8{' '})
|
||||
}
|
||||
|
||||
if current_panel, ok := state.current_panel.?; ok {
|
||||
if panel, ok := util.get(&state.panels, current_panel).?; ok && panel.on_buffer_input != nil {
|
||||
panel->on_buffer_input(state)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -804,8 +849,8 @@ run_editor_frame :: proc(state: ^core.State, input: ArtificialInput, is_ctrl_pre
|
|||
break;
|
||||
}
|
||||
|
||||
if char == '\n' || (char >= 32 && char <= 125 && len(buffer.input_buffer) < 1024-1) {
|
||||
append(&buffer.input_buffer, u8(char));
|
||||
if char == '\n' || (char >= 32 && char <= 125) {
|
||||
core.insert_content(buffer, []u8{u8(char)})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -819,30 +864,5 @@ run_editor_frame :: proc(state: ^core.State, input: ArtificialInput, is_ctrl_pre
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: share this with the main application
|
||||
do_insert_mode :: proc(state: ^core.State, buffer: ^core.FileBuffer) {
|
||||
key := 0;
|
||||
|
||||
for key > 0 {
|
||||
if key >= 32 && key <= 125 && len(buffer.input_buffer) < 1024-1 {
|
||||
append(&buffer.input_buffer, u8(key));
|
||||
}
|
||||
|
||||
key = 0;
|
||||
}
|
||||
}
|
||||
|
||||
switch state.mode {
|
||||
case .Normal:
|
||||
// buffer := core.current_buffer(state);
|
||||
// do_normal_mode(state, buffer);
|
||||
case .Insert:
|
||||
buffer := core.current_buffer(state);
|
||||
do_insert_mode(state, buffer);
|
||||
case .Visual:
|
||||
// buffer := core.current_buffer(state);
|
||||
// do_visual_mode(state, buffer);
|
||||
}
|
||||
|
||||
runtime.free_all(context.temp_allocator);
|
||||
}
|
||||
|
|
50
todo.md
50
todo.md
|
@ -1,12 +1,13 @@
|
|||
# Bugs
|
||||
- Fix jumping forward a word jumping past consecutive brackets
|
||||
- Odd scrolling behavior on small screen heights
|
||||
- Scrolling past end/beginning of results panics
|
||||
- Scrolling past end/beginning of grep results panics
|
||||
- "change selection" not pushing snapshot
|
||||
- Don't allow panel navigation in grep panel
|
||||
|
||||
# Visual QOL
|
||||
- Split grep search results into a table to avoid funky unaligned text
|
||||
|
||||
# Planned Features
|
||||
# TODO
|
||||
- [ ] Jump List
|
||||
- [x] Use grouped lifetimes exclusively for memory allocation/freeing
|
||||
- [ ] Highlight which panel is currently active
|
||||
|
@ -16,7 +17,6 @@
|
|||
- [ ] Finish writing tests for all current user actions
|
||||
- Vim-like Macro replays
|
||||
- [ ] Simple File Search (vim /)
|
||||
- [ ] Auto-indent
|
||||
- Modify input system to allow for keybinds that take input
|
||||
- Vim's f and F movement commands
|
||||
- Vim's r command
|
||||
|
@ -29,26 +29,34 @@
|
|||
- [ ] In-line errors
|
||||
- [ ] Go-to Definition/
|
||||
- [ ] Find references
|
||||
- Re-implement lost features from Plugins
|
||||
- [ ] Integrate tree-sitter
|
||||
- [x] Syntax Highlighting
|
||||
- [ ] Auto Setup Parsers
|
||||
- [ ] Download parser
|
||||
- [ ] Compile/"Install"
|
||||
- [ ] Auto-indent?
|
||||
- [ ] Bootleg Telescope
|
||||
- [ ] Grepping Files
|
||||
- [x] Query across project
|
||||
- [x] Open file in new buffer
|
||||
- [x] Open file in new buffer at found location
|
||||
- [ ] Preview file with context
|
||||
- [x] Show Context
|
||||
- [ ] Properly show lines numbers
|
||||
- [ ] Don't overlap result list with file preview
|
||||
- [ ] Open Buffer Search
|
||||
- [ ] Integrate tree-sitter
|
||||
- [x] Syntax Highlighting
|
||||
- [ ] Auto Setup Parsers
|
||||
- [ ] Download parser
|
||||
- [ ] Compile/"Install"
|
||||
- [ ] Auto-indent
|
||||
- [x] Infer indent with similar lines
|
||||
- [x] Infer indent with C-style scopes (languages using '{')
|
||||
- [ ] Infer indent inside multi-line function calls (again C-style)
|
||||
- [ ] Somehow use tree-sitter to be language agnostic (not sure this is even possible with TS)
|
||||
- [ ] Bootleg Telescope
|
||||
- [ ] Grepping Files
|
||||
- [x] Query across project
|
||||
- [x] Open file in new buffer
|
||||
- [x] Open file in new buffer at found location
|
||||
- [ ] Preview file with context
|
||||
- [x] Show Context
|
||||
- [ ] Properly show lines numbers
|
||||
- [ ] Don't overlap result list with file preview
|
||||
- [ ] Open Buffer Search
|
||||
- [ ] Workspace file search
|
||||
- Re-write the UI (again)
|
||||
- [x] New UI
|
||||
- [ ] Styling
|
||||
- [x] Background colors
|
||||
- [x] Borders + Border Color
|
||||
- [ ] Rounded corners?
|
||||
- [ ] Gradients?
|
||||
- Undo/Redo
|
||||
- [x] Basic/Naive Undo/Redo
|
||||
- [ ] Interface for undo-able actions
|
||||
|
|
Loading…
Reference in New Issue