Basic completion support
This commit is contained in:
2
.clangd
2
.clangd
@@ -1,4 +1,4 @@
|
|||||||
CompileFlags:
|
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: []
|
Remove: []
|
||||||
Compiler: clang++
|
Compiler: clang++
|
||||||
|
|||||||
@@ -6,8 +6,11 @@ A TUI IDE.
|
|||||||
|
|
||||||
# TODO
|
# TODO
|
||||||
|
|
||||||
- [ ] Do auto complete box rendering functions.
|
- [ ] Normalize completions edits if local filtering is used
|
||||||
- [ ] Finish autocomplete box.
|
- [ ] 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
|
- [ ] Add status bar & RUNNER mode
|
||||||
- [ ] Get code context from tree-sitter
|
- [ ] Get code context from tree-sitter
|
||||||
- [ ] Maybe hide boxes in !`normal` mode
|
- [ ] Maybe hide boxes in !`normal` mode
|
||||||
|
|||||||
@@ -4,38 +4,35 @@
|
|||||||
#include "editor/decl.h"
|
#include "editor/decl.h"
|
||||||
#include "pch.h"
|
#include "pch.h"
|
||||||
#include "ui/completionbox.h"
|
#include "ui/completionbox.h"
|
||||||
|
#include "ui/hover.h"
|
||||||
#include "utils/utils.h"
|
#include "utils/utils.h"
|
||||||
|
|
||||||
struct CompletionItem {
|
struct CompletionItem {
|
||||||
std::string label; // Shown in the autocomplete box
|
std::string label;
|
||||||
uint8_t kind; // Function, variable, class, etc.
|
uint8_t kind;
|
||||||
std::optional<std::string> detail; // Shown greyed in autocomplete box
|
std::optional<std::string> detail;
|
||||||
std::optional<std::string> documentation; // Hover box (can be lazy-loaded)
|
std::optional<std::string> documentation;
|
||||||
bool is_markup = false;
|
bool is_markup = false;
|
||||||
bool deprecated = false; // Shown with strikethrough, may push down in list
|
bool deprecated = false;
|
||||||
std::string sort; // Used for sorting
|
std::string sort;
|
||||||
std::string filter; // Used for filtering (default: label)
|
std::string filter;
|
||||||
bool snippet = false;
|
bool snippet = false;
|
||||||
std::vector<TextEdit> edits;
|
std::vector<TextEdit> edits;
|
||||||
json original;
|
json original;
|
||||||
std::vector<char> end_chars; // Ends completion session if typed
|
std::vector<char> end_chars;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct CompletionSession {
|
struct CompletionSession {
|
||||||
std::shared_mutex mtx;
|
std::shared_mutex mtx;
|
||||||
|
|
||||||
bool active = false;
|
bool active = false;
|
||||||
Coord hook; // set to start of word
|
Coord hook;
|
||||||
std::optional<std::string> prefix; // text between hook and cursor
|
std::optional<std::string> prefix;
|
||||||
uint8_t select = 0; // index of selected item (defualts to preselcted one
|
uint8_t select = 0;
|
||||||
// when data requested)
|
|
||||||
std::vector<CompletionItem> items;
|
std::vector<CompletionItem> items;
|
||||||
std::vector<uint8_t> visible;
|
std::vector<uint8_t> visible;
|
||||||
bool complete = true; // If false, client may request more items on filter
|
bool complete = true;
|
||||||
// (but doesnt try filtering on its own)
|
std::optional<char> trigger_char;
|
||||||
std::optional<char> trigger_char; // Character that triggered completion sent
|
uint8_t trigger = 0;
|
||||||
// to lsp for isIncomplete resolving
|
|
||||||
uint8_t trigger = 0; // Type of trigger (1: manual, 2: trigger char, 3: auto)
|
|
||||||
CompletionBox box;
|
CompletionBox box;
|
||||||
|
|
||||||
CompletionSession() : box(this) {}
|
CompletionSession() : box(this) {}
|
||||||
|
|||||||
@@ -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);
|
uint32_t bg, uint8_t flags, uint32_t ul_color);
|
||||||
void update(uint32_t row, uint32_t col, const char *utf8, uint32_t fg,
|
void update(uint32_t row, uint32_t col, const char *utf8, uint32_t fg,
|
||||||
uint32_t bg, uint8_t flags, uint32_t ul_color);
|
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);
|
bool show_cursor_param);
|
||||||
void render();
|
void render();
|
||||||
Coord get_size();
|
Coord get_size();
|
||||||
|
|||||||
@@ -64,7 +64,9 @@ static json client_capabilities = {
|
|||||||
{"labelDetailsSupport", true},
|
{"labelDetailsSupport", true},
|
||||||
{"insertTextModeSupport", {{"valueSet", {1}}}},
|
{"insertTextModeSupport", {{"valueSet", {1}}}},
|
||||||
{"deprecatedSupport", true}}},
|
{"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},
|
{"contextSupport", true},
|
||||||
{"insertTextMode", 1}}}}}};
|
{"insertTextMode", 1}}}}}};
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
struct Bar {
|
struct Bar {
|
||||||
Coord screen;
|
Coord screen;
|
||||||
std::string command = "";
|
std::string command = "";
|
||||||
int cursor = 0;
|
uint32_t cursor = 0;
|
||||||
|
|
||||||
Bar(Coord screen) : screen(screen) {}
|
Bar(Coord screen) : screen(screen) {}
|
||||||
void render();
|
void render();
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include "utils/utils.h"
|
#include "utils/utils.h"
|
||||||
|
|
||||||
struct CompletionBox {
|
struct CompletionBox {
|
||||||
|
std::shared_mutex mtx;
|
||||||
struct CompletionSession *session;
|
struct CompletionSession *session;
|
||||||
bool hidden = true;
|
bool hidden = true;
|
||||||
std::vector<ScreenCell> cells;
|
std::vector<ScreenCell> cells;
|
||||||
|
|||||||
@@ -42,8 +42,11 @@ void word_boundaries_exclusive(Editor *editor, Coord coord, uint32_t *prev_col,
|
|||||||
return;
|
return;
|
||||||
uint32_t line_len;
|
uint32_t line_len;
|
||||||
char *line = next_line(it, &line_len);
|
char *line = next_line(it, &line_len);
|
||||||
if (!line)
|
if (!line) {
|
||||||
|
free(it->buffer);
|
||||||
|
free(it);
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
if (line_len && line[line_len - 1] == '\n')
|
if (line_len && line[line_len - 1] == '\n')
|
||||||
line_len--;
|
line_len--;
|
||||||
uint32_t col = coord.col;
|
uint32_t col = coord.col;
|
||||||
|
|||||||
@@ -5,156 +5,6 @@
|
|||||||
#include "main.h"
|
#include "main.h"
|
||||||
#include "utils/utils.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<json> items_json;
|
|
||||||
std::vector<char> end_chars_def;
|
|
||||||
int insert_text_format = 1;
|
|
||||||
if (message.contains("result")) {
|
|
||||||
auto &result = message["result"];
|
|
||||||
if (result.is_array()) {
|
|
||||||
items_json = result.get<std::vector<json>>();
|
|
||||||
session.complete = true;
|
|
||||||
} else if (result.is_object() && result.contains("items")) {
|
|
||||||
auto &list = result;
|
|
||||||
items_json = list["items"].get<std::vector<json>>();
|
|
||||||
session.complete = !list.value("isIncomplete", false);
|
|
||||||
if (list.contains("itemDefaults")) {
|
|
||||||
auto &defs = list["itemDefaults"];
|
|
||||||
if (defs.contains("insertTextFormat"))
|
|
||||||
insert_text_format = defs["insertTextFormat"].get<int>();
|
|
||||||
if (defs.contains("textEdit"))
|
|
||||||
if (defs["textEdit"].is_array())
|
|
||||||
for (auto &c : defs["textEdit"]) {
|
|
||||||
std::string str = c.get<std::string>();
|
|
||||||
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<std::string>();
|
|
||||||
if (item_json.contains("documentation")) {
|
|
||||||
if (item_json["documentation"].is_string()) {
|
|
||||||
item.documentation = item_json["documentation"].get<std::string>();
|
|
||||||
} else if (item_json["documentation"].contains("value")) {
|
|
||||||
item.is_markup =
|
|
||||||
item_json["documentation"]["kind"].get<std::string>() ==
|
|
||||||
"markdown";
|
|
||||||
item.documentation =
|
|
||||||
item_json["documentation"]["value"].get<std::string>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (item_json.contains("deprecated"))
|
|
||||||
item.deprecated = item_json["deprecated"].get<bool>();
|
|
||||||
auto tags = item_json.value("tags", std::vector<int>());
|
|
||||||
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<bool>())
|
|
||||||
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<std::string>();
|
|
||||||
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<int>() == 2;
|
|
||||||
if (item_json.contains("commitCharacters"))
|
|
||||||
for (auto &c : item_json["commitCharacters"])
|
|
||||||
if (c.is_string() && c.get<std::string>().size() == 1)
|
|
||||||
item.end_chars.push_back(c.get<std::string>()[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) {
|
inline static std::string completion_prefix(Editor *editor) {
|
||||||
Coord hook = editor->completion.hook;
|
Coord hook = editor->completion.hook;
|
||||||
Coord cur = editor->cursor;
|
Coord cur = editor->cursor;
|
||||||
@@ -181,24 +31,185 @@ void completion_filter(Editor *editor) {
|
|||||||
session.visible.clear();
|
session.visible.clear();
|
||||||
for (size_t i = 0; i < session.items.size(); ++i) {
|
for (size_t i = 0; i < session.items.size(); ++i) {
|
||||||
const auto &item = session.items[i];
|
const auto &item = session.items[i];
|
||||||
const std::string &key = item.filter.empty() ? item.label : item.filter;
|
const std::string &key = item.filter;
|
||||||
if (key.size() >= prefix.size() && key.substr(0, prefix.size()) == prefix)
|
if (key.size() >= prefix.size() &&
|
||||||
|
key.compare(0, prefix.size(), prefix) == 0)
|
||||||
session.visible.push_back(i);
|
session.visible.push_back(i);
|
||||||
}
|
}
|
||||||
if (session.visible.empty()) {
|
if (session.visible.empty()) {
|
||||||
session.box.hidden = true;
|
session.box.hidden = true;
|
||||||
return;
|
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.hidden = false;
|
||||||
session.box.render_update();
|
session.box.render_update();
|
||||||
bool found = false;
|
}
|
||||||
for (int i : session.visible)
|
|
||||||
if (i == session.select) {
|
void completion_request(Editor *editor) {
|
||||||
found = true;
|
Coord hook = editor->cursor;
|
||||||
break;
|
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<json> items_json;
|
||||||
|
std::vector<char> end_chars_def;
|
||||||
|
int insert_text_format = 1;
|
||||||
|
if (message.contains("result")) {
|
||||||
|
auto &result = message["result"];
|
||||||
|
if (result.is_array()) {
|
||||||
|
items_json = result.get<std::vector<json>>();
|
||||||
|
session.complete = true;
|
||||||
|
} else if (result.is_object() && result.contains("items")) {
|
||||||
|
auto &list = result;
|
||||||
|
items_json = list["items"].get<std::vector<json>>();
|
||||||
|
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<int>();
|
||||||
|
if (defs.contains("textEdit"))
|
||||||
|
if (defs["textEdit"].is_array())
|
||||||
|
for (auto &c : defs["textEdit"]) {
|
||||||
|
if (!c.is_string())
|
||||||
|
continue;
|
||||||
|
std::string str = c.get<std::string>();
|
||||||
|
if (str.size() != 1)
|
||||||
|
continue;
|
||||||
|
end_chars_def.push_back(str[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!found)
|
session.items.reserve(items_json.size() + 1);
|
||||||
session.select = session.visible[0];
|
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<std::string>();
|
||||||
|
if (item_json.contains("documentation")) {
|
||||||
|
if (item_json["documentation"].is_string()) {
|
||||||
|
item.documentation = item_json["documentation"].get<std::string>();
|
||||||
|
} else if (item_json["documentation"].contains("value") &&
|
||||||
|
item_json["documentation"]["value"].is_string()) {
|
||||||
|
item.is_markup =
|
||||||
|
item_json["documentation"]["kind"].get<std::string>() ==
|
||||||
|
"markdown";
|
||||||
|
item.documentation =
|
||||||
|
item_json["documentation"]["value"].get<std::string>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (item_json.contains("deprecated") &&
|
||||||
|
item_json["deprecated"].is_boolean())
|
||||||
|
item.deprecated = item_json["deprecated"].get<bool>();
|
||||||
|
auto tags = item_json.value("tags", std::vector<int>());
|
||||||
|
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<bool>())
|
||||||
|
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<std::string>();
|
||||||
|
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<int>() == 2;
|
||||||
|
if (item_json.contains("commitCharacters"))
|
||||||
|
for (auto &c : item_json["commitCharacters"])
|
||||||
|
if (c.is_string() && c.get<std::string>().size() == 1)
|
||||||
|
item.end_chars.push_back(c.get<std::string>()[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) {
|
void handle_completion(Editor *editor, KeyEvent event) {
|
||||||
@@ -208,6 +219,7 @@ void handle_completion(Editor *editor, KeyEvent event) {
|
|||||||
editor->completion.active = false;
|
editor->completion.active = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
std::unique_lock lock(editor->completion.mtx);
|
||||||
if (event.key_type == KEY_PASTE) {
|
if (event.key_type == KEY_PASTE) {
|
||||||
editor->completion.active = false;
|
editor->completion.active = false;
|
||||||
return;
|
return;
|
||||||
@@ -222,6 +234,8 @@ void handle_completion(Editor *editor, KeyEvent event) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if (editor->completion.items.empty())
|
||||||
|
return;
|
||||||
const auto &item = editor->completion.items[editor->completion.select];
|
const auto &item = editor->completion.items[editor->completion.select];
|
||||||
const std::vector<char> &end_chars =
|
const std::vector<char> &end_chars =
|
||||||
item.end_chars.empty() ? editor->lsp->end_chars : item.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;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z' ||
|
if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') ||
|
||||||
ch >= '0' && ch <= '9' || ch == '_') {
|
(ch >= '0' && ch <= '9') || ch == '_') {
|
||||||
if (editor->completion.active) {
|
if (editor->completion.active) {
|
||||||
if (editor->completion.complete)
|
if (editor->completion.complete)
|
||||||
completion_filter(editor);
|
completion_filter(editor);
|
||||||
@@ -249,35 +263,48 @@ void handle_completion(Editor *editor, KeyEvent event) {
|
|||||||
editor->completion.trigger = 1;
|
editor->completion.trigger = 1;
|
||||||
completion_request(editor);
|
completion_request(editor);
|
||||||
}
|
}
|
||||||
} else if (ch == CTRL(']')) {
|
} else if (ch == CTRL('p')) {
|
||||||
if (editor->completion.active)
|
if (editor->completion.active)
|
||||||
complete_next(editor);
|
complete_next(editor);
|
||||||
} else if (ch == CTRL('[')) {
|
} else if (ch == CTRL('o')) {
|
||||||
if (editor->completion.active)
|
if (editor->completion.active)
|
||||||
complete_prev(editor);
|
complete_prev(editor);
|
||||||
} else if (ch == 0x7F || ch == 0x08) {
|
} else if (ch == 0x7F || ch == 0x08) {
|
||||||
if (editor->completion.complete)
|
if (editor->completion.active) {
|
||||||
completion_filter(editor);
|
if (editor->completion.complete) {
|
||||||
else
|
if (editor->cursor <= editor->completion.hook)
|
||||||
completion_request(editor);
|
editor->completion.active = false;
|
||||||
|
else
|
||||||
|
completion_filter(editor);
|
||||||
|
} else {
|
||||||
|
completion_request(editor);
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
editor->completion.active = false;
|
editor->completion.active = false;
|
||||||
}
|
}
|
||||||
} else if (event.key_type == KEY_MOUSE && event.mouse_modifier == 0) {
|
} else if (event.key_type == KEY_MOUSE && event.mouse_modifier == 0) {
|
||||||
auto &box = editor->completion.box;
|
// Prolly remove mouse support here
|
||||||
Coord normalized = {event.mouse_y - box.position.row,
|
// auto &box = editor->completion.box;
|
||||||
event.mouse_x - box.position.col};
|
// if (event.mouse_y >= box.position.row &&
|
||||||
if (normalized.row >= 0 && normalized.row < box.size.row &&
|
// event.mouse_x >= box.position.col) {
|
||||||
normalized.col >= 0 && normalized.col < box.size.col) {
|
// uint32_t row = event.mouse_y - box.position.row;
|
||||||
uint8_t idx = 0;
|
// uint32_t col = event.mouse_x - box.position.col;
|
||||||
/* todo: calculate idx based on scroll and mouse position */
|
// if (row < box.size.row && col < box.size.col) {
|
||||||
complete_select(editor, idx);
|
// 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) {
|
void completion_resolve_doc(Editor *editor) {
|
||||||
std::unique_lock lock(editor->completion.mtx);
|
|
||||||
auto &item = editor->completion.items[editor->completion.select];
|
auto &item = editor->completion.items[editor->completion.select];
|
||||||
if (item.documentation)
|
if (item.documentation)
|
||||||
return;
|
return;
|
||||||
@@ -292,6 +319,7 @@ void completion_resolve_doc(Editor *editor) {
|
|||||||
item.documentation = message["documentation"].get<std::string>();
|
item.documentation = message["documentation"].get<std::string>();
|
||||||
else
|
else
|
||||||
item.documentation = "";
|
item.documentation = "";
|
||||||
|
editor->completion.box.render_update();
|
||||||
};
|
};
|
||||||
json message = {{"jsonrpc", "2.0"},
|
json message = {{"jsonrpc", "2.0"},
|
||||||
{"method", "completionItem/resolve"},
|
{"method", "completionItem/resolve"},
|
||||||
@@ -303,6 +331,7 @@ void complete_accept(Editor *editor) {
|
|||||||
if (!editor->completion.active || editor->completion.box.hidden)
|
if (!editor->completion.active || editor->completion.box.hidden)
|
||||||
return;
|
return;
|
||||||
auto &item = editor->completion.items[editor->completion.select];
|
auto &item = editor->completion.items[editor->completion.select];
|
||||||
|
// TODO: support snippets here
|
||||||
apply_lsp_edits(editor, item.edits);
|
apply_lsp_edits(editor, item.edits);
|
||||||
editor->completion.active = false;
|
editor->completion.active = false;
|
||||||
}
|
}
|
||||||
@@ -325,6 +354,7 @@ void complete_next(Editor *editor) {
|
|||||||
vi = (vi + 1) % s.visible.size();
|
vi = (vi + 1) % s.visible.size();
|
||||||
s.select = s.visible[vi];
|
s.select = s.visible[vi];
|
||||||
completion_resolve_doc(editor);
|
completion_resolve_doc(editor);
|
||||||
|
editor->completion.box.render_update();
|
||||||
}
|
}
|
||||||
|
|
||||||
void complete_prev(Editor *editor) {
|
void complete_prev(Editor *editor) {
|
||||||
@@ -338,6 +368,7 @@ void complete_prev(Editor *editor) {
|
|||||||
vi = (vi + s.visible.size() - 1) % s.visible.size();
|
vi = (vi + s.visible.size() - 1) % s.visible.size();
|
||||||
s.select = s.visible[vi];
|
s.select = s.visible[vi];
|
||||||
completion_resolve_doc(editor);
|
completion_resolve_doc(editor);
|
||||||
|
editor->completion.box.render_update();
|
||||||
}
|
}
|
||||||
|
|
||||||
void complete_select(Editor *editor, uint8_t index) {
|
void complete_select(Editor *editor, uint8_t index) {
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
#include "editor/folds.h"
|
#include "editor/folds.h"
|
||||||
#include "lsp/lsp.h"
|
#include "lsp/lsp.h"
|
||||||
#include "utils/utils.h"
|
#include "utils/utils.h"
|
||||||
#include <cstdint>
|
|
||||||
|
|
||||||
void edit_erase(Editor *editor, Coord pos, int64_t len) {
|
void edit_erase(Editor *editor, Coord pos, int64_t len) {
|
||||||
if (len == 0)
|
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);
|
(end_line_byte_start - start_line_byte) + end_col);
|
||||||
free(buf);
|
free(buf);
|
||||||
if (erase_len != 0)
|
if (erase_len != 0)
|
||||||
edit_erase(editor, end, -erase_len);
|
edit_erase(editor, start, erase_len);
|
||||||
if (len > 0)
|
if (len > 0)
|
||||||
edit_insert(editor, start, const_cast<char *>(text), len);
|
edit_insert(editor, start, const_cast<char *>(text), len);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ void handle_editor_event(Editor *editor, KeyEvent event) {
|
|||||||
static uint32_t click_count = 0;
|
static uint32_t click_count = 0;
|
||||||
static Coord last_click_pos = {UINT32_MAX, UINT32_MAX};
|
static Coord last_click_pos = {UINT32_MAX, UINT32_MAX};
|
||||||
Coord start = editor->cursor;
|
Coord start = editor->cursor;
|
||||||
|
uint8_t old_mode = mode;
|
||||||
if (editor->hover_active)
|
if (editor->hover_active)
|
||||||
editor->hover_active = false;
|
editor->hover_active = false;
|
||||||
if (event.key_type == KEY_MOUSE) {
|
if (event.key_type == KEY_MOUSE) {
|
||||||
@@ -612,6 +613,8 @@ void handle_editor_event(Editor *editor, KeyEvent event) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
ensure_scroll(editor);
|
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)
|
if ((event.key_type == KEY_CHAR || event.key_type == KEY_PASTE) && event.c)
|
||||||
free(event.c);
|
free(event.c);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,11 @@ void apply_lsp_edits(Editor *editor, std::vector<TextEdit> edits) {
|
|||||||
for (const auto &edit : edits)
|
for (const auto &edit : edits)
|
||||||
edit_replace(editor, edit.start, edit.end, edit.text.c_str(),
|
edit_replace(editor, edit.start, edit.end, edit.text.c_str(),
|
||||||
edit.text.size());
|
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) {
|
void editor_lsp_handle(Editor *editor, json msg) {
|
||||||
|
|||||||
@@ -468,6 +468,12 @@ void render_editor(Editor *editor) {
|
|||||||
global_byte_offset += line_len + 1;
|
global_byte_offset += line_len + 1;
|
||||||
line_index++;
|
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) {
|
if (cursor.row != UINT32_MAX && cursor.col != UINT32_MAX) {
|
||||||
int type = 0;
|
int type = 0;
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
@@ -483,17 +489,13 @@ void render_editor(Editor *editor) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
set_cursor(cursor.row, cursor.col, type, true);
|
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);
|
editor->hover.render(cursor);
|
||||||
else if (editor->diagnostics_active)
|
else if (editor->diagnostics_active)
|
||||||
editor->diagnostics.render(cursor);
|
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->buffer);
|
||||||
free(it);
|
free(it);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -130,8 +130,8 @@ void render() {
|
|||||||
first_render = false;
|
first_render = false;
|
||||||
}
|
}
|
||||||
for (uint32_t row = 0; row < rows; ++row) {
|
for (uint32_t row = 0; row < rows; ++row) {
|
||||||
uint32_t first_change_col = -1;
|
int64_t first_change_col = -1;
|
||||||
uint32_t last_change_col = -1;
|
int64_t last_change_col = -1;
|
||||||
for (uint32_t col = 0; col < cols; ++col) {
|
for (uint32_t col = 0; col < cols; ++col) {
|
||||||
uint32_t idx = row * cols + col;
|
uint32_t idx = row * cols + col;
|
||||||
ScreenCell &old_cell = old_screen[idx];
|
ScreenCell &old_cell = old_screen[idx];
|
||||||
@@ -144,7 +144,7 @@ void render() {
|
|||||||
if (first_change_col == -1) {
|
if (first_change_col == -1) {
|
||||||
first_change_col = col;
|
first_change_col = col;
|
||||||
if (first_change_col > 0) {
|
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) {
|
++back) {
|
||||||
ScreenCell &prev_cell =
|
ScreenCell &prev_cell =
|
||||||
screen[row * cols + (first_change_col - back)];
|
screen[row * cols + (first_change_col - back)];
|
||||||
@@ -161,7 +161,7 @@ void render() {
|
|||||||
if (first_change_col == -1)
|
if (first_change_col == -1)
|
||||||
continue;
|
continue;
|
||||||
char buf[64];
|
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);
|
out.append(buf);
|
||||||
for (uint32_t col = first_change_col; col <= last_change_col; ++col) {
|
for (uint32_t col = first_change_col; col <= last_change_col; ++col) {
|
||||||
uint32_t idx = row * cols + col;
|
uint32_t idx = row * cols + col;
|
||||||
@@ -241,8 +241,16 @@ void render() {
|
|||||||
current_underline = underline;
|
current_underline = underline;
|
||||||
}
|
}
|
||||||
if (width > 1 && overlap) {
|
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(' ');
|
||||||
|
}
|
||||||
|
out.push_back(' ');
|
||||||
} else {
|
} else {
|
||||||
if (!new_cell.utf8.empty()) {
|
if (!new_cell.utf8.empty()) {
|
||||||
if (new_cell.utf8[0] == '\t')
|
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) {
|
bool show_cursor_param) {
|
||||||
char buf[32];
|
char buf[32];
|
||||||
uint32_t n = snprintf(buf, sizeof(buf), "\x1b[%d;%dH\x1b[%d q", row + 1,
|
uint32_t n = snprintf(buf, sizeof(buf), "\x1b[%d;%dH\x1b[%d q", row + 1,
|
||||||
|
|||||||
@@ -1,5 +1,155 @@
|
|||||||
#include "ui/completionbox.h"
|
#include "ui/completionbox.h"
|
||||||
|
#include "editor/completions.h"
|
||||||
|
#include "utils/utils.h"
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
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);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user