Fix bugs with hl line data structures

This commit is contained in:
2026-01-20 09:37:54 +00:00
parent fd894e4e9f
commit 81da75dc15
19 changed files with 884 additions and 160 deletions

View File

@@ -1,7 +1,6 @@
#include "editor/editor.h"
#include "lsp/lsp.h"
#include "utils/utils.h"
#include <cstdint>
void edit_erase(Editor *editor, Coord pos, int64_t len) {
if (len == 0)
@@ -52,7 +51,7 @@ void edit_erase(Editor *editor, Coord pos, int64_t len) {
editor->root = erase(editor->root, start, byte_pos - start);
lock_2.unlock();
if (editor->parser)
editor->parser->edit(editor->root, start_row, end_row, start_row);
editor->parser->edit(start_row, end_row, 0);
if (do_lsp) {
if (editor->lsp->incremental_sync) {
json message = {
@@ -124,7 +123,7 @@ void edit_erase(Editor *editor, Coord pos, int64_t len) {
editor->root = erase(editor->root, byte_pos, end - byte_pos);
lock_2.unlock();
if (editor->parser)
editor->parser->edit(editor->root, start_row, end_row, start_row);
editor->parser->edit(start_row, end_row, 0);
if (do_lsp) {
if (editor->lsp->incremental_sync) {
json message = {
@@ -175,7 +174,7 @@ void edit_insert(Editor *editor, Coord pos, char *data, uint32_t len) {
apply_hook_insertion(editor, pos.row, rows);
lock_2.unlock();
if (editor->parser)
editor->parser->edit(editor->root, pos.row, pos.row, pos.row + rows);
editor->parser->edit(pos.row, pos.row, rows);
if (editor->lsp) {
if (editor->lsp->incremental_sync) {
lock_1.lock();
@@ -218,19 +217,64 @@ 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);
std::unique_lock lock(editor->knot_mtx);
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;
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);
if (erase_len != 0)
edit_erase(editor, start, erase_len);
LineIterator *it = begin_l_iter(editor->root, start.row);
char *line = next_line(it, nullptr);
int utf16_start = 0;
if (line)
utf16_start = utf8_byte_offset_to_utf16(line, start.col);
free(it->buffer);
free(it);
it = begin_l_iter(editor->root, end.row);
line = next_line(it, nullptr);
int utf16_end = 0;
if (line)
utf16_end = utf8_byte_offset_to_utf16(line, end.col);
free(it->buffer);
free(it);
if (start_byte != end_byte)
editor->root = erase(editor->root, start_byte, end_byte - start_byte);
if (len > 0)
edit_insert(editor, start, const_cast<char *>(text), len);
editor->root = insert(editor->root, start_byte, (char *)text, len);
uint32_t rows = 0;
for (uint32_t i = 0; i < len; i++)
if (text[i] == '\n')
rows++;
if (editor->parser) {
editor->parser->edit(start.row, end.row - 1, 0);
editor->parser->edit(start.row, start.row, rows);
}
if (editor->lsp) {
if (editor->lsp->incremental_sync) {
json message = {
{"jsonrpc", "2.0"},
{"method", "textDocument/didChange"},
{"params",
{{"textDocument",
{{"uri", editor->uri}, {"version", ++editor->lsp_version}}},
{"contentChanges",
json::array(
{{{"range",
{{"start",
{{"line", start.row}, {"character", utf16_start}}},
{"end", {{"line", end.row}, {"character", utf16_end}}}}},
{"text", std::string(text, len)}}})}}}};
lsp_send(editor->lsp, message, nullptr);
} else {
char *buf = read(editor->root, 0, editor->root->char_count);
std::string full_text(buf);
free(buf);
json message = {
{"jsonrpc", "2.0"},
{"method", "textDocument/didChange"},
{"params",
{{"textDocument",
{{"uri", editor->uri}, {"version", ++editor->lsp_version}}},
{"contentChanges", json::array({{{"text", full_text}}})}}}};
lsp_send(editor->lsp, message, nullptr);
}
}
}

View File

@@ -30,8 +30,11 @@ Editor *new_editor(const char *filename_arg, Coord position, Coord size) {
free(str);
editor->lang = language_for_file(filename.c_str());
if (editor->lang.name != "unknown")
editor->parser = new Parser(editor->root, &editor->knot_mtx,
editor->lang.name, size.row + 5);
editor->parser = new Parser(editor, editor->lang.name, size.row + 5);
if (editor->lang.name == "css" || editor->lang.name == "html" ||
editor->lang.name == "javascript" || editor->lang.name == "markdown" ||
editor->lang.name == "typescript")
editor->is_css_color = true;
if (len <= (1024 * 28))
request_add_to_lsp(editor->lang, editor);
editor->indents.compute_indent(editor);
@@ -52,12 +55,13 @@ void save_file(Editor *editor) {
return;
std::shared_lock lock(editor->knot_mtx);
int version = editor->lsp_version;
char *str = read(editor->root, 0, editor->root->char_count);
uint32_t char_count = editor->root->char_count;
char *str = read(editor->root, 0, char_count);
if (!str)
return;
lock.unlock();
std::ofstream out(editor->filename);
out.write(str, editor->root->char_count);
out.write(str, char_count);
out.close();
free(str);
if (editor->lsp) {
@@ -99,12 +103,14 @@ void save_file(Editor *editor) {
apply_lsp_edits(editor, t_edits, false);
ensure_scroll(editor);
std::shared_lock lock(editor->knot_mtx);
char *str = read(editor->root, 0, editor->root->char_count);
uint32_t char_count = editor->root->char_count;
char *str = read(editor->root, 0, char_count);
if (!str)
return;
lock.unlock();
std::ofstream out(editor->filename);
out.write(str, editor->root->char_count);
out.write(str, char_count);
out.close();
free(str);
lsp_send(editor->lsp, save_msg, nullptr);
}

View File

@@ -1,10 +1,13 @@
#include "editor/editor.h"
#include "io/sysio.h"
#include "main.h"
#include "syntax/decl.h"
#include "syntax/parser.h"
#include <cstdint>
void render_editor(Editor *editor) {
uint32_t sel_start = 0, sel_end = 0;
std::shared_lock knot_lock(editor->knot_mtx);
uint32_t numlen =
EXTRA_META + static_cast<int>(std::log10(editor->root->line_count + 1));
uint32_t render_width = editor->size.col - numlen;
@@ -34,7 +37,6 @@ void render_editor(Editor *editor) {
return (int)token.type;
return 0;
};
std::shared_lock knot_lock(editor->knot_mtx);
if (editor->selection_active) {
Coord start, end;
if (editor->cursor >= editor->selection) {
@@ -88,6 +90,18 @@ void render_editor(Editor *editor) {
LineIterator *it = begin_l_iter(editor->root, line_index);
if (!it)
return;
uint32_t prev_col, next_col;
std::string word;
word_boundaries_exclusive(editor, editor->cursor, &prev_col, &next_col);
if (next_col - prev_col > 0 && next_col - prev_col < 256 - 4) {
uint32_t offset = line_to_byte(editor->root, editor->cursor.row, nullptr);
char *word_ptr = read(editor->root, offset + prev_col, next_col - prev_col);
if (word_ptr) {
word = std::string(word_ptr, next_col - prev_col);
free(word_ptr);
}
}
editor->extra_hl.render(editor->root, line_index, word, editor->is_css_color);
uint32_t rendered_rows = 0;
uint32_t global_byte_offset = line_to_byte(editor->root, line_index, nullptr);
while (rendered_rows < editor->size.row) {
@@ -157,9 +171,19 @@ void render_editor(Editor *editor) {
const Highlight *hl = nullptr;
if (editor->parser)
hl = &highlights[get_type(current_byte_offset + local_render_offset)];
uint32_t fg = hl ? hl->fg : 0xFFFFFF;
uint32_t bg = hl ? hl->bg : 0;
uint8_t fl = hl ? hl->flags : 0;
std::optional<std::pair<uint32_t, uint32_t>> extra =
editor->extra_hl.get(
{line_index, current_byte_offset + local_render_offset});
uint32_t fg = extra && extra->second != UINT32_MAX
? extra->first
: (hl ? hl->fg : 0xFFFFFF);
uint32_t bg = extra && extra->second != UINT32_MAX
? extra->second
: (hl ? hl->bg : 0x000000);
uint8_t fl =
(hl ? hl->flags : 0) |
(extra ? (extra->second != UINT32_MAX ? CF_BOLD : CF_UNDERLINE)
: 0);
if (editor->selection_active && absolute_byte_pos >= sel_start &&
absolute_byte_pos < sel_end)
bg = 0x555555;

View File

@@ -1,6 +1,6 @@
#include "syntax/decl.h"
#include "syntax/langs.h"
#include "utils/utils.h"
#include <cstdint>
struct BashFullState {
int brace_level = 0;
@@ -11,13 +11,11 @@ struct BashFullState {
bool line_cont = false;
struct Lit {
std::string delim = "";
int brace_level = 1;
std::string delim = ""; // Only 1 wide for strings
bool allow_interp = false;
bool operator==(const BashFullState::Lit &other) const {
return delim == other.delim && brace_level == other.brace_level &&
allow_interp == other.allow_interp;
return delim == other.delim && allow_interp == other.allow_interp;
}
} lit;
@@ -66,7 +64,35 @@ std::shared_ptr<void> bash_parse(std::vector<Token> *tokens,
if (len == 0)
return state;
while (i < len) {
i += utf8_codepoint_width(text[i]);
if (state->full_state->in_state == BashFullState::STRING) {
uint32_t start = i;
while (i < len) {
if (text[i] == state->full_state->lit.delim[0]) {
tokens->push_back({start, i, TokenKind::String});
state->full_state->in_state = BashFullState::NONE;
break;
}
i++;
}
if (i == len)
tokens->push_back({start, i, TokenKind::String});
continue;
}
if (text[i] == '#') {
if (i == 0 && len > 4 && text[i + 1] == '!') {
tokens->push_back({0, len, TokenKind::Shebang});
return state;
}
tokens->push_back({i, len, TokenKind::Comment});
return state;
} else if (text[i] == '\'') {
state->full_state->in_state = BashFullState::STRING;
state->full_state->lit.delim = "'";
state->full_state->lit.allow_interp = false;
tokens->push_back({i, ++i, TokenKind::String});
continue;
}
i++;
}
return state;
}
@@ -76,3 +102,6 @@ std::shared_ptr<void> bash_parse(std::vector<Token> *tokens,
// ${var} and $((math)) $(command) and `command` expansions ANSI-C quoted
// stirngs - $'' backslash escapes but with \xHH and \uHHHH and \uHHHHHHHH \cX
// too
//
// Lock edit_replace across both delete and insert instead of within to keep the
// parser from glitching

0
src/syntax/extras.cc Normal file
View File

View File

@@ -1,4 +1,5 @@
#include "syntax/parser.h"
#include "editor/editor.h"
#include "io/knot.h"
#include "main.h"
#include "syntax/decl.h"
@@ -6,10 +7,9 @@
std::array<Highlight, TOKEN_KIND_COUNT> highlights = {};
Parser::Parser(Knot *n_root, std::shared_mutex *n_knot_mutex,
std::string n_lang, uint32_t n_scroll_max) {
Parser::Parser(Editor *n_editor, std::string n_lang, uint32_t n_scroll_max) {
editor = n_editor;
scroll_max = n_scroll_max;
knot_mutex = n_knot_mutex;
lang = n_lang;
auto pair = parsers.find(n_lang);
if (pair != parsers.end()) {
@@ -18,22 +18,26 @@ Parser::Parser(Knot *n_root, std::shared_mutex *n_knot_mutex,
} else {
assert("unknown lang should be checked by caller" && 0);
}
edit(n_root, 0, 0, n_root->line_count);
edit(0, 0, editor->root->line_count);
}
void Parser::edit(Knot *n_root, uint32_t start_line, uint32_t old_end_line,
uint32_t new_end_line) {
void Parser::edit(uint32_t start_line, uint32_t old_end_line,
uint32_t inserted_rows) {
std::lock_guard lock(data_mutex);
root = n_root;
if (((int64_t)old_end_line - (int64_t)start_line) > 0)
line_tree.erase(start_line + 1, old_end_line - start_line);
if (((int64_t)new_end_line - (int64_t)old_end_line) > 0)
line_tree.insert(start_line + 1, new_end_line - start_line);
line_tree.erase(start_line, old_end_line - start_line);
if (inserted_rows > 0)
line_tree.insert(start_line, inserted_rows);
if (start_line > 0)
dirty_lines.insert(start_line - 1);
dirty_lines.insert(start_line);
dirty_lines.insert(start_line + 1);
}
void Parser::work() {
std::shared_lock k_lock(*knot_mutex);
if (!editor || !editor->root)
return;
std::shared_lock k_lock(editor->knot_mtx);
k_lock.unlock();
uint32_t capacity = 256;
char *text = (char *)calloc((capacity + 1), sizeof(char));
@@ -45,14 +49,16 @@ void Parser::work() {
std::unique_lock lock(mutex);
lock.unlock();
for (uint32_t c_line : tmp_dirty) {
if (c_line > scroll_max) {
if (c_line > scroll_max + 40) {
remaining_dirty.insert(c_line);
continue;
}
uint32_t line_count = line_tree.count();
lock_data.lock();
std::shared_ptr<void> prev_state =
(c_line > 0) ? line_tree.at(c_line - 1)->out_state : nullptr;
(c_line > 0) && c_line < line_tree.count()
? line_tree.at(c_line - 1)->out_state
: nullptr;
lock_data.unlock();
while (c_line < line_count) {
if (!running.load(std::memory_order_relaxed)) {
@@ -60,14 +66,18 @@ void Parser::work() {
return;
}
k_lock.lock();
if (c_line > editor->root->line_count) {
k_lock.unlock();
continue;
}
uint32_t r_offset, r_len;
r_offset = line_to_byte(root, c_line, &r_len);
r_offset = line_to_byte(editor->root, c_line, &r_len);
if (r_len > capacity) {
capacity = r_len;
text = (char *)realloc(text, capacity + 1);
memset(text, 0, capacity + 1);
}
read_into(root, r_offset, r_len, text);
read_into(editor->root, r_offset, r_len, text);
k_lock.unlock();
if (c_line < scroll_max &&
((scroll_max > 100 && c_line > scroll_max - 100) || c_line < 100))
@@ -79,6 +89,12 @@ void Parser::work() {
}
lock_data.lock();
LineData *line_data = line_tree.at(c_line);
if (!line_data) {
lock_data.unlock();
if (lock.owns_lock())
lock.unlock();
continue;
}
std::shared_ptr<void> new_state =
parse_func(&line_data->tokens, prev_state, text, r_len);
line_data->in_state = prev_state;
@@ -98,8 +114,8 @@ void Parser::work() {
remaining_dirty.insert(c_line);
break;
}
if (c_line < line_count &&
state_match_func(prev_state, line_tree.at(c_line)->in_state)) {
if (c_line < line_count && (line_data = line_tree.at(c_line)) &&
state_match_func(prev_state, line_data->in_state)) {
lock_data.unlock();
if (lock.owns_lock())
lock.unlock();
@@ -129,7 +145,7 @@ void Parser::scroll(uint32_t line) {
if (line_tree.at(c_line)->in_state || line_tree.at(c_line)->out_state)
return;
lock_data.unlock();
std::shared_lock k_lock(*knot_mutex);
std::shared_lock k_lock(editor->knot_mtx);
k_lock.unlock();
uint32_t capacity = 256;
char *text = (char *)calloc((capacity + 1), sizeof(char));
@@ -144,14 +160,18 @@ void Parser::scroll(uint32_t line) {
return;
}
k_lock.lock();
if (c_line > editor->root->line_count) {
k_lock.unlock();
continue;
}
uint32_t r_offset, r_len;
r_offset = line_to_byte(root, c_line, &r_len);
r_offset = line_to_byte(editor->root, c_line, &r_len);
if (r_len > capacity) {
capacity = r_len;
text = (char *)realloc(text, capacity + 1);
memset(text, 0, capacity + 1);
}
read_into(root, r_offset, r_len, text);
read_into(editor->root, r_offset, r_len, text);
k_lock.unlock();
if (c_line < scroll_max &&
((scroll_max > 100 && c_line > scroll_max - 100) || c_line < 100))
@@ -163,6 +183,12 @@ void Parser::scroll(uint32_t line) {
}
lock_data.lock();
LineData *line_data = line_tree.at(c_line);
if (!line_data) {
lock_data.unlock();
if (lock.owns_lock())
lock.unlock();
continue;
}
std::shared_ptr<void> new_state =
parse_func(&line_data->tokens, prev_state, text, r_len);
line_data->in_state = nullptr;

View File

@@ -290,15 +290,15 @@ std::shared_ptr<void> ruby_parse(std::vector<Token> *tokens,
std::shared_ptr<void> in_state,
const char *text, uint32_t len) {
static bool keywords_trie_init = false;
static Trie base_keywords_trie;
static Trie expecting_keywords_trie;
static Trie operator_keywords_trie;
static Trie expecting_operators_trie;
static Trie operator_trie;
static Trie types_trie;
static Trie builtins_trie;
static Trie methods_trie;
static Trie errors_trie;
static Trie<void> base_keywords_trie;
static Trie<void> expecting_keywords_trie;
static Trie<void> operator_keywords_trie;
static Trie<void> expecting_operators_trie;
static Trie<void> operator_trie;
static Trie<void> types_trie;
static Trie<void> builtins_trie;
static Trie<void> methods_trie;
static Trie<void> errors_trie;
if (!keywords_trie_init) {
base_keywords_trie.build(base_keywords);
expecting_keywords_trie.build(expecting_keywords);

View File

@@ -1,5 +1,6 @@
#include "ui/completionbox.h"
#include "editor/completions.h"
#include "io/sysio.h"
#include "utils/utils.h"
std::string item_kind_name(uint8_t kind) {
@@ -139,11 +140,12 @@ void CompletionBox::render(Coord pos) {
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;
// }
Coord screen_size = get_size();
if (start_col + size.col > screen_size.col) {
start_col = screen_size.col - 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++)

View File

@@ -148,11 +148,12 @@ void DiagnosticBox::render(Coord pos) {
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;
// }
Coord screen_size = get_size();
if (start_col + size.col > screen_size.col) {
start_col = screen_size.col - size.col;
if (start_col < 0)
start_col = 0;
}
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,

View File

@@ -104,11 +104,12 @@ void HoverBox::render(Coord pos) {
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;
// }
Coord screen_size = get_size();
if (start_col + size.col > screen_size.col) {
start_col = screen_size.col - size.col;
if (start_col < 0)
start_col = 0;
}
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,