Add indentation engine and other fixes
This commit is contained in:
@@ -5,7 +5,6 @@
|
||||
#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;
|
||||
@@ -58,6 +57,7 @@ void completion_request(Editor *editor) {
|
||||
editor->completion.items.clear();
|
||||
editor->completion.visible.clear();
|
||||
editor->completion.select = 0;
|
||||
editor->completion.version = editor->lsp_version;
|
||||
LSPPending *pending = new LSPPending();
|
||||
pending->editor = editor;
|
||||
pending->method = "textDocument/completion";
|
||||
@@ -67,6 +67,7 @@ void completion_request(Editor *editor) {
|
||||
std::vector<json> items_json;
|
||||
std::vector<char> end_chars_def;
|
||||
int insert_text_format = 1;
|
||||
int insert_text_mode = 1;
|
||||
if (message.contains("result")) {
|
||||
auto &result = message["result"];
|
||||
if (result.is_array()) {
|
||||
@@ -87,6 +88,9 @@ void completion_request(Editor *editor) {
|
||||
if (defs.contains("insertTextFormat") &&
|
||||
defs["insertTextFormat"].is_number())
|
||||
insert_text_format = defs["insertTextFormat"].get<int>();
|
||||
if (defs.contains("insertTextMode") &&
|
||||
defs["insertTextMode"].is_number())
|
||||
insert_text_mode = defs["insertTextMode"].get<int>();
|
||||
if (defs.contains("textEdit"))
|
||||
if (defs["textEdit"].is_array())
|
||||
for (auto &c : defs["textEdit"]) {
|
||||
@@ -119,10 +123,11 @@ void completion_request(Editor *editor) {
|
||||
"markdown";
|
||||
std::string documentation =
|
||||
item_json["documentation"]["value"].get<std::string>();
|
||||
if (documentation.size() > 1024)
|
||||
item.is_markup = false;
|
||||
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");
|
||||
item.documentation =
|
||||
substitute_fence(documentation, editor->lang.name);
|
||||
} else {
|
||||
item.documentation = documentation;
|
||||
}
|
||||
@@ -193,6 +198,8 @@ void completion_request(Editor *editor) {
|
||||
item.snippet = insert_text_format == 2;
|
||||
if (item_json.contains("insertTextFormat"))
|
||||
item.snippet = item_json["insertTextFormat"].get<int>() == 2;
|
||||
if (item_json.contains("insertTextMode"))
|
||||
item.asis = item_json["insertTextMode"].get<int>() == 1;
|
||||
if (item_json.contains("commitCharacters"))
|
||||
for (auto &c : item_json["commitCharacters"])
|
||||
if (c.is_string() && c.get<std::string>().size() == 1)
|
||||
@@ -301,9 +308,6 @@ void handle_completion(Editor *editor, KeyEvent event) {
|
||||
else
|
||||
completion_request(editor);
|
||||
}
|
||||
} else {
|
||||
editor->completion.trigger = 3;
|
||||
completion_request(editor);
|
||||
}
|
||||
} else {
|
||||
editor->completion.active = false;
|
||||
@@ -351,10 +355,11 @@ void completion_resolve_doc(Editor *editor) {
|
||||
"markdown";
|
||||
std::string documentation =
|
||||
message["result"]["documentation"]["value"].get<std::string>();
|
||||
if (documentation.size() > 1024)
|
||||
item.is_markup = false;
|
||||
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");
|
||||
item.documentation =
|
||||
substitute_fence(documentation, editor->lang.name);
|
||||
} else {
|
||||
item.documentation = documentation;
|
||||
}
|
||||
@@ -372,18 +377,21 @@ 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 maybe?
|
||||
int delta_col = 0;
|
||||
TextEdit &e = item.edits[0];
|
||||
if (e.end.row == editor->cursor.row) {
|
||||
delta_col = editor->cursor.col - e.end.col;
|
||||
e.end.col = editor->cursor.col;
|
||||
for (size_t i = 1; i < item.edits.size(); ++i) {
|
||||
TextEdit &e = item.edits[i];
|
||||
if (e.start.row == editor->cursor.row) {
|
||||
e.start.col += delta_col;
|
||||
if (e.end.row == editor->cursor.row)
|
||||
e.end.col += delta_col;
|
||||
// TODO: support snippets and asis here
|
||||
// once indentation engine is implemented
|
||||
if (editor->completion.version != editor->lsp_version) {
|
||||
int delta_col = 0;
|
||||
TextEdit &e = item.edits[0];
|
||||
if (e.end.row == editor->cursor.row) {
|
||||
delta_col = editor->cursor.col - e.end.col;
|
||||
e.end.col = editor->cursor.col;
|
||||
for (size_t i = 1; i < item.edits.size(); ++i) {
|
||||
TextEdit &e = item.edits[i];
|
||||
if (e.start.row == editor->cursor.row) {
|
||||
e.start.col += delta_col;
|
||||
if (e.end.row == editor->cursor.row)
|
||||
e.end.col += delta_col;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -286,10 +286,10 @@ void edit_replace(Editor *editor, Coord start, Coord end, const char *text,
|
||||
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_byte, end_byte - start_byte);
|
||||
if (!buf)
|
||||
return;
|
||||
lock.unlock();
|
||||
uint32_t erase_len =
|
||||
count_clusters(buf, end_byte - start_byte, 0, end_byte - start_byte);
|
||||
free(buf);
|
||||
|
||||
@@ -38,6 +38,7 @@ Editor *new_editor(const char *filename_arg, Coord position, Coord size) {
|
||||
}
|
||||
if (len <= (1024 * 28))
|
||||
request_add_to_lsp(editor->lang, editor);
|
||||
editor->indents.compute_indent(editor);
|
||||
return editor;
|
||||
}
|
||||
|
||||
@@ -69,14 +70,15 @@ void save_file(Editor *editor) {
|
||||
if (!editor || !editor->root)
|
||||
return;
|
||||
std::shared_lock lock(editor->knot_mtx);
|
||||
int version = editor->lsp_version;
|
||||
char *str = read(editor->root, 0, editor->root->char_count);
|
||||
if (!str)
|
||||
return;
|
||||
lock.unlock();
|
||||
std::ofstream out(editor->filename);
|
||||
out.write(str, editor->root->char_count);
|
||||
out.close();
|
||||
free(str);
|
||||
lock.unlock();
|
||||
if (editor->lsp) {
|
||||
json save_msg = {{"jsonrpc", "2.0"},
|
||||
{"method", "textDocument/didSave"},
|
||||
@@ -95,8 +97,10 @@ void save_file(Editor *editor) {
|
||||
LSPPending *pending = new LSPPending();
|
||||
pending->editor = editor;
|
||||
pending->method = "textDocument/formatting";
|
||||
pending->callback = [save_msg](Editor *editor, std::string,
|
||||
json message) {
|
||||
pending->callback = [save_msg, version](Editor *editor, std::string,
|
||||
json message) {
|
||||
if (version != editor->lsp_version)
|
||||
return;
|
||||
auto &edits = message["result"];
|
||||
if (edits.is_array()) {
|
||||
std::vector<TextEdit> t_edits;
|
||||
@@ -113,10 +117,11 @@ void save_file(Editor *editor) {
|
||||
}
|
||||
apply_lsp_edits(editor, t_edits, false);
|
||||
ensure_scroll(editor);
|
||||
std::unique_lock lock(editor->knot_mtx);
|
||||
std::shared_lock lock(editor->knot_mtx);
|
||||
char *str = read(editor->root, 0, editor->root->char_count);
|
||||
if (!str)
|
||||
return;
|
||||
lock.unlock();
|
||||
std::ofstream out(editor->filename);
|
||||
out.write(str, editor->root->char_count);
|
||||
free(str);
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
#include "editor/folds.h"
|
||||
#include "lsp/lsp.h"
|
||||
#include "main.h"
|
||||
#include "utils/utils.h"
|
||||
#include <cstdint>
|
||||
|
||||
void handle_editor_event(Editor *editor, KeyEvent event) {
|
||||
static std::chrono::steady_clock::time_point last_click_time =
|
||||
@@ -21,11 +23,11 @@ void handle_editor_event(Editor *editor, KeyEvent event) {
|
||||
case SCROLL:
|
||||
switch (event.mouse_direction) {
|
||||
case SCROLL_UP:
|
||||
scroll_up(editor, 10);
|
||||
scroll_up(editor, 4);
|
||||
ensure_cursor(editor);
|
||||
break;
|
||||
case SCROLL_DOWN:
|
||||
scroll_down(editor, 10);
|
||||
scroll_down(editor, 4);
|
||||
ensure_cursor(editor);
|
||||
break;
|
||||
case SCROLL_LEFT:
|
||||
@@ -355,13 +357,17 @@ void handle_editor_event(Editor *editor, KeyEvent event) {
|
||||
ensure_cursor(editor);
|
||||
break;
|
||||
case '>':
|
||||
case '.':
|
||||
indent_line(editor, editor->cursor.row);
|
||||
break;
|
||||
case '.': {
|
||||
uint32_t delta = editor->indents.indent_line(editor->cursor.row);
|
||||
editor->cursor.col = start.col + delta;
|
||||
editor->cursor.row = start.row;
|
||||
} break;
|
||||
case '<':
|
||||
case ',':
|
||||
dedent_line(editor, editor->cursor.row);
|
||||
break;
|
||||
case ',': {
|
||||
uint32_t delta = editor->indents.dedent_line(editor->cursor.row);
|
||||
editor->cursor.col = MAX((int64_t)start.col - delta, 0);
|
||||
editor->cursor.row = start.row;
|
||||
} break;
|
||||
case CTRL('s'):
|
||||
save_file(editor);
|
||||
break;
|
||||
@@ -385,33 +391,7 @@ void handle_editor_event(Editor *editor, KeyEvent event) {
|
||||
edit_insert(editor, editor->cursor, (char *)" ", 2);
|
||||
cursor_right(editor, 2);
|
||||
} else if (event.c[0] == '\n' || event.c[0] == '\r') {
|
||||
uint32_t line_len = 0;
|
||||
LineIterator *it = begin_l_iter(editor->root, editor->cursor.row);
|
||||
char *line = next_line(it, &line_len);
|
||||
bool closing = false;
|
||||
if (line && line_len > 0 && line[line_len - 1] == '\n')
|
||||
line_len--;
|
||||
uint32_t indent = get_indent(editor, editor->cursor);
|
||||
if (line) {
|
||||
if (indent == 0)
|
||||
indent = leading_indent(line, line_len);
|
||||
closing = closing_after_cursor(line, line_len, editor->cursor.col);
|
||||
}
|
||||
free(it->buffer);
|
||||
free(it);
|
||||
uint32_t closing_indent =
|
||||
indent >= INDENT_WIDTH ? indent - INDENT_WIDTH : 0;
|
||||
std::string insert_text("\n");
|
||||
insert_text.append(indent, ' ');
|
||||
Coord new_cursor = {editor->cursor.row + 1, indent};
|
||||
if (closing) {
|
||||
insert_text.push_back('\n');
|
||||
insert_text.append(closing_indent, ' ');
|
||||
}
|
||||
edit_insert(editor, editor->cursor, insert_text.data(),
|
||||
insert_text.size());
|
||||
editor->cursor = new_cursor;
|
||||
editor->cursor_preffered = UINT32_MAX;
|
||||
editor->indents.insert_new_line(editor->cursor);
|
||||
} else if (event.c[0] == CTRL('W')) {
|
||||
uint32_t prev_col_byte, prev_col_cluster;
|
||||
word_boundaries(editor, editor->cursor, &prev_col_byte, nullptr,
|
||||
@@ -424,8 +404,15 @@ void handle_editor_event(Editor *editor, KeyEvent event) {
|
||||
char c = event.c[0];
|
||||
uint32_t col = editor->cursor.col;
|
||||
LineIterator *it = begin_l_iter(editor->root, editor->cursor.row);
|
||||
if (!it)
|
||||
return;
|
||||
uint32_t len;
|
||||
char *line = next_line(it, &len);
|
||||
if (!line) {
|
||||
free(it->buffer);
|
||||
free(it);
|
||||
return;
|
||||
}
|
||||
bool skip_insert = false;
|
||||
if (line && col < len) {
|
||||
char next = line[col];
|
||||
@@ -465,6 +452,67 @@ void handle_editor_event(Editor *editor, KeyEvent event) {
|
||||
edit_insert(editor, editor->cursor, &c, 1);
|
||||
cursor_right(editor, 1);
|
||||
}
|
||||
if (editor->lsp && editor->lsp->allow_formatting_on_type) {
|
||||
for (char ch : editor->lsp->format_chars) {
|
||||
if (ch == c) {
|
||||
LineIterator *it =
|
||||
begin_l_iter(editor->root, editor->cursor.row);
|
||||
if (!it)
|
||||
return;
|
||||
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);
|
||||
int version = editor->lsp_version;
|
||||
json message = {
|
||||
{"jsonrpc", "2.0"},
|
||||
{"method", "textDocument/onTypeFormatting"},
|
||||
{"params",
|
||||
{{"textDocument", {{"uri", editor->uri}}},
|
||||
{"position",
|
||||
{{"line", editor->cursor.row}, {"character", col}}},
|
||||
{"ch", std::string(1, c)},
|
||||
{"options",
|
||||
{{"tabSize", 2},
|
||||
{"insertSpaces", true},
|
||||
{"trimTrailingWhitespace", true},
|
||||
{"trimFinalNewlines", true}}}}}};
|
||||
LSPPending *pending = new LSPPending();
|
||||
pending->editor = editor;
|
||||
pending->method = "textDocument/onTypeFormatting";
|
||||
pending->callback = [version](Editor *editor, std::string,
|
||||
json message) {
|
||||
if (version != editor->lsp_version)
|
||||
return;
|
||||
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);
|
||||
}
|
||||
};
|
||||
lsp_send(editor->lsp, message, pending);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (event.c[0] == 0x7F || event.c[0] == 0x08) {
|
||||
Coord prev_pos = editor->cursor;
|
||||
|
||||
@@ -1,87 +1,376 @@
|
||||
#include "editor/editor.h"
|
||||
#include "io/knot.h"
|
||||
#include "lsp/lsp.h"
|
||||
|
||||
uint32_t leading_indent(const char *line, uint32_t len) {
|
||||
uint32_t indent = 0;
|
||||
for (uint32_t i = 0; i < len; i++) {
|
||||
if (line[i] == ' ')
|
||||
indent++;
|
||||
else if (line[i] == '\t')
|
||||
indent += INDENT_WIDTH;
|
||||
else
|
||||
void IndentationEngine::compute_indent(Editor *n_editor) {
|
||||
editor = n_editor;
|
||||
uint32_t line_idx = 0;
|
||||
LineIterator *it = begin_l_iter(editor->root, 0);
|
||||
if (!it)
|
||||
return;
|
||||
auto is_set = kLangtoIndent.find(editor->lang.name);
|
||||
indent = is_set != kLangtoIndent.end() ? is_set->second : 0;
|
||||
while (indent == 0 && line_idx < 64) {
|
||||
uint32_t len;
|
||||
char *line = next_line(it, &len);
|
||||
if (!line)
|
||||
break;
|
||||
}
|
||||
return indent;
|
||||
}
|
||||
|
||||
uint32_t get_indent(Editor *editor, Coord cursor) {
|
||||
if (!editor)
|
||||
return 0;
|
||||
LineIterator *it = begin_l_iter(editor->root, cursor.row);
|
||||
next_line(it, nullptr);
|
||||
uint32_t line_len;
|
||||
char *line;
|
||||
while ((line = prev_line(it, &line_len)) != nullptr) {
|
||||
if (line_len == 0)
|
||||
line_idx++;
|
||||
if (len > 0 && line[len - 1] == '\n')
|
||||
--len;
|
||||
if (len == 0)
|
||||
continue;
|
||||
uint32_t indent = leading_indent(line, line_len);
|
||||
free(it->buffer);
|
||||
free(it);
|
||||
return indent;
|
||||
if (line[0] == '\t') {
|
||||
indent = 1;
|
||||
} else if (line[0] == ' ') {
|
||||
for (uint32_t i = 0; i < len; i++) {
|
||||
if (line[i] == ' ') {
|
||||
indent += 1;
|
||||
} else {
|
||||
if (indent == 1)
|
||||
indent = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (indent == 0)
|
||||
indent = 2;
|
||||
free(it->buffer);
|
||||
free(it);
|
||||
}
|
||||
|
||||
uint32_t IndentationEngine::indent_real(char *line, uint32_t len) {
|
||||
uint32_t spaces = 0;
|
||||
uint32_t tabs = 0;
|
||||
for (uint32_t i = 0; i < len; i++) {
|
||||
if (line[i] == ' ') {
|
||||
spaces += 1;
|
||||
} else if (line[i] == '\t') {
|
||||
tabs += 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return tabs ? tabs : spaces / indent;
|
||||
}
|
||||
|
||||
uint32_t IndentationEngine::indent_expected(uint32_t row) {
|
||||
std::shared_lock lock(editor->knot_mtx);
|
||||
uint32_t line_idx = row;
|
||||
if (row == 0)
|
||||
return 0;
|
||||
LineIterator *it = begin_l_iter(editor->root, row - 1);
|
||||
if (!it)
|
||||
return 0;
|
||||
next_line(it, nullptr);
|
||||
uint32_t c_indent = 0;
|
||||
while (line_idx--) {
|
||||
uint32_t len;
|
||||
char *line = prev_line(it, &len);
|
||||
if (!line)
|
||||
break;
|
||||
if (len > 0 && line[len - 1] == '\n')
|
||||
--len;
|
||||
if (len == 0)
|
||||
continue;
|
||||
c_indent = indent_real(line, len);
|
||||
auto is_end_set = kLangtoBlockStartsEnd.find(editor->lang.name);
|
||||
auto is_start_set = kLangtoBlockStartsStart.find(editor->lang.name);
|
||||
bool is_end = false;
|
||||
if (is_end_set != kLangtoBlockStartsEnd.end())
|
||||
for (auto end : is_end_set->second)
|
||||
if (ends_with(line, end)) {
|
||||
c_indent++;
|
||||
is_end = true;
|
||||
break;
|
||||
}
|
||||
if (!is_end && is_start_set != kLangtoBlockStartsStart.end())
|
||||
for (auto end : is_start_set->second)
|
||||
if (starts_with(line, end)) {
|
||||
c_indent++;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
free(it->buffer);
|
||||
free(it);
|
||||
return 0;
|
||||
return c_indent;
|
||||
}
|
||||
|
||||
bool closing_after_cursor(const char *line, uint32_t len, uint32_t col) {
|
||||
uint32_t i = col;
|
||||
while (i < len && (line[i] == ' ' || line[i] == '\t'))
|
||||
i++;
|
||||
if (i >= len)
|
||||
return false;
|
||||
return line[i] == '}' || line[i] == ']' || line[i] == ')';
|
||||
}
|
||||
|
||||
void indent_line(Editor *editor, uint32_t row) {
|
||||
if (!editor)
|
||||
return;
|
||||
uint32_t IndentationEngine::set_indent(uint32_t row, int64_t new_indent) {
|
||||
std::shared_lock lock(editor->knot_mtx);
|
||||
LineIterator *it = begin_l_iter(editor->root, row);
|
||||
uint32_t line_len;
|
||||
char *line = next_line(it, &line_len);
|
||||
if (!it)
|
||||
return 0;
|
||||
uint32_t len;
|
||||
char *line = next_line(it, &len);
|
||||
if (!line) {
|
||||
free(it->buffer);
|
||||
free(it);
|
||||
return 0;
|
||||
}
|
||||
if (len > 0 && line[len - 1] == '\n')
|
||||
--len;
|
||||
lock.unlock();
|
||||
if (new_indent <= 0)
|
||||
new_indent = 0;
|
||||
uint32_t ws_len = 0;
|
||||
while (ws_len < len && (line[ws_len] == ' ' || line[ws_len] == '\t'))
|
||||
ws_len++;
|
||||
std::string new_ws;
|
||||
if (indent == 1)
|
||||
new_ws.assign(new_indent, '\t');
|
||||
else
|
||||
new_ws.assign(new_indent * indent, ' ');
|
||||
Coord start = {row, 0};
|
||||
Coord end = {row, ws_len};
|
||||
edit_replace(editor, start, end, new_ws.c_str(), new_ws.length());
|
||||
free(it->buffer);
|
||||
free(it);
|
||||
return len - ws_len + (new_indent * indent);
|
||||
}
|
||||
|
||||
uint32_t IndentationEngine::indent_line(uint32_t row) {
|
||||
std::shared_lock lock(editor->knot_mtx);
|
||||
LineIterator *it = begin_l_iter(editor->root, row);
|
||||
if (!it)
|
||||
return 0;
|
||||
uint32_t len;
|
||||
char *line = next_line(it, &len);
|
||||
if (!line) {
|
||||
free(it->buffer);
|
||||
free(it);
|
||||
return 0;
|
||||
}
|
||||
lock.unlock();
|
||||
if (len > 0 && line[len - 1] == '\n')
|
||||
--len;
|
||||
uint32_t new_indent = indent_real(line, len) + 1;
|
||||
uint32_t ws_len = 0;
|
||||
while (ws_len < len && (line[ws_len] == ' ' || line[ws_len] == '\t'))
|
||||
ws_len++;
|
||||
std::string new_ws;
|
||||
if (indent == 1)
|
||||
new_ws.assign(new_indent, '\t');
|
||||
else
|
||||
new_ws.assign(new_indent * indent, ' ');
|
||||
edit_replace(editor, {row, 0}, {row, ws_len}, new_ws.c_str(),
|
||||
new_indent * indent);
|
||||
free(it->buffer);
|
||||
free(it);
|
||||
return (uint32_t)ABS((int64_t)ws_len - (new_indent * indent));
|
||||
}
|
||||
|
||||
uint32_t IndentationEngine::dedent_line(uint32_t row) {
|
||||
std::shared_lock lock(editor->knot_mtx);
|
||||
LineIterator *it = begin_l_iter(editor->root, row);
|
||||
if (!it)
|
||||
return 0;
|
||||
uint32_t len;
|
||||
char *line = next_line(it, &len);
|
||||
if (!line) {
|
||||
free(it->buffer);
|
||||
free(it);
|
||||
return 0;
|
||||
}
|
||||
lock.unlock();
|
||||
if (len > 0 && line[len - 1] == '\n')
|
||||
--len;
|
||||
int64_t new_indent = (int64_t)indent_real(line, len) - 1;
|
||||
if (new_indent < 0)
|
||||
new_indent = 0;
|
||||
uint32_t ws_len = 0;
|
||||
while (ws_len < len && (line[ws_len] == ' ' || line[ws_len] == '\t'))
|
||||
ws_len++;
|
||||
std::string new_ws;
|
||||
if (indent == 1)
|
||||
new_ws.assign(new_indent, '\t');
|
||||
else
|
||||
new_ws.assign(new_indent * indent, ' ');
|
||||
edit_replace(editor, {row, 0}, {row, ws_len}, new_ws.c_str(),
|
||||
new_indent * indent);
|
||||
free(it->buffer);
|
||||
free(it);
|
||||
return (uint32_t)ABS((int64_t)ws_len - (new_indent * indent));
|
||||
}
|
||||
|
||||
void IndentationEngine::insert_new_line(Coord cursor) {
|
||||
std::string formatted;
|
||||
std::shared_lock lock(editor->knot_mtx);
|
||||
LineIterator *it = begin_l_iter(editor->root, cursor.row);
|
||||
if (!it)
|
||||
return;
|
||||
uint32_t len;
|
||||
char *line = next_line(it, &len);
|
||||
if (!line) {
|
||||
free(it->buffer);
|
||||
free(it);
|
||||
return;
|
||||
}
|
||||
char *spaces = (char *)malloc(INDENT_WIDTH);
|
||||
memset(spaces, ' ', INDENT_WIDTH);
|
||||
Coord cursor = editor->cursor;
|
||||
edit_insert(editor, {row, 0}, spaces, INDENT_WIDTH);
|
||||
editor->cursor = cursor;
|
||||
free(spaces);
|
||||
free(it->buffer);
|
||||
free(it);
|
||||
}
|
||||
|
||||
void dedent_line(Editor *editor, uint32_t row) {
|
||||
if (!editor)
|
||||
return;
|
||||
LineIterator *it = begin_l_iter(editor->root, row);
|
||||
uint32_t line_len;
|
||||
char *line = next_line(it, &line_len);
|
||||
if (!line) {
|
||||
lock.unlock();
|
||||
if (len > 0 && line[len - 1] == '\n')
|
||||
--len;
|
||||
if (cursor.col >= len) {
|
||||
auto is_end_full = kLangtoBlockEndsFull.find(editor->lang.name);
|
||||
auto is_end_start = kLangtoBlockEndsStart.find(editor->lang.name);
|
||||
bool end_matched = false;
|
||||
if (is_end_full != kLangtoBlockEndsFull.end())
|
||||
for (auto end : is_end_full->second)
|
||||
if (end == trim(line)) {
|
||||
cursor.col =
|
||||
set_indent(cursor.row, (int64_t)indent_expected(cursor.row) - 1);
|
||||
end_matched = true;
|
||||
break;
|
||||
}
|
||||
if (!end_matched && is_end_start != kLangtoBlockEndsStart.end())
|
||||
for (auto end : is_end_start->second)
|
||||
if (starts_with(trim(line), end)) {
|
||||
cursor.col =
|
||||
set_indent(cursor.row, (int64_t)indent_expected(cursor.row) - 1);
|
||||
break;
|
||||
}
|
||||
lock.lock();
|
||||
free(it->buffer);
|
||||
free(it);
|
||||
return;
|
||||
it = begin_l_iter(editor->root, cursor.row);
|
||||
if (!it)
|
||||
return;
|
||||
line = next_line(it, &len);
|
||||
if (!line) {
|
||||
free(it->buffer);
|
||||
free(it);
|
||||
return;
|
||||
}
|
||||
if (len > 0 && line[len - 1] == '\n')
|
||||
--len;
|
||||
lock.unlock();
|
||||
}
|
||||
uint32_t indent = leading_indent(line, line_len);
|
||||
if (indent == 0) {
|
||||
free(it->buffer);
|
||||
free(it);
|
||||
return;
|
||||
std::string ending = trim(std::string(line + cursor.col, len - cursor.col));
|
||||
std::string before = trim(std::string(line, cursor.col));
|
||||
uint32_t c_indent = indent_real(line, len);
|
||||
if (!ending.empty()) {
|
||||
bool ending_valid = false;
|
||||
bool starting_valid = false;
|
||||
auto is_end_full = kLangtoBlockEndsFull.find(editor->lang.name);
|
||||
auto is_end_start = kLangtoBlockEndsStart.find(editor->lang.name);
|
||||
auto is_end_set = kLangtoBlockStartsEnd.find(editor->lang.name);
|
||||
auto is_start_set = kLangtoBlockStartsStart.find(editor->lang.name);
|
||||
if (is_end_full != kLangtoBlockEndsFull.end())
|
||||
for (auto end : is_end_full->second)
|
||||
if (ending == end) {
|
||||
ending_valid = true;
|
||||
break;
|
||||
}
|
||||
if (!ending_valid && is_end_start != kLangtoBlockEndsStart.end())
|
||||
for (auto end : is_end_start->second)
|
||||
if (starts_with(ending, end)) {
|
||||
ending_valid = true;
|
||||
break;
|
||||
}
|
||||
if (is_end_set != kLangtoBlockStartsEnd.end())
|
||||
for (auto end : is_end_set->second)
|
||||
if (ends_with(before, end)) {
|
||||
c_indent++;
|
||||
starting_valid = true;
|
||||
break;
|
||||
}
|
||||
if (!starting_valid && is_start_set != kLangtoBlockStartsStart.end())
|
||||
for (auto end : is_start_set->second)
|
||||
if (starts_with(before, end)) {
|
||||
c_indent++;
|
||||
break;
|
||||
}
|
||||
if (ending_valid && starting_valid)
|
||||
ending = "\n" +
|
||||
(indent == 1 ? std::string(c_indent, '\t')
|
||||
: std::string(c_indent * indent, ' ')) +
|
||||
ending;
|
||||
else if (ending_valid)
|
||||
c_indent--;
|
||||
}
|
||||
uint32_t remove = indent >= INDENT_WIDTH ? INDENT_WIDTH : indent;
|
||||
edit_erase(editor, {row, 0}, remove);
|
||||
auto is_end_set = kLangtoBlockStartsEnd.find(editor->lang.name);
|
||||
auto is_start_set = kLangtoBlockStartsStart.find(editor->lang.name);
|
||||
bool is_end = false;
|
||||
if (is_end_set != kLangtoBlockStartsEnd.end())
|
||||
for (auto end : is_end_set->second)
|
||||
if (ends_with(before, end)) {
|
||||
c_indent++;
|
||||
is_end = true;
|
||||
break;
|
||||
}
|
||||
if (!is_end && is_start_set != kLangtoBlockStartsStart.end())
|
||||
for (auto end : is_start_set->second)
|
||||
if (starts_with(before, end)) {
|
||||
c_indent++;
|
||||
break;
|
||||
}
|
||||
formatted = "\n" +
|
||||
(indent == 1 ? std::string(c_indent, '\t')
|
||||
: std::string(c_indent * indent, ' ')) +
|
||||
ending;
|
||||
Coord new_cursor = {cursor.row + 1, c_indent * indent};
|
||||
edit_replace(editor, cursor, {cursor.row, len}, formatted.data(),
|
||||
formatted.size());
|
||||
editor->cursor = new_cursor;
|
||||
editor->cursor_preffered = UINT32_MAX;
|
||||
free(it->buffer);
|
||||
free(it);
|
||||
if (!editor->lsp || !editor->lsp->allow_formatting_on_type)
|
||||
return;
|
||||
for (char ch : editor->lsp->format_chars) {
|
||||
if (ch == '\n') {
|
||||
LineIterator *it = begin_l_iter(editor->root, editor->cursor.row);
|
||||
if (!it)
|
||||
return;
|
||||
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);
|
||||
int version = editor->lsp_version;
|
||||
json message = {
|
||||
{"jsonrpc", "2.0"},
|
||||
{"method", "textDocument/onTypeFormatting"},
|
||||
{"params",
|
||||
{{"textDocument", {{"uri", editor->uri}}},
|
||||
{"position", {{"line", editor->cursor.row}, {"character", col}}},
|
||||
{"ch", std::string(1, ch)},
|
||||
{"options",
|
||||
{{"tabSize", 2},
|
||||
{"insertSpaces", true},
|
||||
{"trimTrailingWhitespace", true},
|
||||
{"trimFinalNewlines", true}}}}}};
|
||||
LSPPending *pending = new LSPPending();
|
||||
pending->editor = editor;
|
||||
pending->method = "textDocument/onTypeFormatting";
|
||||
pending->callback = [version](Editor *editor, std::string, json message) {
|
||||
if (version != editor->lsp_version)
|
||||
return;
|
||||
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);
|
||||
}
|
||||
};
|
||||
lsp_send(editor->lsp, message, pending);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "editor/decl.h"
|
||||
#include "editor/editor.h"
|
||||
#include "utils/utils.h"
|
||||
|
||||
void apply_lsp_edits(Editor *editor, std::vector<TextEdit> edits, bool move) {
|
||||
if (!edits.size())
|
||||
@@ -43,13 +44,28 @@ void editor_lsp_handle(Editor *editor, json msg) {
|
||||
for (size_t i = 0; i < diagnostics.size(); i++) {
|
||||
json d = diagnostics[i];
|
||||
VWarn w;
|
||||
// HACK: convert back to utf-8 but as this is only visually affecting it
|
||||
// is not worth getting the line string from the rope.
|
||||
w.line = d["range"]["start"]["line"];
|
||||
w.start = d["range"]["start"]["character"];
|
||||
std::shared_lock lock(editor->knot_mtx);
|
||||
LineIterator *it = begin_l_iter(editor->root, w.line);
|
||||
if (!it)
|
||||
continue;
|
||||
uint32_t len;
|
||||
char *line = next_line(it, &len);
|
||||
if (!line) {
|
||||
free(it->buffer);
|
||||
free(it);
|
||||
continue;
|
||||
}
|
||||
if (len > 0 && line[len - 1] == '\n')
|
||||
--len;
|
||||
lock.unlock();
|
||||
w.start = utf16_offset_to_utf8(line, w.start);
|
||||
uint32_t end = d["range"]["end"]["character"];
|
||||
if (d["range"]["end"]["line"] == w.line)
|
||||
w.end = end;
|
||||
w.end = utf16_offset_to_utf8(line, end);
|
||||
free(it->buffer);
|
||||
free(it);
|
||||
std::string text = trim(d["message"].get<std::string>());
|
||||
w.text_full = text;
|
||||
auto pos = text.find('\n');
|
||||
|
||||
Reference in New Issue
Block a user