From 04179d1a4ee7fd2a9d32981a7dcc08ed698be4ed Mon Sep 17 00:00:00 2001 From: Syed Daanish Date: Mon, 29 Dec 2025 21:55:49 +0000 Subject: [PATCH] Cleanup and add visible whitespaces. --- README.md | 6 +++- grammar/hover.scm | 6 ++++ include/editor.h | 2 ++ include/lsp.h | 2 ++ include/ui.h | 1 - samples/ruby.rb | 4 +-- src/editor.cc | 28 ++++++++++++--- src/editor_events.cc | 81 ++++++++++++++++++++++++++++++++++++++------ src/lsp.cc | 22 +++++++++--- src/renderer.cc | 2 ++ 10 files changed, 130 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 866049c..b7bd62e 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,9 @@ A TUI IDE. # TODO +- [ ] Fix indentation logic +- [ ] For `"insertTextFormat": 2` in `clangd` and similar use only the last word in the signature when replacing +- [ ] Keep a list of words in the current buffer. (for auto completion) (maybe?) - [ ] Add ecma to js and make tsx - [ ] Add support for LSP & autocomplete / snippets. - First research @@ -27,7 +30,6 @@ A TUI IDE. 3. One for Warnings/errors and inlay hints etc. (stuff that adds virtual text to the editor) 4. One for fromatting and stuff like that. (stuff that edits the buffer text) - [ ] Add codeium/copilot support for auto-completion (uses the VAI virtual text) as a test phase. -- [ ] Redo cpp/c/h scm file . also pretty much all of them do manually - [ ] Add a whitespace highlighter (nerd font). for spaces and tabs at start/end of line. not as virtual but instead at render time. - [ ] Once renderer is proven to work well (i.e. redo this commit) merge `experimental` branch into `main`. commit `43f443e` on `experimental`. - [ ] Add snippets from wherever i get them. (like luasnip or vsnip) @@ -50,6 +52,8 @@ A TUI IDE. - [ ] Add the highlight of block edges when cursor is on a bracket (or in). (prolly from lsp) - [ ] Add this thing where selection double click on a bracket selects whole block. - (only on the first time) and sets mode to `WORD`. +- [ ] Redo cpp/c/h scm file . also pretty much all of them do manually +- [ ] Try making `lua-typed` and man pages `tree-sitter` grammar. - [ ] Redo folding system and its relation to move_line_* functions. (Currently its a mess) - [ ] Make whole thing event driven and not clock driven. diff --git a/grammar/hover.scm b/grammar/hover.scm index 4573fc5..de49c50 100644 --- a/grammar/hover.scm +++ b/grammar/hover.scm @@ -123,6 +123,12 @@ ;; !cpp (code_fence_content) @injection.cpp) +(fenced_code_block + (info_string + (language) @injection.language (#match? @injection.language "^objective-cpp$")) +;; !cpp + (code_fence_content) @injection.cpp) + (fenced_code_block (info_string (language) @injection.language (#match? @injection.language "^h$")) diff --git a/include/editor.h b/include/editor.h index acf1513..a932a4e 100644 --- a/include/editor.h +++ b/include/editor.h @@ -33,6 +33,8 @@ struct Editor { Queue edit_queue; std::vector folds; Spans spans; + // TODO: Split into 2 groups to have their own mutex's . one for word hl and + // one for hex colors Spans def_spans; uint32_t hooks[94]; bool jumper_set; diff --git a/include/lsp.h b/include/lsp.h index f6d17c6..f6c34f0 100644 --- a/include/lsp.h +++ b/include/lsp.h @@ -33,6 +33,8 @@ struct LSPInstance { std::atomic exited = false; bool incremental_sync = false; bool allow_hover = false; + bool allow_completion = false; + std::string trigger_chars; uint32_t last_id = 0; Queue inbox; Queue outbox; diff --git a/include/ui.h b/include/ui.h index 6fcb82c..d58ffdd 100644 --- a/include/ui.h +++ b/include/ui.h @@ -3,7 +3,6 @@ #include "./pch.h" #include "./utils.h" -#include #define KEY_CHAR 0 #define KEY_SPECIAL 1 diff --git a/samples/ruby.rb b/samples/ruby.rb index 553ab29..7fbe93c 100644 --- a/samples/ruby.rb +++ b/samples/ruby.rb @@ -12,9 +12,7 @@ end # Emoji-heavy strings emojis = "πŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒπŸ‘‹πŸŒ" -# Mixed-width CJK blocks -# -# https://chatgpt.com/c/695232e9-febc-8331-88c0-b47020948e5b +# Mixed-width CJK block cjk_samples = [ "ζΌ’ε­—γƒ†γ‚Ήγƒˆ", "測試中文字串", diff --git a/src/editor.cc b/src/editor.cc index 69752df..fb43119 100644 --- a/src/editor.cc +++ b/src/editor.cc @@ -1,3 +1,4 @@ +#include extern "C" { #include "../libs/libgrapheme/grapheme.h" } @@ -206,6 +207,14 @@ void render_editor(Editor *editor) { break; if (line_len > 0 && line[line_len - 1] == '\n') line_len--; + uint32_t content_end = line_len; + while (content_end > 0 && + (line[content_end - 1] == ' ' || line[content_end - 1] == '\t')) + content_end--; + uint32_t content_start = 0; + while (content_start < line_len && + (line[content_start] == ' ' || line[content_start] == '\t')) + content_start++; std::vector line_warnings; while (warn_it != editor->warnings.end() && warn_it->line == line_index) { line_warnings.push_back(*warn_it); @@ -258,9 +267,9 @@ void render_editor(Editor *editor) { uint8_t fl = hl ? hl->flags : 0; if (def_hl) { if (def_hl->fg != 0) - fg |= def_hl->fg; + fg = def_hl->fg; if (def_hl->bg != 0) - bg |= def_hl->bg; + bg = def_hl->bg; fl |= def_hl->flags; } if (editor->selection_active && absolute_byte_pos >= sel_start && @@ -297,8 +306,19 @@ void render_editor(Editor *editor) { int width = display_width(cluster.c_str(), cluster_len); if (col + width > render_width) break; - update(editor->position.row + rendered_rows, render_x + col, - cluster.c_str(), fg, bg | color, fl, u_color); + if (current_byte_offset + local_render_offset >= content_start && + current_byte_offset + local_render_offset < content_end) { + update(editor->position.row + rendered_rows, render_x + col, + cluster.c_str(), fg, bg | color, fl, u_color); + } else { + if (cluster[0] == ' ') { + update(editor->position.row + rendered_rows, render_x + col, "Β·", + 0x282828, bg | color, fl, u_color); + } else { + update(editor->position.row + rendered_rows, render_x + col, "-> ", + 0x282828, bg | color, (fl & ~CF_BOLD) | CF_ITALIC, u_color); + } + } local_render_offset += cluster_len; line_left -= cluster_len; col += width; diff --git a/src/editor_events.cc b/src/editor_events.cc index c410073..843dbc6 100644 --- a/src/editor_events.cc +++ b/src/editor_events.cc @@ -264,7 +264,6 @@ void handle_editor_event(Editor *editor, KeyEvent event) { pending->editor = editor; pending->method = "textDocument/hover"; pending->callback = [](Editor *editor, std::string, json hover) { - log("%s\n", hover.dump().c_str()); if (hover.contains("result") && !hover["result"].is_null()) { auto &contents = hover["result"]["contents"]; std::string hover_text = ""; @@ -367,6 +366,45 @@ void handle_editor_event(Editor *editor, KeyEvent event) { case CTRL('s'): save_file(editor); break; + case CTRL(' '): + if (editor->lsp) { + json msg = { + {"jsonrpc", "2.0"}, + {"method", "textDocument/completion"}, + { + "params", + { + { + "textDocument", + { + {"uri", editor->uri}, + }, + }, + { + "position", + { + {"line", editor->cursor.row}, + {"character", editor->cursor.col}, + }, + }, + { + "context", + { + {"triggerKind", 1}, + }, + }, + }, + }, + }; + LSPPending *pending = new LSPPending(); + pending->editor = editor; + pending->method = "textDocument/completion"; + pending->callback = [](Editor *editor, std::string, json completion) { + log("%s\n", completion.dump().c_str()); + }; + lsp_send(editor->lsp, msg, pending); + } + break; case 'p': uint32_t len; char *text = get_from_clipboard(&len); @@ -652,7 +690,7 @@ void hover_diagnostic(Editor *editor) { editor->diagnostics_active = true; } -static Highlight HL_UNDERLINE = {0, 0, 1 << 2, 100}; +static Highlight HL_UNDERLINE = {0, 0, CF_UNDERLINE, UINT8_MAX - 1}; void editor_worker(Editor *editor) { if (!editor || !editor->root) @@ -665,17 +703,18 @@ void editor_worker(Editor *editor) { ts_collect_spans(editor); uint32_t prev_col, next_col; word_boundaries_exclusive(editor, editor->cursor, &prev_col, &next_col); + std::unique_lock lock(editor->def_spans.mtx); + editor->def_spans.spans.clear(); if (next_col - prev_col > 0 && next_col - prev_col < 256 - 4) { - std::shared_lock lock(editor->knot_mtx); + std::shared_lock lockk(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); + lockk.unlock(); 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; @@ -683,15 +722,35 @@ void editor_worker(Editor *editor) { 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(); } + uint8_t top = 0; + static Highlight *hl_s = (Highlight *)calloc(200, sizeof(Highlight)); + if (!hl_s) + exit(ENOMEM); + std::vector> results = + search_rope(editor->root, "(0x|#)[0-9a-fA-F]{6}"); + std::shared_lock lockk(editor->knot_mtx); + for (int i = 0; i < results.size() && top < 200; i++) { + Span s; + s.start = results[i].first; + s.end = results[i].first + results[i].second; + char *buf = read(editor->root, s.start, s.end - s.start); + int x = buf[0] == '#' ? 1 : 2; + uint32_t bg = HEX(buf + x); + free(buf); + uint8_t r = bg >> 16, g = (bg >> 8) & 0xFF, b = bg & 0xFF; + double luminance = 0.299 * r + 0.587 * g + 0.114 * b; + uint32_t fg = (luminance > 128) ? 0x010101 : 0xFEFEFE; + hl_s[top] = {fg, bg, CF_BOLD, UINT8_MAX}; + s.hl = &hl_s[top]; + editor->def_spans.spans.push_back(s); + top++; + } + std::sort(editor->def_spans.spans.begin(), editor->def_spans.spans.end()); + lock.unlock(); + lockk.unlock(); hover_diagnostic(editor); } diff --git a/src/lsp.cc b/src/lsp.cc index 667c742..37a7004 100644 --- a/src/lsp.cc +++ b/src/lsp.cc @@ -84,11 +84,12 @@ std::shared_ptr get_or_init_lsp(uint8_t lsp_id) { lsp->incremental_sync = (change_type == 2); } } - if (caps.contains("hoverProvider")) { + if (caps.contains("hoverProvider")) lsp->allow_hover = caps["hoverProvider"].get(); - } else { + else lsp->allow_hover = false; - } + if (caps.contains("completionProvider")) + lsp->allow_completion = true; } lsp->initialized = true; json initialized = {{"jsonrpc", "2.0"}, @@ -110,7 +111,20 @@ std::shared_ptr get_or_init_lsp(uint8_t lsp_id) { {"capabilities", {{"textDocument", {{"publishDiagnostics", {{"relatedInformation", true}}}, - {"hover", {{"contentFormat", {"markdown", "plaintext"}}}}}}}}}}}; + {"hover", {{"contentFormat", {"markdown", "plaintext"}}}}, + {"completion", + {{"completionItem", + {{"snippetSupport", true}, + {"documentationFormat", {"markdown", "plaintext"}}, + {"resolveSupport", + {{"properties", {"documentation", "detail"}}}}, + {"insertReplaceSupport", true}, + {"labelDetailsSupport", true}, + {"insertTextModeSupport", {{"valueSet", json::array({1})}}}}}, + {"completionItemKind", + {{"valueSet", {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}}}}, + {"contextSupport", true}, + {"insertTextMode", 1}}}}}}}}}}; lsp_send(lsp, init_message, pending); active_lsps[lsp_id] = lsp; return lsp; diff --git a/src/renderer.cc b/src/renderer.cc index 37bcd36..cde20c5 100644 --- a/src/renderer.cc +++ b/src/renderer.cc @@ -62,6 +62,7 @@ void update(uint32_t row, uint32_t col, std::string utf8, uint32_t fg, screen[idx].fg = fg; screen[idx].bg = bg; screen[idx].flags = flags; + screen[idx].ul_color = 0; } void update(uint32_t row, uint32_t col, const char *utf8, uint32_t fg, @@ -77,6 +78,7 @@ void update(uint32_t row, uint32_t col, const char *utf8, uint32_t fg, screen[idx].fg = fg; screen[idx].bg = bg; screen[idx].flags = flags; + screen[idx].ul_color = 0; } void update(uint32_t row, uint32_t col, std::string utf8, uint32_t fg,