From aa25bf0ac3e7b74cfa4533ffe292f358de730e69 Mon Sep 17 00:00:00 2001 From: Syed Daanish Date: Sun, 14 Dec 2025 09:49:17 +0000 Subject: [PATCH] Fixes and cleanup --- Makefile | 8 ++-- README.md | 8 +++- include/editor.h | 4 ++ include/utils.h | 22 +++++++++ src/editor.cc | 19 ++++++-- src/editor_ctrl.cc | 111 ++++++++++++++++++++++++++++++++++++++++++- src/editor_scroll.cc | 10 ++-- src/main.cc | 29 +++++------ 8 files changed, 175 insertions(+), 36 deletions(-) diff --git a/Makefile b/Makefile index f2a5a45..f11d890 100644 --- a/Makefile +++ b/Makefile @@ -11,12 +11,12 @@ CXX_RELEASE := $(CCACHE) clang++ CFLAGS_DEBUG := -std=c++20 -Wall -Wextra -O0 -fno-inline -gsplit-dwarf -g -fsanitize=address -fno-omit-frame-pointer CFLAGS_RELEASE := -std=c++20 -O3 -march=native -flto=thin \ - -fno-exceptions -fno-rtti -fstrict-aliasing -ffast-math -funroll-loops \ + -fno-exceptions -fno-rtti -fstrict-aliasing \ + -ffast-math -funroll-loops \ + -fvisibility=hidden \ -fomit-frame-pointer -DNDEBUG -s \ - -mllvm -inline-threshold=10000 \ -mllvm -vectorize-loops \ - -mllvm -force-vector-width=8 \ - -mllvm -unroll-threshold=500000 + -fno-unwind-tables -fno-asynchronous-unwind-tables UNICODE_SRC := $(wildcard libs/unicode_width/*.c) diff --git a/README.md b/README.md index 4647901..ae463bd 100644 --- a/README.md +++ b/README.md @@ -6,14 +6,18 @@ A TUI IDE. # TODO -- [ ] Add underline highlight for current word and all occurences. +- [ ] Add alt+arrows to move line/block up/down. - [ ] 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. - it should also put stuff like `}` on the next line. -- [ ] Add support for brackets/quotes to auto-close. +- [ ] 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 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 support for undo/redo. - [ ] Add `.scm` files for all the supported languages. (2/14) Done. - [ ] Add splash screen / minigame jumping. diff --git a/include/editor.h b/include/editor.h index 8381d8a..afa2f87 100644 --- a/include/editor.h +++ b/include/editor.h @@ -105,6 +105,7 @@ struct Editor { std::vector query_map; std::vector folded; Spans spans; + Spans def_spans; std::map folded_node; }; @@ -128,8 +129,11 @@ void edit_erase(Editor *editor, Coord pos, int64_t len); void edit_insert(Editor *editor, Coord pos, char *data, uint32_t len); Coord editor_hit_test(Editor *editor, uint32_t x, uint32_t y); char *get_selection(Editor *editor, uint32_t *out_len); +void editor_worker(Editor *editor); void word_boundaries(Editor *editor, Coord coord, uint32_t *prev_col, uint32_t *next_col, uint32_t *prev_clusters, uint32_t *next_clusters); +void word_boundaries_exclusive(Editor *editor, Coord coord, uint32_t *prev_col, + uint32_t *next_col); #endif diff --git a/include/utils.h b/include/utils.h index b203673..f10f6b0 100644 --- a/include/utils.h +++ b/include/utils.h @@ -2,9 +2,12 @@ #define UTILS_H #include "./ts_def.h" +#include +#include #include #include #include +#include #define PCRE2_CODE_UNIT_WIDTH 8 #define PCRE_WORKSPACE_SIZE 512 @@ -63,4 +66,23 @@ void copy_to_clipboard(const char *text, size_t len); char *get_from_clipboard(uint32_t *out_len); uint32_t count_clusters(const char *line, size_t len, size_t from, size_t to); +template +auto throttle(std::chrono::milliseconds min_duration, Func &&func, + Args &&...args) { + auto start = std::chrono::steady_clock::now(); + if constexpr (std::is_void_v>) { + std::invoke(std::forward(func), std::forward(args)...); + } else { + auto result = + std::invoke(std::forward(func), std::forward(args)...); + auto elapsed = std::chrono::steady_clock::now() - start; + if (elapsed < min_duration) + std::this_thread::sleep_for(min_duration - elapsed); + return result; + } + auto elapsed = std::chrono::steady_clock::now() - start; + if (elapsed < min_duration) + std::this_thread::sleep_for(min_duration - elapsed); +} + #endif diff --git a/src/editor.cc b/src/editor.cc index eadc843..2cc8bda 100644 --- a/src/editor.cc +++ b/src/editor.cc @@ -115,6 +115,7 @@ void render_editor(Editor *editor) { return; if (line_len > 0 && line[line_len - 1] == '\n') line_len--; + free(line); end = {editor->selection.row, line_len}; break; } @@ -125,12 +126,14 @@ void render_editor(Editor *editor) { Coord cursor = {UINT32_MAX, UINT32_MAX}; uint32_t line_index = editor->scroll.row; SpanCursor span_cursor(editor->spans); + SpanCursor def_span_cursor(editor->def_spans); LineIterator *it = begin_l_iter(editor->root, line_index); if (!it) return; uint32_t rendered_rows = 0; uint32_t global_byte_offset = line_to_byte(editor->root, line_index, nullptr); 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) { @@ -188,9 +191,17 @@ void render_editor(Editor *editor) { uint32_t absolute_byte_pos = global_byte_offset + current_byte_offset + local_render_offset; Highlight *hl = span_cursor.get_highlight(absolute_byte_pos); + Highlight *def_hl = def_span_cursor.get_highlight(absolute_byte_pos); uint32_t fg = hl ? hl->fg : 0xFFFFFF; uint32_t bg = hl ? hl->bg : 0; uint8_t fl = hl ? hl->flags : 0; + if (def_hl) { + if (def_hl->fg != 0) + fg = def_hl->fg; + if (def_hl->bg != 0) + bg = def_hl->bg; + fl |= def_hl->flags; + } if (editor->selection_active && absolute_byte_pos >= sel_start && absolute_byte_pos < sel_end) bg = 0x555555; @@ -278,10 +289,10 @@ void render_editor(Editor *editor) { } set_cursor(cursor.row, cursor.col, type, true); } - while (rendered_rows < render_width) { - for (uint32_t col = 0; col < render_width; col++) - update(editor->position.row + rendered_rows, render_x + col, " ", - 0xFFFFFF, 0, 0); + while (rendered_rows < editor->size.row) { + for (uint32_t col = 0; col < editor->size.col; col++) + update(editor->position.row + rendered_rows, editor->position.col + col, + " ", 0xFFFFFF, 0, 0); rendered_rows++; } free(it); diff --git a/src/editor_ctrl.cc b/src/editor_ctrl.cc index d32e5d2..236b553 100644 --- a/src/editor_ctrl.cc +++ b/src/editor_ctrl.cc @@ -1,11 +1,15 @@ +#include extern "C" { #include "../libs/libgrapheme/grapheme.h" } #include "../include/editor.h" #include "../include/main.h" +#include "../include/ts.h" #include "../include/utils.h" #include +static Highlight HL_UNDERLINE = {0, 0, 1 << 2, 100}; + void handle_editor_event(Editor *editor, KeyEvent event) { static std::chrono::steady_clock::time_point last_click_time = std::chrono::steady_clock::now(); @@ -77,6 +81,7 @@ void handle_editor_event(Editor *editor, KeyEvent event) { return; if (line_len > 0 && line[line_len - 1] == '\n') line_len--; + free(line); editor->cursor = {p.row, line_len}; } editor->cursor_preffered = UINT32_MAX; @@ -119,6 +124,7 @@ void handle_editor_event(Editor *editor, KeyEvent event) { return; if (line_len > 0 && line[line_len - 1] == '\n') line_len--; + free(line); editor->cursor = {p.row, line_len}; } break; @@ -356,6 +362,101 @@ void handle_editor_event(Editor *editor, KeyEvent event) { free(event.c); } +void editor_worker(Editor *editor) { + if (!editor || !editor->root) + return; + if (editor->parser && editor->query) + ts_collect_spans(editor); + uint32_t prev_col, next_col; + word_boundaries_exclusive(editor, editor->cursor, &prev_col, &next_col); + if (next_col - prev_col > 0 && next_col - prev_col < 256 - 4) { + std::shared_lock lock(editor->knot_mtx); + uint32_t offset = line_to_byte(editor->root, editor->cursor.row, nullptr); + char *word = read(editor->root, offset + prev_col, next_col - prev_col); + if (word) { + char buf[256]; + snprintf(buf, sizeof(buf), "\\b%s\\b", word); + std::vector> results = + search_rope(editor->root, buf); + std::unique_lock lock(editor->def_spans.mtx); + editor->def_spans.spans.clear(); + for (const auto &match : results) { + Span s; + s.start = match.first; + s.end = match.first + match.second; + s.hl = &HL_UNDERLINE; + editor->def_spans.spans.push_back(s); + } + std::sort(editor->def_spans.spans.begin(), editor->def_spans.spans.end()); + lock.unlock(); + free(word); + } + } else { + std::unique_lock lock(editor->def_spans.mtx); + editor->def_spans.spans.clear(); + lock.unlock(); + } +} + +uint32_t scan_left(const char *line, uint32_t len, uint32_t off) { + if (off > len) + off = len; + uint32_t i = off; + while (i > 0) { + unsigned char c = (unsigned char)line[i - 1]; + if ((c & 0x80) != 0) + break; + if (!((c == '_') || (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || + (c >= 'a' && c <= 'z'))) + break; + --i; + } + return i; +} + +uint32_t scan_right(const char *line, uint32_t len, uint32_t off) { + if (off > len) + off = len; + uint32_t i = off; + while (i < len) { + unsigned char c = (unsigned char)line[i]; + if ((c & 0x80) != 0) + break; + if (!((c == '_') || (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || + (c >= 'a' && c <= 'z'))) + break; + ++i; + } + return i; +} + +void word_boundaries_exclusive(Editor *editor, Coord coord, uint32_t *prev_col, + uint32_t *next_col) { + if (!editor) + return; + std::shared_lock lock(editor->knot_mtx); + LineIterator *it = begin_l_iter(editor->root, coord.row); + if (!it) + return; + uint32_t line_len; + char *line = next_line(it, &line_len); + free(it); + if (!line) + return; + if (line_len && line[line_len - 1] == '\n') + line_len--; + uint32_t col = coord.col; + if (col > line_len) + col = line_len; + uint32_t left = scan_left(line, line_len, col); + uint32_t right = scan_right(line, line_len, col); + if (prev_col) + *prev_col = left; + if (next_col) + *next_col = right; + free(line); +} + uint32_t word_jump_right(const char *line, size_t len, uint32_t pos) { if (pos >= len) return len; @@ -717,6 +818,8 @@ void edit_erase(Editor *editor, Coord pos, int64_t len) { apply_edit(editor->spans.spans, start, start - byte_pos); if (editor->spans.mid_parse) editor->spans.edits.push({start, start - byte_pos}); + std::unique_lock lock_4(editor->def_spans.mtx); + apply_edit(editor->def_spans.spans, byte_pos, start - byte_pos); } else { std::shared_lock lock_1(editor->knot_mtx); uint32_t cursor_original = @@ -755,6 +858,8 @@ void edit_erase(Editor *editor, Coord pos, int64_t len) { apply_edit(editor->spans.spans, byte_pos, byte_pos - end); if (editor->spans.mid_parse) editor->spans.edits.push({byte_pos, byte_pos - end}); + std::unique_lock lock_4(editor->def_spans.mtx); + apply_edit(editor->def_spans.spans, byte_pos, byte_pos - end); } } @@ -803,6 +908,8 @@ void edit_insert(Editor *editor, Coord pos, char *data, uint32_t len) { apply_edit(editor->spans.spans, byte_pos, len); if (editor->spans.mid_parse) editor->spans.edits.push({byte_pos, len}); + std::unique_lock lock_4(editor->def_spans.mtx); + apply_edit(editor->def_spans.spans, byte_pos, len); } char *get_selection(Editor *editor, uint32_t *out_len) { @@ -865,11 +972,11 @@ void apply_edit(std::vector &spans, uint32_t x, int64_t y) { 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) + 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) { + if (s.start < x && s.end >= x) { s.end += y; } else if (s.start > x) { s.start += y; diff --git a/src/editor_scroll.cc b/src/editor_scroll.cc index af9785a..63115d3 100644 --- a/src/editor_scroll.cc +++ b/src/editor_scroll.cc @@ -171,12 +171,8 @@ void scroll_down(Editor *editor, uint32_t number) { line_index++; } if (q_size > 0) { - uint32_t idx; - if (q_size < max_visual_lines) - idx = (q_head + q_size - 1) % max_visual_lines; - else - idx = q_head; - editor->scroll = scroll_queue[idx]; + uint32_t advance = (q_size > number) ? number : (q_size - 1); + editor->scroll = scroll_queue[(q_head + advance) % max_visual_lines]; } free(it); free(scroll_queue); @@ -249,7 +245,7 @@ void ensure_cursor(Editor *editor) { } if (line_index == editor->cursor.row) { if (editor->cursor.col >= offset && - editor->cursor.col < offset + advance) { + editor->cursor.col <= offset + advance) { free(line); free(it); return; diff --git a/src/main.cc b/src/main.cc index 437cc10..c195771 100644 --- a/src/main.cc +++ b/src/main.cc @@ -3,11 +3,12 @@ #include "../include/ts.h" #include "../include/ui.h" #include -#include #include #include #include +using namespace std::chrono_literals; + std::atomic running{true}; Queue event_queue; std::vector editors; @@ -16,24 +17,22 @@ uint8_t current_editor = 0; uint8_t mode = NORMAL; void background_worker() { - while (running) { - ts_collect_spans(editors[current_editor]); - std::this_thread::sleep_for(std::chrono::milliseconds(16)); - } + while (running) + throttle(16ms, editor_worker, editors[current_editor]); } void input_listener() { while (running) { - KeyEvent event = read_key(); + KeyEvent event = throttle(1ms, read_key); if (event.key_type == KEY_NONE) continue; - if (event.key_type == KEY_CHAR && event.len == 1 && *event.c == CTRL('q')) { + if (event.key_type == KEY_CHAR && event.len == 1 && + event.c[0] == CTRL('q')) { free(event.c); running = false; return; } event_queue.push(event); - std::this_thread::sleep_for(std::chrono::microseconds(100)); } } @@ -49,10 +48,9 @@ Editor *editor_at(uint8_t x, uint8_t y) { } uint8_t index_of(Editor *ed) { - for (uint8_t i = 0; i < editors.size(); i++) { + for (uint8_t i = 0; i < editors.size(); i++) if (editors[i] == ed) return i; - } return 0; } @@ -85,16 +83,13 @@ int main(int argc, char *argv[]) { current_editor = index_of(target); event.mouse_x -= target->position.col; event.mouse_y -= target->position.row; - handle_editor_event(target, event); + throttle(4ms, handle_editor_event, target, event); } else { - handle_editor_event(editors[current_editor], event); + throttle(4ms, handle_editor_event, editors[current_editor], event); } } - - render_editor(editors[current_editor]); - render(); - - std::this_thread::sleep_for(std::chrono::milliseconds(8)); + throttle(4ms, render_editor, editors[current_editor]); + throttle(4ms, render); } if (input_thread.joinable())