diff --git a/README.md b/README.md index cf2f864..d9fe89c 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,11 @@ A TUI IDE. # TODO - [ ] Fix problem highlighting selection when line is empty. +- [ ] Add mouse support. +- [ ] Add struct as union of structs that can take input. or similar + - then send input to them. + - Also background thread will run worker function for whatever struct is selected. + - And maybe even a list of active editors etc? - [ ] Add ui api for setting cursor types. - [ ] Use that api to set cursor for selection/insertion/normal etc properly. - Sorta unrelated but check kutuwm + kitty problem not keeping cursor mode properly. @@ -17,7 +22,6 @@ A TUI IDE. - [ ] Add support for ctrl + arrow key for moving words. - [ ] Add support for ctrl + backspace / delete for deleting words. - [ ] Add underline highlight for current word and all occurences. -- [ ] Add mouse support. - [ ] Add `hooks` in files that can be set/unset/jumped to. - [ ] Add folding support at tree-sitter level (basic folding is done). - [ ] Add feature where doing enter uses tree-sitter to add newline with indentation. @@ -30,3 +34,4 @@ A TUI IDE. - [ ] Add support for LSP & autocomplete / snippets. - [ ] Add codeium/copilot support. - [ ] Add git stuff. +- [ ] Add splash screen / minigame jumping. diff --git a/include/editor.h b/include/editor.h index 43276b9..eab49e7 100644 --- a/include/editor.h +++ b/include/editor.h @@ -111,6 +111,9 @@ void cursor_up(Editor *editor, uint32_t number); void cursor_down(Editor *editor, 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_down(Editor *editor, uint32_t number); +void ensure_cursor(Editor *editor); void ensure_scroll(Editor *editor); void apply_edit(std::vector &spans, uint32_t x, int64_t y); void edit_erase(Editor *editor, Coord pos, int64_t len); diff --git a/src/editor.cc b/src/editor.cc index fed2c6f..9709a41 100644 --- a/src/editor.cc +++ b/src/editor.cc @@ -48,148 +48,6 @@ void free_editor(Editor *editor) { delete editor; } -void ensure_scroll(Editor *editor) { - std::shared_lock knot_lock(editor->knot_mtx); - if (editor->cursor.row < editor->scroll.row || - (editor->cursor.row == editor->scroll.row && - editor->cursor.col <= editor->scroll.col)) { - LineIterator *it = begin_l_iter(editor->root, editor->cursor.row); - if (!it) - return; - uint32_t len; - char *line = next_line(it, &len); - if (!line) { - free(it); - return; - } - if (len > 0 && line[len - 1] == '\n') - --len; - uint32_t rows = 1; - uint32_t cols = 0; - uint32_t offset = 0; - uint32_t old_offset = 0; - while (offset < len) { - uint32_t inc = - grapheme_next_character_break_utf8(line + offset, len - offset); - int width = display_width(line + offset, inc); - if (cols + width > editor->size.col) { - rows++; - cols = 0; - if (editor->cursor.col > old_offset && editor->cursor.col <= offset) { - editor->scroll.row = editor->cursor.row; - editor->scroll.col = old_offset; - free(line); - free(it); - return; - } - old_offset = offset; - } - cols += width; - offset += inc; - } - free(line); - free(it); - editor->scroll.row = editor->cursor.row; - editor->scroll.col = (editor->cursor.col == 0) ? 0 : old_offset; - } else { - uint32_t line_index = editor->scroll.row; - LineIterator *it = begin_l_iter(editor->root, line_index); - if (!it) - return; - uint32_t max_visual_lines = editor->size.row; - Coord *scroll_queue = (Coord *)malloc(sizeof(Coord) * max_visual_lines); - uint32_t q_head = 0; - uint32_t q_size = 0; - bool first_visual_line = true; - while (true) { - if (editor->folded[line_index]) { - if (editor->folded[line_index] == 2) { - Coord fold_coord = {line_index, 0}; - if (q_size < max_visual_lines) { - scroll_queue[(q_head + q_size) % max_visual_lines] = fold_coord; - q_size++; - } else { - scroll_queue[q_head] = fold_coord; - q_head = (q_head + 1) % max_visual_lines; - } - if (line_index == editor->cursor.row) { - editor->scroll = scroll_queue[q_head]; - break; - } - } - do { - char *line = next_line(it, nullptr); - if (!line) - break; - free(line); - line_index++; - } while (line_index < editor->size.row && - editor->folded[line_index] == 1); - continue; - } - 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--; - uint32_t current_byte_offset = 0; - if (first_visual_line) { - current_byte_offset += editor->scroll.col; - first_visual_line = false; - } - while (current_byte_offset < line_len || - (line_len == 0 && current_byte_offset == 0)) { - Coord current_coord = {line_index, current_byte_offset}; - if (q_size < max_visual_lines) { - scroll_queue[(q_head + q_size) % max_visual_lines] = current_coord; - q_size++; - } else { - scroll_queue[q_head] = current_coord; - q_head = (q_head + 1) % max_visual_lines; - } - uint32_t col = 0; - uint32_t local_render_offset = 0; - uint32_t line_left = line_len - current_byte_offset; - while (line_left > 0 && col < editor->size.col) { - uint32_t cluster_len = grapheme_next_character_break_utf8( - line + current_byte_offset + local_render_offset, line_left); - int width = display_width( - line + current_byte_offset + local_render_offset, cluster_len); - if (col + width > editor->size.col) - break; - local_render_offset += cluster_len; - line_left -= cluster_len; - col += width; - } - if (line_index == editor->cursor.row) { - bool cursor_found = false; - if (editor->cursor.col >= current_byte_offset && - editor->cursor.col < current_byte_offset + local_render_offset) - cursor_found = true; - else if (editor->cursor.col == line_len && - current_byte_offset + local_render_offset == line_len) - cursor_found = true; - if (cursor_found) { - editor->scroll = scroll_queue[q_head]; - free(line); - free(scroll_queue); - free(it); - return; - } - } - current_byte_offset += local_render_offset; - if (line_len == 0) - break; - } - line_index++; - free(line); - } - free(scroll_queue); - free(it); - } -} - void fold(Editor *editor, uint32_t start_line, uint32_t end_line) { if (!editor) return; @@ -208,29 +66,6 @@ void update_render_fold_marker(uint32_t row, uint32_t cols) { update(row, i, " ", 0xc6c6c6, 0, 0); } -void apply_edit(std::vector &spans, uint32_t x, int64_t y) { - Span key{.start = x, .end = 0, .hl = nullptr}; - auto it = std::lower_bound( - spans.begin(), spans.end(), key, - [](const Span &a, const Span &b) { return a.start < b.start; }); - size_t idx = std::distance(spans.begin(), it); - while (idx > 0 && spans.at(idx - 1).end > x) - --idx; - for (size_t i = idx; i < spans.size();) { - Span &s = spans.at(i); - if (s.start < x && s.end > x) { - s.end += y; - } else if (s.start > x) { - s.start += y; - s.end += y; - } - if (s.end <= s.start) - spans.erase(spans.begin() + i); - else - ++i; - } -} - void render_editor(Editor *editor) { uint32_t sel_start = 0, sel_end = 0; if (editor->selection_active) { diff --git a/src/editor_ctrl.cc b/src/editor_ctrl.cc index bbe8c2b..f4e6d07 100644 --- a/src/editor_ctrl.cc +++ b/src/editor_ctrl.cc @@ -462,3 +462,26 @@ void edit_insert(Editor *editor, Coord pos, char *data, uint32_t len) { if (editor->spans.mid_parse) editor->spans.edits.push({byte_pos, len}); } + +void apply_edit(std::vector &spans, uint32_t x, int64_t y) { + Span key{.start = x, .end = 0, .hl = nullptr}; + auto it = std::lower_bound( + spans.begin(), spans.end(), key, + [](const Span &a, const Span &b) { return a.start < b.start; }); + size_t idx = std::distance(spans.begin(), it); + while (idx > 0 && spans.at(idx - 1).end > x) + --idx; + for (size_t i = idx; i < spans.size();) { + Span &s = spans.at(i); + if (s.start < x && s.end > x) { + s.end += y; + } else if (s.start > x) { + s.start += y; + s.end += y; + } + if (s.end <= s.start) + spans.erase(spans.begin() + i); + else + ++i; + } +} diff --git a/src/editor_scroll.cc b/src/editor_scroll.cc new file mode 100644 index 0000000..645c32d --- /dev/null +++ b/src/editor_scroll.cc @@ -0,0 +1,394 @@ +#include +extern "C" { +#include "../libs/libgrapheme/grapheme.h" +} +#include "../include/editor.h" +#include "../include/utils.h" + +void scroll_up(Editor *editor, uint32_t number) { + number++; + 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; + LineIterator *it = begin_l_iter(editor->root, line_index); + if (!it) { + free(scroll_queue); + 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 (i == editor->scroll.row && 0 == editor->scroll.col) { + editor->scroll = scroll_queue[q_head]; + 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 > editor->size.col) { + 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); + free(it); + free(scroll_queue); + return; + } + col = 0; + } + col += width; + offset += inc; + } + free(line); + } + editor->scroll = {0, 0}; + free(scroll_queue); + free(it); +} + +void scroll_down(Editor *editor, uint32_t number) { + if (!editor || number == 0) + return; + uint32_t line_index = editor->scroll.row; + LineIterator *it = begin_l_iter(editor->root, line_index); + if (!it) + return; + const uint32_t max_visual_lines = editor->size.row; + Coord *scroll_queue = (Coord *)malloc(sizeof(Coord) * max_visual_lines); + uint32_t q_head = 0; + uint32_t q_size = 0; + uint32_t visual_seen = 0; + bool first_visual_line = true; + while (true) { + if (editor->folded[line_index]) { + if (editor->folded[line_index] == 2) { + Coord fold_coord = {line_index, 0}; + if (q_size < max_visual_lines) { + scroll_queue[(q_head + q_size) % max_visual_lines] = fold_coord; + q_size++; + } else { + scroll_queue[q_head] = fold_coord; + q_head = (q_head + 1) % max_visual_lines; + } + visual_seen++; + if (visual_seen >= number + max_visual_lines) { + editor->scroll = scroll_queue[q_head]; + break; + } + } + do { + char *line = next_line(it, nullptr); + if (!line) { + free(scroll_queue); + free(it); + return; + } + free(line); + line_index++; + } while (editor->folded[line_index] == 1); + continue; + } + uint32_t line_len; + char *line = next_line(it, &line_len); + if (!line) + break; + if (line_len && line[line_len - 1] == '\n') + line_len--; + uint32_t current_byte_offset = 0; + if (first_visual_line) { + current_byte_offset += editor->scroll.col; + first_visual_line = false; + } + while (current_byte_offset < line_len || + (line_len == 0 && current_byte_offset == 0)) { + Coord coord = {line_index, current_byte_offset}; + if (q_size < max_visual_lines) { + scroll_queue[(q_head + q_size) % max_visual_lines] = coord; + q_size++; + } else { + scroll_queue[q_head] = coord; + q_head = (q_head + 1) % max_visual_lines; + } + visual_seen++; + if (visual_seen >= number + max_visual_lines) { + editor->scroll = scroll_queue[q_head]; + free(line); + free(scroll_queue); + free(it); + return; + } + uint32_t col = 0; + uint32_t local_render_offset = 0; + uint32_t left = line_len - current_byte_offset; + while (left > 0 && col < editor->size.col) { + uint32_t cluster_len = grapheme_next_character_break_utf8( + line + current_byte_offset + local_render_offset, left); + int width = display_width( + line + current_byte_offset + local_render_offset, cluster_len); + if (col + width > editor->size.col) + break; + local_render_offset += cluster_len; + left -= cluster_len; + col += width; + } + current_byte_offset += local_render_offset; + if (line_len == 0) + break; + } + free(line); + line_index++; + } + free(it); + free(scroll_queue); +} + +void ensure_cursor(Editor *editor) { + std::shared_lock knot_lock(editor->knot_mtx); + if (editor->cursor.row < editor->scroll.row || + (editor->cursor.row == editor->scroll.row && + editor->cursor.col < editor->scroll.col)) { + editor->cursor = editor->scroll; + return; + } + uint32_t visual_rows = 0; + uint32_t line_index = editor->scroll.row; + bool first_visual_line = true; + LineIterator *it = begin_l_iter(editor->root, line_index); + if (!it) + return; + Coord last_visible = editor->scroll; + while (true) { + if (visual_rows >= editor->size.row) + break; + if (editor->folded[line_index]) { + if (editor->folded[line_index] == 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); + if (!line) + break; + free(line); + line_index++; + } while (editor->folded[line_index] == 1); + continue; + } + 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--; + uint32_t offset = first_visual_line ? editor->scroll.col : 0; + first_visual_line = false; + while (offset < line_len || (line_len == 0 && offset == 0)) { + Coord current = {line_index, offset}; + last_visible = current; + visual_rows++; + if (line_index == editor->cursor.row) { + if (editor->cursor.col >= offset) { + free(line); + free(it); + return; + } + } + if (visual_rows >= editor->size.row) + break; + uint32_t col = 0; + uint32_t advance = 0; + uint32_t left = line_len - offset; + while (left > 0 && col < editor->size.col) { + uint32_t g = + grapheme_next_character_break_utf8(line + offset + advance, left); + int w = display_width(line + offset + advance, g); + if (col + w > editor->size.col) + break; + advance += g; + left -= g; + col += w; + } + if (advance == 0) + break; + offset += advance; + if (line_len == 0) + break; + } + free(line); + line_index++; + } + editor->cursor = last_visible; + free(it); +} + +void ensure_scroll(Editor *editor) { + std::shared_lock knot_lock(editor->knot_mtx); + if (editor->cursor.row < editor->scroll.row || + (editor->cursor.row == editor->scroll.row && + editor->cursor.col < editor->scroll.col)) { + LineIterator *it = begin_l_iter(editor->root, editor->cursor.row); + if (!it) + return; + uint32_t len; + char *line = next_line(it, &len); + if (!line) { + free(it); + return; + } + if (len > 0 && line[len - 1] == '\n') + --len; + uint32_t rows = 1; + uint32_t cols = 0; + uint32_t offset = 0; + uint32_t old_offset = 0; + while (offset < len) { + uint32_t inc = + grapheme_next_character_break_utf8(line + offset, len - offset); + int width = display_width(line + offset, inc); + if (cols + width > editor->size.col) { + rows++; + cols = 0; + if (editor->cursor.col > old_offset && editor->cursor.col <= offset) { + editor->scroll.row = editor->cursor.row; + editor->scroll.col = old_offset; + free(line); + free(it); + return; + } + old_offset = offset; + } + cols += width; + offset += inc; + } + free(line); + free(it); + editor->scroll.row = editor->cursor.row; + editor->scroll.col = (editor->cursor.col == 0) ? 0 : old_offset; + } else { + uint32_t line_index = editor->scroll.row; + LineIterator *it = begin_l_iter(editor->root, line_index); + if (!it) + return; + uint32_t max_visual_lines = editor->size.row; + Coord *scroll_queue = (Coord *)malloc(sizeof(Coord) * max_visual_lines); + uint32_t q_head = 0; + uint32_t q_size = 0; + bool first_visual_line = true; + while (true) { + if (editor->folded[line_index]) { + if (editor->folded[line_index] == 2) { + Coord fold_coord = {line_index, 0}; + if (q_size < max_visual_lines) { + scroll_queue[(q_head + q_size) % max_visual_lines] = fold_coord; + q_size++; + } else { + scroll_queue[q_head] = fold_coord; + q_head = (q_head + 1) % max_visual_lines; + } + if (line_index == editor->cursor.row) { + editor->scroll = scroll_queue[q_head]; + break; + } + } + do { + char *line = next_line(it, nullptr); + if (!line) + break; + free(line); + line_index++; + } while (line_index < editor->size.row && + editor->folded[line_index] == 1); + continue; + } + 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--; + uint32_t current_byte_offset = 0; + if (first_visual_line) { + current_byte_offset += editor->scroll.col; + first_visual_line = false; + } + while (current_byte_offset < line_len || + (line_len == 0 && current_byte_offset == 0)) { + Coord current_coord = {line_index, current_byte_offset}; + if (q_size < max_visual_lines) { + scroll_queue[(q_head + q_size) % max_visual_lines] = current_coord; + q_size++; + } else { + scroll_queue[q_head] = current_coord; + q_head = (q_head + 1) % max_visual_lines; + } + uint32_t col = 0; + uint32_t local_render_offset = 0; + uint32_t line_left = line_len - current_byte_offset; + while (line_left > 0 && col < editor->size.col) { + uint32_t cluster_len = grapheme_next_character_break_utf8( + line + current_byte_offset + local_render_offset, line_left); + int width = display_width( + line + current_byte_offset + local_render_offset, cluster_len); + if (col + width > editor->size.col) + break; + local_render_offset += cluster_len; + line_left -= cluster_len; + col += width; + } + if (line_index == editor->cursor.row) { + bool cursor_found = false; + if (editor->cursor.col >= current_byte_offset && + editor->cursor.col < current_byte_offset + local_render_offset) + cursor_found = true; + else if (editor->cursor.col == line_len && + current_byte_offset + local_render_offset == line_len) + cursor_found = true; + if (cursor_found) { + editor->scroll = scroll_queue[q_head]; + free(line); + free(scroll_queue); + free(it); + return; + } + } + current_byte_offset += local_render_offset; + if (line_len == 0) + break; + } + line_index++; + free(line); + } + free(scroll_queue); + free(it); + } +} diff --git a/src/main.cc b/src/main.cc index 8bb8756..c710551 100644 --- a/src/main.cc +++ b/src/main.cc @@ -24,8 +24,11 @@ void input_listener() { KeyEvent event = read_key(); if (event.key_type == KEY_NONE) continue; - if (event.key_type == KEY_CHAR && *event.c == CTRL('q')) + if (event.key_type == KEY_CHAR && event.len == 1 && *event.c == CTRL('q')) { + free(event.c); running = false; + return; + } event_queue.push(event); std::this_thread::sleep_for(std::chrono::microseconds(100)); } @@ -72,6 +75,22 @@ void handle_editor_event(Editor *editor, KeyEvent event) { case ' ': cursor_right(editor, 1); break; + case '\r': + case '\n': + cursor_down(editor, 1); + break; + case '\\': + case '|': + cursor_up(editor, 1); + break; + case CTRL('d'): + scroll_down(editor, 1); + ensure_cursor(editor); + break; + case CTRL('u'): + scroll_up(editor, 1); + ensure_cursor(editor); + break; } } break; diff --git a/src/ts.cc b/src/ts.cc index a9dae70..ee1b1d1 100644 --- a/src/ts.cc +++ b/src/ts.cc @@ -1,7 +1,6 @@ #include "../include/ts.h" #include "../include/editor.h" #include "../include/knot.h" -#include "../include/main.h" #include #include #include @@ -11,9 +10,8 @@ std::unordered_map regex_cache; void clear_regex_cache() { - for (auto &kv : regex_cache) { + for (auto &kv : regex_cache) pcre2_code_free(kv.second); - } regex_cache.clear(); } @@ -168,10 +166,6 @@ static inline bool ts_predicate(TSQuery *query, const TSQueryMatch &match, const char *read_ts(void *payload, uint32_t byte_index, TSPoint, uint32_t *bytes_read) { - if (!running) { - *bytes_read = 0; - return ""; - } Editor *editor = (Editor *)payload; if (byte_index >= editor->root->char_count) { *bytes_read = 0; @@ -201,8 +195,6 @@ void ts_collect_spans(Editor *editor) { if (editor->tree) copy = ts_tree_copy(editor->tree); knot_mtx.unlock(); - if (!running) - return; std::vector edits; TSInputEdit edit; if (copy) @@ -234,13 +226,9 @@ void ts_collect_spans(Editor *editor) { new_spans.reserve(4096); TSQueryMatch match; while (ts_query_cursor_next_match(cursor, &match)) { - if (!running) - break; if (!ts_predicate(editor->query, match, editor->root)) continue; for (uint32_t i = 0; i < match.capture_count; i++) { - if (!running) - break; TSQueryCapture cap = match.captures[i]; uint32_t start = ts_node_start_byte(cap.node); uint32_t end = ts_node_end_byte(cap.node); @@ -251,8 +239,6 @@ void ts_collect_spans(Editor *editor) { } ts_query_cursor_delete(cursor); ts_tree_delete(copy); - if (!running) - return; std::sort(new_spans.begin(), new_spans.end()); std::pair span_edit; while (editor->spans.edits.pop(span_edit))