Completions bug fixes

This commit is contained in:
2026-01-10 07:56:40 +00:00
parent e9da17eb34
commit b2a64f219f
21 changed files with 400 additions and 193 deletions

View File

@@ -1,9 +1,11 @@
#include "editor/decl.h"
#include "editor/editor.h"
#include "io/knot.h"
#include "io/sysio.h"
#include "lsp/lsp.h"
#include "main.h"
#include "utils/utils.h"
#include <regex>
inline static std::string completion_prefix(Editor *editor) {
Coord hook = editor->completion.hook;
@@ -50,23 +52,15 @@ void completion_filter(Editor *editor) {
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) {
pending->callback = [](Editor *editor, std::string, json message) {
auto &session = editor->completion;
std::unique_lock lock(session.mtx);
std::vector<json> items_json;
@@ -115,8 +109,15 @@ void completion_request(Editor *editor) {
item.is_markup =
item_json["documentation"]["kind"].get<std::string>() ==
"markdown";
item.documentation =
std::string documentation =
item_json["documentation"]["value"].get<std::string>();
if (item.is_markup) {
static const std::regex fence_no_lang("```(\\s*\\n)");
item.documentation = std::regex_replace(
documentation, fence_no_lang, "```" + editor->lang.name + "$1");
} else {
item.documentation = documentation;
}
}
}
if (item_json.contains("deprecated") &&
@@ -147,26 +148,27 @@ void completion_request(Editor *editor) {
edit.start.col = te["insert"]["start"]["character"];
edit.end.row = te["insert"]["end"]["line"];
edit.end.col = te["insert"]["end"]["character"];
} else if (te.contains("range")) {
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 {
edit.start = session.hook;
edit.end = editor->cursor;
}
} 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};
edit.end = editor->cursor;
} 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};
edit.end = editor->cursor;
}
utf8_normalize_edit(editor, &edit);
item.edits.push_back(edit);
if (item_json.contains("additionalTextEdits")) {
for (auto &te : item_json["additionalTextEdits"]) {
@@ -176,6 +178,7 @@ void completion_request(Editor *editor) {
edit.start.col = te["range"]["start"]["character"];
edit.end.row = te["range"]["end"]["line"];
edit.end.col = te["range"]["end"]["character"];
utf8_normalize_edit(editor, &edit);
item.edits.push_back(edit);
}
}
@@ -187,15 +190,23 @@ void completion_request(Editor *editor) {
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();
};
std::shared_lock lock(editor->knot_mtx);
LineIterator *it = begin_l_iter(editor->root, hook.row);
char *line = next_line(it, nullptr);
if (!line) {
free(it->buffer);
free(it);
};
return;
}
uint32_t col = utf8_byte_offset_to_utf16(line, editor->cursor.col);
free(it->buffer);
free(it);
lock.unlock();
json message = {
{"jsonrpc", "2.0"},
{"method", "textDocument/completion"},
@@ -315,10 +326,26 @@ void completion_resolve_doc(Editor *editor) {
pending->callback = [](Editor *editor, std::string, json message) {
std::unique_lock lock(editor->completion.mtx);
auto &item = editor->completion.items[editor->completion.select];
if (message.contains("documentation"))
item.documentation = message["documentation"].get<std::string>();
else
item.documentation = "";
if (message["result"].contains("documentation")) {
if (message["result"]["documentation"].is_string()) {
item.documentation =
message["result"]["documentation"].get<std::string>();
} else if (message["result"]["documentation"].contains("value") &&
message["result"]["documentation"]["value"].is_string()) {
item.is_markup =
message["result"]["documentation"]["kind"].get<std::string>() ==
"markdown";
std::string documentation =
message["result"]["documentation"]["value"].get<std::string>();
if (item.is_markup) {
static const std::regex fence_no_lang("```(\\s*\\n)");
item.documentation = std::regex_replace(
documentation, fence_no_lang, "```" + editor->lang.name + "$1");
} else {
item.documentation = documentation;
}
}
}
editor->completion.box.render_update();
};
json message = {{"jsonrpc", "2.0"},
@@ -332,7 +359,7 @@ void complete_accept(Editor *editor) {
return;
auto &item = editor->completion.items[editor->completion.select];
// TODO: support snippets here
apply_lsp_edits(editor, item.edits);
apply_lsp_edits(editor, item.edits, true);
editor->completion.active = false;
}

View File

@@ -283,21 +283,15 @@ void edit_insert(Editor *editor, Coord pos, char *data, uint32_t len) {
void edit_replace(Editor *editor, Coord start, Coord end, const char *text,
uint32_t len) {
std::shared_lock lock(editor->knot_mtx);
uint32_t start_line_byte = line_to_byte(editor->root, start.row, nullptr);
uint32_t end_len;
uint32_t end_line_byte_start = line_to_byte(editor->root, end.row, &end_len);
uint32_t end_line_byte = end_line_byte_start + len;
uint32_t start_byte =
line_to_byte(editor->root, start.row, nullptr) + start.col;
uint32_t end_byte = line_to_byte(editor->root, end.row, nullptr) + end.col;
lock.unlock();
char *buf =
read(editor->root, start_line_byte, end_line_byte - start_line_byte);
char *buf = read(editor->root, start_byte, end_byte - start_byte);
if (!buf)
return;
uint32_t start_col = utf16_offset_to_utf8(buf, start.col);
uint32_t end_col = utf16_offset_to_utf8(
buf + (end_line_byte_start - start_line_byte), end.col);
uint32_t erase_len =
count_clusters(buf, end_line_byte - start_line_byte, start_col,
(end_line_byte_start - start_line_byte) + end_col);
count_clusters(buf, end_byte - start_byte, 0, end_byte - start_byte);
free(buf);
if (erase_len != 0)
edit_erase(editor, start, erase_len);

View File

@@ -1,6 +1,8 @@
#include "editor/editor.h"
#include "editor/decl.h"
#include "lsp/lsp.h"
#include "utils/utils.h"
#include <shared_mutex>
Editor *new_editor(const char *filename_arg, Coord position, Coord size) {
Editor *editor = new Editor();
@@ -66,15 +68,62 @@ void free_editor(Editor *editor) {
void save_file(Editor *editor) {
if (!editor || !editor->root)
return;
std::shared_lock lock(editor->knot_mtx);
char *str = read(editor->root, 0, editor->root->char_count);
if (!str)
return;
std::ofstream out(editor->filename);
out.write(str, editor->root->char_count);
out.close();
free(str);
json msg = {{"jsonrpc", "2.0"},
{"method", "textDocument/didSave"},
{"params", {{"textDocument", {{"uri", editor->uri}}}}}};
if (editor->lsp)
lsp_send(editor->lsp, msg, nullptr);
lock.unlock();
if (editor->lsp) {
json save_msg = {{"jsonrpc", "2.0"},
{"method", "textDocument/didSave"},
{"params", {{"textDocument", {{"uri", editor->uri}}}}}};
lsp_send(editor->lsp, save_msg, nullptr);
if (editor->lsp->allow_formatting) {
json msg = {{"jsonrpc", "2.0"},
{"method", "textDocument/formatting"},
{"params",
{{"textDocument", {{"uri", editor->uri}}},
{"options",
{{"tabSize", 2},
{"insertSpaces", true},
{"trimTrailingWhitespace", true},
{"trimFinalNewlines", true}}}}}};
LSPPending *pending = new LSPPending();
pending->editor = editor;
pending->method = "textDocument/formatting";
pending->callback = [save_msg](Editor *editor, std::string,
json message) {
auto &edits = message["result"];
if (edits.is_array()) {
std::vector<TextEdit> t_edits;
t_edits.reserve(edits.size());
for (auto &edit : edits) {
TextEdit t_edit;
t_edit.text = edit.value("newText", "");
t_edit.start.row = edit["range"]["start"]["line"];
t_edit.start.col = edit["range"]["start"]["character"];
t_edit.end.row = edit["range"]["end"]["line"];
t_edit.end.col = edit["range"]["end"]["character"];
utf8_normalize_edit(editor, &t_edit);
t_edits.push_back(t_edit);
}
apply_lsp_edits(editor, t_edits, false);
ensure_scroll(editor);
std::unique_lock lock(editor->knot_mtx);
char *str = read(editor->root, 0, editor->root->char_count);
if (!str)
return;
std::ofstream out(editor->filename);
out.write(str, editor->root->char_count);
free(str);
lsp_send(editor->lsp, save_msg, nullptr);
}
};
lsp_send(editor->lsp, msg, pending);
}
}
}

View File

@@ -612,9 +612,9 @@ void handle_editor_event(Editor *editor, KeyEvent event) {
mode = NORMAL;
break;
}
ensure_scroll(editor);
if (old_mode == mode || mode != INSERT)
handle_completion(editor, event);
ensure_scroll(editor);
if ((event.key_type == KEY_CHAR || event.key_type == KEY_PASTE) && event.c)
free(event.c);
}

View File

@@ -1,17 +1,37 @@
#include "editor/decl.h"
#include "editor/editor.h"
void apply_lsp_edits(Editor *editor, std::vector<TextEdit> edits) {
void apply_lsp_edits(Editor *editor, std::vector<TextEdit> edits, bool move) {
if (!edits.size())
return;
TextEdit first = edits[0];
Coord cursor = editor->cursor;
std::sort(
edits.begin(), edits.end(),
[](const TextEdit &a, const TextEdit &b) { return a.start > b.start; });
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()));
if (move) {
std::shared_lock lock(editor->knot_mtx);
editor->cursor = first.start;
editor->cursor =
move_right_pure(editor, editor->cursor,
count_clusters(first.text.c_str(), first.text.size(), 0,
first.text.size()));
} else {
if (cursor.row >= editor->root->line_count) {
editor->cursor.row = editor->root->line_count - 1;
editor->cursor.col = 0;
} else {
std::shared_lock lock(editor->knot_mtx);
uint32_t len;
line_to_byte(editor->root, cursor.row, &len);
len--;
editor->cursor.row = cursor.row;
editor->cursor.col = cursor.col < len ? cursor.col : len;
}
}
}
void editor_lsp_handle(Editor *editor, json msg) {