diff --git a/.clangd b/.clangd index 4a88f2e..744b794 100644 --- a/.clangd +++ b/.clangd @@ -1,4 +1,4 @@ CompileFlags: - Add: [-I/home/syed/main/crib/include, -I/home/syed/main/crib/libs] + Add: [-I/home/syed/main/crib/include, -I/home/syed/main/crib/libs, c++20] Remove: [] Compiler: clang++ diff --git a/README.md b/README.md index 4cf37c4..d401ade 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,11 @@ A TUI IDE. # TODO -- [ ] Do auto complete box rendering functions. -- [ ] Finish autocomplete box. +- [ ] Normalize completions edits if local filtering is used +- [ ] Check why fish is behaving soo off with completions filtering +- [ ] Also why clangd doesnt show any completions but applys them at 0,0 +- [ ] Also why solargraph applys edits at 0,0 +- [ ] Finish autocomplete box style functions. - [ ] Add status bar & RUNNER mode - [ ] Get code context from tree-sitter - [ ] Maybe hide boxes in !`normal` mode diff --git a/include/editor/completions.h b/include/editor/completions.h index 26f4dfe..8a44512 100644 --- a/include/editor/completions.h +++ b/include/editor/completions.h @@ -4,38 +4,35 @@ #include "editor/decl.h" #include "pch.h" #include "ui/completionbox.h" +#include "ui/hover.h" #include "utils/utils.h" struct CompletionItem { - std::string label; // Shown in the autocomplete box - uint8_t kind; // Function, variable, class, etc. - std::optional detail; // Shown greyed in autocomplete box - std::optional documentation; // Hover box (can be lazy-loaded) + std::string label; + uint8_t kind; + std::optional detail; + std::optional documentation; bool is_markup = false; - bool deprecated = false; // Shown with strikethrough, may push down in list - std::string sort; // Used for sorting - std::string filter; // Used for filtering (default: label) + bool deprecated = false; + std::string sort; + std::string filter; bool snippet = false; std::vector edits; json original; - std::vector end_chars; // Ends completion session if typed + std::vector end_chars; }; struct CompletionSession { std::shared_mutex mtx; - bool active = false; - Coord hook; // set to start of word - std::optional prefix; // text between hook and cursor - uint8_t select = 0; // index of selected item (defualts to preselcted one - // when data requested) + Coord hook; + std::optional prefix; + uint8_t select = 0; std::vector items; std::vector visible; - bool complete = true; // If false, client may request more items on filter - // (but doesnt try filtering on its own) - std::optional trigger_char; // Character that triggered completion sent - // to lsp for isIncomplete resolving - uint8_t trigger = 0; // Type of trigger (1: manual, 2: trigger char, 3: auto) + bool complete = true; + std::optional trigger_char; + uint8_t trigger = 0; CompletionBox box; CompletionSession() : box(this) {} diff --git a/include/io/sysio.h b/include/io/sysio.h index 901db49..e31ec3b 100644 --- a/include/io/sysio.h +++ b/include/io/sysio.h @@ -111,7 +111,7 @@ void update(uint32_t row, uint32_t col, std::string utf8, uint32_t fg, uint32_t bg, uint8_t flags, uint32_t ul_color); void update(uint32_t row, uint32_t col, const char *utf8, uint32_t fg, uint32_t bg, uint8_t flags, uint32_t ul_color); -void set_cursor(uint32_t row, uint32_t col, uint32_t type, +void set_cursor(uint8_t row, uint8_t col, uint32_t type, bool show_cursor_param); void render(); Coord get_size(); diff --git a/include/lsp/lsp.h b/include/lsp/lsp.h index 1934fef..d67a336 100644 --- a/include/lsp/lsp.h +++ b/include/lsp/lsp.h @@ -64,7 +64,9 @@ static json client_capabilities = { {"labelDetailsSupport", true}, {"insertTextModeSupport", {{"valueSet", {1}}}}, {"deprecatedSupport", true}}}, - {"completionItemKind", {{"valueSet", {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}}}}, + {"completionItemKind", + {{"valueSet", {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, + 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25}}}}, {"contextSupport", true}, {"insertTextMode", 1}}}}}}; diff --git a/include/ui/bar.h b/include/ui/bar.h index df8f3a2..d5a4b5f 100644 --- a/include/ui/bar.h +++ b/include/ui/bar.h @@ -8,7 +8,7 @@ struct Bar { Coord screen; std::string command = ""; - int cursor = 0; + uint32_t cursor = 0; Bar(Coord screen) : screen(screen) {} void render(); diff --git a/include/ui/completionbox.h b/include/ui/completionbox.h index e99d2c2..a99f4c7 100644 --- a/include/ui/completionbox.h +++ b/include/ui/completionbox.h @@ -6,6 +6,7 @@ #include "utils/utils.h" struct CompletionBox { + std::shared_mutex mtx; struct CompletionSession *session; bool hidden = true; std::vector cells; diff --git a/src/editor/boundaries.cc b/src/editor/boundaries.cc index 8d6dc3d..c9182d1 100644 --- a/src/editor/boundaries.cc +++ b/src/editor/boundaries.cc @@ -42,8 +42,11 @@ void word_boundaries_exclusive(Editor *editor, Coord coord, uint32_t *prev_col, return; uint32_t line_len; char *line = next_line(it, &line_len); - if (!line) + if (!line) { + free(it->buffer); + free(it); return; + } if (line_len && line[line_len - 1] == '\n') line_len--; uint32_t col = coord.col; diff --git a/src/editor/completions.cc b/src/editor/completions.cc index ecfe6ee..55ff86a 100644 --- a/src/editor/completions.cc +++ b/src/editor/completions.cc @@ -5,156 +5,6 @@ #include "main.h" #include "utils/utils.h" -void completion_request(Editor *editor) { - Coord hook = editor->cursor; - word_boundaries(editor, editor->cursor, &hook.col, nullptr, nullptr, nullptr); - LineIterator *it = begin_l_iter(editor->root, hook.row); - char *line = next_line(it, nullptr); - if (!line) { - free(it->buffer); - free(it); - return; - } - hook.col = utf8_byte_offset_to_utf16(line, hook.col); - editor->completion.hook = hook; - LSPPending *pending = new LSPPending(); - pending->editor = editor; - pending->method = "textDocument/completion"; - pending->callback = [line, it](Editor *editor, std::string, json message) { - auto &session = editor->completion; - std::unique_lock lock(session.mtx); - session.active = true; - session.items.clear(); - session.select = 0; - std::vector items_json; - std::vector end_chars_def; - int insert_text_format = 1; - if (message.contains("result")) { - auto &result = message["result"]; - if (result.is_array()) { - items_json = result.get>(); - session.complete = true; - } else if (result.is_object() && result.contains("items")) { - auto &list = result; - items_json = list["items"].get>(); - session.complete = !list.value("isIncomplete", false); - if (list.contains("itemDefaults")) { - auto &defs = list["itemDefaults"]; - if (defs.contains("insertTextFormat")) - insert_text_format = defs["insertTextFormat"].get(); - if (defs.contains("textEdit")) - if (defs["textEdit"].is_array()) - for (auto &c : defs["textEdit"]) { - std::string str = c.get(); - if (str.size() != 1) - continue; - end_chars_def.push_back(str[0]); - } - } - } - } - session.items.reserve(items_json.size()); - for (auto &item_json : items_json) { - CompletionItem item; - item.original = item_json; - item.label = item_json.value("label", ""); - item.kind = item_json.value("kind", 0); - if (item_json.contains("detail")) - item.detail = item_json["detail"].get(); - if (item_json.contains("documentation")) { - if (item_json["documentation"].is_string()) { - item.documentation = item_json["documentation"].get(); - } else if (item_json["documentation"].contains("value")) { - item.is_markup = - item_json["documentation"]["kind"].get() == - "markdown"; - item.documentation = - item_json["documentation"]["value"].get(); - } - } - if (item_json.contains("deprecated")) - item.deprecated = item_json["deprecated"].get(); - auto tags = item_json.value("tags", std::vector()); - for (auto tag : tags) - if (tag == 1) - item.deprecated = true; - item.sort = item_json.value("sortText", item.label); - item.filter = item_json.value("filterText", item.label); - if (item_json.contains("preselect") && item_json["preselect"].get()) - session.select = session.items.size() - 1; - TextEdit edit; - if (item_json.contains("textEdit")) { - auto &te = item_json["textEdit"]; - if (te.contains("newText")) { - edit.text = te.value("newText", ""); - if (te.contains("replace")) { - edit.start.row = te["replace"]["start"]["line"]; - edit.start.col = te["replace"]["start"]["character"]; - edit.end.row = te["replace"]["end"]["line"]; - edit.end.col = te["replace"]["end"]["character"]; - } else if (te.contains("insert")) { - edit.start.row = te["insert"]["start"]["line"]; - edit.start.col = te["insert"]["start"]["character"]; - edit.end.row = te["insert"]["end"]["line"]; - edit.end.col = te["insert"]["end"]["character"]; - } - } else { - edit.text = te.value("newText", ""); - edit.start.row = te["range"]["start"]["line"]; - edit.start.col = te["range"]["start"]["character"]; - edit.end.row = te["range"]["end"]["line"]; - edit.end.col = te["range"]["end"]["character"]; - } - } else if (item_json.contains("insertText")) { - edit.text = item_json["insertText"].get(); - edit.start = session.hook; - uint32_t col = utf8_byte_offset_to_utf16(line, editor->cursor.col); - edit.end = {editor->cursor.row, col}; - } - item.edits.push_back(edit); - if (item_json.contains("additionalTextEdits")) { - for (auto &te : item_json["additionalTextEdits"]) { - TextEdit edit; - edit.text = te.value("newText", ""); - edit.start.row = te["range"]["start"]["line"]; - edit.start.col = te["range"]["start"]["character"]; - edit.end.row = te["range"]["end"]["line"]; - edit.end.col = te["range"]["end"]["character"]; - item.edits.push_back(edit); - } - } - item.snippet = insert_text_format == 2; - if (item_json.contains("insertTextFormat")) - item.snippet = item_json["insertTextFormat"].get() == 2; - if (item_json.contains("commitCharacters")) - for (auto &c : item_json["commitCharacters"]) - if (c.is_string() && c.get().size() == 1) - item.end_chars.push_back(c.get()[0]); - session.items.push_back(std::move(item)); - session.visible.push_back(session.items.size() - 1); - } - session.box.hidden = false; - session.box.render_update(); - free(it->buffer); - free(it); - }; - uint32_t col = utf8_byte_offset_to_utf16(line, editor->cursor.col); - json message = { - {"jsonrpc", "2.0"}, - {"method", "textDocument/completion"}, - {"params", - {{"textDocument", {{"uri", editor->uri}}}, - {"position", {{"line", editor->cursor.row}, {"character", col}}}}}}; - if (editor->completion.trigger > 0) { - json context = {{"triggerKind", editor->completion.trigger}}; - if (editor->completion.trigger == 2 && editor->completion.trigger_char) - context["triggerCharacter"] = - std::string(1, *editor->completion.trigger_char); - message["params"]["context"] = context; - } - lsp_send(editor->lsp, message, pending); -} - inline static std::string completion_prefix(Editor *editor) { Coord hook = editor->completion.hook; Coord cur = editor->cursor; @@ -181,24 +31,185 @@ void completion_filter(Editor *editor) { session.visible.clear(); for (size_t i = 0; i < session.items.size(); ++i) { const auto &item = session.items[i]; - const std::string &key = item.filter.empty() ? item.label : item.filter; - if (key.size() >= prefix.size() && key.substr(0, prefix.size()) == prefix) + const std::string &key = item.filter; + if (key.size() >= prefix.size() && + key.compare(0, prefix.size(), prefix) == 0) session.visible.push_back(i); } if (session.visible.empty()) { session.box.hidden = true; return; } + if (std::find(session.visible.begin(), session.visible.end(), + session.select) == session.visible.end()) + session.select = session.visible[0]; session.box.hidden = false; session.box.render_update(); - bool found = false; - for (int i : session.visible) - if (i == session.select) { - found = true; - break; +} + +void completion_request(Editor *editor) { + Coord hook = editor->cursor; + word_boundaries(editor, editor->cursor, &hook.col, nullptr, nullptr, nullptr); + LineIterator *it = begin_l_iter(editor->root, hook.row); + char *line = next_line(it, nullptr); + if (!line) { + free(it->buffer); + free(it); + return; + } + editor->completion.active = true; + editor->completion.items.clear(); + editor->completion.visible.clear(); + editor->completion.select = 0; + hook.col = utf8_byte_offset_to_utf16(line, hook.col); + editor->completion.hook = hook; + LSPPending *pending = new LSPPending(); + pending->editor = editor; + pending->method = "textDocument/completion"; + pending->callback = [line, it](Editor *editor, std::string, json message) { + auto &session = editor->completion; + std::unique_lock lock(session.mtx); + std::vector items_json; + std::vector end_chars_def; + int insert_text_format = 1; + if (message.contains("result")) { + auto &result = message["result"]; + if (result.is_array()) { + items_json = result.get>(); + session.complete = true; + } else if (result.is_object() && result.contains("items")) { + auto &list = result; + items_json = list["items"].get>(); + session.complete = !list.value("isIncomplete", false); + if (list.contains("itemDefaults") && list["itemDefaults"].is_object()) { + auto &defs = list["itemDefaults"]; + if (defs.contains("insertTextFormat") && + defs["insertTextFormat"].is_number()) + insert_text_format = defs["insertTextFormat"].get(); + if (defs.contains("textEdit")) + if (defs["textEdit"].is_array()) + for (auto &c : defs["textEdit"]) { + if (!c.is_string()) + continue; + std::string str = c.get(); + if (str.size() != 1) + continue; + end_chars_def.push_back(str[0]); + } + } + } } - if (!found) - session.select = session.visible[0]; + session.items.reserve(items_json.size() + 1); + for (auto &item_json : items_json) { + CompletionItem item; + item.original = item_json; + item.label = item_json.value("label", ""); + item.kind = item_json.value("kind", 0); + if (item_json.contains("detail") && item_json["detail"].is_string()) + item.detail = item_json["detail"].get(); + if (item_json.contains("documentation")) { + if (item_json["documentation"].is_string()) { + item.documentation = item_json["documentation"].get(); + } else if (item_json["documentation"].contains("value") && + item_json["documentation"]["value"].is_string()) { + item.is_markup = + item_json["documentation"]["kind"].get() == + "markdown"; + item.documentation = + item_json["documentation"]["value"].get(); + } + } + if (item_json.contains("deprecated") && + item_json["deprecated"].is_boolean()) + item.deprecated = item_json["deprecated"].get(); + auto tags = item_json.value("tags", std::vector()); + for (auto tag : tags) + if (tag == 1) + item.deprecated = true; + item.sort = item_json.value("sortText", item.label); + item.filter = item_json.value("filterText", item.label); + if (item_json.contains("preselect") && + item_json["preselect"].is_boolean() && + item_json["preselect"].get()) + session.select = session.items.size() - 1; + TextEdit edit; + if (item_json.contains("textEdit")) { + auto &te = item_json["textEdit"]; + if (te.contains("newText")) { + edit.text = te.value("newText", ""); + if (te.contains("replace")) { + edit.start.row = te["replace"]["start"]["line"]; + edit.start.col = te["replace"]["start"]["character"]; + edit.end.row = te["replace"]["end"]["line"]; + edit.end.col = te["replace"]["end"]["character"]; + } else if (te.contains("insert")) { + edit.start.row = te["insert"]["start"]["line"]; + edit.start.col = te["insert"]["start"]["character"]; + edit.end.row = te["insert"]["end"]["line"]; + edit.end.col = te["insert"]["end"]["character"]; + } + } else { + edit.text = te.value("newText", ""); + edit.start.row = te["range"]["start"]["line"]; + edit.start.col = te["range"]["start"]["character"]; + edit.end.row = te["range"]["end"]["line"]; + edit.end.col = te["range"]["end"]["character"]; + } + } else if (item_json.contains("insertText") && + item_json["insertText"].is_string()) { + edit.text = item_json["insertText"].get(); + edit.start = session.hook; + uint32_t col = utf8_byte_offset_to_utf16(line, editor->cursor.col); + edit.end = {editor->cursor.row, col}; + } else { + edit.text = item.label; + edit.start = session.hook; + uint32_t col = utf8_byte_offset_to_utf16(line, editor->cursor.col); + edit.end = {editor->cursor.row, col}; + } + item.edits.push_back(edit); + if (item_json.contains("additionalTextEdits")) { + for (auto &te : item_json["additionalTextEdits"]) { + TextEdit edit; + edit.text = te.value("newText", ""); + edit.start.row = te["range"]["start"]["line"]; + edit.start.col = te["range"]["start"]["character"]; + edit.end.row = te["range"]["end"]["line"]; + edit.end.col = te["range"]["end"]["character"]; + item.edits.push_back(edit); + } + } + item.snippet = insert_text_format == 2; + if (item_json.contains("insertTextFormat")) + item.snippet = item_json["insertTextFormat"].get() == 2; + if (item_json.contains("commitCharacters")) + for (auto &c : item_json["commitCharacters"]) + if (c.is_string() && c.get().size() == 1) + item.end_chars.push_back(c.get()[0]); + session.items.push_back(std::move(item)); + session.visible.push_back(session.items.size() - 1); + } + completion_filter(editor); + session.box.hidden = false; + session.box.render_update(); + free(it->buffer); + free(it); + }; + uint32_t col = utf8_byte_offset_to_utf16(line, editor->cursor.col); + json message = { + {"jsonrpc", "2.0"}, + {"method", "textDocument/completion"}, + {"params", + {{"textDocument", {{"uri", editor->uri}}}, + {"position", {{"line", editor->cursor.row}, {"character", col}}}}}}; + if (editor->completion.trigger > 0) { + json context = {{"triggerKind", editor->completion.trigger}}; + if (editor->completion.trigger == 2 && editor->completion.trigger_char) + context["triggerCharacter"] = + std::string(1, *editor->completion.trigger_char); + message["params"]["context"] = context; + } + lsp_send(editor->lsp, message, pending); } void handle_completion(Editor *editor, KeyEvent event) { @@ -208,6 +219,7 @@ void handle_completion(Editor *editor, KeyEvent event) { editor->completion.active = false; return; } + std::unique_lock lock(editor->completion.mtx); if (event.key_type == KEY_PASTE) { editor->completion.active = false; return; @@ -222,6 +234,8 @@ void handle_completion(Editor *editor, KeyEvent event) { return; } } else { + if (editor->completion.items.empty()) + return; const auto &item = editor->completion.items[editor->completion.select]; const std::vector &end_chars = item.end_chars.empty() ? editor->lsp->end_chars : item.end_chars; @@ -231,8 +245,8 @@ void handle_completion(Editor *editor, KeyEvent event) { return; } } - if (ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z' || - ch >= '0' && ch <= '9' || ch == '_') { + if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || + (ch >= '0' && ch <= '9') || ch == '_') { if (editor->completion.active) { if (editor->completion.complete) completion_filter(editor); @@ -249,35 +263,48 @@ void handle_completion(Editor *editor, KeyEvent event) { editor->completion.trigger = 1; completion_request(editor); } - } else if (ch == CTRL(']')) { + } else if (ch == CTRL('p')) { if (editor->completion.active) complete_next(editor); - } else if (ch == CTRL('[')) { + } else if (ch == CTRL('o')) { if (editor->completion.active) complete_prev(editor); } else if (ch == 0x7F || ch == 0x08) { - if (editor->completion.complete) - completion_filter(editor); - else - completion_request(editor); + if (editor->completion.active) { + if (editor->completion.complete) { + if (editor->cursor <= editor->completion.hook) + editor->completion.active = false; + else + completion_filter(editor); + } else { + completion_request(editor); + } + } } else { editor->completion.active = false; } } else if (event.key_type == KEY_MOUSE && event.mouse_modifier == 0) { - auto &box = editor->completion.box; - Coord normalized = {event.mouse_y - box.position.row, - event.mouse_x - box.position.col}; - if (normalized.row >= 0 && normalized.row < box.size.row && - normalized.col >= 0 && normalized.col < box.size.col) { - uint8_t idx = 0; - /* todo: calculate idx based on scroll and mouse position */ - complete_select(editor, idx); - } + // Prolly remove mouse support here + // auto &box = editor->completion.box; + // if (event.mouse_y >= box.position.row && + // event.mouse_x >= box.position.col) { + // uint32_t row = event.mouse_y - box.position.row; + // uint32_t col = event.mouse_x - box.position.col; + // if (row < box.size.row && col < box.size.col) { + // uint8_t idx = 0; + // /* TODO: fix index relative to scroll */ + // complete_select(editor, idx); + // } + // } + // if it is being implemented then stop main event handler from processing + // when click inside the box + editor->completion.active = false; + } else { + editor->completion.active = false; } } void completion_resolve_doc(Editor *editor) { - std::unique_lock lock(editor->completion.mtx); auto &item = editor->completion.items[editor->completion.select]; if (item.documentation) return; @@ -292,6 +319,7 @@ void completion_resolve_doc(Editor *editor) { item.documentation = message["documentation"].get(); else item.documentation = ""; + editor->completion.box.render_update(); }; json message = {{"jsonrpc", "2.0"}, {"method", "completionItem/resolve"}, @@ -303,6 +331,7 @@ void complete_accept(Editor *editor) { if (!editor->completion.active || editor->completion.box.hidden) return; auto &item = editor->completion.items[editor->completion.select]; + // TODO: support snippets here apply_lsp_edits(editor, item.edits); editor->completion.active = false; } @@ -325,6 +354,7 @@ void complete_next(Editor *editor) { vi = (vi + 1) % s.visible.size(); s.select = s.visible[vi]; completion_resolve_doc(editor); + editor->completion.box.render_update(); } void complete_prev(Editor *editor) { @@ -338,6 +368,7 @@ void complete_prev(Editor *editor) { vi = (vi + s.visible.size() - 1) % s.visible.size(); s.select = s.visible[vi]; completion_resolve_doc(editor); + editor->completion.box.render_update(); } void complete_select(Editor *editor, uint8_t index) { diff --git a/src/editor/edit.cc b/src/editor/edit.cc index d4169a2..914a838 100644 --- a/src/editor/edit.cc +++ b/src/editor/edit.cc @@ -2,7 +2,6 @@ #include "editor/folds.h" #include "lsp/lsp.h" #include "utils/utils.h" -#include void edit_erase(Editor *editor, Coord pos, int64_t len) { if (len == 0) @@ -301,7 +300,7 @@ void edit_replace(Editor *editor, Coord start, Coord end, const char *text, (end_line_byte_start - start_line_byte) + end_col); free(buf); if (erase_len != 0) - edit_erase(editor, end, -erase_len); + edit_erase(editor, start, erase_len); if (len > 0) edit_insert(editor, start, const_cast(text), len); } diff --git a/src/editor/events.cc b/src/editor/events.cc index e482b8c..6228c38 100644 --- a/src/editor/events.cc +++ b/src/editor/events.cc @@ -9,6 +9,7 @@ void handle_editor_event(Editor *editor, KeyEvent event) { static uint32_t click_count = 0; static Coord last_click_pos = {UINT32_MAX, UINT32_MAX}; Coord start = editor->cursor; + uint8_t old_mode = mode; if (editor->hover_active) editor->hover_active = false; if (event.key_type == KEY_MOUSE) { @@ -612,6 +613,8 @@ void handle_editor_event(Editor *editor, KeyEvent event) { break; } ensure_scroll(editor); + if (old_mode == mode || mode != INSERT) + handle_completion(editor, event); if ((event.key_type == KEY_CHAR || event.key_type == KEY_PASTE) && event.c) free(event.c); } diff --git a/src/editor/lsp.cc b/src/editor/lsp.cc index 5107f50..13055bf 100644 --- a/src/editor/lsp.cc +++ b/src/editor/lsp.cc @@ -7,6 +7,11 @@ void apply_lsp_edits(Editor *editor, std::vector edits) { for (const auto &edit : edits) edit_replace(editor, edit.start, edit.end, edit.text.c_str(), edit.text.size()); + editor->cursor = edits[0].start; + editor->cursor = move_right_pure(editor, editor->cursor, + count_clusters(edits[0].text.c_str(), + edits[0].text.size(), 0, + edits[0].text.size())); } void editor_lsp_handle(Editor *editor, json msg) { diff --git a/src/editor/renderer.cc b/src/editor/renderer.cc index c6a949e..e030d06 100644 --- a/src/editor/renderer.cc +++ b/src/editor/renderer.cc @@ -468,6 +468,12 @@ void render_editor(Editor *editor) { global_byte_offset += line_len + 1; line_index++; } + 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++; + } if (cursor.row != UINT32_MAX && cursor.col != UINT32_MAX) { int type = 0; switch (mode) { @@ -483,17 +489,13 @@ void render_editor(Editor *editor) { break; } set_cursor(cursor.row, cursor.col, type, true); - if (editor->hover_active) + if (editor->completion.active && !editor->completion.box.hidden) + editor->completion.box.render(cursor); + else if (editor->hover_active) editor->hover.render(cursor); else if (editor->diagnostics_active) editor->diagnostics.render(cursor); } - 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->buffer); free(it); } diff --git a/src/io/renderer.cc b/src/io/renderer.cc index 5752ca9..498bc0e 100644 --- a/src/io/renderer.cc +++ b/src/io/renderer.cc @@ -130,8 +130,8 @@ void render() { first_render = false; } for (uint32_t row = 0; row < rows; ++row) { - uint32_t first_change_col = -1; - uint32_t last_change_col = -1; + int64_t first_change_col = -1; + int64_t last_change_col = -1; for (uint32_t col = 0; col < cols; ++col) { uint32_t idx = row * cols + col; ScreenCell &old_cell = old_screen[idx]; @@ -144,7 +144,7 @@ void render() { if (first_change_col == -1) { first_change_col = col; if (first_change_col > 0) { - for (uint32_t back = 1; back <= 3 && first_change_col - back >= 0; + for (int64_t back = 1; back <= 4 && first_change_col - back >= 0; ++back) { ScreenCell &prev_cell = screen[row * cols + (first_change_col - back)]; @@ -161,7 +161,7 @@ void render() { if (first_change_col == -1) continue; char buf[64]; - snprintf(buf, sizeof(buf), "\x1b[%d;%dH", row + 1, first_change_col + 1); + snprintf(buf, sizeof(buf), "\x1b[%d;%ldH", row + 1, first_change_col + 1); out.append(buf); for (uint32_t col = first_change_col; col <= last_change_col; ++col) { uint32_t idx = row * cols + col; @@ -241,8 +241,16 @@ void render() { current_underline = underline; } if (width > 1 && overlap) { - for (uint32_t i = 1; i < width; ++i) + for (uint32_t i = 1; i < width; ++i) { + uint32_t next_col = col + i; + if (next_col >= cols) + break; + const ScreenCell &next = screen[row * cols + next_col]; + if (!is_empty_cell(next)) + break; out.push_back(' '); + } + out.push_back(' '); } else { if (!new_cell.utf8.empty()) { if (new_cell.utf8[0] == '\t') @@ -282,7 +290,7 @@ void render() { } } -void set_cursor(uint32_t row, uint32_t col, uint32_t type, +void set_cursor(uint8_t row, uint8_t col, uint32_t type, bool show_cursor_param) { char buf[32]; uint32_t n = snprintf(buf, sizeof(buf), "\x1b[%d;%dH\x1b[%d q", row + 1, diff --git a/src/ui/completionbox.cc b/src/ui/completionbox.cc index 8c04666..41cf9a3 100644 --- a/src/ui/completionbox.cc +++ b/src/ui/completionbox.cc @@ -1,5 +1,155 @@ #include "ui/completionbox.h" +#include "editor/completions.h" +#include "utils/utils.h" +#include +#include -void CompletionBox::render_update() {} +std::string item_kind_name(uint8_t kind) { + switch (kind) { + case 1: + return "Text"; + case 2: + return "Method"; + case 3: + return "Function"; + case 4: + return "Constructor"; + case 5: + return "Field"; + case 6: + return "Variable"; + case 7: + return "Class"; + case 8: + return "Interface"; + case 9: + return "Module"; + case 10: + return "Property"; + case 11: + return "Unit"; + case 12: + return "Value"; + case 13: + return "Enum"; + case 14: + return "Keyword"; + case 15: + return "Snippet"; + case 16: + return "Color"; + case 17: + return "File"; + case 18: + return "Reference"; + case 19: + return "Folder"; + case 20: + return "EnumMember"; + case 21: + return "Constant"; + case 22: + return "Struct"; + case 23: + return "Event"; + case 24: + return "Operator"; + case 25: + return "TypeParameter"; + default: + return "Unknown"; + } +} -void CompletionBox::render(Coord pos) {} +const char *item_symbol(uint8_t kind) { return "●"; } + +uint32_t kind_color(uint8_t kind) { return 0x82AAFF; } + +void CompletionBox::render_update() { + if (hidden || session->visible.empty()) + return; + std::unique_lock lock(mtx); + uint32_t max_label_len = 0; + uint32_t max_detail_len = 0; + uint32_t max_kind_len = 0; + for (auto i : session->visible) { + if (i >= session->items.size()) + continue; + auto &item = session->items[i]; + max_label_len = std::max(max_label_len, (uint32_t)item.label.size()); + if (item.detail) + max_detail_len = std::max(max_detail_len, (uint32_t)item.detail->size()); + max_kind_len = + std::max(max_kind_len, (uint32_t)item_kind_name(item.kind).size()); + } + size.row = session->visible.size() + 2; + size.col = 2 + 2 + max_label_len + 1 + max_detail_len + 2 + max_kind_len + 1; + cells.assign(size.row * size.col, {" ", 0, 0, 0, 0, 0}); + auto set = [&](uint32_t r, uint32_t c, const char *text, uint32_t fg, + uint32_t bg, uint8_t flags) { + if (r < size.row && c < size.col) + cells[r * size.col + c] = {std::string(text), 0, fg, bg, flags, 0}; + }; + uint32_t border_fg = 0x82AAFF; + uint32_t sel_bg = 0xFFFF00; + set(0, 0, "┌", border_fg, 0, 0); + for (uint32_t c = 1; c < size.col - 1; c++) + set(0, c, "─", border_fg, 0, 0); + set(0, size.col - 1, "┐", border_fg, 0, 0); + for (uint32_t row_idx = 0; row_idx < session->visible.size(); row_idx++) { + uint32_t r = row_idx + 1; + auto &item = session->items[session->visible[row_idx]]; + uint32_t bg = (session->visible[row_idx] == session->select) ? sel_bg : 0; + uint32_t fg = 0xFFFFFF; + set(r, 0, "│", border_fg, 0, 0); + uint32_t c = 1; + const char *sym = item_symbol(item.kind); + set(r, c++, sym, kind_color(item.kind), bg, 0); + set(r, c++, " ", fg, bg, 0); + for (size_t i = 0; i < item.label.size(); i++) + set(r, c + i, (char[2]){item.label[i], 0}, fg, bg, + item.deprecated ? CF_STRIKETHROUGH : 0); + c += item.label.size(); + set(r, c++, " ", fg, bg, 0); + uint32_t detail_fg = 0xAAAAAA; + if (item.detail) { + for (size_t i = 0; i < item.detail->size(); i++) + set(r, c + i, (char[2]){(*item.detail)[i], 0}, detail_fg, bg, 0); + c += item.detail->size(); + } + uint32_t pad = size.col - 1 - c - max_kind_len; + for (uint32_t i = 0; i < pad; i++) + set(r, c + i, " ", fg, bg, 0); + c += pad; + std::string kind_name = item_kind_name(item.kind); + for (size_t i = 0; i < kind_name.size(); i++) + set(r, c + i, (char[2]){kind_name[i], 0}, kind_color(item.kind), bg, 0); + set(r, size.col - 1, "│", border_fg, 0, 0); + } + uint32_t bottom = size.row - 1; + set(bottom, 0, "└", border_fg, 0, 0); + for (uint32_t c = 1; c < size.col - 1; c++) + set(bottom, c, "─", border_fg, 0, 0); + set(bottom, size.col - 1, "┘", border_fg, 0, 0); +} + +void CompletionBox::render(Coord pos) { + if (hidden) + return; + std::shared_lock lock(mtx); + int32_t start_row = (int32_t)pos.row - (int32_t)size.row; + if (start_row < 0) + start_row = pos.row + 1; + int32_t start_col = pos.col; + if (start_col + size.col > cols) { + start_col = cols - size.col; + if (start_col < 0) + start_col = 0; + } + position = {(uint32_t)start_row, (uint32_t)start_col}; + for (uint32_t r = 0; r < size.row; r++) + for (uint32_t c = 0; c < size.col; c++) + update(start_row + r, start_col + c, cells[r * size.col + c].utf8, + cells[r * size.col + c].fg, cells[r * size.col + c].bg, + cells[r * size.col + c].flags); +}