Compare commits
No commits in common. "aae0c2450405797d982f4b3399fd0ed7d96067e4" and "722f05be6177f63152ae3b9e9f3799fcd6068762" have entirely different histories.
aae0c24504
...
722f05be61
|
@ -144,7 +144,7 @@ yank_whole_line :: proc(state: ^State) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if buffer := current_buffer(state); buffer != nil {
|
if buffer := current_buffer(state); buffer != nil {
|
||||||
selection := new_selection(buffer, buffer.history.cursor)
|
selection := new_selection(buffer, buffer.cursor)
|
||||||
length := selection_length(buffer, selection)
|
length := selection_length(buffer, selection)
|
||||||
|
|
||||||
state.yank_register.whole_line = true
|
state.yank_register.whole_line = true
|
||||||
|
|
|
@ -27,10 +27,15 @@ ContentSlice :: struct {
|
||||||
slice: []u8,
|
slice: []u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FileBufferIndex :: struct {
|
||||||
|
slice_index: int,
|
||||||
|
content_index: int,
|
||||||
|
}
|
||||||
|
|
||||||
Cursor :: struct {
|
Cursor :: struct {
|
||||||
col: int,
|
col: int,
|
||||||
line: int,
|
line: int,
|
||||||
index: PieceTableIndex,
|
index: FileBufferIndex,
|
||||||
}
|
}
|
||||||
|
|
||||||
Selection :: struct {
|
Selection :: struct {
|
||||||
|
@ -46,10 +51,13 @@ FileBuffer :: struct {
|
||||||
extension: string,
|
extension: string,
|
||||||
|
|
||||||
top_line: int,
|
top_line: int,
|
||||||
// cursor: Cursor,
|
cursor: Cursor,
|
||||||
selection: Maybe(Selection),
|
selection: Maybe(Selection),
|
||||||
|
|
||||||
history: FileHistory,
|
original_content: [dynamic]u8,
|
||||||
|
added_content: [dynamic]u8,
|
||||||
|
content_slices: [dynamic][]u8,
|
||||||
|
|
||||||
glyphs: GlyphBuffer,
|
glyphs: GlyphBuffer,
|
||||||
|
|
||||||
input_buffer: [dynamic]u8,
|
input_buffer: [dynamic]u8,
|
||||||
|
@ -58,27 +66,14 @@ FileBuffer :: struct {
|
||||||
FileBufferIter :: struct {
|
FileBufferIter :: struct {
|
||||||
cursor: Cursor,
|
cursor: Cursor,
|
||||||
buffer: ^FileBuffer,
|
buffer: ^FileBuffer,
|
||||||
piter: PieceTableIter,
|
|
||||||
hit_end: bool,
|
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 {
|
new_file_buffer_iter_from_beginning :: proc(file_buffer: ^FileBuffer) -> FileBufferIter {
|
||||||
return FileBufferIter {
|
return FileBufferIter { buffer = file_buffer };
|
||||||
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 {
|
new_file_buffer_iter_with_cursor :: proc(file_buffer: ^FileBuffer, cursor: Cursor) -> FileBufferIter {
|
||||||
return FileBufferIter {
|
return FileBufferIter { buffer = file_buffer, cursor = cursor };
|
||||||
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};
|
new_file_buffer_iter :: proc{new_file_buffer_iter_from_beginning, new_file_buffer_iter_with_cursor};
|
||||||
|
|
||||||
|
@ -86,55 +81,89 @@ file_buffer_end :: proc(buffer: ^FileBuffer) -> Cursor {
|
||||||
return Cursor {
|
return Cursor {
|
||||||
col = 0,
|
col = 0,
|
||||||
line = 0,
|
line = 0,
|
||||||
index = new_piece_table_index_from_end(buffer_piece_table(buffer))
|
index = FileBufferIndex {
|
||||||
|
slice_index = len(buffer.content_slices)-1,
|
||||||
|
content_index = len(buffer.content_slices[len(buffer.content_slices)-1])-1,
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
iterate_file_buffer :: proc(it: ^FileBufferIter) -> (character: u8, idx: PieceTableIndex, cond: bool) {
|
iterate_file_buffer :: proc(it: ^FileBufferIter) -> (character: u8, idx: FileBufferIndex, cond: bool) {
|
||||||
character, idx, cond = iterate_piece_table_iter(&it.piter)
|
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;
|
||||||
|
|
||||||
it.cursor.index = it.piter.index
|
character = it.buffer.content_slices[it.cursor.index.slice_index][it.cursor.index.content_index];
|
||||||
it.hit_end = it.piter.hit_end
|
if it.cursor.index.content_index < len(it.buffer.content_slices[it.cursor.index.slice_index])-1 {
|
||||||
|
it.cursor.index.content_index += 1;
|
||||||
if cond && !it.hit_end {
|
} else if it.cursor.index.slice_index < len(it.buffer.content_slices)-1 {
|
||||||
if character == '\n' {
|
it.cursor.index.content_index = 0;
|
||||||
it.cursor.col = 0
|
it.cursor.index.slice_index += 1;
|
||||||
it.cursor.line += 1
|
} else if it.hit_end {
|
||||||
} else {
|
return character, it.cursor.index, false;
|
||||||
it.cursor.col += 1
|
} else {
|
||||||
}
|
it.hit_end = true;
|
||||||
|
return character, it.cursor.index, true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: figure out how to give the first character of the buffer
|
character = it.buffer.content_slices[it.cursor.index.slice_index][it.cursor.index.content_index];
|
||||||
iterate_file_buffer_reverse :: proc(it: ^FileBufferIter) -> (character: u8, idx: PieceTableIndex, cond: bool) {
|
if it.cursor.index.content_index == 0 {
|
||||||
character, idx, cond = iterate_piece_table_iter_reverse(&it.piter)
|
if it.cursor.index.slice_index > 0 {
|
||||||
|
it.cursor.index.slice_index -= 1;
|
||||||
it.cursor.index = it.piter.index
|
it.cursor.index.content_index = len(it.buffer.content_slices[it.cursor.index.slice_index])-1;
|
||||||
it.hit_end = it.piter.hit_end
|
} else if it.hit_end {
|
||||||
|
return character, it.cursor.index, false;
|
||||||
if cond && !it.hit_end {
|
} else {
|
||||||
if it.cursor.col > 0 {
|
it.hit_end = true;
|
||||||
it.cursor.col -= 1
|
return character, it.cursor.index, true;
|
||||||
} 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
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
it.cursor.index.content_index -= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return character, it.cursor.index, true;
|
||||||
|
}
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return character, it.cursor.index, cond;
|
||||||
}
|
}
|
||||||
|
|
||||||
get_character_at_iter :: proc(it: FileBufferIter) -> u8 {
|
get_character_at_iter :: proc(it: FileBufferIter) -> u8 {
|
||||||
return get_character_at_piece_table_index(buffer_piece_table(it.buffer), it.cursor.index);
|
return it.buffer.content_slices[it.cursor.index.slice_index][it.cursor.index.content_index];
|
||||||
}
|
}
|
||||||
|
|
||||||
IterProc :: proc(it: ^FileBufferIter) -> (character: u8, idx: PieceTableIndex, cond: bool);
|
IterProc :: proc(it: ^FileBufferIter) -> (character: u8, idx: FileBufferIndex, cond: bool);
|
||||||
UntilProc :: proc(it: ^FileBufferIter, iter_proc: IterProc) -> bool;
|
UntilProc :: proc(it: ^FileBufferIter, iter_proc: IterProc) -> bool;
|
||||||
|
|
||||||
iterate_file_buffer_until :: proc(it: ^FileBufferIter, until_proc: UntilProc) {
|
iterate_file_buffer_until :: proc(it: ^FileBufferIter, until_proc: UntilProc) {
|
||||||
|
@ -327,7 +356,7 @@ until_single_quote :: proc(it: ^FileBufferIter, iter_proc: IterProc) -> bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
until_line_break :: proc(it: ^FileBufferIter, iter_proc: IterProc) -> (cond: bool) {
|
until_line_break :: proc(it: ^FileBufferIter, iter_proc: IterProc) -> (cond: bool) {
|
||||||
if get_character_at_piece_table_index(buffer_piece_table(it.buffer), it.cursor.index) == '\n' {
|
if it.buffer.content_slices[it.cursor.index.slice_index][it.cursor.index.content_index] == '\n' {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -343,7 +372,7 @@ update_file_buffer_index_from_cursor :: proc(buffer: ^FileBuffer) {
|
||||||
rendered_line := 0;
|
rendered_line := 0;
|
||||||
|
|
||||||
for character in iterate_file_buffer(&it) {
|
for character in iterate_file_buffer(&it) {
|
||||||
if line_length == buffer.history.cursor.col && rendered_line == buffer.history.cursor.line {
|
if line_length == buffer.cursor.col && rendered_line == buffer.cursor.line {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -358,23 +387,23 @@ update_file_buffer_index_from_cursor :: proc(buffer: ^FileBuffer) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: just swap cursors
|
// FIXME: just swap cursors
|
||||||
buffer.history.cursor.index = before_it.cursor.index;
|
buffer.cursor.index = before_it.cursor.index;
|
||||||
|
|
||||||
update_file_buffer_scroll(buffer);
|
update_file_buffer_scroll(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
file_buffer_line_length :: proc(buffer: ^FileBuffer, index: PieceTableIndex) -> int {
|
file_buffer_line_length :: proc(buffer: ^FileBuffer, index: FileBufferIndex) -> int {
|
||||||
line_length := 0;
|
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 := get_character_at_piece_table_index(buffer_piece_table(buffer), index);
|
first_character := buffer.content_slices[index.slice_index][index.content_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' {
|
if first_character == '\n' {
|
||||||
iterate_piece_table_iter_reverse(&left_it);
|
iterate_file_buffer_reverse_mangle_cursor(&left_it);
|
||||||
}
|
}
|
||||||
|
|
||||||
for character in iterate_piece_table_iter_reverse(&left_it) {
|
for character in iterate_file_buffer_reverse_mangle_cursor(&left_it) {
|
||||||
if character == '\n' {
|
if character == '\n' {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -382,9 +411,9 @@ file_buffer_line_length :: proc(buffer: ^FileBuffer, index: PieceTableIndex) ->
|
||||||
line_length += 1;
|
line_length += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
right_it := new_piece_table_iter_from_index(buffer_piece_table(buffer), index);
|
right_it := new_file_buffer_iter_with_cursor(buffer, Cursor { index = index });
|
||||||
first := true;
|
first := true;
|
||||||
for character in iterate_piece_table_iter(&right_it) {
|
for character in iterate_file_buffer(&right_it) {
|
||||||
if character == '\n' {
|
if character == '\n' {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -402,7 +431,7 @@ move_cursor_start_of_line :: proc(buffer: ^FileBuffer, cursor: Maybe(^Cursor) =
|
||||||
cursor := cursor;
|
cursor := cursor;
|
||||||
|
|
||||||
if cursor == nil {
|
if cursor == nil {
|
||||||
cursor = &buffer.history.cursor;
|
cursor = &buffer.cursor;
|
||||||
}
|
}
|
||||||
|
|
||||||
if cursor.?.col > 0 {
|
if cursor.?.col > 0 {
|
||||||
|
@ -421,7 +450,7 @@ move_cursor_end_of_line :: proc(buffer: ^FileBuffer, stop_at_end: bool = true, c
|
||||||
cursor := cursor;
|
cursor := cursor;
|
||||||
|
|
||||||
if cursor == nil {
|
if cursor == nil {
|
||||||
cursor = &buffer.history.cursor;
|
cursor = &buffer.cursor;
|
||||||
}
|
}
|
||||||
|
|
||||||
it := new_file_buffer_iter_with_cursor(buffer, cursor.?^);
|
it := new_file_buffer_iter_with_cursor(buffer, cursor.?^);
|
||||||
|
@ -445,7 +474,7 @@ move_cursor_up :: proc(buffer: ^FileBuffer, amount: int = 1, cursor: Maybe(^Curs
|
||||||
cursor := cursor;
|
cursor := cursor;
|
||||||
|
|
||||||
if cursor == nil {
|
if cursor == nil {
|
||||||
cursor = &buffer.history.cursor;
|
cursor = &buffer.cursor;
|
||||||
}
|
}
|
||||||
|
|
||||||
if cursor.?.line > 0 {
|
if cursor.?.line > 0 {
|
||||||
|
@ -479,7 +508,7 @@ move_cursor_down :: proc(buffer: ^FileBuffer, amount: int = 1, cursor: Maybe(^Cu
|
||||||
cursor := cursor;
|
cursor := cursor;
|
||||||
|
|
||||||
if cursor == nil {
|
if cursor == nil {
|
||||||
cursor = &buffer.history.cursor;
|
cursor = &buffer.cursor;
|
||||||
}
|
}
|
||||||
|
|
||||||
current_line := cursor.?.line;
|
current_line := cursor.?.line;
|
||||||
|
@ -512,7 +541,7 @@ move_cursor_left :: proc(buffer: ^FileBuffer, cursor: Maybe(^Cursor) = nil) {
|
||||||
cursor := cursor;
|
cursor := cursor;
|
||||||
|
|
||||||
if cursor == nil {
|
if cursor == nil {
|
||||||
cursor = &buffer.history.cursor;
|
cursor = &buffer.cursor;
|
||||||
}
|
}
|
||||||
|
|
||||||
if cursor.?.col > 0 {
|
if cursor.?.col > 0 {
|
||||||
|
@ -526,7 +555,7 @@ move_cursor_right :: proc(buffer: ^FileBuffer, stop_at_end: bool = true, amt: in
|
||||||
cursor := cursor;
|
cursor := cursor;
|
||||||
|
|
||||||
if cursor == nil {
|
if cursor == nil {
|
||||||
cursor = &buffer.history.cursor;
|
cursor = &buffer.cursor;
|
||||||
}
|
}
|
||||||
|
|
||||||
it := new_file_buffer_iter_with_cursor(buffer, cursor.?^);
|
it := new_file_buffer_iter_with_cursor(buffer, cursor.?^);
|
||||||
|
@ -544,7 +573,7 @@ move_cursor_forward_start_of_word :: proc(buffer: ^FileBuffer, cursor: Maybe(^Cu
|
||||||
cursor := cursor;
|
cursor := cursor;
|
||||||
|
|
||||||
if cursor == nil {
|
if cursor == nil {
|
||||||
cursor = &buffer.history.cursor;
|
cursor = &buffer.cursor;
|
||||||
}
|
}
|
||||||
|
|
||||||
it := new_file_buffer_iter_with_cursor(buffer, cursor.?^);
|
it := new_file_buffer_iter_with_cursor(buffer, cursor.?^);
|
||||||
|
@ -558,7 +587,7 @@ move_cursor_forward_end_of_word :: proc(buffer: ^FileBuffer, cursor: Maybe(^Curs
|
||||||
cursor := cursor;
|
cursor := cursor;
|
||||||
|
|
||||||
if cursor == nil {
|
if cursor == nil {
|
||||||
cursor = &buffer.history.cursor;
|
cursor = &buffer.cursor;
|
||||||
}
|
}
|
||||||
|
|
||||||
it := new_file_buffer_iter_with_cursor(buffer, cursor.?^);
|
it := new_file_buffer_iter_with_cursor(buffer, cursor.?^);
|
||||||
|
@ -572,7 +601,7 @@ move_cursor_backward_start_of_word :: proc(buffer: ^FileBuffer, cursor: Maybe(^C
|
||||||
cursor := cursor;
|
cursor := cursor;
|
||||||
|
|
||||||
if cursor == nil {
|
if cursor == nil {
|
||||||
cursor = &buffer.history.cursor;
|
cursor = &buffer.cursor;
|
||||||
}
|
}
|
||||||
|
|
||||||
it := new_file_buffer_iter_with_cursor(buffer, cursor.?^);
|
it := new_file_buffer_iter_with_cursor(buffer, cursor.?^);
|
||||||
|
@ -587,7 +616,7 @@ move_cursor_backward_end_of_word :: proc(buffer: ^FileBuffer, cursor: Maybe(^Cur
|
||||||
cursor := cursor;
|
cursor := cursor;
|
||||||
|
|
||||||
if cursor == nil {
|
if cursor == nil {
|
||||||
cursor = &buffer.history.cursor;
|
cursor = &buffer.cursor;
|
||||||
}
|
}
|
||||||
|
|
||||||
it := new_file_buffer_iter_with_cursor(buffer, cursor.?^);
|
it := new_file_buffer_iter_with_cursor(buffer, cursor.?^);
|
||||||
|
@ -637,11 +666,10 @@ swap_selections :: proc(selection: Selection) -> (swapped: Selection) {
|
||||||
return swapped
|
return swapped
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: don't access PieceTableIndex directly
|
|
||||||
is_selection_inverted :: proc(selection: Selection) -> bool {
|
is_selection_inverted :: proc(selection: Selection) -> bool {
|
||||||
return selection.start.index.chunk_index > selection.end.index.chunk_index ||
|
return selection.start.index.slice_index > selection.end.index.slice_index ||
|
||||||
(selection.start.index.chunk_index == selection.end.index.chunk_index
|
(selection.start.index.slice_index == selection.end.index.slice_index
|
||||||
&& selection.start.index.char_index > selection.end.index.char_index)
|
&& selection.start.index.content_index > selection.end.index.content_index)
|
||||||
}
|
}
|
||||||
|
|
||||||
selection_length :: proc(buffer: ^FileBuffer, selection: Selection) -> int {
|
selection_length :: proc(buffer: ^FileBuffer, selection: Selection) -> int {
|
||||||
|
@ -669,12 +697,16 @@ new_virtual_file_buffer :: proc(allocator: mem.Allocator) -> FileBuffer {
|
||||||
allocator = allocator,
|
allocator = allocator,
|
||||||
file_path = "virtual_buffer",
|
file_path = "virtual_buffer",
|
||||||
|
|
||||||
history = make_history(),
|
original_content = slice.clone_to_dynamic([]u8{'\n'}),
|
||||||
|
added_content = make([dynamic]u8, 0, 1024*1024),
|
||||||
|
content_slices = make([dynamic][]u8, 0, 1024*1024),
|
||||||
|
|
||||||
glyphs = make_glyph_buffer(width, height),
|
glyphs = make_glyph_buffer(width, height),
|
||||||
input_buffer = make([dynamic]u8, 0, 1024),
|
input_buffer = make([dynamic]u8, 0, 1024),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
append(&buffer.content_slices, buffer.original_content[:]);
|
||||||
|
|
||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -717,12 +749,21 @@ new_file_buffer :: proc(allocator: mem.Allocator, file_path: string, base_dir: s
|
||||||
// file_path = fi.fullpath[4:],
|
// file_path = fi.fullpath[4:],
|
||||||
extension = extension,
|
extension = extension,
|
||||||
|
|
||||||
history = make_history(original_content),
|
original_content = slice.clone_to_dynamic(original_content),
|
||||||
|
added_content = make([dynamic]u8, 0, 1024*1024),
|
||||||
|
content_slices = make([dynamic][]u8, 0, 1024*1024),
|
||||||
|
|
||||||
glyphs = make_glyph_buffer(width, height),
|
glyphs = make_glyph_buffer(width, height),
|
||||||
input_buffer = make([dynamic]u8, 0, 1024),
|
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();
|
return buffer, error();
|
||||||
} else {
|
} else {
|
||||||
return FileBuffer{}, error(ErrorType.FileIOError, fmt.aprintf("failed to read from file"));
|
return FileBuffer{}, error(ErrorType.FileIOError, fmt.aprintf("failed to read from file"));
|
||||||
|
@ -734,10 +775,10 @@ save_buffer_to_disk :: proc(state: ^State, buffer: ^FileBuffer) -> (error: os.Er
|
||||||
defer os.close(fd);
|
defer os.close(fd);
|
||||||
|
|
||||||
offset: i64 = 0
|
offset: i64 = 0
|
||||||
for chunk in buffer_piece_table(buffer).chunks {
|
for content_slice in buffer.content_slices {
|
||||||
os.write(fd, chunk) or_return
|
os.write(fd, content_slice) or_return
|
||||||
|
|
||||||
offset += i64(len(chunk))
|
offset += i64(len(content_slice))
|
||||||
}
|
}
|
||||||
os.flush(fd)
|
os.flush(fd)
|
||||||
|
|
||||||
|
@ -760,9 +801,11 @@ next_buffer :: proc(state: ^State, prev_buffer: ^int) -> int {
|
||||||
|
|
||||||
// TODO: replace this with arena for the file buffer
|
// TODO: replace this with arena for the file buffer
|
||||||
free_file_buffer :: proc(buffer: ^FileBuffer) {
|
free_file_buffer :: proc(buffer: ^FileBuffer) {
|
||||||
free_history(&buffer.history)
|
delete(buffer.original_content);
|
||||||
delete(buffer.glyphs.buffer)
|
delete(buffer.added_content);
|
||||||
delete(buffer.input_buffer)
|
delete(buffer.content_slices);
|
||||||
|
delete(buffer.glyphs.buffer);
|
||||||
|
delete(buffer.input_buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
color_character :: proc(buffer: ^FileBuffer, start: Cursor, end: Cursor, palette_index: theme.PaletteColor) {
|
color_character :: proc(buffer: ^FileBuffer, start: Cursor, end: Cursor, palette_index: theme.PaletteColor) {
|
||||||
|
@ -811,8 +854,8 @@ draw_file_buffer :: proc(state: ^State, buffer: ^FileBuffer, x: int, y: int, sho
|
||||||
}
|
}
|
||||||
|
|
||||||
begin := buffer.top_line;
|
begin := buffer.top_line;
|
||||||
cursor_x := x + padding + buffer.history.cursor.col * state.source_font_width;
|
cursor_x := x + padding + buffer.cursor.col * state.source_font_width;
|
||||||
cursor_y := y + buffer.history.cursor.line * state.source_font_height;
|
cursor_y := y + buffer.cursor.line * state.source_font_height;
|
||||||
|
|
||||||
cursor_y -= begin * state.source_font_height;
|
cursor_y -= begin * state.source_font_height;
|
||||||
|
|
||||||
|
@ -908,7 +951,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) {
|
update_file_buffer_scroll :: proc(buffer: ^FileBuffer, cursor: Maybe(^Cursor) = nil) {
|
||||||
cursor := cursor;
|
cursor := cursor;
|
||||||
if cursor == nil {
|
if cursor == nil {
|
||||||
cursor = &buffer.history.cursor;
|
cursor = &buffer.cursor;
|
||||||
}
|
}
|
||||||
|
|
||||||
if cursor.?.line > (buffer.top_line + buffer.glyphs.height - 5) {
|
if cursor.?.line > (buffer.top_line + buffer.glyphs.height - 5) {
|
||||||
|
@ -917,10 +960,10 @@ update_file_buffer_scroll :: proc(buffer: ^FileBuffer, cursor: Maybe(^Cursor) =
|
||||||
buffer.top_line = math.max(cursor.?.line - 5, 0);
|
buffer.top_line = math.max(cursor.?.line - 5, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// if buffer.history.cursor.line > (buffer.top_line + buffer.glyphs.height - 5) {
|
// if buffer.cursor.line > (buffer.top_line + buffer.glyphs.height - 5) {
|
||||||
// buffer.top_line = math.max(buffer.history.cursor.line - buffer.glyphs.height + 5, 0);
|
// buffer.top_line = math.max(buffer.cursor.line - buffer.glyphs.height + 5, 0);
|
||||||
// } else if buffer.history.cursor.line < (buffer.top_line + 5) {
|
// } else if buffer.cursor.line < (buffer.top_line + 5) {
|
||||||
// buffer.top_line = math.max(buffer.history.cursor.line - 5, 0);
|
// buffer.top_line = math.max(buffer.cursor.line - 5, 0);
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -944,9 +987,28 @@ insert_content :: proc(buffer: ^FileBuffer, to_be_inserted: []u8, append_to_end:
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
index := buffer.history.cursor.index if !append_to_end else new_piece_table_index_from_end(buffer_piece_table(buffer))
|
// 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));
|
||||||
|
|
||||||
insert_text(buffer_piece_table(buffer), to_be_inserted, buffer.history.cursor.index)
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
if !append_to_end {
|
if !append_to_end {
|
||||||
update_file_buffer_index_from_cursor(buffer);
|
update_file_buffer_index_from_cursor(buffer);
|
||||||
|
@ -954,6 +1016,44 @@ 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) {
|
delete_content_from_buffer_cursor :: proc(buffer: ^FileBuffer, amount: int) {
|
||||||
if amount <= len(buffer.input_buffer) {
|
if amount <= len(buffer.input_buffer) {
|
||||||
runtime.resize(&buffer.input_buffer, len(buffer.input_buffer)-amount);
|
runtime.resize(&buffer.input_buffer, len(buffer.input_buffer)-amount);
|
||||||
|
@ -961,22 +1061,63 @@ delete_content_from_buffer_cursor :: proc(buffer: ^FileBuffer, amount: int) {
|
||||||
amount := amount - len(buffer.input_buffer);
|
amount := amount - len(buffer.input_buffer);
|
||||||
runtime.clear(&buffer.input_buffer);
|
runtime.clear(&buffer.input_buffer);
|
||||||
|
|
||||||
// Calculate proper line/col values
|
if len(buffer.content_slices) < 1 {
|
||||||
it := new_file_buffer_iter_with_cursor(buffer, buffer.history.cursor);
|
return;
|
||||||
iterate_file_buffer_reverse(&it)
|
}
|
||||||
|
|
||||||
delete_text(buffer_piece_table(buffer), &buffer.history.cursor.index)
|
split_content_slice(buffer, &buffer.cursor);
|
||||||
|
|
||||||
buffer.history.cursor.line = it.cursor.line
|
it := new_file_buffer_iter_with_cursor(buffer, buffer.cursor);
|
||||||
buffer.history.cursor.col = it.cursor.col
|
|
||||||
|
// 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
delete_content_from_selection :: proc(buffer: ^FileBuffer, selection: ^Selection) {
|
delete_content_from_selection :: proc(buffer: ^FileBuffer, selection: ^Selection) {
|
||||||
selection^ = swap_selections(selection^)
|
assert(len(buffer.content_slices) >= 1);
|
||||||
delete_text_in_span(buffer_piece_table(buffer), &selection.start.index, &selection.end.index)
|
|
||||||
|
|
||||||
buffer.history.cursor.index = selection.start.index
|
selection^ = swap_selections(selection^)
|
||||||
|
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
delete_content :: proc{delete_content_from_buffer_cursor, delete_content_from_selection};
|
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; }
|
if rendered_line >= begin && screen_line >= buffer.glyphs.height { break; }
|
||||||
|
|
||||||
// render INSERT mode text into glyph buffer
|
// 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) {
|
if len(buffer.input_buffer) > 0 && rendered_line == buffer.cursor.line && rendered_col >= buffer.cursor.col && rendered_col < buffer.cursor.col + len(buffer.input_buffer) {
|
||||||
for k in 0..<len(buffer.input_buffer) {
|
for k in 0..<len(buffer.input_buffer) {
|
||||||
screen_line = rendered_line - begin;
|
screen_line = rendered_line - begin;
|
||||||
|
|
||||||
|
|
|
@ -1,135 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
|
@ -1,241 +0,0 @@
|
||||||
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;
|
state.mode = .Visual;
|
||||||
core.reset_input_map(state)
|
core.reset_input_map(state)
|
||||||
|
|
||||||
core.current_buffer(state).selection = core.new_selection(core.current_buffer(state).history.cursor);
|
core.current_buffer(state).selection = core.new_selection(core.current_buffer(state).cursor);
|
||||||
}, "enter visual mode");
|
}, "enter visual mode");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -158,8 +158,6 @@ register_default_visual_actions :: proc(input_map: ^core.InputActions) {
|
||||||
// Text Modification
|
// Text Modification
|
||||||
{
|
{
|
||||||
core.register_key_action(input_map, .D, proc(state: ^State) {
|
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.?);
|
sel_cur := &(core.current_buffer(state).selection.?);
|
||||||
|
|
||||||
core.delete_content(core.current_buffer(state), sel_cur);
|
core.delete_content(core.current_buffer(state), sel_cur);
|
||||||
|
@ -171,8 +169,6 @@ register_default_visual_actions :: proc(input_map: ^core.InputActions) {
|
||||||
}, "delete selection");
|
}, "delete selection");
|
||||||
|
|
||||||
core.register_key_action(input_map, .C, proc(state: ^State) {
|
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.?);
|
sel_cur := &(core.current_buffer(state).selection.?);
|
||||||
|
|
||||||
core.delete_content(core.current_buffer(state), sel_cur);
|
core.delete_content(core.current_buffer(state), sel_cur);
|
||||||
|
@ -198,8 +194,6 @@ register_default_visual_actions :: proc(input_map: ^core.InputActions) {
|
||||||
}, "Yank Line");
|
}, "Yank Line");
|
||||||
|
|
||||||
core.register_key_action(input_map, .P, proc(state: ^State) {
|
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 {
|
if state.yank_register.whole_line {
|
||||||
core.insert_content(core.current_buffer(state), []u8{'\n'});
|
core.insert_content(core.current_buffer(state), []u8{'\n'});
|
||||||
core.paste_register(state, state.yank_register)
|
core.paste_register(state, state.yank_register)
|
||||||
|
@ -215,32 +209,18 @@ register_default_visual_actions :: proc(input_map: ^core.InputActions) {
|
||||||
|
|
||||||
register_default_text_input_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.register_key_action(input_map, .I, proc(state: ^State) {
|
||||||
core.push_new_snapshot(&core.current_buffer(state).history)
|
|
||||||
|
|
||||||
state.mode = .Insert;
|
state.mode = .Insert;
|
||||||
sdl2.StartTextInput();
|
sdl2.StartTextInput();
|
||||||
}, "enter insert mode");
|
}, "enter insert mode");
|
||||||
core.register_key_action(input_map, .A, proc(state: ^State) {
|
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);
|
core.move_cursor_right(core.current_buffer(state), false);
|
||||||
state.mode = .Insert;
|
state.mode = .Insert;
|
||||||
sdl2.StartTextInput();
|
sdl2.StartTextInput();
|
||||||
}, "enter insert mode after character (append)");
|
}, "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
|
// TODO: add shift+o to insert newline above current one
|
||||||
|
|
||||||
core.register_key_action(input_map, .O, proc(state: ^State) {
|
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 {
|
if buffer := core.current_buffer(state); buffer != nil {
|
||||||
core.move_cursor_end_of_line(buffer, false);
|
core.move_cursor_end_of_line(buffer, false);
|
||||||
runtime.clear(&buffer.input_buffer)
|
runtime.clear(&buffer.input_buffer)
|
||||||
|
@ -267,17 +247,13 @@ register_default_text_input_actions :: proc(input_map: ^core.InputActions) {
|
||||||
}
|
}
|
||||||
|
|
||||||
core.register_key_action(input_map, .P, proc(state: ^State) {
|
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 {
|
if state.yank_register.whole_line {
|
||||||
core.move_cursor_end_of_line(core.current_buffer(state), false);
|
core.move_cursor_end_of_line(core.current_buffer(state), false);
|
||||||
core.insert_content(core.current_buffer(state), []u8{'\n'});
|
core.insert_content(core.current_buffer(state), []u8{'\n'});
|
||||||
core.move_cursor_right(core.current_buffer(state), false);
|
|
||||||
} else {
|
} else {
|
||||||
core.move_cursor_right(core.current_buffer(state))
|
core.move_cursor_right(core.current_buffer(state))
|
||||||
}
|
}
|
||||||
core.paste_register(state, state.yank_register)
|
core.paste_register(state, state.yank_register)
|
||||||
core.move_cursor_start_of_line(core.current_buffer(state))
|
|
||||||
|
|
||||||
core.reset_input_map(state)
|
core.reset_input_map(state)
|
||||||
}, "Paste");
|
}, "Paste");
|
||||||
|
|
|
@ -389,8 +389,6 @@ main :: proc() {
|
||||||
|
|
||||||
sdl2.AddEventWatch(expose_event_watcher, &state);
|
sdl2.AddEventWatch(expose_event_watcher, &state);
|
||||||
|
|
||||||
core.push_new_snapshot(&core.current_buffer(&state).history)
|
|
||||||
|
|
||||||
control_key_pressed: bool;
|
control_key_pressed: bool;
|
||||||
for !state.should_close {
|
for !state.should_close {
|
||||||
{
|
{
|
||||||
|
|
|
@ -135,10 +135,10 @@ open_file_buffer_in_new_panel :: proc(state: ^core.State, file_path: string, lin
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer.history.cursor.line = line
|
buffer.cursor.line = line
|
||||||
buffer.history.cursor.col = col
|
buffer.cursor.col = col
|
||||||
buffer.top_line = buffer.history.cursor.line
|
|
||||||
core.update_file_buffer_index_from_cursor(&buffer)
|
core.update_file_buffer_index_from_cursor(&buffer)
|
||||||
|
core.update_file_buffer_scroll(&buffer)
|
||||||
|
|
||||||
buffer_index = len(state.buffers)
|
buffer_index = len(state.buffers)
|
||||||
runtime.append(&state.buffers, buffer);
|
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.open_element(s, nil, { kind = {ui.Grow{}, ui.Grow{}}})
|
||||||
ui.close_element(s)
|
ui.close_element(s)
|
||||||
|
|
||||||
it := core.new_file_buffer_iter_with_cursor(buffer, buffer.history.cursor)
|
it := core.new_file_buffer_iter_with_cursor(buffer, buffer.cursor)
|
||||||
ui.open_element(
|
ui.open_element(
|
||||||
s,
|
s,
|
||||||
fmt.tprintf(
|
fmt.tprintf(
|
||||||
"%v:%v - Slice %v:%v - Char: %v",
|
"%v:%v - Slice %v:%v - Char: %v",
|
||||||
buffer.history.cursor.line + 1,
|
buffer.cursor.line + 1,
|
||||||
buffer.history.cursor.col + 1,
|
buffer.cursor.col + 1,
|
||||||
buffer.history.cursor.index.chunk_index,
|
buffer.cursor.index.slice_index,
|
||||||
buffer.history.cursor.index.char_index,
|
buffer.cursor.index.content_index,
|
||||||
core.get_character_at_iter(it)
|
core.get_character_at_iter(it)
|
||||||
),
|
),
|
||||||
{}
|
{}
|
||||||
|
@ -301,13 +301,11 @@ make_grep_panel :: proc(state: ^core.State) -> core.Panel {
|
||||||
free_grep_results(rs_results)
|
free_grep_results(rs_results)
|
||||||
|
|
||||||
panel_state.selected_result = 0
|
panel_state.selected_result = 0
|
||||||
if len(panel_state.query_results) > 0 {
|
core.update_glyph_buffer_from_bytes(
|
||||||
core.update_glyph_buffer_from_bytes(
|
&panel_state.glyphs,
|
||||||
&panel_state.glyphs,
|
transmute([]u8)panel_state.query_results[panel_state.selected_result].file_context,
|
||||||
transmute([]u8)panel_state.query_results[panel_state.selected_result].file_context,
|
panel_state.query_results[panel_state.selected_result].line,
|
||||||
panel_state.query_results[panel_state.selected_result].line,
|
)
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
core.register_key_action(&input_map.mode[.Normal], .ENTER, proc(state: ^core.State) {
|
core.register_key_action(&input_map.mode[.Normal], .ENTER, proc(state: ^core.State) {
|
||||||
|
@ -419,19 +417,12 @@ make_grep_panel :: proc(state: ^core.State) -> core.Panel {
|
||||||
{
|
{
|
||||||
if panel_state.query_results != nil {
|
if panel_state.query_results != nil {
|
||||||
// query results
|
// query results
|
||||||
query_result_container := ui.open_element(s, nil, {
|
ui.open_element(s, nil, {
|
||||||
dir = .TopToBottom,
|
dir = .TopToBottom,
|
||||||
kind = {ui.Grow{}, ui.Grow{}}
|
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 {
|
for result, i in panel_state.query_results {
|
||||||
if i > max_results {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.open_element(s, nil, {
|
ui.open_element(s, nil, {
|
||||||
dir = .LeftToRight,
|
dir = .LeftToRight,
|
||||||
kind = {ui.Fit{}, ui.Fit{}},
|
kind = {ui.Fit{}, ui.Fit{}},
|
||||||
|
@ -457,12 +448,6 @@ make_grep_panel :: proc(state: ^core.State) -> core.Panel {
|
||||||
|
|
||||||
// file contents
|
// file contents
|
||||||
selected_result := &panel_state.query_results[panel_state.selected_result]
|
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)
|
render_glyph_buffer(state, s, &panel_state.glyphs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,17 @@
|
||||||
use std::{
|
use std::{
|
||||||
error::Error,
|
error::Error,
|
||||||
ffi::CStr,
|
ffi::{CStr, CString, OsString},
|
||||||
|
path::Path,
|
||||||
|
str::FromStr,
|
||||||
|
sync::mpsc::{Receiver, Sender},
|
||||||
|
thread,
|
||||||
};
|
};
|
||||||
|
|
||||||
use grep::{
|
use grep::{
|
||||||
regex::RegexMatcherBuilder,
|
regex::RegexMatcherBuilder,
|
||||||
searcher::{BinaryDetection, SearcherBuilder, Sink, SinkError},
|
searcher::{BinaryDetection, SearcherBuilder, Sink, SinkError},
|
||||||
};
|
};
|
||||||
|
use std::sync::mpsc::channel;
|
||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
|
@ -32,15 +32,15 @@ buffer_to_string :: proc(buffer: ^core.FileBuffer) -> string {
|
||||||
}
|
}
|
||||||
|
|
||||||
length := 0
|
length := 0
|
||||||
for chunk in core.buffer_piece_table(buffer).chunks {
|
for content_slice in buffer.content_slices {
|
||||||
length += len(chunk)
|
length += len(content_slice)
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer_contents := make([]u8, length)
|
buffer_contents := make([]u8, length)
|
||||||
|
|
||||||
offset := 0
|
offset := 0
|
||||||
for chunk in core.buffer_piece_table(buffer).chunks {
|
for content_slice in buffer.content_slices {
|
||||||
for c in chunk {
|
for c in content_slice {
|
||||||
buffer_contents[offset] = c
|
buffer_contents[offset] = c
|
||||||
offset += 1
|
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)
|
testing.expect_value(t, cursor.col, col)
|
||||||
}
|
}
|
||||||
|
|
||||||
expect_cursor_index :: proc(t: ^testing.T, cursor: core.Cursor, chunk_index, char_index: int) {
|
expect_cursor_index :: proc(t: ^testing.T, cursor: core.Cursor, slice_index, content_index: int) {
|
||||||
testing.expect_value(t, cursor.index.chunk_index, chunk_index)
|
testing.expect_value(t, cursor.index.slice_index, slice_index)
|
||||||
testing.expect_value(t, cursor.index.char_index, char_index)
|
testing.expect_value(t, cursor.index.content_index, content_index)
|
||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
|
@ -142,8 +142,8 @@ insert_from_empty_no_newlines :: proc(t: ^testing.T) {
|
||||||
expected_text := fmt.aprintf("%v\n", inputted_text)
|
expected_text := fmt.aprintf("%v\n", inputted_text)
|
||||||
run_text_insertion(&e, inputted_text)
|
run_text_insertion(&e, inputted_text)
|
||||||
|
|
||||||
expect_line_col(t, buffer.history.cursor, 0, 12)
|
expect_line_col(t, buffer.cursor, 0, 12)
|
||||||
expect_cursor_index(t, buffer.history.cursor, 0, 12)
|
expect_cursor_index(t, buffer.cursor, 0, 12)
|
||||||
|
|
||||||
contents := buffer_to_string(core.current_buffer(&e))
|
contents := buffer_to_string(core.current_buffer(&e))
|
||||||
testing.expectf(t, contents == expected_text, "got '%v', expected '%v'", contents, expected_text)
|
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)
|
expected_text := fmt.aprintf("%v\n", inputted_text)
|
||||||
run_text_insertion(&e, inputted_text)
|
run_text_insertion(&e, inputted_text)
|
||||||
|
|
||||||
expect_line_col(t, buffer.history.cursor, 1, 17)
|
expect_line_col(t, buffer.cursor, 1, 17)
|
||||||
expect_cursor_index(t, buffer.history.cursor, 0, 31)
|
expect_cursor_index(t, buffer.cursor, 0, 31)
|
||||||
|
|
||||||
contents := buffer_to_string(core.current_buffer(&e))
|
contents := buffer_to_string(core.current_buffer(&e))
|
||||||
testing.expectf(t, contents == expected_text, "got '%v', expected '%v'", contents, expected_text)
|
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")
|
run_text_insertion(&e, " beautiful")
|
||||||
|
|
||||||
expect_line_col(t, buffer.history.cursor, 0, 15)
|
expect_line_col(t, buffer.cursor, 0, 15)
|
||||||
expect_cursor_index(t, buffer.history.cursor, 1, 9)
|
expect_cursor_index(t, buffer.cursor, 1, 9)
|
||||||
|
|
||||||
contents := buffer_to_string(core.current_buffer(&e))
|
contents := buffer_to_string(core.current_buffer(&e))
|
||||||
testing.expectf(t, contents == expected_text, "got '%v', expected '%v'", contents, expected_text)
|
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_inputs(&e, []ArtificialInput{ press_key(.G), press_key(.H)})
|
||||||
run_text_insertion(&e, "Well, ")
|
run_text_insertion(&e, "Well, ")
|
||||||
|
|
||||||
expect_line_col(t, buffer.history.cursor, 0, 5)
|
expect_line_col(t, buffer.cursor, 0, 5)
|
||||||
expect_cursor_index(t, buffer.history.cursor, 0, 5)
|
expect_cursor_index(t, buffer.cursor, 0, 5)
|
||||||
|
|
||||||
contents := buffer_to_string(core.current_buffer(&e))
|
contents := buffer_to_string(core.current_buffer(&e))
|
||||||
testing.expectf(t, contents == expected_text, "got '%v', expected '%v'", contents, expected_text)
|
testing.expectf(t, contents == expected_text, "got '%v', expected '%v'", contents, expected_text)
|
||||||
|
@ -239,46 +239,13 @@ insert_before_slice :: proc(t: ^testing.T) {
|
||||||
|
|
||||||
run_text_insertion(&e, " rich")
|
run_text_insertion(&e, " rich")
|
||||||
|
|
||||||
expect_line_col(t, buffer.history.cursor, 0, 20)
|
expect_line_col(t, buffer.cursor, 0, 20)
|
||||||
expect_cursor_index(t, buffer.history.cursor, 2, 4)
|
expect_cursor_index(t, buffer.cursor, 2, 4)
|
||||||
|
|
||||||
contents := buffer_to_string(core.current_buffer(&e))
|
contents := buffer_to_string(core.current_buffer(&e))
|
||||||
testing.expectf(t, contents == expected_text, "got '%v', expected '%v'", contents, expected_text)
|
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)
|
@(test)
|
||||||
delete_in_slice :: proc(t: ^testing.T) {
|
delete_in_slice :: proc(t: ^testing.T) {
|
||||||
e := new_test_editor()
|
e := new_test_editor()
|
||||||
|
@ -306,8 +273,8 @@ delete_in_slice :: proc(t: ^testing.T) {
|
||||||
run_input_multiple(&e, press_key(.BACKSPACE), 3)
|
run_input_multiple(&e, press_key(.BACKSPACE), 3)
|
||||||
run_input_multiple(&e, press_key(.ESCAPE), 1)
|
run_input_multiple(&e, press_key(.ESCAPE), 1)
|
||||||
|
|
||||||
expect_line_col(t, buffer.history.cursor, 0, 17)
|
expect_line_col(t, buffer.cursor, 0, 17)
|
||||||
expect_cursor_index(t, buffer.history.cursor, 3, 0)
|
expect_cursor_index(t, buffer.cursor, 3, 0)
|
||||||
|
|
||||||
contents := buffer_to_string(core.current_buffer(&e))
|
contents := buffer_to_string(core.current_buffer(&e))
|
||||||
testing.expectf(t, contents == expected_text, "got '%v', expected '%v'", contents, expected_text)
|
testing.expectf(t, contents == expected_text, "got '%v', expected '%v'", contents, expected_text)
|
||||||
|
@ -348,8 +315,8 @@ delete_across_slices :: proc(t: ^testing.T) {
|
||||||
run_input_multiple(&e, press_key(.BACKSPACE), 2)
|
run_input_multiple(&e, press_key(.BACKSPACE), 2)
|
||||||
run_input_multiple(&e, press_key(.ESCAPE), 1)
|
run_input_multiple(&e, press_key(.ESCAPE), 1)
|
||||||
|
|
||||||
expect_line_col(t, buffer.history.cursor, 0, 16)
|
expect_line_col(t, buffer.cursor, 0, 16)
|
||||||
expect_cursor_index(t, buffer.history.cursor, 2, 0)
|
expect_cursor_index(t, buffer.cursor, 2, 0)
|
||||||
|
|
||||||
contents := buffer_to_string(core.current_buffer(&e))
|
contents := buffer_to_string(core.current_buffer(&e))
|
||||||
testing.expectf(t, contents == expected_text, "got '%v', expected '%v'", contents, expected_text)
|
testing.expectf(t, contents == expected_text, "got '%v', expected '%v'", contents, expected_text)
|
||||||
|
@ -375,8 +342,8 @@ move_down_next_line_has_shorter_length :: proc(t: ^testing.T) {
|
||||||
// Move down to the second line
|
// Move down to the second line
|
||||||
run_input_multiple(&e, press_key(.J), 1)
|
run_input_multiple(&e, press_key(.J), 1)
|
||||||
|
|
||||||
expect_line_col(t, buffer.history.cursor, 1, 0)
|
expect_line_col(t, buffer.cursor, 1, 0)
|
||||||
expect_cursor_index(t, buffer.history.cursor, 0, 10)
|
expect_cursor_index(t, buffer.cursor, 0, 10)
|
||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
|
@ -394,8 +361,8 @@ move_down_on_last_line :: proc(t: ^testing.T) {
|
||||||
run_input_multiple(&e, press_key(.J), 1)
|
run_input_multiple(&e, press_key(.J), 1)
|
||||||
|
|
||||||
// Cursor should stay where it is
|
// Cursor should stay where it is
|
||||||
expect_line_col(t, buffer.history.cursor, 0, 8)
|
expect_line_col(t, buffer.cursor, 0, 8)
|
||||||
expect_cursor_index(t, buffer.history.cursor, 0, 8)
|
expect_cursor_index(t, buffer.cursor, 0, 8)
|
||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
|
@ -410,15 +377,15 @@ move_left_at_beginning_of_file :: proc(t: ^testing.T) {
|
||||||
// to ------------------^
|
// to ------------------^
|
||||||
run_input_multiple(&e, press_key(.H), 4)
|
run_input_multiple(&e, press_key(.H), 4)
|
||||||
|
|
||||||
expect_line_col(t, buffer.history.cursor, 0, 0)
|
expect_line_col(t, buffer.cursor, 0, 0)
|
||||||
expect_cursor_index(t, buffer.history.cursor, 0, 0)
|
expect_cursor_index(t, buffer.cursor, 0, 0)
|
||||||
|
|
||||||
// Try to move before the beginning of the file
|
// Try to move before the beginning of the file
|
||||||
run_input_multiple(&e, press_key(.H), 1)
|
run_input_multiple(&e, press_key(.H), 1)
|
||||||
|
|
||||||
// Should stay the same
|
// Should stay the same
|
||||||
expect_line_col(t, buffer.history.cursor, 0, 0)
|
expect_line_col(t, buffer.cursor, 0, 0)
|
||||||
expect_cursor_index(t, buffer.history.cursor, 0, 0)
|
expect_cursor_index(t, buffer.cursor, 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
|
@ -432,15 +399,15 @@ move_right_at_end_of_file :: proc(t: ^testing.T) {
|
||||||
|
|
||||||
run_text_insertion(&e, "01234")
|
run_text_insertion(&e, "01234")
|
||||||
|
|
||||||
expect_line_col(t, buffer.history.cursor, 0, 4)
|
expect_line_col(t, buffer.cursor, 0, 4)
|
||||||
expect_cursor_index(t, buffer.history.cursor, 0, 4)
|
expect_cursor_index(t, buffer.cursor, 0, 4)
|
||||||
|
|
||||||
// Try to move after the end of the file
|
// Try to move after the end of the file
|
||||||
run_input_multiple(&e, press_key(.L), 1)
|
run_input_multiple(&e, press_key(.L), 1)
|
||||||
|
|
||||||
// Should stay the same
|
// Should stay the same
|
||||||
expect_line_col(t, buffer.history.cursor, 0, 4)
|
expect_line_col(t, buffer.cursor, 0, 4)
|
||||||
expect_cursor_index(t, buffer.history.cursor, 0, 4)
|
expect_cursor_index(t, buffer.cursor, 0, 4)
|
||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
|
@ -460,8 +427,8 @@ move_to_end_of_line_from_end :: proc(t: ^testing.T) {
|
||||||
// Move to the end of the line
|
// Move to the end of the line
|
||||||
run_inputs(&e, []ArtificialInput{ press_key(.G), press_key(.L)})
|
run_inputs(&e, []ArtificialInput{ press_key(.G), press_key(.L)})
|
||||||
|
|
||||||
expect_line_col(t, buffer.history.cursor, 0, 4)
|
expect_line_col(t, buffer.cursor, 0, 4)
|
||||||
expect_cursor_index(t, buffer.history.cursor, 0, 4)
|
expect_cursor_index(t, buffer.cursor, 0, 4)
|
||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
|
@ -484,8 +451,8 @@ move_to_end_of_line_from_middle :: proc(t: ^testing.T) {
|
||||||
// Move to the end of the line
|
// Move to the end of the line
|
||||||
run_inputs(&e, []ArtificialInput{ press_key(.G), press_key(.L)})
|
run_inputs(&e, []ArtificialInput{ press_key(.G), press_key(.L)})
|
||||||
|
|
||||||
expect_line_col(t, buffer.history.cursor, 0, 4)
|
expect_line_col(t, buffer.cursor, 0, 4)
|
||||||
expect_cursor_index(t, buffer.history.cursor, 0, 4)
|
expect_cursor_index(t, buffer.cursor, 0, 4)
|
||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
|
@ -508,8 +475,8 @@ move_to_beginning_of_line_from_middle :: proc(t: ^testing.T) {
|
||||||
// Move to the beginning of the line
|
// Move to the beginning of the line
|
||||||
run_inputs(&e, []ArtificialInput{ press_key(.G), press_key(.H)})
|
run_inputs(&e, []ArtificialInput{ press_key(.G), press_key(.H)})
|
||||||
|
|
||||||
expect_line_col(t, buffer.history.cursor, 0, 0)
|
expect_line_col(t, buffer.cursor, 0, 0)
|
||||||
expect_cursor_index(t, buffer.history.cursor, 0, 0)
|
expect_cursor_index(t, buffer.cursor, 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
|
@ -532,30 +499,8 @@ move_to_beginning_of_line_from_start :: proc(t: ^testing.T) {
|
||||||
// Move to the beginning of the line
|
// Move to the beginning of the line
|
||||||
run_inputs(&e, []ArtificialInput{ press_key(.G), press_key(.H)})
|
run_inputs(&e, []ArtificialInput{ press_key(.G), press_key(.H)})
|
||||||
|
|
||||||
expect_line_col(t, buffer.history.cursor, 0, 0)
|
expect_line_col(t, buffer.cursor, 0, 0)
|
||||||
expect_cursor_index(t, buffer.history.cursor, 0, 0)
|
expect_cursor_index(t, buffer.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)
|
@(test)
|
||||||
|
@ -581,44 +526,18 @@ insert_line_under_current :: proc(t: ^testing.T) {
|
||||||
|
|
||||||
// Technically the cursor is still on the first line, because the `input_buffer`
|
// Technically the cursor is still on the first line, because the `input_buffer`
|
||||||
// has been modified but not the actual contents of the filebuffer
|
// has been modified but not the actual contents of the filebuffer
|
||||||
expect_line_col(t, buffer.history.cursor, 0, 13)
|
expect_line_col(t, buffer.cursor, 0, 13)
|
||||||
expect_cursor_index(t, buffer.history.cursor, 0, 13)
|
expect_cursor_index(t, buffer.cursor, 0, 13)
|
||||||
|
|
||||||
run_text_insertion(&e, "This is the second line")
|
run_text_insertion(&e, "This is the second line")
|
||||||
|
|
||||||
expect_line_col(t, buffer.history.cursor, 1, 22)
|
expect_line_col(t, buffer.cursor, 1, 22)
|
||||||
expect_cursor_index(t, buffer.history.cursor, 1, 23)
|
expect_cursor_index(t, buffer.cursor, 1, 23)
|
||||||
|
|
||||||
contents := buffer_to_string(core.current_buffer(&e))
|
contents := buffer_to_string(core.current_buffer(&e))
|
||||||
testing.expectf(t, contents == expected_text, "got '%v', expected '%v'", contents, expected_text)
|
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) {
|
run_editor_frame :: proc(state: ^core.State, input: ArtificialInput, is_ctrl_pressed: ^bool) {
|
||||||
log.infof("running input: %v", input)
|
log.infof("running input: %v", input)
|
||||||
|
|
|
@ -66,7 +66,7 @@ UI_Direction :: enum {
|
||||||
BottomToTop,
|
BottomToTop,
|
||||||
}
|
}
|
||||||
|
|
||||||
open_element :: proc(state: ^State, kind: UI_Element_Kind, layout: UI_Layout) -> UI_Element {
|
open_element :: proc(state: ^State, kind: UI_Element_Kind, layout: UI_Layout) {
|
||||||
e := UI_Element {
|
e := UI_Element {
|
||||||
kind = kind,
|
kind = kind,
|
||||||
layout = layout,
|
layout = layout,
|
||||||
|
@ -93,8 +93,6 @@ open_element :: proc(state: ^State, kind: UI_Element_Kind, layout: UI_Layout) ->
|
||||||
state.curr_elements[state.num_curr] = e
|
state.curr_elements[state.num_curr] = e
|
||||||
state.current_open_element = state.num_curr
|
state.current_open_element = state.num_curr
|
||||||
state.num_curr += 1
|
state.num_curr += 1
|
||||||
|
|
||||||
return e
|
|
||||||
}
|
}
|
||||||
|
|
||||||
close_element :: proc(state: ^State, loc := #caller_location) -> UI_Layout {
|
close_element :: proc(state: ^State, loc := #caller_location) -> UI_Layout {
|
||||||
|
|
10
todo.md
10
todo.md
|
@ -5,8 +5,6 @@
|
||||||
- Closing the only panel crashes
|
- Closing the only panel crashes
|
||||||
|
|
||||||
# Planned Features
|
# Planned Features
|
||||||
- [ ] Highlight which panel is currently active
|
|
||||||
- [ ] Persist end of line cursor position
|
|
||||||
- Testing Harness
|
- Testing Harness
|
||||||
- [x] Replay user inputs and assert buffer contents/changes
|
- [x] Replay user inputs and assert buffer contents/changes
|
||||||
- [ ] Finish writing tests for all current user actions
|
- [ ] Finish writing tests for all current user actions
|
||||||
|
@ -33,10 +31,7 @@
|
||||||
- [x] Query across project
|
- [x] Query across project
|
||||||
- [x] Open file in new buffer
|
- [x] Open file in new buffer
|
||||||
- [x] Open file in new buffer at found location
|
- [x] Open file in new buffer at found location
|
||||||
- [ ] Preview file with context
|
- [ ] Preview file with context (instead of just the single matched line)
|
||||||
- [x] Show Context
|
|
||||||
- [ ] Properly show lines numbers
|
|
||||||
- [ ] Don't overlap result list with file preview
|
|
||||||
- [ ] Open Buffer Search
|
- [ ] Open Buffer Search
|
||||||
- Re-write the UI (again)
|
- Re-write the UI (again)
|
||||||
- [x] New UI
|
- [x] New UI
|
||||||
|
@ -50,7 +45,7 @@
|
||||||
- [x] Yank
|
- [x] Yank
|
||||||
- [x] Delete
|
- [x] Delete
|
||||||
- [ ] Change
|
- [ ] Change
|
||||||
- [x] Change
|
- [ ] Change
|
||||||
- [ ] Change word
|
- [ ] Change word
|
||||||
- [ ] Change inside delimiter
|
- [ ] Change inside delimiter
|
||||||
- Virtual Whitespace
|
- Virtual Whitespace
|
||||||
|
@ -58,3 +53,4 @@
|
||||||
- Command Search and Execution
|
- Command Search and Execution
|
||||||
- Refactor to remove generics added specifically for plugins
|
- Refactor to remove generics added specifically for plugins
|
||||||
- Palette based UI?
|
- Palette based UI?
|
||||||
|
- Persist end of line cursor position
|
Loading…
Reference in New Issue