diff --git a/README.md b/README.md index 8f1b65a..bb30410 100644 --- a/README.md +++ b/README.md @@ -6,16 +6,20 @@ A TUI IDE. # TODO -- [ ] Add folding support at tree-sitter level (basic folding is done). +- [ ] FIX: bug where `move_line_up/down` dont honor folding. + - idk .. wyyy but it is soo hard. somehow handle \n seperately without interfering with folding. + +- [ ] Do this thing where folds are properly shifted on newline addition / deletion. +- [ ] Add support for brackets/quotes to auto-close. (also for backspace) + - it also doesnt actually add a closer if it exists and is matched. - [ ] Add feature where doing enter uses tree-sitter to add newline with indentation. - it should also put stuff like `}` on the next line. -- [ ] Add this thing where selection double click on a bracket selects whole block. - - (only on the first time) and sets mode to WORD. - [ ] Add the highlight of block edges when cursor is on a bracket (or in). -- [ ] Add support for brackets/quotes to auto-close. (also for backspace) +- [ ] Add this thing where selection double click on a bracket selects whole block. + - (only on the first time) and sets mode to `WORD`. - [ ] Add support for virtual cursor where edits apply at all the places. -- [ ] Add search / replace along with search / virtual cursors are searched pos. - [ ] Add alt + click to set multiple cursors. +- [ ] Add search / replace along with search / virtual cursors are searched pos. - [ ] Add support for undo/redo. - [ ] Add `.scm` files for all the supported languages. (2/14) Done. - [ ] Add splash screen / minigame jumping. @@ -24,3 +28,4 @@ A TUI IDE. - [ ] Normalize / validate unicode on file open. - [ ] Add git stuff. - [ ] Fix bug where alt+up at eof adds extra line. +- [ ] Think about how i would keep fold states sensical if i added code prettying. diff --git a/grammar/bash.scm b/grammar/bash.scm index eac7aec..766116c 100644 --- a/grammar/bash.scm +++ b/grammar/bash.scm @@ -1,13 +1,3 @@ -[ - (function_definition) - (if_statement) - (case_statement) - (for_statement) - (while_statement) - (c_style_for_statement) - (heredoc_redirect) -] @fold - ;; #bd9ae6 #000000 0 0 0 1 [ "(" diff --git a/grammar/ruby.scm b/grammar/ruby.scm index 3dde384..2e89c49 100644 --- a/grammar/ruby.scm +++ b/grammar/ruby.scm @@ -1,19 +1,3 @@ -[ - (method) - (singleton_method) - (class) - (module) - (if) - (else) - (case) - (when) - (in) - (do_block) - (singleton_class) - (heredoc_content) - (lambda) -] @fold - ;; #ffffff #000000 0 0 0 1 [ (identifier) diff --git a/include/editor.h b/include/editor.h index 3c8ddbd..a9dbfff 100644 --- a/include/editor.h +++ b/include/editor.h @@ -15,7 +15,7 @@ #define WORD 1 #define LINE 2 -#define EXTRA_META 3 +#define EXTRA_META 4 struct Highlight { uint32_t fg; @@ -105,10 +105,9 @@ struct Editor { const TSLanguage *language; Queue edit_queue; std::vector query_map; - std::vector folded; + std::vector> folded; Spans spans; Spans def_spans; - std::map folded_node; uint32_t hooks[94]; bool jumper_set; }; @@ -124,7 +123,7 @@ Coord move_left(Editor *editor, Coord cursor, uint32_t number); Coord move_right(Editor *editor, Coord cursor, uint32_t number); void cursor_left(Editor *editor, uint32_t number); void cursor_right(Editor *editor, uint32_t number); -void scroll_up(Editor *editor, uint32_t number); +void scroll_up(Editor *editor, int32_t number); void scroll_down(Editor *editor, uint32_t number); void ensure_cursor(Editor *editor); void ensure_scroll(Editor *editor); diff --git a/include/knot.h b/include/knot.h index 8753373..d9303e6 100644 --- a/include/knot.h +++ b/include/knot.h @@ -117,6 +117,8 @@ LineIterator *begin_l_iter(Knot *root, uint32_t start_line); // freed by the caller char *next_line(LineIterator *it, uint32_t *out_len); +char *prev_line(LineIterator *it, uint32_t *out_len); + // Used to start an iterator over leaf data // root is the root of the rope // the caller must free the iterator after use diff --git a/src/editor.cc b/src/editor.cc index ec8a2d8..eef64b3 100644 --- a/src/editor.cc +++ b/src/editor.cc @@ -45,24 +45,6 @@ void free_editor(Editor *editor) { delete editor; } -void fold(Editor *editor, uint32_t start_line, uint32_t end_line) { - if (!editor) - return; - for (uint32_t i = start_line; i <= end_line && i < editor->size.row; i++) - editor->folded[i] = 1; - editor->folded[start_line] = 2; -} - -void update_render_fold_marker(uint32_t row, uint32_t cols) { - const char *marker = "... folded ..."; - uint32_t len = strlen(marker); - uint32_t i = 0; - for (; i < len && i < cols; i++) - update(row, i, (char[2]){marker[i], 0}, 0xc6c6c6, 0, 0); - for (; i < cols; i++) - update(row, i, " ", 0xc6c6c6, 0, 0); -} - void render_editor(Editor *editor) { uint32_t sel_start = 0, sel_end = 0; uint32_t numlen = @@ -74,6 +56,7 @@ void render_editor(Editor *editor) { if (editor->hooks[i] != 0) v.push_back({editor->hooks[i], '!' + i}); std::sort(v.begin(), v.end()); + auto hook_it = v.begin(); std::shared_lock knot_lock(editor->knot_mtx); if (editor->selection_active) { Coord start, end; @@ -135,9 +118,25 @@ void render_editor(Editor *editor) { span_cursor.sync(global_byte_offset); def_span_cursor.sync(global_byte_offset); while (rendered_rows < editor->size.row) { - if (editor->folded[line_index]) { - if (editor->folded[line_index] == 2) { - update_render_fold_marker(rendered_rows, render_width); + if (editor->folded[line_index].first) { + if (editor->folded[line_index].first == 2) { + update(editor->position.row + rendered_rows, editor->position.col, + "", 0xAAAAAA, 0, 0); + char buf[16]; + int len = snprintf(buf, sizeof(buf), "%*u", numlen - 3, line_index + 1); + uint32_t num_color = + editor->cursor.row == line_index ? 0xFFFFFF : 0x555555; + for (int i = 0; i < len; i++) + update(editor->position.row + rendered_rows, + editor->position.col + i + 2, (char[2]){buf[i], 0}, num_color, + 0, 0); + const char marker[15] = "... folded ..."; + uint32_t i = 0; + for (; i < 14 && i < render_width; i++) + update(rendered_rows, i + render_x, (char[2]){marker[i], 0}, 0xc6c6c6, + 0, 0); + for (; i < render_width; i++) + update(rendered_rows, i + render_x, " ", 0xc6c6c6, 0, 0); rendered_rows++; } do { @@ -152,7 +151,7 @@ void render_editor(Editor *editor) { free(line); line_index++; } while (line_index < editor->size.row && - editor->folded[line_index] == 1); + editor->folded[line_index].first == 1); continue; } uint32_t line_len; @@ -167,21 +166,27 @@ void render_editor(Editor *editor) { while (current_byte_offset < line_len && rendered_rows < editor->size.row) { uint32_t color = editor->cursor.row == line_index ? 0x222222 : 0; if (current_byte_offset == 0 || rendered_rows == 0) { - char buf[EXTRA_META + 16]; - char hook = ' '; - for (auto &p : v) { - if (p.first == line_index + 1) { - hook = p.second; + const char *hook = nullptr; + char h[2] = {0, 0}; + auto it2 = hook_it; + for (; it2 != v.end(); ++it2) { + if (it2->first == line_index + 1) { + h[0] = it2->second; + hook = h; + hook_it = it2; break; } } - int len = snprintf(buf, sizeof(buf), "%c%*u", hook, numlen - 2, - line_index + 1); + update(editor->position.row + rendered_rows, editor->position.col, hook, + 0xAAAAAA, 0, 0); + char buf[16]; + int len = snprintf(buf, sizeof(buf), "%*u", numlen - 3, line_index + 1); uint32_t num_color = editor->cursor.row == line_index ? 0xFFFFFF : 0x555555; for (int i = 0; i < len; i++) - update(editor->position.row + rendered_rows, editor->position.col + i, - std::string(1, buf[i]).c_str(), num_color, 0, 0); + update(editor->position.row + rendered_rows, + editor->position.col + i + 2, (char[2]){buf[i], 0}, num_color, + 0, 0); } else { for (uint32_t i = 0; i < numlen; i++) update(editor->position.row + rendered_rows, editor->position.col + i, @@ -252,21 +257,27 @@ void render_editor(Editor *editor) { if (line_len == 0 || (current_byte_offset >= line_len && rendered_rows == 0)) { uint32_t color = editor->cursor.row == line_index ? 0x222222 : 0; - char buf[EXTRA_META + 16]; - char hook = ' '; - for (auto &p : v) { - if (p.first == line_index + 1) { - hook = p.second; + const char *hook = nullptr; + char h[2] = {0, 0}; + auto it2 = hook_it; + for (; it2 != v.end(); ++it2) { + if (it2->first == line_index + 1) { + h[0] = it2->second; + hook = h; + hook_it = it2; break; } } - int len = - snprintf(buf, sizeof(buf), "%c%*u", hook, numlen - 2, line_index + 1); + update(editor->position.row + rendered_rows, editor->position.col, hook, + 0xAAAAAA, 0, 0); + char buf[16]; + int len = snprintf(buf, sizeof(buf), "%*u", numlen - 3, line_index + 1); uint32_t num_color = editor->cursor.row == line_index ? 0xFFFFFF : 0x555555; for (int i = 0; i < len; i++) - update(editor->position.row + rendered_rows, editor->position.col + i, - std::string(1, buf[i]).c_str(), num_color, 0, 0); + update(editor->position.row + rendered_rows, + editor->position.col + i + 2, (char[2]){buf[i], 0}, num_color, 0, + 0); if (editor->cursor.row == line_index) { cursor.row = editor->position.row + rendered_rows; cursor.col = render_x; diff --git a/src/editor_ctrl.cc b/src/editor_ctrl.cc index ba79f14..44f0e17 100644 --- a/src/editor_ctrl.cc +++ b/src/editor_ctrl.cc @@ -1,4 +1,3 @@ -#include extern "C" { #include "../libs/libgrapheme/grapheme.h" } @@ -49,6 +48,8 @@ void handle_editor_event(Editor *editor, KeyEvent event) { last_click_time = now; last_click_pos = cur_pos; Coord p = editor_hit_test(editor, event.mouse_x, event.mouse_y); + if (p.row == UINT32_MAX && p.col == UINT32_MAX) + return; editor->cursor_preffered = UINT32_MAX; if (click_count == 1) { editor->cursor = p; @@ -95,6 +96,8 @@ void handle_editor_event(Editor *editor, KeyEvent event) { case DRAG: if (event.mouse_button == LEFT_BTN) { Coord p = editor_hit_test(editor, event.mouse_x, event.mouse_y); + if (p.row == UINT32_MAX && p.col == UINT32_MAX) + return; editor->cursor_preffered = UINT32_MAX; mode = SELECT; if (!editor->selection_active) { @@ -208,6 +211,29 @@ void handle_editor_event(Editor *editor, KeyEvent event) { case NORMAL: if (event.key_type == KEY_CHAR && event.len == 1) { switch (event.c[0]) { + case 'u': + if (editor->root->line_count > 0) { + editor->cursor.row = editor->root->line_count - 1; + LineIterator *it = begin_l_iter(editor->root, editor->cursor.row); + if (!it) + break; + uint32_t line_len; + char *line = next_line(it, &line_len); + if (!line) + break; + if (line_len > 0 && line[line_len - 1] == '\n') + line_len--; + free(line); + free(it); + line_len = count_clusters(line, line_len, 0, line_len); + editor->cursor.col = line_len; + editor->cursor_preffered = UINT32_MAX; + mode = SELECT; + editor->selection_active = true; + editor->selection = {0, 0}; + editor->selection_type = LINE; + } + break; case 'a': mode = INSERT; cursor_right(editor, 1); @@ -235,6 +261,7 @@ void handle_editor_event(Editor *editor, KeyEvent event) { mode = SELECT; editor->selection_active = true; editor->selection = editor->cursor; + editor->selection_type = CHAR; break; case ';': case ':': @@ -328,6 +355,20 @@ void handle_editor_event(Editor *editor, KeyEvent event) { uint32_t len; char *text; switch (event.c[0]) { + case 'f': + if (editor->cursor.row != editor->selection.row) { + uint32_t start = MIN(editor->cursor.row, editor->selection.row); + uint32_t end = MAX(editor->cursor.row, editor->selection.row); + for (uint32_t row = start + 1; row <= end; row++) + editor->folded[row].first = 1; + editor->folded[start].first = 2; + editor->folded[start].second = end - start; + } + cursor_left(editor, 1); + cursor_down(editor, 1); + editor->selection_active = false; + mode = NORMAL; + break; case 0x1B: case 's': case 'v': @@ -376,7 +417,7 @@ void handle_editor_event(Editor *editor, KeyEvent event) { break; case JUMPER: if (event.key_type == KEY_CHAR && event.len == 1 && - (event.c[0] > '!' && event.c[0] < '~')) { + (event.c[0] >= '!' && event.c[0] <= '~')) { if (editor->jumper_set) { for (uint8_t i = 0; i < 94; i++) if (editor->hooks[i] == editor->cursor.row + 1) { @@ -387,7 +428,9 @@ void handle_editor_event(Editor *editor, KeyEvent event) { } else { uint32_t line = editor->hooks[event.c[0] - '!']; if (line > 0) { - editor->cursor = {line - 1, 0}; + if (editor->folded[--line].first) + break; + editor->cursor = {line, 0}; editor->cursor_preffered = UINT32_MAX; } } @@ -564,6 +607,7 @@ Coord editor_hit_test(Editor *editor, uint32_t x, uint32_t y) { x++; uint32_t numlen = EXTRA_META + static_cast(std::log10(editor->root->line_count + 1)); + bool is_gutter_click = (x < numlen); uint32_t render_width = editor->size.col - numlen; x = MAX(x, numlen) - numlen; uint32_t target_visual_row = y; @@ -575,11 +619,18 @@ Coord editor_hit_test(Editor *editor, uint32_t x, uint32_t y) { if (!it) return editor->scroll; while (visual_row <= target_visual_row) { - if (editor->folded[line_index]) { - if (editor->folded[line_index] == 2) { + if (editor->folded[line_index].first) { + if (editor->folded[line_index].first == 2) { if (visual_row == target_visual_row) { free(it); - return {line_index, 0}; + if (is_gutter_click) { + editor->folded[line_index].first = 0; + for (uint32_t row = line_index + 1; editor->folded[row].first == 1; + row++) + editor->folded[row].first = 0; + return {UINT32_MAX, UINT32_MAX}; + } + return {line_index - 1, 0}; } visual_row++; } @@ -589,7 +640,7 @@ Coord editor_hit_test(Editor *editor, uint32_t x, uint32_t y) { break; free(l); line_index++; - } while (editor->folded[line_index] == 1); + } while (editor->folded[line_index].first == 1); continue; } uint32_t line_len; @@ -642,6 +693,120 @@ Coord editor_hit_test(Editor *editor, uint32_t x, uint32_t y) { return editor->scroll; } +Coord move_right_pure(Editor *editor, Coord cursor, uint32_t number) { + Coord result = cursor; + if (!editor || !editor->root || number == 0) + return result; + + uint32_t row = result.row; + uint32_t col = result.col; + uint32_t line_len = 0; + + LineIterator *it = begin_l_iter(editor->root, row); + char *line = next_line(it, &line_len); + free(it); + + if (!line) + return result; + + if (line_len > 0 && line[line_len - 1] == '\n') + --line_len; + + while (number > 0) { + if (col >= line_len) { + free(line); + line = nullptr; + + uint32_t next_row = row + 1; + if (next_row >= editor->root->line_count) { + col = line_len; + break; + } + + row = next_row; + col = 0; + + it = begin_l_iter(editor->root, row); + line = next_line(it, &line_len); + free(it); + + if (!line) + break; + + if (line_len > 0 && line[line_len - 1] == '\n') + --line_len; + } else { + uint32_t inc = + grapheme_next_character_break_utf8(line + col, line_len - col); + if (inc == 0) + break; + col += inc; + } + number--; + } + + if (line) + free(line); + + result.row = row; + result.col = col; + return result; +} + +Coord move_left_pure(Editor *editor, Coord cursor, uint32_t number) { + Coord result = cursor; + if (!editor || !editor->root || number == 0) + return result; + uint32_t row = result.row; + uint32_t col = result.col; + uint32_t len = 0; + LineIterator *it = begin_l_iter(editor->root, row); + char *line = next_line(it, &len); + if (!line) { + free(it); + return result; + } + if (len > 0 && line[len - 1] == '\n') + --len; + bool iterator_ahead = true; + while (number > 0) { + if (col == 0) { + if (row == 0) + break; + if (iterator_ahead) { + free(prev_line(it, nullptr)); + iterator_ahead = false; + } + free(line); + line = nullptr; + row--; + line = prev_line(it, &len); + if (!line) + break; + if (len > 0 && line[len - 1] == '\n') + --len; + col = len; + } else { + uint32_t new_col = 0; + while (new_col < col) { + uint32_t inc = + grapheme_next_character_break_utf8(line + new_col, len - new_col); + if (new_col + inc >= col) + break; + new_col += inc; + } + col = new_col; + } + number--; + } + if (line) + free(line); + free(it); + result.row = row; + result.col = col; + return result; +} + Coord move_right(Editor *editor, Coord cursor, uint32_t number) { Coord result = cursor; if (!editor || !editor->root || number == 0) @@ -662,7 +827,7 @@ Coord move_right(Editor *editor, Coord cursor, uint32_t number) { line = nullptr; uint32_t next_row = row + 1; while (next_row < editor->root->line_count && - editor->folded[next_row] != 0) + editor->folded[next_row].first != 0) next_row++; if (next_row >= editor->root->line_count) { col = line_len; @@ -702,26 +867,35 @@ Coord move_left(Editor *editor, Coord cursor, uint32_t number) { uint32_t len = 0; LineIterator *it = begin_l_iter(editor->root, row); char *line = next_line(it, &len); - free(it); - if (!line) + if (!line) { + free(it); return result; + } if (len > 0 && line[len - 1] == '\n') --len; + bool iterator_ahead = true; while (number > 0) { if (col == 0) { - free(line); - line = nullptr; if (row == 0) break; - int32_t prev_row = row - 1; - while (prev_row >= 0 && editor->folded[prev_row] != 0) - prev_row--; - if (prev_row < 0) + if (iterator_ahead) { + free(prev_line(it, nullptr)); + iterator_ahead = false; + } + free(line); + line = nullptr; + while (row > 0) { + row--; + line = prev_line(it, &len); + if (!line) + break; + if (editor->folded[row].first != 0) { + free(line); + line = nullptr; + continue; + } break; - row = prev_row; - it = begin_l_iter(editor->root, row); - line = next_line(it, &len); - free(it); + } if (!line) break; if (len > 0 && line[len - 1] == '\n') @@ -742,6 +916,7 @@ Coord move_left(Editor *editor, Coord cursor, uint32_t number) { } if (line) free(line); + free(it); result.row = row; result.col = col; return result; @@ -767,7 +942,7 @@ void cursor_down(Editor *editor, uint32_t number) { editor->cursor.row = editor->root->line_count - 1; break; }; - if (editor->folded[editor->cursor.row] != 0) + if (editor->folded[editor->cursor.row].first != 0) number++; } while (--number > 0); free(it); @@ -778,7 +953,7 @@ void cursor_down(Editor *editor, uint32_t number) { } void cursor_up(Editor *editor, uint32_t number) { - if (!editor || !editor->root || number == 0) + if (!editor || !editor->root || number == 0 || editor->cursor.row == 0) return; LineIterator *it = begin_l_iter(editor->root, editor->cursor.row); uint32_t len; @@ -792,22 +967,26 @@ void cursor_up(Editor *editor, uint32_t number) { get_visual_col_from_bytes(line_content, len, editor->cursor.col); uint32_t visual_col = editor->cursor_preffered; free(line_content); - while (number > 0 && editor->cursor.row > 0) { + line_content = prev_line(it, &len); + do { + free(line_content); + line_content = prev_line(it, &len); + if (!line_content) { + editor->cursor.row = 0; + break; + } editor->cursor.row--; - if (editor->folded[editor->cursor.row] != 0) - continue; - number--; - } + if (editor->folded[editor->cursor.row].first != 0) + number++; + } while (--number > 0 && editor->cursor.row > 0); free(it); - it = begin_l_iter(editor->root, editor->cursor.row); - line_content = next_line(it, &len); - if (!line_content) { - free(it); - return; + if (line_content) { + editor->cursor.col = + get_bytes_from_visual_col(line_content, len, visual_col); + free(line_content); + } else { + editor->cursor.col = 0; } - editor->cursor.col = get_bytes_from_visual_col(line_content, len, visual_col); - free(line_content); - free(it); } void cursor_right(Editor *editor, uint32_t number) { @@ -837,13 +1016,23 @@ void move_line_up(Editor *editor) { lock.unlock(); return; } + if (line_len > 0 && line[line_len - 1] == '\n') + line_len--; line_cluster_len = count_clusters(line, line_len, 0, line_len); + uint32_t up_by = 1; + while (editor->cursor.row >= up_by && + editor->folded[editor->cursor.row - up_by].first) + up_by++; + if (up_by > 1) + up_by--; lock.unlock(); Coord cursor = editor->cursor; edit_erase(editor, {cursor.row, 0}, line_cluster_len); - edit_insert(editor, {cursor.row - 1, 0}, line, line_len); + edit_erase(editor, {cursor.row, 0}, -1); + edit_insert(editor, {cursor.row - up_by, 0}, (char *)"\n", 1); + edit_insert(editor, {cursor.row - up_by, 0}, line, line_len); free(line); - editor->cursor = {cursor.row - 1, cursor.col}; + editor->cursor = {cursor.row - up_by, cursor.col}; } else if (mode == SELECT) { uint32_t start_row = MIN(editor->cursor.row, editor->selection.row); uint32_t end_row = MAX(editor->cursor.row, editor->selection.row); @@ -880,13 +1069,24 @@ void move_line_down(Editor *editor) { lock.unlock(); return; } + if (line_len && line[line_len - 1] == '\n') + line_len--; line_cluster_len = count_clusters(line, line_len, 0, line_len); + uint32_t down_by = 1; + while (editor->folded[editor->cursor.row + down_by].first) + down_by++; + if (down_by > 1) + down_by--; + uint32_t ln; + line_to_byte(editor->root, editor->cursor.row + down_by - 1, &ln); lock.unlock(); Coord cursor = editor->cursor; edit_erase(editor, {cursor.row, 0}, line_cluster_len); - edit_insert(editor, {cursor.row + 1, 0}, line, line_len); + edit_erase(editor, {cursor.row, 0}, -1); + edit_insert(editor, {cursor.row + down_by, 0}, (char *)"\n", 1); + edit_insert(editor, {cursor.row + down_by, 0}, line, line_len); free(line); - editor->cursor = {cursor.row + 1, cursor.col}; + editor->cursor = {cursor.row + down_by, cursor.col}; } else if (mode == SELECT) { if (editor->cursor.row >= editor->root->line_count - 1 || editor->selection.row >= editor->root->line_count - 1) @@ -914,6 +1114,7 @@ void move_line_down(Editor *editor) { void edit_erase(Editor *editor, Coord pos, int64_t len) { if (len == 0) return; + uint32_t erased_rows, erase_start_row; if (len < 0) { std::shared_lock lock_1(editor->knot_mtx); uint32_t cursor_original = @@ -921,7 +1122,7 @@ void edit_erase(Editor *editor, Coord pos, int64_t len) { editor->cursor.col; TSPoint old_point = {pos.row, pos.col}; uint32_t byte_pos = line_to_byte(editor->root, pos.row, nullptr) + pos.col; - Coord point = move_left(editor, pos, -len); + Coord point = move_left_pure(editor, pos, -len); uint32_t start = line_to_byte(editor->root, point.row, nullptr) + point.col; if (cursor_original > start && cursor_original <= byte_pos) { editor->cursor = point; @@ -934,6 +1135,8 @@ void edit_erase(Editor *editor, Coord pos, int64_t len) { editor->cursor_preffered = UINT32_MAX; } lock_1.unlock(); + erased_rows = point.row - pos.row; + erase_start_row = pos.row; std::unique_lock lock_2(editor->knot_mtx); editor->root = erase(editor->root, start, byte_pos - start); lock_2.unlock(); @@ -961,7 +1164,7 @@ void edit_erase(Editor *editor, Coord pos, int64_t len) { editor->cursor.col; TSPoint old_point = {pos.row, pos.col}; uint32_t byte_pos = line_to_byte(editor->root, pos.row, nullptr) + pos.col; - Coord point = move_right(editor, pos, len); + Coord point = move_right_pure(editor, pos, len); uint32_t end = line_to_byte(editor->root, point.row, nullptr) + point.col; if (cursor_original > byte_pos && cursor_original <= end) { editor->cursor = pos; @@ -974,6 +1177,26 @@ void edit_erase(Editor *editor, Coord pos, int64_t len) { editor->cursor_preffered = UINT32_MAX; } lock_1.unlock(); + int32_t fold_start = -1; + int32_t fold_end = -1; + if (editor->folded[point.row].first > 0) { + fold_start = point.row; + while (fold_start > 0 && editor->folded[fold_start].first == 1) + fold_start--; + fold_end = point.row; + while (fold_end + 1 < editor->folded.size() && + editor->folded[fold_end + 1].first == 1) + fold_end++; + } + if (fold_start == point.row) + editor->folded.erase(editor->folded.begin() + point.row, + editor->folded.begin() + pos.row); + else if (fold_end == point.row) + editor->folded.erase(editor->folded.begin() + point.row, + editor->folded.begin()); + else if (fold_start != -1 && fold_start == fold_end) + editor->folded.erase(editor->folded.begin() + point.row, + editor->folded.begin()); std::unique_lock lock_2(editor->knot_mtx); editor->root = erase(editor->root, byte_pos, end - byte_pos); lock_2.unlock(); @@ -1013,8 +1236,6 @@ void edit_insert(Editor *editor, Coord pos, char *data, uint32_t len) { lock_1.unlock(); std::unique_lock lock_2(editor->knot_mtx); editor->root = insert(editor->root, byte_pos, data, len); - if (memchr(data, '\n', len)) - editor->folded.resize(editor->root->line_count + 2); lock_2.unlock(); uint32_t cols = 0; uint32_t rows = 0; @@ -1026,6 +1247,21 @@ void edit_insert(Editor *editor, Coord pos, char *data, uint32_t len) { cols++; } } + int32_t fold_start = -1; + int32_t fold_end = -1; + if (editor->folded[pos.row].first > 0) { + fold_start = pos.row; + while (fold_start > 0 && editor->folded[fold_start].first == 1) + fold_start--; + fold_end = pos.row; + while (fold_end + 1 < editor->folded.size() && + editor->folded[fold_end + 1].first == 1) + fold_end++; + } + if (fold_start == pos.row) + editor->folded.insert(editor->folded.begin() + pos.row, rows, {0, 0}); + else if (fold_end == pos.row) + editor->folded.insert(editor->folded.begin() + pos.row + 1, rows, {0, 0}); if (editor->tree) { TSInputEdit edit = { .start_byte = byte_pos, diff --git a/src/editor_scroll.cc b/src/editor_scroll.cc index 5dcb45a..05de218 100644 --- a/src/editor_scroll.cc +++ b/src/editor_scroll.cc @@ -1,4 +1,3 @@ -#include extern "C" { #include "../libs/libgrapheme/grapheme.h" } @@ -6,72 +5,108 @@ extern "C" { #include "../include/utils.h" #include -void scroll_up(Editor *editor, uint32_t number) { - number++; +void scroll_up(Editor *editor, int32_t number) { + if (!editor || number == 0) + return; uint32_t numlen = EXTRA_META + static_cast(std::log10(editor->root->line_count + 1)); uint32_t render_width = editor->size.col - numlen; - Coord *scroll_queue = (Coord *)malloc(sizeof(Coord) * number); - uint32_t q_head = 0; - uint32_t q_size = 0; - int line_index = editor->scroll.row - number; - line_index = line_index < 0 ? 0 : line_index; + uint32_t line_index = editor->scroll.row; LineIterator *it = begin_l_iter(editor->root, line_index); - if (!it) { - free(scroll_queue); + if (!it) + return; + uint32_t len; + char *line = next_line(it, &len); + if (!line) { + free(it); return; } - for (uint32_t i = line_index; i <= editor->scroll.row; i++) { - uint32_t line_len; - char *line = next_line(it, &line_len); - if (!line) - break; - if (line[line_len - 1] == '\n') - --line_len; - uint32_t offset = 0; - uint32_t col = 0; - if (q_size < number) { - scroll_queue[(q_head + q_size) % number] = {i, 0}; - q_size++; - } else { - scroll_queue[q_head] = {i, 0}; - q_head = (q_head + 1) % number; + if (len > 0 && line[len - 1] == '\n') + len--; + uint32_t current_byte_offset = 0; + uint32_t col = 0; + std::vector segment_starts; + segment_starts.reserve(16); + if (current_byte_offset < editor->scroll.col) + segment_starts.push_back(0); + while (current_byte_offset < editor->scroll.col && + current_byte_offset < len) { + uint32_t cluster_len = grapheme_next_character_break_utf8( + line + current_byte_offset, len - current_byte_offset); + int width = display_width(line + current_byte_offset, cluster_len); + if (col + width > render_width) { + segment_starts.push_back(current_byte_offset); + col = 0; } - if (i == editor->scroll.row && 0 == editor->scroll.col) { - editor->scroll = scroll_queue[q_head]; + current_byte_offset += cluster_len; + col += width; + } + for (auto it_seg = segment_starts.rbegin(); it_seg != segment_starts.rend(); + ++it_seg) { + if (--number == 0) { + editor->scroll = {line_index, *it_seg}; free(line); free(it); - free(scroll_queue); return; } - while (offset < line_len) { - uint32_t inc = - grapheme_next_character_break_utf8(line + offset, line_len - offset); - int width = display_width(line + offset, inc); - if (col + width > render_width) { - if (q_size < number) { - scroll_queue[(q_head + q_size) % number] = {i, offset}; - q_size++; - } else { - scroll_queue[q_head] = {i, offset}; - q_head = (q_head + 1) % number; - } - if (i == editor->scroll.row && offset >= editor->scroll.col) { - editor->scroll = scroll_queue[q_head]; + } + free(line); + line = prev_line(it, &len); + if (!line) { + editor->scroll = {0, 0}; + free(it); + return; + } + free(line); + do { + line_index--; + line = prev_line(it, &len); + if (!line) { + editor->scroll = {0, 0}; + free(it); + return; + } + if (editor->folded[line_index].first) { + if (editor->folded[line_index].first == 2) { + if (--number == 0) { + editor->scroll = {line_index, 0}; free(line); free(it); - free(scroll_queue); return; } + } + free(line); + continue; + } + if (len > 0 && line[len - 1] == '\n') + len--; + current_byte_offset = 0; + col = 0; + std::vector segment_starts; + segment_starts.reserve(16); + segment_starts.push_back(0); + while (current_byte_offset < len) { + uint32_t cluster_len = grapheme_next_character_break_utf8( + line + current_byte_offset, len - current_byte_offset); + int width = display_width(line + current_byte_offset, cluster_len); + if (col + width > render_width) { + segment_starts.push_back(current_byte_offset); col = 0; } + current_byte_offset += cluster_len; col += width; - offset += inc; + } + for (auto it_seg = segment_starts.rbegin(); it_seg != segment_starts.rend(); + ++it_seg) { + if (--number == 0) { + editor->scroll = {line_index, *it_seg}; + free(line); + free(it); + return; + } } free(line); - } - editor->scroll = {0, 0}; - free(scroll_queue); + } while (number > 0); free(it); } @@ -92,8 +127,8 @@ void scroll_down(Editor *editor, uint32_t number) { uint32_t visual_seen = 0; bool first_visual_line = true; while (true) { - if (editor->folded[line_index]) { - if (editor->folded[line_index] == 2) { + if (editor->folded[line_index].first) { + if (editor->folded[line_index].first == 2) { Coord fold_coord = {line_index, 0}; if (q_size < max_visual_lines) { scroll_queue[(q_head + q_size) % max_visual_lines] = fold_coord; @@ -117,7 +152,7 @@ void scroll_down(Editor *editor, uint32_t number) { } free(line); line_index++; - } while (editor->folded[line_index] == 1); + } while (editor->folded[line_index].first == 1); continue; } uint32_t line_len; @@ -181,7 +216,14 @@ void scroll_down(Editor *editor, uint32_t number) { void ensure_cursor(Editor *editor) { std::shared_lock knot_lock(editor->knot_mtx); if (editor->cursor < editor->scroll) { - editor->cursor = editor->scroll; + uint32_t line_idx = editor->scroll.row; + while (line_idx < editor->root->line_count && + editor->folded[line_idx].first) + line_idx++; + editor->cursor.row = line_idx; + editor->cursor.col = + line_idx == editor->scroll.row ? editor->scroll.col : 0; + editor->cursor_preffered = UINT32_MAX; return; } uint32_t numlen = @@ -197,15 +239,11 @@ void ensure_cursor(Editor *editor) { while (true) { if (visual_rows >= editor->size.row) break; - if (editor->folded[line_index]) { - if (editor->folded[line_index] == 2) { + if (editor->folded[line_index].first) { + if (editor->folded[line_index].first == 2) { Coord c = {line_index, 0}; last_visible = c; visual_rows++; - if (line_index == editor->cursor.row) { - free(it); - return; - } } do { char *line = next_line(it, nullptr); @@ -213,7 +251,7 @@ void ensure_cursor(Editor *editor) { break; free(line); line_index++; - } while (editor->folded[line_index] == 1); + } while (editor->folded[line_index].first == 1); continue; } uint32_t line_len; @@ -260,7 +298,12 @@ void ensure_cursor(Editor *editor) { free(line); line_index++; } - editor->cursor = last_visible; + uint32_t last_real_row = last_visible.row; + while (last_visible.row > 0 && editor->folded[last_visible.row].first) + last_visible.row--; + editor->cursor.row = last_visible.row; + editor->cursor.col = last_visible.row == last_real_row ? last_visible.col : 0; + editor->cursor_preffered = UINT32_MAX; free(it); } @@ -319,8 +362,8 @@ void ensure_scroll(Editor *editor) { uint32_t q_size = 0; bool first_visual_line = true; while (true) { - if (editor->folded[line_index]) { - if (editor->folded[line_index] == 2) { + if (editor->folded[line_index].first) { + if (editor->folded[line_index].first == 2) { Coord fold_coord = {line_index, 0}; if (q_size < max_visual_lines) { scroll_queue[(q_head + q_size) % max_visual_lines] = fold_coord; @@ -341,7 +384,7 @@ void ensure_scroll(Editor *editor) { free(line); line_index++; } while (line_index < editor->size.row && - editor->folded[line_index] == 1); + editor->folded[line_index].first == 1); continue; } uint32_t line_len; diff --git a/src/knot.cc b/src/knot.cc index aaf8d7f..452962c 100644 --- a/src/knot.cc +++ b/src/knot.cc @@ -520,6 +520,86 @@ LineIterator *begin_l_iter(Knot *root, uint32_t start_line) { return it; } +static inline void iter_retreat_leaf(LineIterator *it) { + if (it->top == 0) { + it->node = nullptr; + return; + } + Knot *curr = it->stack[--it->top]; + while (it->top > 0) { + Knot *parent = it->stack[it->top - 1]; + if (parent->right == curr && parent->left) { + Knot *target = parent->left; + while (target) { + it->stack[it->top++] = target; + if (!target->left && !target->right) { + if (target->char_count == 0) + break; + it->node = target; + it->offset = target->char_count; + return; + } + target = (target->right) ? target->right : target->left; + } + } + curr = it->stack[--it->top]; + } + it->node = nullptr; +} + +static void str_reverse(char *begin, char *end) { + char temp; + while (begin < end) { + temp = *begin; + *begin++ = *end; + *end-- = temp; + } +} + +char *prev_line(LineIterator *it, uint32_t *out_len) { + if (!it || !it->node) + return nullptr; + size_t capacity = 128; + size_t len = 0; + char *buffer = (char *)malloc(capacity); + if (!buffer) + return nullptr; + while (it->node) { + if (it->offset == 0) { + iter_retreat_leaf(it); + if (!it->node) + break; + } + it->offset--; + char c = it->node->data[it->offset]; + if (c == '\n') { + if (len > 0) { + it->offset++; + break; + } + } + if (len + 1 >= capacity) { + capacity *= 2; + char *new_buf = (char *)realloc(buffer, capacity); + if (!new_buf) { + free(buffer); + return nullptr; + } + buffer = new_buf; + } + buffer[len++] = c; + } + if (len > 0) { + buffer[len] = '\0'; + str_reverse(buffer, buffer + len - 1); + if (out_len) + *out_len = len; + return buffer; + } + free(buffer); + return nullptr; +} + static inline void iter_advance_leaf(LineIterator *it) { if (it->top == 0) { it->node = nullptr; @@ -533,6 +613,8 @@ static inline void iter_advance_leaf(LineIterator *it) { while (curr) { it->stack[it->top++] = curr; if (!curr->left && !curr->right) { + if (curr->char_count == 0) + break; it->node = curr; it->offset = 0; return; diff --git a/src/utils.cc b/src/utils.cc index 201ef5e..c442089 100644 --- a/src/utils.cc +++ b/src/utils.cc @@ -17,6 +17,15 @@ extern "C" { #include #include +uint64_t fnv1a_64(const char *s, size_t len) { + uint64_t hash = 1469598103934665603ull; + for (size_t i = 0; i < len; ++i) { + hash ^= (uint8_t)s[i]; + hash *= 1099511628211ull; + } + return hash; +} + char *get_from_clipboard(uint32_t *out_len) { FILE *pipe = popen("xclip -selection clipboard -o", "r"); if (!pipe) {