378 lines
11 KiB
C++
378 lines
11 KiB
C++
#include "editor/editor.h"
|
|
#include "io/knot.h"
|
|
#include "lsp/lsp.h"
|
|
|
|
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;
|
|
line_idx++;
|
|
if (len > 0 && line[len - 1] == '\n')
|
|
--len;
|
|
if (len == 0)
|
|
continue;
|
|
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 c_indent;
|
|
}
|
|
|
|
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);
|
|
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;
|
|
}
|
|
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);
|
|
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();
|
|
}
|
|
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--;
|
|
}
|
|
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;
|
|
uint32_t line_len;
|
|
char *line = next_line(it, &line_len);
|
|
if (!line) {
|
|
free(it->buffer);
|
|
free(it);
|
|
return;
|
|
}
|
|
uint32_t col = utf8_offset_to_utf16(line, line_len, 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;
|
|
}
|
|
}
|
|
}
|