Compare commits
7 Commits
722f05be61
...
aae0c24504
Author | SHA1 | Date |
---|---|---|
|
aae0c24504 | |
|
7e128d08cc | |
|
cf21773e6f | |
|
d28a707a8f | |
|
22a7d40d30 | |
|
34d56eb47c | |
|
d170e7d63c |
|
@ -144,7 +144,7 @@ yank_whole_line :: proc(state: ^State) {
|
|||
}
|
||||
|
||||
if buffer := current_buffer(state); buffer != nil {
|
||||
selection := new_selection(buffer, buffer.cursor)
|
||||
selection := new_selection(buffer, buffer.history.cursor)
|
||||
length := selection_length(buffer, selection)
|
||||
|
||||
state.yank_register.whole_line = true
|
||||
|
|
|
@ -27,15 +27,10 @@ ContentSlice :: struct {
|
|||
slice: []u8,
|
||||
}
|
||||
|
||||
FileBufferIndex :: struct {
|
||||
slice_index: int,
|
||||
content_index: int,
|
||||
}
|
||||
|
||||
Cursor :: struct {
|
||||
col: int,
|
||||
line: int,
|
||||
index: FileBufferIndex,
|
||||
index: PieceTableIndex,
|
||||
}
|
||||
|
||||
Selection :: struct {
|
||||
|
@ -51,13 +46,10 @@ FileBuffer :: struct {
|
|||
extension: string,
|
||||
|
||||
top_line: int,
|
||||
cursor: Cursor,
|
||||
// cursor: Cursor,
|
||||
selection: Maybe(Selection),
|
||||
|
||||
original_content: [dynamic]u8,
|
||||
added_content: [dynamic]u8,
|
||||
content_slices: [dynamic][]u8,
|
||||
|
||||
history: FileHistory,
|
||||
glyphs: GlyphBuffer,
|
||||
|
||||
input_buffer: [dynamic]u8,
|
||||
|
@ -66,14 +58,27 @@ FileBuffer :: struct {
|
|||
FileBufferIter :: struct {
|
||||
cursor: Cursor,
|
||||
buffer: ^FileBuffer,
|
||||
piter: PieceTableIter,
|
||||
hit_end: bool,
|
||||
}
|
||||
|
||||
// TODO: don't make this panic on nil snapshot
|
||||
buffer_piece_table :: proc(file_buffer: ^FileBuffer) -> ^PieceTable {
|
||||
return &file_buffer.history.piece_table
|
||||
}
|
||||
|
||||
new_file_buffer_iter_from_beginning :: proc(file_buffer: ^FileBuffer) -> FileBufferIter {
|
||||
return FileBufferIter { buffer = file_buffer };
|
||||
return FileBufferIter {
|
||||
buffer = file_buffer,
|
||||
piter = new_piece_table_iter(buffer_piece_table(file_buffer))
|
||||
};
|
||||
}
|
||||
new_file_buffer_iter_with_cursor :: proc(file_buffer: ^FileBuffer, cursor: Cursor) -> FileBufferIter {
|
||||
return FileBufferIter { buffer = file_buffer, cursor = cursor };
|
||||
return FileBufferIter {
|
||||
buffer = file_buffer,
|
||||
cursor = cursor,
|
||||
piter = new_piece_table_iter_from_index(buffer_piece_table(file_buffer), cursor.index)
|
||||
};
|
||||
}
|
||||
new_file_buffer_iter :: proc{new_file_buffer_iter_from_beginning, new_file_buffer_iter_with_cursor};
|
||||
|
||||
|
@ -81,89 +86,55 @@ file_buffer_end :: proc(buffer: ^FileBuffer) -> Cursor {
|
|||
return Cursor {
|
||||
col = 0,
|
||||
line = 0,
|
||||
index = FileBufferIndex {
|
||||
slice_index = len(buffer.content_slices)-1,
|
||||
content_index = len(buffer.content_slices[len(buffer.content_slices)-1])-1,
|
||||
}
|
||||
index = new_piece_table_index_from_end(buffer_piece_table(buffer))
|
||||
};
|
||||
}
|
||||
|
||||
iterate_file_buffer :: proc(it: ^FileBufferIter) -> (character: u8, idx: FileBufferIndex, cond: bool) {
|
||||
if it.cursor.index.slice_index >= len(it.buffer.content_slices) || it.cursor.index.content_index >= len(it.buffer.content_slices[it.cursor.index.slice_index]) {
|
||||
return;
|
||||
}
|
||||
cond = true;
|
||||
iterate_file_buffer :: proc(it: ^FileBufferIter) -> (character: u8, idx: PieceTableIndex, cond: bool) {
|
||||
character, idx, cond = iterate_piece_table_iter(&it.piter)
|
||||
|
||||
character = it.buffer.content_slices[it.cursor.index.slice_index][it.cursor.index.content_index];
|
||||
if it.cursor.index.content_index < len(it.buffer.content_slices[it.cursor.index.slice_index])-1 {
|
||||
it.cursor.index.content_index += 1;
|
||||
} else if it.cursor.index.slice_index < len(it.buffer.content_slices)-1 {
|
||||
it.cursor.index.content_index = 0;
|
||||
it.cursor.index.slice_index += 1;
|
||||
} else if it.hit_end {
|
||||
return character, it.cursor.index, false;
|
||||
} else {
|
||||
it.hit_end = true;
|
||||
return character, it.cursor.index, true;
|
||||
}
|
||||
it.cursor.index = it.piter.index
|
||||
it.hit_end = it.piter.hit_end
|
||||
|
||||
if character == '\n' {
|
||||
it.cursor.col = 0;
|
||||
it.cursor.line += 1;
|
||||
} else {
|
||||
it.cursor.col += 1;
|
||||
}
|
||||
|
||||
return character, it.cursor.index, true;
|
||||
}
|
||||
iterate_file_buffer_reverse_mangle_cursor :: proc(it: ^FileBufferIter) -> (character: u8, idx: FileBufferIndex, cond: bool) {
|
||||
if len(it.buffer.content_slices[it.cursor.index.slice_index]) < 0 {
|
||||
return character, idx, false;
|
||||
}
|
||||
|
||||
character = it.buffer.content_slices[it.cursor.index.slice_index][it.cursor.index.content_index];
|
||||
if it.cursor.index.content_index == 0 {
|
||||
if it.cursor.index.slice_index > 0 {
|
||||
it.cursor.index.slice_index -= 1;
|
||||
it.cursor.index.content_index = len(it.buffer.content_slices[it.cursor.index.slice_index])-1;
|
||||
} else if it.hit_end {
|
||||
return character, it.cursor.index, false;
|
||||
if cond && !it.hit_end {
|
||||
if character == '\n' {
|
||||
it.cursor.col = 0
|
||||
it.cursor.line += 1
|
||||
} else {
|
||||
it.hit_end = true;
|
||||
return character, it.cursor.index, true;
|
||||
it.cursor.col += 1
|
||||
}
|
||||
} else {
|
||||
it.cursor.index.content_index -= 1;
|
||||
}
|
||||
|
||||
return character, it.cursor.index, true;
|
||||
return
|
||||
}
|
||||
// TODO: figure out how to give the first character of the buffer
|
||||
iterate_file_buffer_reverse :: proc(it: ^FileBufferIter) -> (character: u8, idx: FileBufferIndex, cond: bool) {
|
||||
if character, idx, cond = iterate_file_buffer_reverse_mangle_cursor(it); cond {
|
||||
if it.cursor.col < 1 {
|
||||
if it.cursor.line > 0 {
|
||||
line_length := file_buffer_line_length(it.buffer, it.cursor.index);
|
||||
if line_length < 0 { line_length = 0; }
|
||||
|
||||
it.cursor.line -= 1;
|
||||
it.cursor.col = line_length;
|
||||
} else {
|
||||
return character, it.cursor.index, false;
|
||||
}
|
||||
} else {
|
||||
it.cursor.col -= 1;
|
||||
}
|
||||
// TODO: figure out how to give the first character of the buffer
|
||||
iterate_file_buffer_reverse :: proc(it: ^FileBufferIter) -> (character: u8, idx: PieceTableIndex, cond: bool) {
|
||||
character, idx, cond = iterate_piece_table_iter_reverse(&it.piter)
|
||||
|
||||
it.cursor.index = it.piter.index
|
||||
it.hit_end = it.piter.hit_end
|
||||
|
||||
if cond && !it.hit_end {
|
||||
if it.cursor.col > 0 {
|
||||
it.cursor.col -= 1
|
||||
} else if it.cursor.line > 0 {
|
||||
line_length := file_buffer_line_length(it.buffer, it.cursor.index)
|
||||
if line_length < 0 { line_length = 0 }
|
||||
|
||||
it.cursor.line -= 1
|
||||
it.cursor.col = line_length
|
||||
}
|
||||
}
|
||||
|
||||
return character, it.cursor.index, cond;
|
||||
return
|
||||
}
|
||||
|
||||
get_character_at_iter :: proc(it: FileBufferIter) -> u8 {
|
||||
return it.buffer.content_slices[it.cursor.index.slice_index][it.cursor.index.content_index];
|
||||
return get_character_at_piece_table_index(buffer_piece_table(it.buffer), it.cursor.index);
|
||||
}
|
||||
|
||||
IterProc :: proc(it: ^FileBufferIter) -> (character: u8, idx: FileBufferIndex, cond: bool);
|
||||
IterProc :: proc(it: ^FileBufferIter) -> (character: u8, idx: PieceTableIndex, cond: bool);
|
||||
UntilProc :: proc(it: ^FileBufferIter, iter_proc: IterProc) -> bool;
|
||||
|
||||
iterate_file_buffer_until :: proc(it: ^FileBufferIter, until_proc: UntilProc) {
|
||||
|
@ -356,7 +327,7 @@ until_single_quote :: proc(it: ^FileBufferIter, iter_proc: IterProc) -> bool {
|
|||
}
|
||||
|
||||
until_line_break :: proc(it: ^FileBufferIter, iter_proc: IterProc) -> (cond: bool) {
|
||||
if it.buffer.content_slices[it.cursor.index.slice_index][it.cursor.index.content_index] == '\n' {
|
||||
if get_character_at_piece_table_index(buffer_piece_table(it.buffer), it.cursor.index) == '\n' {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -372,7 +343,7 @@ update_file_buffer_index_from_cursor :: proc(buffer: ^FileBuffer) {
|
|||
rendered_line := 0;
|
||||
|
||||
for character in iterate_file_buffer(&it) {
|
||||
if line_length == buffer.cursor.col && rendered_line == buffer.cursor.line {
|
||||
if line_length == buffer.history.cursor.col && rendered_line == buffer.history.cursor.line {
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -387,23 +358,23 @@ update_file_buffer_index_from_cursor :: proc(buffer: ^FileBuffer) {
|
|||
}
|
||||
|
||||
// FIXME: just swap cursors
|
||||
buffer.cursor.index = before_it.cursor.index;
|
||||
buffer.history.cursor.index = before_it.cursor.index;
|
||||
|
||||
update_file_buffer_scroll(buffer);
|
||||
}
|
||||
|
||||
file_buffer_line_length :: proc(buffer: ^FileBuffer, index: FileBufferIndex) -> int {
|
||||
file_buffer_line_length :: proc(buffer: ^FileBuffer, index: PieceTableIndex) -> int {
|
||||
line_length := 0;
|
||||
if len(buffer.content_slices) <= 0 do return line_length
|
||||
// if len(buffer.content_slices) <= 0 do return line_length
|
||||
|
||||
first_character := buffer.content_slices[index.slice_index][index.content_index];
|
||||
first_character := get_character_at_piece_table_index(buffer_piece_table(buffer), index);
|
||||
left_it := new_piece_table_iter_from_index(buffer_piece_table(buffer), index);
|
||||
|
||||
left_it := new_file_buffer_iter_with_cursor(buffer, Cursor { index = index });
|
||||
if first_character == '\n' {
|
||||
iterate_file_buffer_reverse_mangle_cursor(&left_it);
|
||||
iterate_piece_table_iter_reverse(&left_it);
|
||||
}
|
||||
|
||||
for character in iterate_file_buffer_reverse_mangle_cursor(&left_it) {
|
||||
for character in iterate_piece_table_iter_reverse(&left_it) {
|
||||
if character == '\n' {
|
||||
break;
|
||||
}
|
||||
|
@ -411,9 +382,9 @@ file_buffer_line_length :: proc(buffer: ^FileBuffer, index: FileBufferIndex) ->
|
|||
line_length += 1;
|
||||
}
|
||||
|
||||
right_it := new_file_buffer_iter_with_cursor(buffer, Cursor { index = index });
|
||||
right_it := new_piece_table_iter_from_index(buffer_piece_table(buffer), index);
|
||||
first := true;
|
||||
for character in iterate_file_buffer(&right_it) {
|
||||
for character in iterate_piece_table_iter(&right_it) {
|
||||
if character == '\n' {
|
||||
break;
|
||||
}
|
||||
|
@ -431,7 +402,7 @@ move_cursor_start_of_line :: proc(buffer: ^FileBuffer, cursor: Maybe(^Cursor) =
|
|||
cursor := cursor;
|
||||
|
||||
if cursor == nil {
|
||||
cursor = &buffer.cursor;
|
||||
cursor = &buffer.history.cursor;
|
||||
}
|
||||
|
||||
if cursor.?.col > 0 {
|
||||
|
@ -450,7 +421,7 @@ move_cursor_end_of_line :: proc(buffer: ^FileBuffer, stop_at_end: bool = true, c
|
|||
cursor := cursor;
|
||||
|
||||
if cursor == nil {
|
||||
cursor = &buffer.cursor;
|
||||
cursor = &buffer.history.cursor;
|
||||
}
|
||||
|
||||
it := new_file_buffer_iter_with_cursor(buffer, cursor.?^);
|
||||
|
@ -474,7 +445,7 @@ move_cursor_up :: proc(buffer: ^FileBuffer, amount: int = 1, cursor: Maybe(^Curs
|
|||
cursor := cursor;
|
||||
|
||||
if cursor == nil {
|
||||
cursor = &buffer.cursor;
|
||||
cursor = &buffer.history.cursor;
|
||||
}
|
||||
|
||||
if cursor.?.line > 0 {
|
||||
|
@ -508,7 +479,7 @@ move_cursor_down :: proc(buffer: ^FileBuffer, amount: int = 1, cursor: Maybe(^Cu
|
|||
cursor := cursor;
|
||||
|
||||
if cursor == nil {
|
||||
cursor = &buffer.cursor;
|
||||
cursor = &buffer.history.cursor;
|
||||
}
|
||||
|
||||
current_line := cursor.?.line;
|
||||
|
@ -541,7 +512,7 @@ move_cursor_left :: proc(buffer: ^FileBuffer, cursor: Maybe(^Cursor) = nil) {
|
|||
cursor := cursor;
|
||||
|
||||
if cursor == nil {
|
||||
cursor = &buffer.cursor;
|
||||
cursor = &buffer.history.cursor;
|
||||
}
|
||||
|
||||
if cursor.?.col > 0 {
|
||||
|
@ -555,7 +526,7 @@ move_cursor_right :: proc(buffer: ^FileBuffer, stop_at_end: bool = true, amt: in
|
|||
cursor := cursor;
|
||||
|
||||
if cursor == nil {
|
||||
cursor = &buffer.cursor;
|
||||
cursor = &buffer.history.cursor;
|
||||
}
|
||||
|
||||
it := new_file_buffer_iter_with_cursor(buffer, cursor.?^);
|
||||
|
@ -573,7 +544,7 @@ move_cursor_forward_start_of_word :: proc(buffer: ^FileBuffer, cursor: Maybe(^Cu
|
|||
cursor := cursor;
|
||||
|
||||
if cursor == nil {
|
||||
cursor = &buffer.cursor;
|
||||
cursor = &buffer.history.cursor;
|
||||
}
|
||||
|
||||
it := new_file_buffer_iter_with_cursor(buffer, cursor.?^);
|
||||
|
@ -587,7 +558,7 @@ move_cursor_forward_end_of_word :: proc(buffer: ^FileBuffer, cursor: Maybe(^Curs
|
|||
cursor := cursor;
|
||||
|
||||
if cursor == nil {
|
||||
cursor = &buffer.cursor;
|
||||
cursor = &buffer.history.cursor;
|
||||
}
|
||||
|
||||
it := new_file_buffer_iter_with_cursor(buffer, cursor.?^);
|
||||
|
@ -601,7 +572,7 @@ move_cursor_backward_start_of_word :: proc(buffer: ^FileBuffer, cursor: Maybe(^C
|
|||
cursor := cursor;
|
||||
|
||||
if cursor == nil {
|
||||
cursor = &buffer.cursor;
|
||||
cursor = &buffer.history.cursor;
|
||||
}
|
||||
|
||||
it := new_file_buffer_iter_with_cursor(buffer, cursor.?^);
|
||||
|
@ -616,7 +587,7 @@ move_cursor_backward_end_of_word :: proc(buffer: ^FileBuffer, cursor: Maybe(^Cur
|
|||
cursor := cursor;
|
||||
|
||||
if cursor == nil {
|
||||
cursor = &buffer.cursor;
|
||||
cursor = &buffer.history.cursor;
|
||||
}
|
||||
|
||||
it := new_file_buffer_iter_with_cursor(buffer, cursor.?^);
|
||||
|
@ -666,10 +637,11 @@ swap_selections :: proc(selection: Selection) -> (swapped: Selection) {
|
|||
return swapped
|
||||
}
|
||||
|
||||
// TODO: don't access PieceTableIndex directly
|
||||
is_selection_inverted :: proc(selection: Selection) -> bool {
|
||||
return selection.start.index.slice_index > selection.end.index.slice_index ||
|
||||
(selection.start.index.slice_index == selection.end.index.slice_index
|
||||
&& selection.start.index.content_index > selection.end.index.content_index)
|
||||
return selection.start.index.chunk_index > selection.end.index.chunk_index ||
|
||||
(selection.start.index.chunk_index == selection.end.index.chunk_index
|
||||
&& selection.start.index.char_index > selection.end.index.char_index)
|
||||
}
|
||||
|
||||
selection_length :: proc(buffer: ^FileBuffer, selection: Selection) -> int {
|
||||
|
@ -697,16 +669,12 @@ new_virtual_file_buffer :: proc(allocator: mem.Allocator) -> FileBuffer {
|
|||
allocator = allocator,
|
||||
file_path = "virtual_buffer",
|
||||
|
||||
original_content = slice.clone_to_dynamic([]u8{'\n'}),
|
||||
added_content = make([dynamic]u8, 0, 1024*1024),
|
||||
content_slices = make([dynamic][]u8, 0, 1024*1024),
|
||||
history = make_history(),
|
||||
|
||||
glyphs = make_glyph_buffer(width, height),
|
||||
input_buffer = make([dynamic]u8, 0, 1024),
|
||||
};
|
||||
|
||||
append(&buffer.content_slices, buffer.original_content[:]);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
|
@ -749,21 +717,12 @@ new_file_buffer :: proc(allocator: mem.Allocator, file_path: string, base_dir: s
|
|||
// file_path = fi.fullpath[4:],
|
||||
extension = extension,
|
||||
|
||||
original_content = slice.clone_to_dynamic(original_content),
|
||||
added_content = make([dynamic]u8, 0, 1024*1024),
|
||||
content_slices = make([dynamic][]u8, 0, 1024*1024),
|
||||
history = make_history(original_content),
|
||||
|
||||
glyphs = make_glyph_buffer(width, height),
|
||||
input_buffer = make([dynamic]u8, 0, 1024),
|
||||
};
|
||||
|
||||
if len(buffer.original_content) > 0 {
|
||||
append(&buffer.content_slices, buffer.original_content[:]);
|
||||
} else {
|
||||
append(&buffer.added_content, '\n')
|
||||
append(&buffer.content_slices, buffer.added_content[:])
|
||||
}
|
||||
|
||||
return buffer, error();
|
||||
} else {
|
||||
return FileBuffer{}, error(ErrorType.FileIOError, fmt.aprintf("failed to read from file"));
|
||||
|
@ -775,10 +734,10 @@ save_buffer_to_disk :: proc(state: ^State, buffer: ^FileBuffer) -> (error: os.Er
|
|||
defer os.close(fd);
|
||||
|
||||
offset: i64 = 0
|
||||
for content_slice in buffer.content_slices {
|
||||
os.write(fd, content_slice) or_return
|
||||
for chunk in buffer_piece_table(buffer).chunks {
|
||||
os.write(fd, chunk) or_return
|
||||
|
||||
offset += i64(len(content_slice))
|
||||
offset += i64(len(chunk))
|
||||
}
|
||||
os.flush(fd)
|
||||
|
||||
|
@ -801,11 +760,9 @@ next_buffer :: proc(state: ^State, prev_buffer: ^int) -> int {
|
|||
|
||||
// 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.glyphs.buffer);
|
||||
delete(buffer.input_buffer);
|
||||
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) {
|
||||
|
@ -854,8 +811,8 @@ draw_file_buffer :: proc(state: ^State, buffer: ^FileBuffer, x: int, y: int, sho
|
|||
}
|
||||
|
||||
begin := buffer.top_line;
|
||||
cursor_x := x + padding + buffer.cursor.col * state.source_font_width;
|
||||
cursor_y := y + buffer.cursor.line * state.source_font_height;
|
||||
cursor_x := x + padding + buffer.history.cursor.col * state.source_font_width;
|
||||
cursor_y := y + buffer.history.cursor.line * state.source_font_height;
|
||||
|
||||
cursor_y -= begin * state.source_font_height;
|
||||
|
||||
|
@ -951,7 +908,7 @@ draw_file_buffer :: proc(state: ^State, buffer: ^FileBuffer, x: int, y: int, sho
|
|||
update_file_buffer_scroll :: proc(buffer: ^FileBuffer, cursor: Maybe(^Cursor) = nil) {
|
||||
cursor := cursor;
|
||||
if cursor == nil {
|
||||
cursor = &buffer.cursor;
|
||||
cursor = &buffer.history.cursor;
|
||||
}
|
||||
|
||||
if cursor.?.line > (buffer.top_line + buffer.glyphs.height - 5) {
|
||||
|
@ -960,10 +917,10 @@ update_file_buffer_scroll :: proc(buffer: ^FileBuffer, cursor: Maybe(^Cursor) =
|
|||
buffer.top_line = math.max(cursor.?.line - 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);
|
||||
// if buffer.history.cursor.line > (buffer.top_line + buffer.glyphs.height - 5) {
|
||||
// buffer.top_line = math.max(buffer.history.cursor.line - buffer.glyphs.height + 5, 0);
|
||||
// } else if buffer.history.cursor.line < (buffer.top_line + 5) {
|
||||
// buffer.top_line = math.max(buffer.history.cursor.line - 5, 0);
|
||||
// }
|
||||
}
|
||||
|
||||
|
@ -987,28 +944,9 @@ insert_content :: proc(buffer: ^FileBuffer, to_be_inserted: []u8, append_to_end:
|
|||
return;
|
||||
}
|
||||
|
||||
// TODO: is this even needed? would mean that the cursor isn't always in a valid state.
|
||||
// update_file_buffer_index_from_cursor(buffer);
|
||||
it := new_file_buffer_iter_with_cursor(buffer, buffer.cursor) if !append_to_end else new_file_buffer_iter_with_cursor(buffer, file_buffer_end(buffer));
|
||||
index := buffer.history.cursor.index if !append_to_end else new_piece_table_index_from_end(buffer_piece_table(buffer))
|
||||
|
||||
length := append(&buffer.added_content, ..to_be_inserted);
|
||||
inserted_slice: []u8 = buffer.added_content[len(buffer.added_content)-length:];
|
||||
|
||||
if it.cursor.index.content_index == 0 {
|
||||
// insertion happening in beginning of content slice
|
||||
|
||||
inject_at(&buffer.content_slices, it.cursor.index.slice_index, inserted_slice);
|
||||
}
|
||||
else {
|
||||
// insertion is happening in middle of content slice
|
||||
|
||||
// cut current slice
|
||||
end_slice := buffer.content_slices[it.cursor.index.slice_index][it.cursor.index.content_index:];
|
||||
buffer.content_slices[it.cursor.index.slice_index] = buffer.content_slices[it.cursor.index.slice_index][:it.cursor.index.content_index];
|
||||
|
||||
inject_at(&buffer.content_slices, it.cursor.index.slice_index+1, inserted_slice);
|
||||
inject_at(&buffer.content_slices, it.cursor.index.slice_index+2, end_slice);
|
||||
}
|
||||
insert_text(buffer_piece_table(buffer), to_be_inserted, buffer.history.cursor.index)
|
||||
|
||||
if !append_to_end {
|
||||
update_file_buffer_index_from_cursor(buffer);
|
||||
|
@ -1016,44 +954,6 @@ insert_content :: proc(buffer: ^FileBuffer, to_be_inserted: []u8, append_to_end:
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: potentially add FileBufferIndex as parameter
|
||||
split_content_slice_from_cursor :: proc(buffer: ^FileBuffer, cursor: ^Cursor) -> (did_split: bool) {
|
||||
if cursor.index.content_index == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
end_slice := buffer.content_slices[cursor.index.slice_index][cursor.index.content_index:];
|
||||
buffer.content_slices[cursor.index.slice_index] = buffer.content_slices[cursor.index.slice_index][:cursor.index.content_index];
|
||||
|
||||
inject_at(&buffer.content_slices, cursor.index.slice_index+1, end_slice);
|
||||
|
||||
// TODO: maybe move this out of this function
|
||||
cursor.index.slice_index += 1;
|
||||
cursor.index.content_index = 0;
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
split_content_slice_from_selection :: proc(buffer: ^FileBuffer, selection: ^Selection) {
|
||||
// TODO: swap selections
|
||||
|
||||
log.info("start:", selection.start, "- end:", selection.end);
|
||||
|
||||
// move the end cursor forward one (we want the splitting to be exclusive, not inclusive)
|
||||
it := new_file_buffer_iter_with_cursor(buffer, selection.end);
|
||||
iterate_file_buffer(&it);
|
||||
selection.end = it.cursor;
|
||||
|
||||
split_content_slice_from_cursor(buffer, &selection.end);
|
||||
if split_content_slice_from_cursor(buffer, &selection.start) {
|
||||
selection.end.index.slice_index += 1;
|
||||
}
|
||||
|
||||
log.info("start:", selection.start, "- end:", selection.end);
|
||||
}
|
||||
|
||||
split_content_slice :: proc{split_content_slice_from_cursor, split_content_slice_from_selection};
|
||||
|
||||
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);
|
||||
|
@ -1061,63 +961,22 @@ delete_content_from_buffer_cursor :: proc(buffer: ^FileBuffer, amount: int) {
|
|||
amount := amount - len(buffer.input_buffer);
|
||||
runtime.clear(&buffer.input_buffer);
|
||||
|
||||
if len(buffer.content_slices) < 1 {
|
||||
return;
|
||||
}
|
||||
// Calculate proper line/col values
|
||||
it := new_file_buffer_iter_with_cursor(buffer, buffer.history.cursor);
|
||||
iterate_file_buffer_reverse(&it)
|
||||
|
||||
split_content_slice(buffer, &buffer.cursor);
|
||||
delete_text(buffer_piece_table(buffer), &buffer.history.cursor.index)
|
||||
|
||||
it := new_file_buffer_iter_with_cursor(buffer, buffer.cursor);
|
||||
|
||||
// go back one (to be at the end of the content slice)
|
||||
iterate_file_buffer_reverse(&it);
|
||||
|
||||
for i in 0..<amount {
|
||||
content_slice_ptr := &buffer.content_slices[it.cursor.index.slice_index];
|
||||
content_slice_len := len(content_slice_ptr^);
|
||||
|
||||
if content_slice_len == 1 {
|
||||
// move cursor to previous content_slice so we can delete the current one
|
||||
iterate_file_buffer_reverse(&it);
|
||||
|
||||
if it.hit_end {
|
||||
runtime.ordered_remove(&buffer.content_slices, it.cursor.index.slice_index);
|
||||
} else {
|
||||
runtime.ordered_remove(&buffer.content_slices, it.cursor.index.slice_index+1);
|
||||
}
|
||||
} else if !it.hit_end {
|
||||
iterate_file_buffer_reverse(&it);
|
||||
content_slice_ptr^ = content_slice_ptr^[:len(content_slice_ptr^)-1];
|
||||
}
|
||||
}
|
||||
|
||||
if !it.hit_end {
|
||||
iterate_file_buffer(&it);
|
||||
}
|
||||
buffer.cursor = it.cursor;
|
||||
buffer.history.cursor.line = it.cursor.line
|
||||
buffer.history.cursor.col = it.cursor.col
|
||||
}
|
||||
}
|
||||
|
||||
delete_content_from_selection :: proc(buffer: ^FileBuffer, selection: ^Selection) {
|
||||
assert(len(buffer.content_slices) >= 1);
|
||||
|
||||
selection^ = swap_selections(selection^)
|
||||
delete_text_in_span(buffer_piece_table(buffer), &selection.start.index, &selection.end.index)
|
||||
|
||||
split_content_slice(buffer, selection);
|
||||
|
||||
it := new_file_buffer_iter_with_cursor(buffer, selection.start);
|
||||
|
||||
// go back one (to be at the end of the content slice)
|
||||
iterate_file_buffer_reverse(&it);
|
||||
|
||||
for _ in selection.start.index.slice_index..<selection.end.index.slice_index {
|
||||
runtime.ordered_remove(&buffer.content_slices, selection.start.index.slice_index);
|
||||
}
|
||||
|
||||
if !it.hit_end {
|
||||
iterate_file_buffer(&it);
|
||||
}
|
||||
buffer.cursor = it.cursor;
|
||||
buffer.history.cursor.index = selection.start.index
|
||||
}
|
||||
|
||||
delete_content :: proc{delete_content_from_buffer_cursor, delete_content_from_selection};
|
||||
|
|
|
@ -43,7 +43,7 @@ update_glyph_buffer_from_file_buffer :: proc(buffer: ^FileBuffer) {
|
|||
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) {
|
||||
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;
|
||||
|
||||
|
|
|
@ -0,0 +1,135 @@
|
|||
package core
|
||||
|
||||
import "core:log"
|
||||
|
||||
FileHistory :: struct {
|
||||
piece_table: PieceTable,
|
||||
cursor: Cursor,
|
||||
|
||||
snapshots: []Snapshot,
|
||||
next: int,
|
||||
first: int
|
||||
}
|
||||
|
||||
Snapshot :: struct {
|
||||
chunks: [dynamic][]u8,
|
||||
cursor: Cursor,
|
||||
}
|
||||
|
||||
make_history_with_data :: proc(initial_data: []u8, starting_capacity: int = 1024, allocator := context.allocator) -> FileHistory {
|
||||
context.allocator = allocator
|
||||
|
||||
return FileHistory {
|
||||
piece_table = make_piece_table(initial_data, starting_capacity = starting_capacity),
|
||||
snapshots = make([]Snapshot, starting_capacity),
|
||||
next = 0,
|
||||
first = 0,
|
||||
}
|
||||
}
|
||||
|
||||
make_history_empty :: proc(starting_capacity: int = 1024, allocator := context.allocator) -> FileHistory {
|
||||
context.allocator = allocator
|
||||
|
||||
return FileHistory {
|
||||
piece_table = make_piece_table(starting_capacity = starting_capacity),
|
||||
snapshots = make([]Snapshot, starting_capacity),
|
||||
next = 0,
|
||||
first = 0,
|
||||
}
|
||||
}
|
||||
|
||||
make_history :: proc{make_history_with_data, make_history_empty}
|
||||
|
||||
free_history :: proc(history: ^FileHistory) {
|
||||
for snapshot in &history.snapshots {
|
||||
if snapshot.chunks != nil {
|
||||
delete(snapshot.chunks);
|
||||
}
|
||||
}
|
||||
delete(history.snapshots)
|
||||
|
||||
delete(history.piece_table.original_content)
|
||||
delete(history.piece_table.added_content)
|
||||
delete(history.piece_table.chunks)
|
||||
}
|
||||
|
||||
push_new_snapshot :: proc(history: ^FileHistory) {
|
||||
if history.snapshots[history.next].chunks != nil {
|
||||
delete(history.snapshots[history.next].chunks)
|
||||
}
|
||||
|
||||
history.snapshots[history.next].chunks = clone_chunk(history.piece_table.chunks)
|
||||
history.snapshots[history.next].cursor = history.cursor
|
||||
|
||||
history.next, history.first = next_indexes(history)
|
||||
}
|
||||
|
||||
pop_snapshot :: proc(history: ^FileHistory, make_new_snapshot: bool = false) {
|
||||
new_next, _ := next_indexes(history, backward = true)
|
||||
if new_next == history.next do return
|
||||
|
||||
if make_new_snapshot {
|
||||
push_new_snapshot(history)
|
||||
}
|
||||
|
||||
history.next = new_next
|
||||
|
||||
delete(history.piece_table.chunks)
|
||||
|
||||
history.piece_table.chunks = clone_chunk(history.snapshots[history.next].chunks)
|
||||
history.cursor = history.snapshots[history.next].cursor
|
||||
}
|
||||
|
||||
recover_snapshot :: proc(history: ^FileHistory) {
|
||||
new_next, _ := next_indexes(history)
|
||||
if history.snapshots[new_next].chunks == nil do return
|
||||
history.next = new_next
|
||||
|
||||
delete(history.piece_table.chunks)
|
||||
|
||||
history.piece_table.chunks = clone_chunk(history.snapshots[history.next].chunks)
|
||||
history.cursor = history.snapshots[history.next].cursor
|
||||
}
|
||||
|
||||
clone_chunk :: proc(chunks: [dynamic][]u8) -> [dynamic][]u8 {
|
||||
new_chunks := make([dynamic][]u8, len(chunks), len(chunks))
|
||||
|
||||
for ptr, i in chunks {
|
||||
new_chunks[i] = ptr
|
||||
}
|
||||
|
||||
return new_chunks
|
||||
}
|
||||
|
||||
next_indexes :: proc(history: ^FileHistory, backward: bool = false) -> (next: int, first: int) {
|
||||
next = history.next
|
||||
first = history.first
|
||||
|
||||
if backward {
|
||||
if history.next == history.first {
|
||||
return
|
||||
}
|
||||
|
||||
next = history.next - 1
|
||||
|
||||
if next < 0 {
|
||||
next = len(history.snapshots) - 1
|
||||
}
|
||||
} else {
|
||||
next = history.next + 1
|
||||
|
||||
if next >= len(history.snapshots) {
|
||||
next = 0
|
||||
}
|
||||
|
||||
if next == first {
|
||||
first += 1
|
||||
}
|
||||
|
||||
if first >= len(history.snapshots) {
|
||||
first = 0
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
|
@ -0,0 +1,241 @@
|
|||
package core
|
||||
|
||||
PieceTable :: struct {
|
||||
original_content: []u8,
|
||||
added_content: [dynamic]u8,
|
||||
chunks: [dynamic][]u8,
|
||||
}
|
||||
|
||||
PieceTableIter :: struct {
|
||||
t: ^PieceTable,
|
||||
index: PieceTableIndex,
|
||||
hit_end: bool,
|
||||
}
|
||||
|
||||
PieceTableIndex :: struct {
|
||||
chunk_index: int,
|
||||
char_index: int,
|
||||
}
|
||||
|
||||
make_empty_piece_table :: proc(starting_capacity: int = 1024*1024, allocator := context.allocator) -> PieceTable {
|
||||
context.allocator = allocator
|
||||
|
||||
original_content := transmute([]u8)string("\n")
|
||||
chunks := make([dynamic][]u8, 0, starting_capacity)
|
||||
|
||||
append(&chunks, original_content[:])
|
||||
|
||||
return PieceTable {
|
||||
original_content = original_content,
|
||||
added_content = make([dynamic]u8, 0, starting_capacity),
|
||||
chunks = chunks,
|
||||
}
|
||||
}
|
||||
|
||||
make_piece_table_from_bytes :: proc(data: []u8, starting_capacity: int = 1024*1024, allocator := context.allocator) -> PieceTable {
|
||||
context.allocator = allocator
|
||||
|
||||
added_content := make([dynamic]u8, 0, starting_capacity)
|
||||
chunks := make([dynamic][]u8, 0, starting_capacity)
|
||||
|
||||
if len(data) > 0 {
|
||||
append(&chunks, data[:])
|
||||
} else {
|
||||
append(&added_content, '\n')
|
||||
append(&chunks, added_content[:])
|
||||
}
|
||||
|
||||
return PieceTable {
|
||||
original_content = data,
|
||||
added_content = added_content,
|
||||
chunks = chunks,
|
||||
}
|
||||
}
|
||||
|
||||
make_piece_table :: proc{make_empty_piece_table, make_piece_table_from_bytes}
|
||||
|
||||
new_piece_table_iter :: proc(t: ^PieceTable) -> PieceTableIter {
|
||||
return PieceTableIter {
|
||||
t = t,
|
||||
index = PieceTableIndex {
|
||||
chunk_index = 0,
|
||||
char_index = 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
new_piece_table_iter_from_index :: proc(t: ^PieceTable, index: PieceTableIndex) -> PieceTableIter {
|
||||
return PieceTableIter {
|
||||
t = t,
|
||||
index = index
|
||||
}
|
||||
}
|
||||
|
||||
new_piece_table_index_from_end :: proc(t: ^PieceTable) -> PieceTableIndex {
|
||||
chunk_index := len(t.chunks)-1
|
||||
char_index := len(t.chunks[chunk_index])-1
|
||||
|
||||
return PieceTableIndex {
|
||||
chunk_index = chunk_index,
|
||||
char_index = char_index,
|
||||
}
|
||||
}
|
||||
|
||||
iterate_piece_table_iter :: proc(it: ^PieceTableIter) -> (character: u8, index: PieceTableIndex, cond: bool) {
|
||||
if it.index.chunk_index >= len(it.t.chunks) || it.index.char_index >= len(it.t.chunks[it.index.chunk_index]) {
|
||||
return
|
||||
}
|
||||
|
||||
character = it.t.chunks[it.index.chunk_index][it.index.char_index]
|
||||
if it.hit_end {
|
||||
return character, it.index, false
|
||||
}
|
||||
|
||||
if it.index.char_index < len(it.t.chunks[it.index.chunk_index])-1 {
|
||||
it.index.char_index += 1
|
||||
} else if it.index.chunk_index < len(it.t.chunks)-1 {
|
||||
it.index.char_index = 0
|
||||
it.index.chunk_index += 1
|
||||
} else {
|
||||
it.hit_end = true
|
||||
}
|
||||
|
||||
return character, it.index, true
|
||||
}
|
||||
|
||||
iterate_piece_table_iter_reverse :: proc(it: ^PieceTableIter) -> (character: u8, index: PieceTableIndex, cond: bool) {
|
||||
if it.index.chunk_index >= len(it.t.chunks) || it.index.char_index >= len(it.t.chunks[it.index.chunk_index]) {
|
||||
return
|
||||
}
|
||||
|
||||
character = it.t.chunks[it.index.chunk_index][it.index.char_index]
|
||||
if it.hit_end {
|
||||
return character, it.index, false
|
||||
}
|
||||
|
||||
if it.index.char_index > 0 {
|
||||
it.index.char_index -= 1
|
||||
} else if it.index.chunk_index > 0 {
|
||||
it.index.chunk_index -= 1
|
||||
it.index.char_index = len(it.t.chunks[it.index.chunk_index])-1
|
||||
} else {
|
||||
it.hit_end = true
|
||||
}
|
||||
|
||||
return character, it.index, true
|
||||
}
|
||||
|
||||
get_character_at_piece_table_index :: proc(t: ^PieceTable, index: PieceTableIndex) -> u8 {
|
||||
return t.chunks[index.chunk_index][index.char_index]
|
||||
}
|
||||
|
||||
insert_text :: proc(t: ^PieceTable, to_be_inserted: []u8, index: PieceTableIndex) {
|
||||
length := append(&t.added_content, ..to_be_inserted);
|
||||
inserted_slice: []u8 = t.added_content[len(t.added_content)-length:];
|
||||
|
||||
if index.char_index == 0 {
|
||||
// insertion happening in beginning of content slice
|
||||
|
||||
inject_at(&t.chunks, index.chunk_index, inserted_slice);
|
||||
}
|
||||
else {
|
||||
// insertion is happening in middle of content slice
|
||||
|
||||
// cut current slice
|
||||
end_slice := t.chunks[index.chunk_index][index.char_index:];
|
||||
t.chunks[index.chunk_index] = t.chunks[index.chunk_index][:index.char_index];
|
||||
|
||||
inject_at(&t.chunks, index.chunk_index+1, inserted_slice);
|
||||
inject_at(&t.chunks, index.chunk_index+2, end_slice);
|
||||
}
|
||||
}
|
||||
|
||||
delete_text :: proc(t: ^PieceTable, index: ^PieceTableIndex) {
|
||||
if len(t.chunks) < 1 {
|
||||
return;
|
||||
}
|
||||
|
||||
split_from_index(t, index);
|
||||
|
||||
it := new_piece_table_iter_from_index(t, index^);
|
||||
|
||||
// go back one (to be at the end of the chunk)
|
||||
iterate_piece_table_iter_reverse(&it);
|
||||
|
||||
chunk_ptr := &t.chunks[it.index.chunk_index];
|
||||
chunk_len := len(chunk_ptr^);
|
||||
|
||||
if chunk_len == 1 {
|
||||
// move cursor to previous chunk so we can delete the current one
|
||||
iterate_piece_table_iter_reverse(&it);
|
||||
|
||||
if it.hit_end {
|
||||
if len(t.chunks) > 1 {
|
||||
ordered_remove(&t.chunks, it.index.chunk_index);
|
||||
}
|
||||
} else {
|
||||
ordered_remove(&t.chunks, it.index.chunk_index+1);
|
||||
}
|
||||
} else if !it.hit_end {
|
||||
iterate_piece_table_iter_reverse(&it);
|
||||
chunk_ptr^ = chunk_ptr^[:len(chunk_ptr^)-1];
|
||||
}
|
||||
|
||||
|
||||
if !it.hit_end {
|
||||
iterate_piece_table_iter(&it);
|
||||
}
|
||||
|
||||
index^ = it.index
|
||||
}
|
||||
|
||||
// Assumes end >= start
|
||||
delete_text_in_span :: proc(t: ^PieceTable, start: ^PieceTableIndex, end: ^PieceTableIndex) {
|
||||
assert(len(t.chunks) >= 1);
|
||||
|
||||
split_from_span(t, start, end);
|
||||
|
||||
it := new_piece_table_iter_from_index(t, start^);
|
||||
|
||||
// go back one (to be at the end of the content slice)
|
||||
iterate_piece_table_iter_reverse(&it);
|
||||
|
||||
for _ in start.chunk_index..<end.chunk_index {
|
||||
ordered_remove(&t.chunks, start.chunk_index);
|
||||
}
|
||||
|
||||
if !it.hit_end {
|
||||
iterate_piece_table_iter(&it);
|
||||
}
|
||||
|
||||
start^ = it.index
|
||||
end^ = it.index
|
||||
}
|
||||
|
||||
split_from_index :: proc(t: ^PieceTable, index: ^PieceTableIndex) -> (did_split: bool) {
|
||||
if index.char_index == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
end_slice := t.chunks[index.chunk_index][index.char_index:];
|
||||
t.chunks[index.chunk_index] = t.chunks[index.chunk_index][:index.char_index];
|
||||
|
||||
inject_at(&t.chunks, index.chunk_index+1, end_slice);
|
||||
|
||||
index.chunk_index += 1;
|
||||
index.char_index = 0;
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
split_from_span :: proc(t: ^PieceTable, start: ^PieceTableIndex, end: ^PieceTableIndex) {
|
||||
// move the end cursor forward one (we want the splitting to be exclusive, not inclusive)
|
||||
it := new_piece_table_iter_from_index(t, end^);
|
||||
iterate_piece_table_iter(&it);
|
||||
end^ = it.index;
|
||||
|
||||
split_from_index(t, end);
|
||||
if split_from_index(t, start) {
|
||||
end.chunk_index += 1;
|
||||
}
|
||||
}
|
|
@ -89,7 +89,7 @@ register_default_input_actions :: proc(input_map: ^core.InputActions) {
|
|||
state.mode = .Visual;
|
||||
core.reset_input_map(state)
|
||||
|
||||
core.current_buffer(state).selection = core.new_selection(core.current_buffer(state).cursor);
|
||||
core.current_buffer(state).selection = core.new_selection(core.current_buffer(state).history.cursor);
|
||||
}, "enter visual mode");
|
||||
|
||||
}
|
||||
|
@ -158,6 +158,8 @@ register_default_visual_actions :: proc(input_map: ^core.InputActions) {
|
|||
// Text Modification
|
||||
{
|
||||
core.register_key_action(input_map, .D, proc(state: ^State) {
|
||||
core.push_new_snapshot(&core.current_buffer(state).history)
|
||||
|
||||
sel_cur := &(core.current_buffer(state).selection.?);
|
||||
|
||||
core.delete_content(core.current_buffer(state), sel_cur);
|
||||
|
@ -169,6 +171,8 @@ register_default_visual_actions :: proc(input_map: ^core.InputActions) {
|
|||
}, "delete selection");
|
||||
|
||||
core.register_key_action(input_map, .C, proc(state: ^State) {
|
||||
core.push_new_snapshot(&core.current_buffer(state).history)
|
||||
|
||||
sel_cur := &(core.current_buffer(state).selection.?);
|
||||
|
||||
core.delete_content(core.current_buffer(state), sel_cur);
|
||||
|
@ -194,6 +198,8 @@ register_default_visual_actions :: proc(input_map: ^core.InputActions) {
|
|||
}, "Yank Line");
|
||||
|
||||
core.register_key_action(input_map, .P, proc(state: ^State) {
|
||||
core.push_new_snapshot(&core.current_buffer(state).history)
|
||||
|
||||
if state.yank_register.whole_line {
|
||||
core.insert_content(core.current_buffer(state), []u8{'\n'});
|
||||
core.paste_register(state, state.yank_register)
|
||||
|
@ -209,18 +215,32 @@ register_default_visual_actions :: proc(input_map: ^core.InputActions) {
|
|||
|
||||
register_default_text_input_actions :: proc(input_map: ^core.InputActions) {
|
||||
core.register_key_action(input_map, .I, proc(state: ^State) {
|
||||
core.push_new_snapshot(&core.current_buffer(state).history)
|
||||
|
||||
state.mode = .Insert;
|
||||
sdl2.StartTextInput();
|
||||
}, "enter insert mode");
|
||||
core.register_key_action(input_map, .A, proc(state: ^State) {
|
||||
core.push_new_snapshot(&core.current_buffer(state).history)
|
||||
|
||||
core.move_cursor_right(core.current_buffer(state), false);
|
||||
state.mode = .Insert;
|
||||
sdl2.StartTextInput();
|
||||
}, "enter insert mode after character (append)");
|
||||
|
||||
core.register_key_action(input_map, .U, proc(state: ^State) {
|
||||
core.pop_snapshot(&core.current_buffer(state).history, true)
|
||||
}, "Undo");
|
||||
|
||||
core.register_ctrl_key_action(input_map, .R, proc(state: ^State) {
|
||||
core.recover_snapshot(&core.current_buffer(state).history)
|
||||
}, "Redo");
|
||||
|
||||
// TODO: add shift+o to insert newline above current one
|
||||
|
||||
core.register_key_action(input_map, .O, proc(state: ^State) {
|
||||
core.push_new_snapshot(&core.current_buffer(state).history)
|
||||
|
||||
if buffer := core.current_buffer(state); buffer != nil {
|
||||
core.move_cursor_end_of_line(buffer, false);
|
||||
runtime.clear(&buffer.input_buffer)
|
||||
|
@ -247,13 +267,17 @@ register_default_text_input_actions :: proc(input_map: ^core.InputActions) {
|
|||
}
|
||||
|
||||
core.register_key_action(input_map, .P, proc(state: ^State) {
|
||||
core.push_new_snapshot(&core.current_buffer(state).history)
|
||||
|
||||
if state.yank_register.whole_line {
|
||||
core.move_cursor_end_of_line(core.current_buffer(state), false);
|
||||
core.insert_content(core.current_buffer(state), []u8{'\n'});
|
||||
core.move_cursor_right(core.current_buffer(state), false);
|
||||
} else {
|
||||
core.move_cursor_right(core.current_buffer(state))
|
||||
}
|
||||
core.paste_register(state, state.yank_register)
|
||||
core.move_cursor_start_of_line(core.current_buffer(state))
|
||||
|
||||
core.reset_input_map(state)
|
||||
}, "Paste");
|
||||
|
|
|
@ -389,6 +389,8 @@ main :: proc() {
|
|||
|
||||
sdl2.AddEventWatch(expose_event_watcher, &state);
|
||||
|
||||
core.push_new_snapshot(&core.current_buffer(&state).history)
|
||||
|
||||
control_key_pressed: bool;
|
||||
for !state.should_close {
|
||||
{
|
||||
|
|
|
@ -135,10 +135,10 @@ open_file_buffer_in_new_panel :: proc(state: ^core.State, file_path: string, lin
|
|||
return;
|
||||
}
|
||||
|
||||
buffer.cursor.line = line
|
||||
buffer.cursor.col = col
|
||||
buffer.history.cursor.line = line
|
||||
buffer.history.cursor.col = col
|
||||
buffer.top_line = buffer.history.cursor.line
|
||||
core.update_file_buffer_index_from_cursor(&buffer)
|
||||
core.update_file_buffer_scroll(&buffer)
|
||||
|
||||
buffer_index = len(state.buffers)
|
||||
runtime.append(&state.buffers, buffer);
|
||||
|
@ -183,15 +183,15 @@ render_file_buffer :: proc(state: ^core.State, s: ^ui.State, buffer: ^core.FileB
|
|||
ui.open_element(s, nil, { kind = {ui.Grow{}, ui.Grow{}}})
|
||||
ui.close_element(s)
|
||||
|
||||
it := core.new_file_buffer_iter_with_cursor(buffer, buffer.cursor)
|
||||
it := core.new_file_buffer_iter_with_cursor(buffer, buffer.history.cursor)
|
||||
ui.open_element(
|
||||
s,
|
||||
fmt.tprintf(
|
||||
"%v:%v - Slice %v:%v - Char: %v",
|
||||
buffer.cursor.line + 1,
|
||||
buffer.cursor.col + 1,
|
||||
buffer.cursor.index.slice_index,
|
||||
buffer.cursor.index.content_index,
|
||||
buffer.history.cursor.line + 1,
|
||||
buffer.history.cursor.col + 1,
|
||||
buffer.history.cursor.index.chunk_index,
|
||||
buffer.history.cursor.index.char_index,
|
||||
core.get_character_at_iter(it)
|
||||
),
|
||||
{}
|
||||
|
@ -301,11 +301,13 @@ make_grep_panel :: proc(state: ^core.State) -> core.Panel {
|
|||
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,
|
||||
)
|
||||
if len(panel_state.query_results) > 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) {
|
||||
|
@ -417,12 +419,19 @@ make_grep_panel :: proc(state: ^core.State) -> core.Panel {
|
|||
{
|
||||
if panel_state.query_results != nil {
|
||||
// query results
|
||||
ui.open_element(s, nil, {
|
||||
query_result_container := ui.open_element(s, nil, {
|
||||
dir = .TopToBottom,
|
||||
kind = {ui.Grow{}, ui.Grow{}}
|
||||
})
|
||||
{
|
||||
container_height := query_result_container.layout.size.y
|
||||
max_results := container_height / 16
|
||||
|
||||
for result, i in panel_state.query_results {
|
||||
if i > max_results {
|
||||
break
|
||||
}
|
||||
|
||||
ui.open_element(s, nil, {
|
||||
dir = .LeftToRight,
|
||||
kind = {ui.Fit{}, ui.Fit{}},
|
||||
|
@ -448,6 +457,12 @@ make_grep_panel :: proc(state: ^core.State) -> core.Panel {
|
|||
|
||||
// file contents
|
||||
selected_result := &panel_state.query_results[panel_state.selected_result]
|
||||
|
||||
core.update_glyph_buffer_from_bytes(
|
||||
&panel_state.glyphs,
|
||||
transmute([]u8)selected_result.file_context,
|
||||
selected_result.line,
|
||||
)
|
||||
render_glyph_buffer(state, s, &panel_state.glyphs)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,17 +1,12 @@
|
|||
use std::{
|
||||
error::Error,
|
||||
ffi::{CStr, CString, OsString},
|
||||
path::Path,
|
||||
str::FromStr,
|
||||
sync::mpsc::{Receiver, Sender},
|
||||
thread,
|
||||
ffi::CStr,
|
||||
};
|
||||
|
||||
use grep::{
|
||||
regex::RegexMatcherBuilder,
|
||||
searcher::{BinaryDetection, SearcherBuilder, Sink, SinkError},
|
||||
};
|
||||
use std::sync::mpsc::channel;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
|
@ -32,15 +32,15 @@ buffer_to_string :: proc(buffer: ^core.FileBuffer) -> string {
|
|||
}
|
||||
|
||||
length := 0
|
||||
for content_slice in buffer.content_slices {
|
||||
length += len(content_slice)
|
||||
for chunk in core.buffer_piece_table(buffer).chunks {
|
||||
length += len(chunk)
|
||||
}
|
||||
|
||||
buffer_contents := make([]u8, length)
|
||||
|
||||
offset := 0
|
||||
for content_slice in buffer.content_slices {
|
||||
for c in content_slice {
|
||||
for chunk in core.buffer_piece_table(buffer).chunks {
|
||||
for c in chunk {
|
||||
buffer_contents[offset] = c
|
||||
offset += 1
|
||||
}
|
||||
|
@ -126,9 +126,9 @@ expect_line_col :: proc(t: ^testing.T, cursor: core.Cursor, line, col: int) {
|
|||
testing.expect_value(t, cursor.col, col)
|
||||
}
|
||||
|
||||
expect_cursor_index :: proc(t: ^testing.T, cursor: core.Cursor, slice_index, content_index: int) {
|
||||
testing.expect_value(t, cursor.index.slice_index, slice_index)
|
||||
testing.expect_value(t, cursor.index.content_index, content_index)
|
||||
expect_cursor_index :: proc(t: ^testing.T, cursor: core.Cursor, chunk_index, char_index: int) {
|
||||
testing.expect_value(t, cursor.index.chunk_index, chunk_index)
|
||||
testing.expect_value(t, cursor.index.char_index, char_index)
|
||||
}
|
||||
|
||||
@(test)
|
||||
|
@ -142,8 +142,8 @@ insert_from_empty_no_newlines :: proc(t: ^testing.T) {
|
|||
expected_text := fmt.aprintf("%v\n", inputted_text)
|
||||
run_text_insertion(&e, inputted_text)
|
||||
|
||||
expect_line_col(t, buffer.cursor, 0, 12)
|
||||
expect_cursor_index(t, buffer.cursor, 0, 12)
|
||||
expect_line_col(t, buffer.history.cursor, 0, 12)
|
||||
expect_cursor_index(t, buffer.history.cursor, 0, 12)
|
||||
|
||||
contents := buffer_to_string(core.current_buffer(&e))
|
||||
testing.expectf(t, contents == expected_text, "got '%v', expected '%v'", contents, expected_text)
|
||||
|
@ -160,8 +160,8 @@ insert_from_empty_with_newline :: proc(t: ^testing.T) {
|
|||
expected_text := fmt.aprintf("%v\n", inputted_text)
|
||||
run_text_insertion(&e, inputted_text)
|
||||
|
||||
expect_line_col(t, buffer.cursor, 1, 17)
|
||||
expect_cursor_index(t, buffer.cursor, 0, 31)
|
||||
expect_line_col(t, buffer.history.cursor, 1, 17)
|
||||
expect_cursor_index(t, buffer.history.cursor, 0, 31)
|
||||
|
||||
contents := buffer_to_string(core.current_buffer(&e))
|
||||
testing.expectf(t, contents == expected_text, "got '%v', expected '%v'", contents, expected_text)
|
||||
|
@ -184,8 +184,8 @@ insert_in_between_text :: proc(t: ^testing.T) {
|
|||
|
||||
run_text_insertion(&e, " beautiful")
|
||||
|
||||
expect_line_col(t, buffer.cursor, 0, 15)
|
||||
expect_cursor_index(t, buffer.cursor, 1, 9)
|
||||
expect_line_col(t, buffer.history.cursor, 0, 15)
|
||||
expect_cursor_index(t, buffer.history.cursor, 1, 9)
|
||||
|
||||
contents := buffer_to_string(core.current_buffer(&e))
|
||||
testing.expectf(t, contents == expected_text, "got '%v', expected '%v'", contents, expected_text)
|
||||
|
@ -211,8 +211,8 @@ insert_before_slice_at_beginning_of_file :: proc(t: ^testing.T) {
|
|||
run_inputs(&e, []ArtificialInput{ press_key(.G), press_key(.H)})
|
||||
run_text_insertion(&e, "Well, ")
|
||||
|
||||
expect_line_col(t, buffer.cursor, 0, 5)
|
||||
expect_cursor_index(t, buffer.cursor, 0, 5)
|
||||
expect_line_col(t, buffer.history.cursor, 0, 5)
|
||||
expect_cursor_index(t, buffer.history.cursor, 0, 5)
|
||||
|
||||
contents := buffer_to_string(core.current_buffer(&e))
|
||||
testing.expectf(t, contents == expected_text, "got '%v', expected '%v'", contents, expected_text)
|
||||
|
@ -239,13 +239,46 @@ insert_before_slice :: proc(t: ^testing.T) {
|
|||
|
||||
run_text_insertion(&e, " rich")
|
||||
|
||||
expect_line_col(t, buffer.cursor, 0, 20)
|
||||
expect_cursor_index(t, buffer.cursor, 2, 4)
|
||||
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))
|
||||
testing.expectf(t, contents == expected_text, "got '%v', expected '%v'", contents, expected_text)
|
||||
}
|
||||
|
||||
@(test)
|
||||
delete_last_content_slice_beginning_of_file :: proc(t: ^testing.T) {
|
||||
e := new_test_editor()
|
||||
setup_empty_buffer(&e)
|
||||
|
||||
buffer := &e.buffers[0]
|
||||
|
||||
run_text_insertion(&e, "Hello, world!")
|
||||
|
||||
// Delete just the text
|
||||
run_input_multiple(&e, press_key(.I), 1)
|
||||
run_input_multiple(&e, press_key(.BACKSPACE), 13)
|
||||
|
||||
expect_line_col(t, buffer.history.cursor, 0, 0)
|
||||
expect_cursor_index(t, buffer.history.cursor, 0, 0)
|
||||
|
||||
// Try to delete when there is no text
|
||||
run_input_multiple(&e, press_key(.BACKSPACE), 1)
|
||||
|
||||
expect_line_col(t, buffer.history.cursor, 0, 0)
|
||||
expect_cursor_index(t, buffer.history.cursor, 0, 0)
|
||||
testing.expect(t, len(core.buffer_piece_table(buffer).chunks) > 0, "BACKSPACE deleted final content slice in buffer")
|
||||
|
||||
// "commit" insert mode changes, then re-enter insert mode and try to delete again
|
||||
run_input_multiple(&e, press_key(.ESCAPE), 1)
|
||||
run_input_multiple(&e, press_key(.I), 1)
|
||||
run_input_multiple(&e, press_key(.BACKSPACE), 1)
|
||||
|
||||
expect_line_col(t, buffer.history.cursor, 0, 0)
|
||||
expect_cursor_index(t, buffer.history.cursor, 0, 0)
|
||||
testing.expect(t, len(core.buffer_piece_table(buffer).chunks) > 0, "BACKSPACE deleted final content slice in buffer")
|
||||
}
|
||||
|
||||
@(test)
|
||||
delete_in_slice :: proc(t: ^testing.T) {
|
||||
e := new_test_editor()
|
||||
|
@ -273,8 +306,8 @@ 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.cursor, 0, 17)
|
||||
expect_cursor_index(t, buffer.cursor, 3, 0)
|
||||
expect_line_col(t, buffer.history.cursor, 0, 17)
|
||||
expect_cursor_index(t, buffer.history.cursor, 3, 0)
|
||||
|
||||
contents := buffer_to_string(core.current_buffer(&e))
|
||||
testing.expectf(t, contents == expected_text, "got '%v', expected '%v'", contents, expected_text)
|
||||
|
@ -315,8 +348,8 @@ delete_across_slices :: proc(t: ^testing.T) {
|
|||
run_input_multiple(&e, press_key(.BACKSPACE), 2)
|
||||
run_input_multiple(&e, press_key(.ESCAPE), 1)
|
||||
|
||||
expect_line_col(t, buffer.cursor, 0, 16)
|
||||
expect_cursor_index(t, buffer.cursor, 2, 0)
|
||||
expect_line_col(t, buffer.history.cursor, 0, 16)
|
||||
expect_cursor_index(t, buffer.history.cursor, 2, 0)
|
||||
|
||||
contents := buffer_to_string(core.current_buffer(&e))
|
||||
testing.expectf(t, contents == expected_text, "got '%v', expected '%v'", contents, expected_text)
|
||||
|
@ -342,8 +375,8 @@ move_down_next_line_has_shorter_length :: proc(t: ^testing.T) {
|
|||
// 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)
|
||||
expect_line_col(t, buffer.history.cursor, 1, 0)
|
||||
expect_cursor_index(t, buffer.history.cursor, 0, 10)
|
||||
}
|
||||
|
||||
@(test)
|
||||
|
@ -361,8 +394,8 @@ move_down_on_last_line :: proc(t: ^testing.T) {
|
|||
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)
|
||||
expect_line_col(t, buffer.history.cursor, 0, 8)
|
||||
expect_cursor_index(t, buffer.history.cursor, 0, 8)
|
||||
}
|
||||
|
||||
@(test)
|
||||
|
@ -377,15 +410,15 @@ move_left_at_beginning_of_file :: proc(t: ^testing.T) {
|
|||
// to ------------------^
|
||||
run_input_multiple(&e, press_key(.H), 4)
|
||||
|
||||
expect_line_col(t, buffer.cursor, 0, 0)
|
||||
expect_cursor_index(t, buffer.cursor, 0, 0)
|
||||
expect_line_col(t, buffer.history.cursor, 0, 0)
|
||||
expect_cursor_index(t, buffer.history.cursor, 0, 0)
|
||||
|
||||
// Try to move before the beginning of the file
|
||||
run_input_multiple(&e, press_key(.H), 1)
|
||||
|
||||
// Should stay the same
|
||||
expect_line_col(t, buffer.cursor, 0, 0)
|
||||
expect_cursor_index(t, buffer.cursor, 0, 0)
|
||||
expect_line_col(t, buffer.history.cursor, 0, 0)
|
||||
expect_cursor_index(t, buffer.history.cursor, 0, 0)
|
||||
}
|
||||
|
||||
@(test)
|
||||
|
@ -399,15 +432,15 @@ move_right_at_end_of_file :: proc(t: ^testing.T) {
|
|||
|
||||
run_text_insertion(&e, "01234")
|
||||
|
||||
expect_line_col(t, buffer.cursor, 0, 4)
|
||||
expect_cursor_index(t, buffer.cursor, 0, 4)
|
||||
expect_line_col(t, buffer.history.cursor, 0, 4)
|
||||
expect_cursor_index(t, buffer.history.cursor, 0, 4)
|
||||
|
||||
// Try to move after the end of the file
|
||||
run_input_multiple(&e, press_key(.L), 1)
|
||||
|
||||
// Should stay the same
|
||||
expect_line_col(t, buffer.cursor, 0, 4)
|
||||
expect_cursor_index(t, buffer.cursor, 0, 4)
|
||||
expect_line_col(t, buffer.history.cursor, 0, 4)
|
||||
expect_cursor_index(t, buffer.history.cursor, 0, 4)
|
||||
}
|
||||
|
||||
@(test)
|
||||
|
@ -427,8 +460,8 @@ move_to_end_of_line_from_end :: proc(t: ^testing.T) {
|
|||
// Move to the end of the line
|
||||
run_inputs(&e, []ArtificialInput{ press_key(.G), press_key(.L)})
|
||||
|
||||
expect_line_col(t, buffer.cursor, 0, 4)
|
||||
expect_cursor_index(t, buffer.cursor, 0, 4)
|
||||
expect_line_col(t, buffer.history.cursor, 0, 4)
|
||||
expect_cursor_index(t, buffer.history.cursor, 0, 4)
|
||||
}
|
||||
|
||||
@(test)
|
||||
|
@ -451,8 +484,8 @@ move_to_end_of_line_from_middle :: proc(t: ^testing.T) {
|
|||
// Move to the end of the line
|
||||
run_inputs(&e, []ArtificialInput{ press_key(.G), press_key(.L)})
|
||||
|
||||
expect_line_col(t, buffer.cursor, 0, 4)
|
||||
expect_cursor_index(t, buffer.cursor, 0, 4)
|
||||
expect_line_col(t, buffer.history.cursor, 0, 4)
|
||||
expect_cursor_index(t, buffer.history.cursor, 0, 4)
|
||||
}
|
||||
|
||||
@(test)
|
||||
|
@ -475,8 +508,8 @@ move_to_beginning_of_line_from_middle :: proc(t: ^testing.T) {
|
|||
// Move to the beginning of the line
|
||||
run_inputs(&e, []ArtificialInput{ press_key(.G), press_key(.H)})
|
||||
|
||||
expect_line_col(t, buffer.cursor, 0, 0)
|
||||
expect_cursor_index(t, buffer.cursor, 0, 0)
|
||||
expect_line_col(t, buffer.history.cursor, 0, 0)
|
||||
expect_cursor_index(t, buffer.history.cursor, 0, 0)
|
||||
}
|
||||
|
||||
@(test)
|
||||
|
@ -499,8 +532,30 @@ move_to_beginning_of_line_from_start :: proc(t: ^testing.T) {
|
|||
// Move to the beginning of the line
|
||||
run_inputs(&e, []ArtificialInput{ press_key(.G), press_key(.H)})
|
||||
|
||||
expect_line_col(t, buffer.cursor, 0, 0)
|
||||
expect_cursor_index(t, buffer.cursor, 0, 0)
|
||||
expect_line_col(t, buffer.history.cursor, 0, 0)
|
||||
expect_cursor_index(t, buffer.history.cursor, 0, 0)
|
||||
}
|
||||
|
||||
@(test)
|
||||
append_end_of_line :: proc(t: ^testing.T) {
|
||||
e := new_test_editor()
|
||||
setup_empty_buffer(&e)
|
||||
|
||||
buffer := &e.buffers[0]
|
||||
|
||||
run_text_insertion(&e, "hello")
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
@(test)
|
||||
|
@ -526,18 +581,44 @@ insert_line_under_current :: proc(t: ^testing.T) {
|
|||
|
||||
// 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)
|
||||
expect_line_col(t, buffer.history.cursor, 0, 13)
|
||||
expect_cursor_index(t, buffer.history.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)
|
||||
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))
|
||||
testing.expectf(t, contents == expected_text, "got '%v', expected '%v'", contents, expected_text)
|
||||
}
|
||||
|
||||
@(test)
|
||||
yank_and_paste_whole_line :: 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 a new line\nThis is a new line\n"
|
||||
|
||||
// Copy whole line
|
||||
run_input_multiple(&e, press_key(.Y), 2)
|
||||
|
||||
// Move up to "Hello, world!"
|
||||
run_input_multiple(&e, press_key(.K), 1)
|
||||
|
||||
// Paste it below current one
|
||||
run_input_multiple(&e, press_key(.P), 1)
|
||||
|
||||
expect_line_col(t, buffer.history.cursor, 1, 0)
|
||||
|
||||
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)
|
||||
|
|
|
@ -66,7 +66,7 @@ UI_Direction :: enum {
|
|||
BottomToTop,
|
||||
}
|
||||
|
||||
open_element :: proc(state: ^State, kind: UI_Element_Kind, layout: UI_Layout) {
|
||||
open_element :: proc(state: ^State, kind: UI_Element_Kind, layout: UI_Layout) -> UI_Element {
|
||||
e := UI_Element {
|
||||
kind = kind,
|
||||
layout = layout,
|
||||
|
@ -93,6 +93,8 @@ open_element :: proc(state: ^State, kind: UI_Element_Kind, layout: UI_Layout) {
|
|||
state.curr_elements[state.num_curr] = e
|
||||
state.current_open_element = state.num_curr
|
||||
state.num_curr += 1
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
close_element :: proc(state: ^State, loc := #caller_location) -> UI_Layout {
|
||||
|
|
10
todo.md
10
todo.md
|
@ -5,6 +5,8 @@
|
|||
- Closing the only panel crashes
|
||||
|
||||
# Planned Features
|
||||
- [ ] Highlight which panel is currently active
|
||||
- [ ] Persist end of line cursor position
|
||||
- Testing Harness
|
||||
- [x] Replay user inputs and assert buffer contents/changes
|
||||
- [ ] Finish writing tests for all current user actions
|
||||
|
@ -31,7 +33,10 @@
|
|||
- [x] Query across project
|
||||
- [x] Open file in new buffer
|
||||
- [x] Open file in new buffer at found location
|
||||
- [ ] Preview file with context (instead of just the single matched line)
|
||||
- [ ] Preview file with context
|
||||
- [x] Show Context
|
||||
- [ ] Properly show lines numbers
|
||||
- [ ] Don't overlap result list with file preview
|
||||
- [ ] Open Buffer Search
|
||||
- Re-write the UI (again)
|
||||
- [x] New UI
|
||||
|
@ -45,7 +50,7 @@
|
|||
- [x] Yank
|
||||
- [x] Delete
|
||||
- [ ] Change
|
||||
- [ ] Change
|
||||
- [x] Change
|
||||
- [ ] Change word
|
||||
- [ ] Change inside delimiter
|
||||
- Virtual Whitespace
|
||||
|
@ -53,4 +58,3 @@
|
|||
- Command Search and Execution
|
||||
- Refactor to remove generics added specifically for plugins
|
||||
- Palette based UI?
|
||||
- Persist end of line cursor position
|
Loading…
Reference in New Issue