#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 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; } } }