Add custom syntax highlighter and optimize

This commit is contained in:
2026-01-16 21:47:05 +00:00
parent 04cce25bf2
commit 1fda5bf246
77 changed files with 1487 additions and 1673 deletions

View File

@@ -1,13 +1,10 @@
#include "editor/editor.h"
#include "editor/folds.h"
void ensure_cursor(Editor *editor) {
std::shared_lock knot_lock(editor->knot_mtx);
if (editor->cursor < editor->scroll) {
uint32_t line_idx = next_unfolded_row(editor, editor->scroll.row);
editor->cursor.row = line_idx;
editor->cursor.col =
line_idx == editor->scroll.row ? editor->scroll.col : 0;
editor->cursor.row = editor->scroll.row;
editor->cursor.col = editor->scroll.col;
editor->cursor_preffered = UINT32_MAX;
return;
}
@@ -24,20 +21,6 @@ void ensure_cursor(Editor *editor) {
while (true) {
if (visual_rows >= editor->size.row)
break;
const Fold *fold = fold_for_line(editor->folds, line_index);
if (fold) {
Coord c = {fold->start, 0};
last_visible = c;
visual_rows++;
uint32_t skip_until = fold->end;
while (line_index <= skip_until) {
char *line = next_line(it, nullptr);
if (!line)
break;
line_index++;
}
continue;
}
uint32_t line_len;
char *line = next_line(it, &line_len);
if (!line)
@@ -81,14 +64,8 @@ void ensure_cursor(Editor *editor) {
}
line_index++;
}
uint32_t last_real_row = last_visible.row;
const Fold *last_fold = fold_for_line(editor->folds, last_visible.row);
if (last_fold) {
last_visible.row = last_fold->start == 0 ? 0 : last_fold->start - 1;
last_visible.col = 0;
}
editor->cursor.row = last_visible.row;
editor->cursor.col = last_visible.row == last_real_row ? last_visible.col : 0;
editor->cursor.col = last_visible.col;
editor->cursor_preffered = UINT32_MAX;
free(it->buffer);
free(it);
@@ -112,7 +89,6 @@ void ensure_scroll(Editor *editor) {
}
if (len > 0 && line[len - 1] == '\n')
--len;
uint32_t rows = 1;
uint32_t cols = 0;
uint32_t offset = 0;
uint32_t old_offset = 0;
@@ -121,7 +97,6 @@ void ensure_scroll(Editor *editor) {
grapheme_next_character_break_utf8(line + offset, len - offset);
int width = display_width(line + offset, inc);
if (cols + width > render_width) {
rows++;
cols = 0;
if (editor->cursor.col > old_offset && editor->cursor.col <= offset) {
editor->scroll.row = editor->cursor.row;
@@ -139,7 +114,7 @@ void ensure_scroll(Editor *editor) {
free(it);
editor->scroll.row = editor->cursor.row;
editor->scroll.col = (editor->cursor.col == 0) ? 0 : old_offset;
} else {
} else if (editor->cursor.row - editor->scroll.row < editor->size.row * 2) {
uint32_t line_index = editor->scroll.row;
LineIterator *it = begin_l_iter(editor->root, line_index);
if (!it)
@@ -150,30 +125,6 @@ void ensure_scroll(Editor *editor) {
uint32_t q_size = 0;
bool first_visual_line = true;
while (true) {
const Fold *fold = fold_for_line(editor->folds, line_index);
if (fold) {
Coord fold_coord = {fold->start, 0};
if (q_size < max_visual_lines) {
scroll_queue[(q_head + q_size) % max_visual_lines] = fold_coord;
q_size++;
} else {
scroll_queue[q_head] = fold_coord;
q_head = (q_head + 1) % max_visual_lines;
}
if (fold->start <= editor->cursor.row &&
editor->cursor.row <= fold->end) {
editor->scroll = scroll_queue[q_head];
break;
}
uint32_t skip_until = fold->end;
while (line_index <= skip_until) {
char *line = next_line(it, nullptr);
if (!line)
break;
line_index++;
}
continue;
}
uint32_t line_len;
char *line = next_line(it, &line_len);
if (!line)
@@ -234,5 +185,11 @@ void ensure_scroll(Editor *editor) {
free(scroll_queue);
free(it->buffer);
free(it);
} else {
editor->scroll.row = (editor->cursor.row > editor->size.row * 1.5)
? editor->cursor.row - editor->size.row * 1.5
: 0;
editor->scroll.col = 0;
ensure_scroll(editor);
}
}

View File

@@ -1,5 +1,4 @@
#include "editor/editor.h"
#include "editor/folds.h"
#include "main.h"
Coord editor_hit_test(Editor *editor, uint32_t x, uint32_t y) {
@@ -7,7 +6,6 @@ Coord editor_hit_test(Editor *editor, uint32_t x, uint32_t y) {
x++;
uint32_t numlen =
EXTRA_META + static_cast<int>(std::log10(editor->root->line_count + 1));
bool is_gutter_click = (x < numlen);
uint32_t render_width = editor->size.col - numlen;
x = MAX(x, numlen) - numlen;
uint32_t target_visual_row = y;
@@ -21,28 +19,6 @@ Coord editor_hit_test(Editor *editor, uint32_t x, uint32_t y) {
if (!it)
return editor->scroll;
while (visual_row <= target_visual_row) {
const Fold *fold = fold_for_line(editor->folds, line_index);
if (fold) {
if (visual_row == target_visual_row) {
free(it->buffer);
free(it);
if (is_gutter_click) {
remove_fold(editor, fold->start);
return {UINT32_MAX, UINT32_MAX};
}
return {fold->start > 0 ? fold->start - 1 : 0, 0};
}
visual_row++;
while (line_index <= fold->end) {
char *l = next_line(it, nullptr);
if (!l)
break;
line_index++;
}
last_line_index = fold->end;
last_col = 0;
continue;
}
uint32_t line_len;
char *line = next_line(it, &line_len);
if (!line)

View File

@@ -1,8 +1,7 @@
#include "editor/editor.h"
#include "editor/folds.h"
#include "utils/utils.h"
Coord move_right_pure(Editor *editor, Coord cursor, uint32_t number) {
Coord move_right(Editor *editor, Coord cursor, uint32_t number) {
Coord result = cursor;
if (!editor || !editor->root || number == 0)
return result;
@@ -50,7 +49,7 @@ Coord move_right_pure(Editor *editor, Coord cursor, uint32_t number) {
return result;
}
Coord move_left_pure(Editor *editor, Coord cursor, uint32_t number) {
Coord move_left(Editor *editor, Coord cursor, uint32_t number) {
Coord result = cursor;
if (!editor || !editor->root || number == 0)
return result;
@@ -103,170 +102,38 @@ Coord move_left_pure(Editor *editor, Coord cursor, uint32_t number) {
return result;
}
Coord move_right(Editor *editor, Coord cursor, uint32_t number) {
Coord result = cursor;
void cursor_down(Editor *editor, uint32_t number) {
if (!editor || !editor->root || number == 0)
return result;
uint32_t row = result.row;
uint32_t col = result.col;
uint32_t line_len = 0;
LineIterator *it = begin_l_iter(editor->root, row);
if (!it)
return result;
uint32_t target_row = next_unfolded_row(editor, row);
while (row < target_row) {
if (!next_line(it, &line_len)) {
return;
uint32_t visual_col = editor->cursor_preffered;
if (visual_col == UINT32_MAX) {
uint32_t len;
LineIterator *it = begin_l_iter(editor->root, editor->cursor.row);
char *line = next_line(it, &len);
if (!line) {
free(it->buffer);
free(it);
return result;
return;
}
++row;
}
char *line = next_line(it, &line_len);
if (!line) {
editor->cursor_preffered =
get_visual_col_from_bytes(line, len, editor->cursor.col);
visual_col = editor->cursor_preffered;
free(it->buffer);
free(it);
return result;
}
if (line_len > 0 && line[line_len - 1] == '\n')
--line_len;
while (number > 0) {
if (col >= line_len) {
uint32_t next_row = next_unfolded_row(editor, row + 1);
if (next_row >= editor->root->line_count) {
col = line_len;
break;
}
while (row < next_row) {
line = next_line(it, &line_len);
if (!line) {
free(it->buffer);
free(it);
result.row = row;
result.col = col;
return result;
}
++row;
}
if (line_len > 0 && line[line_len - 1] == '\n')
--line_len;
col = 0;
--number;
continue;
} else {
uint32_t inc =
grapheme_next_character_break_utf8(line + col, line_len - col);
if (inc == 0)
break;
col += inc;
--number;
}
}
free(it->buffer);
free(it);
result.row = row;
result.col = col;
return result;
}
Coord move_left(Editor *editor, Coord cursor, uint32_t number) {
Coord result = cursor;
if (!editor || !editor->root || number == 0)
return result;
uint32_t row = result.row;
uint32_t col = result.col;
uint32_t len = 0;
LineIterator *it = begin_l_iter(editor->root, row);
editor->cursor.row =
MIN(editor->cursor.row + number, editor->root->line_count - 1);
uint32_t len;
LineIterator *it = begin_l_iter(editor->root, editor->cursor.row);
char *line = next_line(it, &len);
if (!line) {
free(it->buffer);
free(it);
return result;
return;
}
if (len > 0 && line[len - 1] == '\n')
--len;
bool iterator_ahead = true;
while (number > 0) {
if (col == 0) {
if (row == 0)
break;
if (iterator_ahead) {
prev_line(it, nullptr);
iterator_ahead = false;
}
line = nullptr;
while (row > 0) {
row--;
line = prev_line(it, &len);
if (!line)
break;
const Fold *fold = fold_for_line(editor->folds, row);
if (fold) {
while (line && row > fold->start) {
line = prev_line(it, &len);
row--;
}
line = nullptr;
continue;
}
break;
}
if (!line)
break;
if (len > 0 && line[len - 1] == '\n')
--len;
col = len;
} else {
uint32_t new_col = 0;
while (new_col < col) {
uint32_t inc =
grapheme_next_character_break_utf8(line + new_col, len - new_col);
if (new_col + inc >= col)
break;
new_col += inc;
}
col = new_col;
}
number--;
}
free(it->buffer);
free(it);
result.row = row;
result.col = col;
return result;
}
void cursor_down(Editor *editor, uint32_t number) {
if (!editor || !editor->root || number == 0)
return;
uint32_t len;
LineIterator *it = begin_l_iter(editor->root, editor->cursor.row);
char *line_content = next_line(it, &len);
if (line_content == nullptr)
return;
if (editor->cursor_preffered == UINT32_MAX)
editor->cursor_preffered =
get_visual_col_from_bytes(line_content, len, editor->cursor.col);
uint32_t visual_col = editor->cursor_preffered;
free(it->buffer);
free(it);
uint32_t target_row = editor->cursor.row;
while (number > 0 && target_row < editor->root->line_count - 1) {
target_row = next_unfolded_row(editor, target_row + 1);
if (target_row >= editor->root->line_count) {
target_row = editor->root->line_count - 1;
break;
}
number--;
}
it = begin_l_iter(editor->root, target_row);
line_content = next_line(it, &len);
if (!line_content)
return;
if (len > 0 && line_content[len - 1] == '\n')
--len;
editor->cursor.row = target_row;
editor->cursor.col = get_bytes_from_visual_col(line_content, len, visual_col);
editor->cursor.col = get_bytes_from_visual_col(line, len, visual_col);
free(it->buffer);
free(it);
}
@@ -287,7 +154,7 @@ void cursor_up(Editor *editor, uint32_t number) {
free(it);
uint32_t target_row = editor->cursor.row;
while (number > 0 && target_row > 0) {
target_row = prev_unfolded_row(editor, target_row - 1);
target_row--;
if (target_row == 0) {
number--;
break;

View File

@@ -1,7 +1,7 @@
#include "editor/editor.h"
#include "editor/folds.h"
#include "lsp/lsp.h"
#include "utils/utils.h"
#include <cstdint>
void edit_erase(Editor *editor, Coord pos, int64_t len) {
if (len == 0)
@@ -11,9 +11,8 @@ void edit_erase(Editor *editor, Coord pos, int64_t len) {
uint32_t cursor_original =
line_to_byte(editor->root, editor->cursor.row, nullptr) +
editor->cursor.col;
TSPoint old_point = {pos.row, pos.col};
uint32_t byte_pos = line_to_byte(editor->root, pos.row, nullptr) + pos.col;
Coord point = move_left_pure(editor, pos, -len);
Coord point = move_left(editor, pos, -len);
json lsp_range;
bool do_lsp = (editor->lsp != nullptr);
if (do_lsp) {
@@ -48,30 +47,12 @@ void edit_erase(Editor *editor, Coord pos, int64_t len) {
lock_1.unlock();
uint32_t start_row = point.row;
uint32_t end_row = pos.row;
apply_line_deletion(editor, start_row + 1, end_row);
apply_hook_deletion(editor, start_row + 1, end_row);
std::unique_lock lock_2(editor->knot_mtx);
editor->root = erase(editor->root, start, byte_pos - start);
if (editor->ts.tree) {
TSInputEdit edit = {
.start_byte = start,
.old_end_byte = byte_pos,
.new_end_byte = start,
.start_point = {point.row, point.col},
.old_end_point = old_point,
.new_end_point = {point.row, point.col},
};
editor->edit_queue.push(edit);
}
std::unique_lock lock_3(editor->spans.mtx);
apply_edit(editor->spans.spans, start, start - byte_pos);
if (editor->spans.mid_parse)
editor->spans.edits.push({start, start - byte_pos});
lock_3.unlock();
lock_2.unlock();
std::unique_lock lock_4(editor->hex_color_spans.mtx);
apply_edit(editor->hex_color_spans.spans, byte_pos, start - byte_pos);
lock_4.unlock();
if (editor->parser)
editor->parser->edit(editor->root, start_row, end_row, start_row);
if (do_lsp) {
if (editor->lsp->incremental_sync) {
json message = {
@@ -102,9 +83,8 @@ void edit_erase(Editor *editor, Coord pos, int64_t len) {
uint32_t cursor_original =
line_to_byte(editor->root, editor->cursor.row, nullptr) +
editor->cursor.col;
TSPoint old_point = {pos.row, pos.col};
uint32_t byte_pos = line_to_byte(editor->root, pos.row, nullptr) + pos.col;
Coord point = move_right_pure(editor, pos, len);
Coord point = move_right(editor, pos, len);
json lsp_range;
bool do_lsp = (editor->lsp != nullptr);
if (do_lsp) {
@@ -139,30 +119,12 @@ void edit_erase(Editor *editor, Coord pos, int64_t len) {
lock_1.unlock();
uint32_t start_row = pos.row;
uint32_t end_row = point.row;
apply_line_deletion(editor, start_row + 1, end_row);
apply_hook_deletion(editor, start_row + 1, end_row);
std::unique_lock lock_2(editor->knot_mtx);
editor->root = erase(editor->root, byte_pos, end - byte_pos);
if (editor->ts.tree) {
TSInputEdit edit = {
.start_byte = byte_pos,
.old_end_byte = end,
.new_end_byte = byte_pos,
.start_point = old_point,
.old_end_point = {point.row, point.col},
.new_end_point = old_point,
};
editor->edit_queue.push(edit);
}
std::unique_lock lock_3(editor->spans.mtx);
apply_edit(editor->spans.spans, byte_pos, byte_pos - end);
if (editor->spans.mid_parse)
editor->spans.edits.push({byte_pos, byte_pos - end});
lock_3.unlock();
lock_2.unlock();
std::unique_lock lock_4(editor->hex_color_spans.mtx);
apply_edit(editor->hex_color_spans.spans, byte_pos, byte_pos - end);
lock_4.unlock();
if (editor->parser)
editor->parser->edit(editor->root, start_row, end_row, start_row);
if (do_lsp) {
if (editor->lsp->incremental_sync) {
json message = {
@@ -197,7 +159,6 @@ void edit_insert(Editor *editor, Coord pos, char *data, uint32_t len) {
line_to_byte(editor->root, editor->cursor.row, nullptr) +
editor->cursor.col;
uint32_t byte_pos = line_to_byte(editor->root, pos.row, nullptr) + pos.col;
TSPoint start_point = {pos.row, pos.col};
if (cursor_original > byte_pos) {
uint32_t cursor_new = cursor_original + len;
uint32_t new_col;
@@ -207,39 +168,14 @@ void edit_insert(Editor *editor, Coord pos, char *data, uint32_t len) {
lock_1.unlock();
std::unique_lock lock_2(editor->knot_mtx);
editor->root = insert(editor->root, byte_pos, data, len);
uint32_t cols = 0;
uint32_t rows = 0;
for (uint32_t i = 0; i < len; i++) {
if (data[i] == '\n') {
for (uint32_t i = 0; i < len; i++)
if (data[i] == '\n')
rows++;
cols = 0;
} else {
cols++;
}
}
apply_line_insertion(editor, pos.row, rows);
apply_hook_insertion(editor, pos.row, rows);
if (editor->ts.tree) {
TSInputEdit edit = {
.start_byte = byte_pos,
.old_end_byte = byte_pos,
.new_end_byte = byte_pos + len,
.start_point = start_point,
.old_end_point = start_point,
.new_end_point = {start_point.row + rows,
(rows == 0) ? (start_point.column + cols) : cols},
};
editor->edit_queue.push(edit);
}
std::unique_lock lock_3(editor->spans.mtx);
apply_edit(editor->spans.spans, byte_pos, len);
if (editor->spans.mid_parse)
editor->spans.edits.push({byte_pos, len});
lock_3.unlock();
lock_2.unlock();
std::unique_lock lock_4(editor->hex_color_spans.mtx);
apply_edit(editor->hex_color_spans.spans, byte_pos, len);
lock_4.unlock();
if (editor->parser)
editor->parser->edit(editor->root, pos.row, pos.row, pos.row + rows);
if (editor->lsp) {
if (editor->lsp->incremental_sync) {
lock_1.lock();

View File

@@ -29,39 +29,20 @@ Editor *new_editor(const char *filename_arg, Coord position, Coord size) {
editor->root = load(str, len, optimal_chunk_size(len));
free(str);
editor->lang = language_for_file(filename.c_str());
if (editor->lang.name != "unknown" && len <= (1024 * 128)) {
editor->ts.parser = ts_parser_new();
editor->ts.language = editor->lang.fn();
ts_parser_set_language(editor->ts.parser, editor->ts.language);
editor->ts.query_file =
get_exe_dir() + "/../grammar/" + editor->lang.name + ".scm";
}
if (len <= (1024 * 28))
request_add_to_lsp(editor->lang, editor);
if (editor->lang.name != "unknown")
editor->parser = new Parser(editor->root, &editor->knot_mtx,
editor->lang.name, size.row + 5);
// if (len <= (1024 * 28))
// request_add_to_lsp(editor->lang, editor);
editor->indents.compute_indent(editor);
return editor;
}
void free_tsset(TSSetMain *set) {
if (set->parser)
ts_parser_delete(set->parser);
if (set->tree)
ts_tree_delete(set->tree);
if (set->query)
ts_query_delete(set->query);
for (auto &inj : set->injections) {
if (inj.second.parser)
ts_parser_delete(inj.second.parser);
if (inj.second.query)
ts_query_delete(inj.second.query);
if (inj.second.tree)
ts_tree_delete(inj.second.tree);
}
}
void free_editor(Editor *editor) {
remove_from_lsp(editor);
free_tsset(&editor->ts);
if (editor->parser)
delete editor->parser;
editor->parser = nullptr;
free_rope(editor->root);
delete editor;
}

View File

@@ -1,9 +1,7 @@
#include "editor/editor.h"
#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 =
@@ -579,17 +577,6 @@ void handle_editor_event(Editor *editor, KeyEvent event) {
char *text;
Coord start;
switch (event.c[0]) {
case 'f':
if (editor->cursor.row != editor->selection.row) {
uint32_t start = MIN(editor->cursor.row, editor->selection.row);
uint32_t end = MAX(editor->cursor.row, editor->selection.row);
add_fold(editor, start, end);
}
cursor_left(editor, 1);
cursor_down(editor, 1);
editor->selection_active = false;
mode = NORMAL;
break;
case 0x1B:
case 's':
case 'v':
@@ -648,10 +635,8 @@ void handle_editor_event(Editor *editor, KeyEvent event) {
}
editor->hooks[event.c[0] - '!'] = editor->cursor.row + 1;
} else {
uint32_t line = editor->hooks[event.c[0] - '!'];
uint32_t line = editor->hooks[event.c[0] - '!'] - 1;
if (line > 0) {
if (line_is_folded(editor->folds, --line))
break;
editor->cursor = {line, 0};
editor->cursor_preffered = UINT32_MAX;
}

View File

@@ -17,9 +17,9 @@ void apply_lsp_edits(Editor *editor, std::vector<TextEdit> edits, bool 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()));
move_right(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;

View File

@@ -1,5 +1,4 @@
#include "editor/editor.h"
#include "editor/folds.h"
#include "main.h"
void move_line_up(Editor *editor) {
@@ -17,7 +16,7 @@ void move_line_up(Editor *editor) {
if (line_len > 0 && line[line_len - 1] == '\n')
line_len--;
line_cluster_len = count_clusters(line, line_len, 0, line_len);
uint32_t target_row = prev_unfolded_row(editor, editor->cursor.row - 1);
uint32_t target_row = editor->cursor.row - 1;
uint32_t up_by = editor->cursor.row - target_row;
if (up_by > 1)
up_by--;
@@ -68,7 +67,7 @@ void move_line_down(Editor *editor) {
if (line_len && line[line_len - 1] == '\n')
line_len--;
line_cluster_len = count_clusters(line, line_len, 0, line_len);
uint32_t target_row = next_unfolded_row(editor, editor->cursor.row + 1);
uint32_t target_row = editor->cursor.row + 1;
if (target_row >= editor->root->line_count) {
free(line);
lock.unlock();
@@ -95,7 +94,7 @@ void move_line_down(Editor *editor) {
std::shared_lock lock(editor->knot_mtx);
uint32_t start_row = MIN(editor->cursor.row, editor->selection.row);
uint32_t end_row = MAX(editor->cursor.row, editor->selection.row);
uint32_t target_row = next_unfolded_row(editor, end_row + 1);
uint32_t target_row = end_row + 1;
if (target_row >= editor->root->line_count)
return;
uint32_t down_by = target_row - end_row;

View File

@@ -1,7 +1,5 @@
#include "editor/editor.h"
#include "editor/folds.h"
#include "main.h"
#include "ts/decl.h"
void render_editor(Editor *editor) {
uint32_t sel_start = 0, sel_end = 0;
@@ -22,6 +20,9 @@ void render_editor(Editor *editor) {
while (warn_it != editor->warnings.end() &&
warn_it->line < editor->scroll.row)
++warn_it;
std::unique_lock<std::mutex> lock;
if (editor->parser)
lock = std::unique_lock<std::mutex>(editor->parser->mutex);
std::shared_lock knot_lock(editor->knot_mtx);
if (editor->selection_active) {
Coord start, end;
@@ -73,56 +74,12 @@ void render_editor(Editor *editor) {
}
Coord cursor = {UINT32_MAX, UINT32_MAX};
uint32_t line_index = editor->scroll.row;
SpanCursor span_cursor(editor->spans);
SpanCursor word_span_cursor(editor->word_spans);
SpanCursor hex_span_cursor(editor->hex_color_spans);
LineIterator *it = begin_l_iter(editor->root, line_index);
if (!it)
return;
uint32_t rendered_rows = 0;
uint32_t global_byte_offset = line_to_byte(editor->root, line_index, nullptr);
span_cursor.sync(global_byte_offset);
word_span_cursor.sync(global_byte_offset);
hex_span_cursor.sync(global_byte_offset);
while (rendered_rows < editor->size.row) {
const Fold *fold = fold_for_line(editor->folds, line_index);
if (fold) {
update(editor->position.row + rendered_rows, editor->position.col, "",
0xAAAAAA, 0, 0);
char buf[16];
int len = snprintf(buf, sizeof(buf), "%*u ", numlen - 3, fold->start + 1);
uint32_t num_color =
editor->cursor.row == fold->start ? 0xFFFFFF : 0x555555;
for (int i = 0; i < len; i++)
update(editor->position.row + rendered_rows,
editor->position.col + i + 2, (char[2]){buf[i], 0}, num_color, 0,
0);
const char marker[15] = "... folded ...";
uint32_t i = 0;
for (; i < 14 && i < render_width; i++)
update(rendered_rows, i + render_x, (char[2]){marker[i], 0}, 0xc6c6c6,
0, 0);
for (; i < render_width; i++)
update(rendered_rows, i + render_x, " ", 0xc6c6c6, 0, 0);
rendered_rows++;
uint32_t skip_until = fold->end;
while (line_index <= skip_until) {
if (hook_it != v.end() && hook_it->first == line_index + 1)
hook_it++;
while (warn_it != editor->warnings.end() && warn_it->line == line_index)
++warn_it;
uint32_t line_len;
char *line = next_line(it, &line_len);
if (!line)
break;
global_byte_offset += line_len;
if (line_len > 0 && line[line_len - 1] == '\n')
global_byte_offset--;
global_byte_offset++;
line_index++;
}
continue;
}
uint32_t line_len;
char *line = next_line(it, &line_len);
if (!line)
@@ -182,25 +139,13 @@ void render_editor(Editor *editor) {
}
uint32_t absolute_byte_pos =
global_byte_offset + current_byte_offset + local_render_offset;
Highlight *hl = span_cursor.get_highlight(absolute_byte_pos);
Highlight *word_hl = word_span_cursor.get_highlight(absolute_byte_pos);
Highlight *hex_hl = hex_span_cursor.get_highlight(absolute_byte_pos);
const Highlight *hl = nullptr;
if (editor->parser && editor->parser->line_data.size() > line_index)
hl = &highlight_map.at(editor->parser->get_type(
{line_index, 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;
if (hex_hl) {
if (hex_hl->fg != 0)
fg = hex_hl->fg;
if (hex_hl->bg != 0)
bg = hex_hl->bg;
fl |= hex_hl->flags;
} else if (word_hl) {
if (word_hl->fg != 0)
fg |= word_hl->fg;
if (word_hl->bg != 0)
bg |= word_hl->bg;
fl |= word_hl->flags;
}
if (editor->selection_active && absolute_byte_pos >= sel_start &&
absolute_byte_pos < sel_end)
bg = 0x555555;
@@ -468,6 +413,8 @@ void render_editor(Editor *editor) {
global_byte_offset += line_len + 1;
line_index++;
}
if (lock.owns_lock())
lock.unlock();
while (rendered_rows < editor->size.row) {
for (uint32_t col = 0; col < editor->size.col; col++)
update(editor->position.row + rendered_rows, editor->position.col + col,
@@ -498,4 +445,6 @@ void render_editor(Editor *editor) {
}
free(it->buffer);
free(it);
if (editor->parser)
editor->parser->scroll(line_index + 5);
}

View File

@@ -1,5 +1,4 @@
#include "editor/editor.h"
#include "editor/folds.h"
void scroll_up(Editor *editor, int32_t number) {
if (!editor || number == 0)
@@ -63,41 +62,6 @@ void scroll_up(Editor *editor, int32_t number) {
free(it);
return;
}
const Fold *fold = fold_for_line(editor->folds, line_index);
if (fold) {
while (line && line_index > fold->start) {
free(line);
line = prev_line(it, &len);
line_index--;
if (!line) {
editor->scroll = {0, 0};
free(it->buffer);
free(it);
return;
}
}
if (--number == 0) {
editor->scroll = {fold->start, 0};
free(it->buffer);
free(it);
return;
}
if (fold->start == 0) {
editor->scroll = {0, 0};
free(it->buffer);
free(it);
return;
}
line_index = fold->start - 1;
line = prev_line(it, &len);
if (!line) {
editor->scroll = {0, 0};
free(it->buffer);
free(it);
return;
}
continue;
}
if (len > 0 && line[len - 1] == '\n')
len--;
current_byte_offset = 0;
@@ -147,34 +111,6 @@ void scroll_down(Editor *editor, uint32_t number) {
uint32_t visual_seen = 0;
bool first_visual_line = true;
while (true) {
const Fold *fold = fold_for_line(editor->folds, line_index);
if (fold) {
Coord fold_coord = {fold->start, 0};
if (q_size < max_visual_lines) {
scroll_queue[(q_head + q_size) % max_visual_lines] = fold_coord;
q_size++;
} else {
scroll_queue[q_head] = fold_coord;
q_head = (q_head + 1) % max_visual_lines;
}
visual_seen++;
if (visual_seen >= number + max_visual_lines) {
editor->scroll = scroll_queue[q_head];
break;
}
uint32_t skip_until = fold->end;
while (line_index <= skip_until) {
char *line = next_line(it, nullptr);
if (!line) {
free(scroll_queue);
free(it->buffer);
free(it);
return;
}
line_index++;
}
continue;
}
uint32_t line_len;
char *line = next_line(it, &line_len);
if (!line)

View File

@@ -1,7 +1,4 @@
#include "editor/editor.h"
#include "ts/ts.h"
static Highlight HL_UNDERLINE = {0, 0, CF_UNDERLINE, UINT8_MAX - 1};
void hover_diagnostic(Editor *editor) {
std::shared_lock lock(editor->v_mtx);
@@ -30,78 +27,8 @@ void hover_diagnostic(Editor *editor) {
void editor_worker(Editor *editor) {
if (!editor || !editor->root)
return;
if (editor->root->char_count > (1024 * 128))
return;
if (editor->ts.query_file != "" && !editor->ts.query)
editor->ts.query = load_query(editor->ts.query_file.c_str(), &editor->ts);
if (editor->ts.parser && editor->ts.query)
ts_collect_spans(editor);
if (editor->root->char_count > (1024 * 32))
return;
uint32_t prev_col, next_col;
word_boundaries_exclusive(editor, editor->cursor, &prev_col, &next_col);
std::unique_lock lock(editor->word_spans.mtx);
editor->word_spans.spans.clear();
lock.unlock();
if (next_col - prev_col > 0 && next_col - prev_col < 256 - 4) {
std::shared_lock lockk(editor->knot_mtx);
uint32_t offset = line_to_byte(editor->root, editor->cursor.row, nullptr);
char *word = read(editor->root, offset + prev_col, next_col - prev_col);
lockk.unlock();
if (word) {
char buf[256];
snprintf(buf, sizeof(buf), "\\b%s\\b", word);
std::shared_lock lockk(editor->knot_mtx);
std::vector<std::pair<size_t, size_t>> results =
search_rope_dfa(editor->root, buf);
lockk.unlock();
std::unique_lock lock2(editor->word_spans.mtx);
editor->word_spans.spans.reserve(results.size());
for (const auto &match : results) {
Span s;
s.start = match.first;
s.end = match.first + match.second;
s.hl = &HL_UNDERLINE;
editor->word_spans.spans.push_back(s);
}
free(word);
lock2.unlock();
}
}
static uint16_t limit = 150;
static Highlight *hl_s = (Highlight *)calloc(limit, sizeof(Highlight));
if (!hl_s)
exit(ENOMEM);
std::shared_lock lockk(editor->knot_mtx);
std::vector<Match> results =
search_rope(editor->root, "(?:0x|#)[0-9a-fA-F]{6,8}\\b");
if (results.size() > limit) {
limit = results.size() + 50;
free(hl_s);
hl_s = (Highlight *)calloc(limit, sizeof(Highlight));
if (!hl_s)
exit(ENOMEM);
}
lockk.unlock();
std::unique_lock lock2(editor->hex_color_spans.mtx);
editor->hex_color_spans.spans.clear();
editor->hex_color_spans.spans.reserve(results.size());
for (size_t i = 0; i < results.size(); ++i) {
Span s;
s.start = results[i].start;
s.end = results[i].end;
int x = results[i].text[0] == '#' ? 1 : 2;
uint32_t bg = HEX(results[i].text.substr(x, 6));
uint8_t r = bg >> 16;
uint8_t g = (bg >> 8) & 0xFF;
uint8_t b = bg & 0xFF;
double luminance = 0.299 * r + 0.587 * g + 0.114 * b;
uint32_t fg = (luminance > 128) ? 0x010101 : 0xFEFEFE;
hl_s[i] = {fg, bg, CF_BOLD, UINT8_MAX};
s.hl = &hl_s[i];
editor->hex_color_spans.spans.push_back(s);
}
lock2.unlock();
if (editor->parser)
editor->parser->work();
hover_diagnostic(editor);
if (editor->completion.active && editor->completion.hover_dirty) {
editor->completion.hover.render_first();

View File

@@ -301,7 +301,7 @@ Knot *erase(Knot *node, uint32_t offset, uint32_t len) {
return balance(node);
}
static void _read_into(Knot *node, uint32_t offset, uint32_t len, char *dest) {
void read_into(Knot *node, uint32_t offset, uint32_t len, char *dest) {
if (!node || len == 0)
return;
if (node->depth == 0) {
@@ -314,7 +314,7 @@ static void _read_into(Knot *node, uint32_t offset, uint32_t len, char *dest) {
uint32_t chunk_len = left_count - offset;
if (chunk_len > len)
chunk_len = len;
_read_into(left, offset, chunk_len, dest);
read_into(left, offset, chunk_len, dest);
dest += chunk_len;
len -= chunk_len;
offset = 0;
@@ -322,7 +322,7 @@ static void _read_into(Knot *node, uint32_t offset, uint32_t len, char *dest) {
offset -= left_count;
}
if (len > 0 && node->right)
_read_into(node->right, offset, len, dest);
read_into(node->right, offset, len, dest);
}
char *read(Knot *root, uint32_t offset, uint32_t len) {
@@ -340,7 +340,7 @@ char *read(Knot *root, uint32_t offset, uint32_t len) {
char *buffer = (char *)malloc((len + 1) * sizeof(char));
if (!buffer)
return nullptr;
_read_into(root, offset, len, buffer);
read_into(root, offset, len, buffer);
buffer[len] = '\0';
return buffer;
}

View File

@@ -1,12 +1,11 @@
#include "io/sysio.h"
#include <cstdint>
uint32_t rows, cols;
bool show_cursor = 0;
std::vector<ScreenCell> screen;
std::vector<ScreenCell> old_screen;
std::mutex screen_mutex;
termios orig_termios;
static uint32_t rows, cols;
static bool show_cursor = 0;
static std::vector<ScreenCell> screen;
static std::vector<ScreenCell> old_screen;
static std::mutex screen_mutex;
static termios orig_termios;
void disable_raw_mode() {
std::string os = "\x1b[?1049l\x1b[2 q\x1b[?1002l\x1b[?25h\x1b[?2004l";

View File

@@ -161,7 +161,7 @@ void close_lsp(uint8_t lsp_id) {
lsp->initialized = false;
LSPPending *shutdown_pending = new LSPPending();
shutdown_pending->method = "shutdown";
shutdown_pending->callback = [lsp, lsp_id](Editor *, std::string, json) {
shutdown_pending->callback = [lsp](Editor *, std::string, json) {
json exit = {{"jsonrpc", "2.0"}, {"method", "exit"}};
lsp_send(lsp, exit, nullptr);
};

View File

@@ -2,7 +2,6 @@
#include "editor/editor.h"
#include "io/sysio.h"
#include "lsp/lsp.h"
#include "ts/ts.h"
#include "ui/bar.h"
#include "utils/utils.h"
@@ -15,7 +14,7 @@ std::atomic<uint8_t> mode = NORMAL;
void background_worker() {
while (running)
throttle(8ms, editor_worker, editors[current_editor]);
throttle(16ms, editor_worker, editors[current_editor]);
}
void background_lsp() {
@@ -119,14 +118,15 @@ int main(int argc, char *argv[]) {
for (auto editor : editors)
free_editor(editor);
std::unique_lock lk(active_lsps_mtx);
lk.unlock();
while (true) {
std::unique_lock lk(active_lsps_mtx);
lk.lock();
if (active_lsps.empty())
break;
lk.unlock();
throttle(16ms, lsp_worker);
}
clear_regex_cache();
return 0;
}

730
src/syntax/ruby.cc Normal file
View File

@@ -0,0 +1,730 @@
#include "syntax/langs.h"
const static std::vector<std::string> base_keywords = {
// style 4
"if", "else", "elsif", "case", "rescue", "ensure", "do", "for",
"while", "until", "def", "class", "module", "begin", "end", "unless",
};
const static std::vector<std::string> operator_keywords = {
// style 5
"alias", "and", "BEGIN", "break", "catch", "defined?", "in", "next",
"not", "or", "redo", "rescue", "retry", "return", "super", "yield",
"self", "nil", "true", "false", "undef", "when",
};
const static std::vector<std::string> operators = {
"+", "-", "*", "/", "%", "**", "==", "!=", "===",
"<=>", ">", ">=", "<", "<=", "&&", "||", "!", "&",
"|", "^", "~", "<<", ">>", "=", "+=", "-=", "*=",
"/=", "%=", "**=", "&=", "|=", "^=", "<<=", ">>=", "..",
"...", "===", "=", "=>", "&.", "[]", "[]=", "`", "->",
};
struct HeredocInfo {
std::string delim;
bool allow_interpolation{true};
bool allow_indentation{false};
bool operator==(const HeredocInfo &other) const {
return delim == other.delim &&
allow_interpolation == other.allow_interpolation &&
allow_indentation == other.allow_indentation;
}
};
struct RubyFullState {
// TODO: use this to highlight each level seperaletly like vscode colored
// braces extention thingy does
int brace_level = 0;
int paren_level = 0;
int bracket_level = 0;
enum : uint8_t { NONE, STRING, REGEXP, COMMENT, HEREDOC, END };
uint8_t in_state = RubyFullState::NONE;
struct Lit {
char delim_start = '\0';
char delim_end = '\0';
// For stuff like %Q{ { these braces are valid } this part is still str }
int brace_level = 1;
bool allow_interp = false;
bool operator==(const RubyFullState::Lit &other) const {
return delim_start == other.delim_start && delim_end == other.delim_end &&
brace_level == other.brace_level &&
allow_interp == other.allow_interp;
}
} lit;
bool operator==(const RubyFullState &other) const {
return in_state == other.in_state && lit == other.lit &&
brace_level == other.brace_level &&
paren_level == other.paren_level &&
bracket_level == other.bracket_level;
}
};
struct RubyState {
int interp_level = 0;
std::stack<std::shared_ptr<RubyFullState>> interp_stack;
std::shared_ptr<RubyFullState> full_state;
std::deque<HeredocInfo> heredocs;
bool operator==(const RubyState &other) const {
return interp_level == other.interp_level &&
interp_stack == other.interp_stack &&
((full_state && other.full_state &&
*full_state == *other.full_state)) &&
heredocs == other.heredocs;
}
};
inline std::shared_ptr<RubyState>
ensure_state(std::shared_ptr<RubyState> state) {
if (!state)
state = std::make_shared<RubyState>();
if (state.unique())
return state;
return std::make_shared<RubyState>(*state);
}
inline std::shared_ptr<RubyState>
ensure_full_state(std::shared_ptr<RubyState> state) {
state = ensure_state(state);
if (!state->full_state)
state->full_state = std::make_shared<RubyFullState>();
else if (!state->full_state.unique())
state->full_state = std::make_shared<RubyFullState>(*state->full_state);
return state;
}
bool identifier_start_char(char c) {
return !isascii(c) || isalpha(c) || c == '_';
}
bool identifier_char(char c) { return !isascii(c) || isalnum(c) || c == '_'; }
uint32_t get_next_word(const char *text, uint32_t i, uint32_t len) {
if (i >= len || !identifier_start_char(text[i]))
return 0;
uint32_t width = 1;
while (i + width < len && identifier_char(text[i + width]))
width++;
if (i + width < len && (text[i + width] == '!' || text[i + width] == '?'))
width++;
return width;
}
bool compare(const char *a, const char *b, size_t n) {
size_t i = 0;
for (; i < n; ++i)
if (a[i] != b[i])
return false;
return true;
}
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 operator_keywords_trie;
static Trie operator_trie;
if (!keywords_trie_init) {
base_keywords_trie.build(base_keywords);
operator_keywords_trie.build(operator_keywords);
operator_trie.build(operators);
keywords_trie_init = true;
}
tokens->clear();
if (!in_state)
in_state = std::make_shared<RubyState>();
std::shared_ptr<RubyState> state =
std::static_pointer_cast<RubyState>(in_state);
if (!state->full_state)
state->full_state = std::make_shared<RubyFullState>();
uint32_t i = 0;
while (len > 0 && (text[len - 1] == '\n' || text[len - 1] == '\r' ||
text[len - 1] == '\t' || text[len - 1] == ' '))
len--;
if (len == 0)
return state;
bool heredoc_first = false;
while (i < len) {
if (state->full_state->in_state == RubyFullState::END) {
tokens->clear();
return state;
}
if (state->full_state->in_state == RubyFullState::COMMENT) {
tokens->push_back({i, len, 1});
if (i == 0 && len == 4 && text[i] == '=' && text[i + 1] == 'e' &&
text[i + 2] == 'n' && text[i + 3] == 'd') {
state = ensure_full_state(state);
state->full_state->in_state = RubyFullState::NONE;
}
return state;
}
if (!heredoc_first &&
state->full_state->in_state == RubyFullState::HEREDOC) {
if (i == 0) {
uint32_t start = 0;
if (state->heredocs.front().allow_indentation)
while (start < len && (text[start] == ' ' || text[start] == '\t'))
start++;
if (len - start == state->heredocs.front().delim.length() &&
compare(text + start, state->heredocs.front().delim.c_str(),
state->heredocs.front().delim.length())) {
state = ensure_full_state(state);
state->heredocs.pop_front();
if (state->heredocs.empty())
state->full_state->in_state = RubyFullState::NONE;
tokens->push_back({i, len, 10});
return state;
}
}
uint32_t start = i;
if (!state->heredocs.front().allow_interpolation) {
tokens->push_back({i, len, 2});
return state;
} else {
while (i < len) {
if (text[i] == '\\') {
// TODO: highlight the escape character
i++;
if (i < len)
i++;
continue;
}
if (text[i] == '#' && i + 1 < len && text[i + 1] == '{') {
tokens->push_back({start, i, 2});
tokens->push_back({i, i + 2, 10});
i += 2;
state = ensure_state(state);
state->interp_stack.push(state->full_state);
state->full_state = std::make_shared<RubyFullState>();
state->interp_level = 1;
break;
}
i++;
}
if (i == len)
tokens->push_back({start, len, 2});
continue;
}
}
if (state->full_state->in_state == RubyFullState::STRING) {
uint32_t start = i;
while (i < len) {
if (text[i] == '\\') {
// TODO: highlight the escape character - need to make priority work
// and this have higher
i++;
if (i < len)
i++;
continue;
}
if (state->full_state->lit.allow_interp && text[i] == '#' &&
i + 1 < len && text[i + 1] == '{') {
tokens->push_back({start, i, 2});
tokens->push_back({i, i + 2, 10});
i += 2;
state = ensure_state(state);
state->interp_stack.push(state->full_state);
state->full_state = std::make_shared<RubyFullState>();
state->interp_level = 1;
break;
}
if (text[i] == state->full_state->lit.delim_start &&
state->full_state->lit.delim_start !=
state->full_state->lit.delim_end) {
state = ensure_full_state(state);
state->full_state->lit.brace_level++;
}
if (text[i] == state->full_state->lit.delim_end) {
state = ensure_full_state(state);
if (state->full_state->lit.delim_start ==
state->full_state->lit.delim_end) {
i++;
tokens->push_back({start, i, 2});
state->full_state->in_state = RubyFullState::NONE;
break;
} else {
state->full_state->lit.brace_level--;
if (state->full_state->lit.brace_level == 0) {
i++;
tokens->push_back({start, i, 2});
state->full_state->in_state = RubyFullState::NONE;
break;
}
}
}
i++;
}
if (i == len)
tokens->push_back({start, len, 2});
continue;
}
if (i == 0 && len == 6) {
if (text[i] == '=' && text[i + 1] == 'b' && text[i + 2] == 'e' &&
text[i + 3] == 'g' && text[i + 4] == 'i' && text[i + 5] == 'n') {
state = ensure_full_state(state);
state->full_state->in_state = RubyFullState::COMMENT;
tokens->push_back({0, len, 1});
return state;
}
}
if (i == 0 && len == 7) {
if (text[i] == '_' && text[i + 1] == '_' && text[i + 2] == 'E' &&
text[i + 3] == 'N' && text[i + 4] == 'D' && text[i + 5] == '_' &&
text[i + 6] == '_') {
state = ensure_full_state(state);
tokens->clear();
state->full_state->in_state = RubyFullState::END;
return state;
}
}
if (i + 3 <= len && text[i] == '<' && text[i + 1] == '<') {
uint32_t j = i + 2;
bool indented = false;
if (text[j] == '~')
indented = true;
if (text[j] == '~' || text[j] == '-')
j++;
tokens->push_back({i, j, 10});
if (j >= len)
continue;
std::string delim;
bool interpolation = true;
uint32_t s = j;
if (text[j] == '\'' || text[j] == '"') {
char q = text[j++];
if (q == '\'')
interpolation = false;
while (j < len && text[j] != q)
delim += text[j++];
} else {
while (j < len && identifier_char(text[j]))
delim += text[j++];
}
if (!delim.empty()) {
tokens->push_back({s, j, 10});
state = ensure_full_state(state);
state->heredocs.push_back({delim, interpolation, indented});
state->full_state->in_state = RubyFullState::HEREDOC;
heredoc_first = true;
}
i = j;
continue;
}
if (text[i] == '#') {
tokens->push_back({i, len, 1});
return state;
} else if (text[i] == ':') {
uint32_t start = i;
i++;
if (i >= len) {
tokens->push_back({start, i, 3});
continue;
}
if (text[i] == '\'' || text[i] == '"') {
tokens->push_back({start, i, 6});
continue;
}
if (text[i] == '$' || text[i] == '@') {
uint32_t var_start = i;
i++;
if (i < len && text[var_start] == '@' && text[var_start + 1] == '@')
i++;
while (i < len && identifier_char(text[i]))
i++;
tokens->push_back({start, i, 6});
continue;
}
uint32_t op_len = operator_trie.match(text, i, len, identifier_char);
if (op_len > 0) {
tokens->push_back({start, i + op_len, 6});
i += op_len;
continue;
}
if (identifier_start_char(text[i])) {
uint32_t word_len = get_next_word(text, i, len);
tokens->push_back({start, i + word_len, 6});
i += word_len;
continue;
}
tokens->push_back({start, i, 3});
continue;
} else if (text[i] == '@') {
uint32_t start = i;
i++;
if (i >= len)
continue;
if (text[i] == '@')
i++;
if (i < len && identifier_start_char(text[i]))
i++;
else
continue;
while (i < len && identifier_char(text[i]))
i++;
tokens->push_back({start, i, 7});
continue;
} else if (text[i] == '$') {
uint32_t start = i;
i++;
if (i >= len)
continue;
if (identifier_start_char(text[i])) {
i++;
while (i < len && identifier_char(text[i]))
i++;
} else if (i + 1 < len && text[i] == '-' && isalpha(text[i + 1])) {
i += 2;
} else if (isdigit(text[i])) {
i++;
while (i < len && isdigit(text[i]))
i++;
} else if (text[i] != '-' && !isalnum(text[i])) {
i++;
} else {
continue;
}
tokens->push_back({start, i, 8});
continue;
} else if (text[i] == '?') {
uint32_t start = i;
i++;
if (i < len && text[i] == '\\') {
i++;
if (i < len && text[i] == 'x') {
i++;
if (i < len && isxdigit(text[i]))
i++;
else
continue;
if (i < len && isxdigit(text[i]))
i++;
tokens->push_back({start, i, 7});
continue;
} else if (i < len && text[i] == 'u') {
i++;
if (i < len && isxdigit(text[i]))
i++;
else
continue;
if (i < len && isxdigit(text[i]))
i++;
else
continue;
if (i < len && isxdigit(text[i]))
i++;
else
continue;
if (i < len && isxdigit(text[i]))
i++;
else
continue;
tokens->push_back({start, i, 7});
continue;
} else if (i < len) {
i++;
tokens->push_back({start, i, 7});
continue;
}
} else if (i < len && text[i] != ' ') {
i++;
tokens->push_back({start, i, 7});
continue;
} else {
tokens->push_back({start, i, 3});
continue;
}
} else if (text[i] == '{') {
tokens->push_back({i, i + 1, 3});
state = ensure_state(state);
state->interp_level++;
i++;
continue;
} else if (text[i] == '}') {
state = ensure_full_state(state);
state->interp_level--;
if (state->interp_level == 0 && !state->interp_stack.empty()) {
state->full_state = state->interp_stack.top();
state->interp_stack.pop();
tokens->push_back({i, i + 1, 10});
} else {
tokens->push_back({i, i + 1, 3});
}
i++;
continue;
} else if (text[i] == '\'') {
tokens->push_back({i, i + 1, 2});
state = ensure_full_state(state);
state->full_state->in_state = RubyFullState::STRING;
state->full_state->lit.delim_start = '\'';
state->full_state->lit.delim_end = '\'';
state->full_state->lit.allow_interp = false;
i++;
continue;
} else if (text[i] == '"') {
tokens->push_back({i, i + 1, 2});
state = ensure_full_state(state);
state->full_state->in_state = RubyFullState::STRING;
state->full_state->lit.delim_start = '"';
state->full_state->lit.delim_end = '"';
state->full_state->lit.allow_interp = true;
i++;
continue;
} else if (text[i] == '`') {
tokens->push_back({i, i + 1, 2});
state = ensure_full_state(state);
state->full_state->in_state = RubyFullState::STRING;
state->full_state->lit.delim_start = '`';
state->full_state->lit.delim_end = '`';
state->full_state->lit.allow_interp = true;
i++;
continue;
} else if (text[i] == '%') {
if (i + 1 >= len) {
i++;
continue;
}
char type = text[i + 1];
char delim_start = '\0';
char delim_end = '\0';
bool allow_interp = true;
int prefix_len = 1;
switch (type) {
case 'Q':
case 'x':
allow_interp = true;
prefix_len = 2;
break;
case 'w':
case 'q':
case 'i':
allow_interp = false;
prefix_len = 2;
break;
default:
allow_interp = true;
prefix_len = 1;
break;
}
if (i + prefix_len >= len) {
i += prefix_len;
continue;
}
delim_start = text[i + prefix_len];
if (!isascii(delim_start) ||
(isalnum(delim_start) || delim_start == '_' || delim_start == ' ')) {
i += prefix_len;
continue;
}
switch (delim_start) {
case '(':
delim_end = ')';
break;
case '{':
delim_end = '}';
break;
case '[':
delim_end = ']';
break;
case '<':
delim_end = '>';
break;
default:
delim_end = delim_start;
break;
}
tokens->push_back({i, i + prefix_len + 1, 2});
state = ensure_full_state(state);
state->full_state->in_state = RubyFullState::STRING;
state->full_state->lit.delim_start = delim_start;
state->full_state->lit.delim_end = delim_end;
state->full_state->lit.allow_interp = allow_interp;
state->full_state->lit.brace_level = 1;
i += prefix_len + 1;
continue;
} else if (isdigit(text[i])) {
uint32_t start = i;
if (text[i] == '0') {
i++;
if (i < len && text[i] == 'x') {
i++;
if (i < len && isxdigit(text[i]))
i++;
else
continue;
bool is_underscore = false;
while (i < len && (isxdigit(text[i]) || text[i] == '_')) {
if (text[i] == '_')
is_underscore = true;
else
is_underscore = false;
i++;
}
if (is_underscore)
i--;
} else if (i < len && text[i] == 'b') {
i++;
if (i < len && (text[i] == '0' || text[i] == '1'))
i++;
else
continue;
bool is_underscore = false;
while (i < len &&
(text[i] == '0' || text[i] == '1' || text[i] == '_')) {
if (text[i] == '_')
is_underscore = true;
else
is_underscore = false;
i++;
}
if (is_underscore)
i--;
} else if (i < len && text[i] == 'o') {
i++;
if (i < len && text[i] >= '0' && text[i] <= '7')
i++;
else
continue;
bool is_underscore = false;
while (i < len &&
((text[i] >= '0' && text[i] <= '7') || text[i] == '_')) {
if (text[i] == '_')
is_underscore = true;
else
is_underscore = false;
i++;
}
if (is_underscore)
i--;
}
} else {
bool is_underscore = true;
while (i < len &&
(isdigit(text[i]) || (text[i] == '_' && !is_underscore))) {
if (text[i] == '_')
is_underscore = true;
else
is_underscore = false;
i++;
}
if (is_underscore)
i--;
if (i < len && text[i] == '.') {
i++;
bool is_underscore = true;
while (i < len &&
(isdigit(text[i]) || (text[i] == '_' && !is_underscore))) {
if (text[i] == '_')
is_underscore = true;
else
is_underscore = false;
i++;
}
if (is_underscore)
i--;
}
if (i < len && (text[i] == 'E' || text[i] == 'e')) {
i++;
if (i < len && (text[i] == '+' || text[i] == '-'))
i++;
bool is_underscore = true;
while (i < len &&
(isdigit(text[i]) || (text[i] == '_' && !is_underscore))) {
if (text[i] == '_')
is_underscore = true;
else
is_underscore = false;
i++;
}
if (is_underscore)
i--;
}
}
tokens->push_back({start, i, 9});
continue;
} else if (identifier_start_char(text[i])) {
uint32_t length;
if ((length = base_keywords_trie.match(text, i, len, identifier_char)) >
0) {
tokens->push_back({i, i + length, 4});
i += length;
continue;
} else if ((length = operator_keywords_trie.match(text, i, len,
identifier_char)) > 0) {
tokens->push_back({i, i + length, 5});
i += length;
continue;
} else if (text[i] >= 'A' && text[i] <= 'Z') {
uint32_t start = i;
i += get_next_word(text, i, len);
tokens->push_back({start, i, 10});
continue;
} else {
uint32_t start = i;
while (i < len && identifier_char(text[i]))
i++;
if (i < len && text[i] == ':') {
i++;
tokens->push_back({start, i, 6});
continue;
} else if (i < len && (text[i] == '!' || text[i] == '?')) {
i++;
}
continue;
}
} else {
uint32_t op_len;
if ((op_len = operator_trie.match(text, i, len,
[](char) { return false; })) > 0) {
tokens->push_back({i, i + op_len, 3});
i += op_len;
continue;
}
}
i += utf8_codepoint_width(text[i]);
}
return state;
}
bool ruby_state_match(std::shared_ptr<void> state_1,
std::shared_ptr<void> state_2) {
if (!state_1 || !state_2)
return false;
return *std::static_pointer_cast<RubyState>(state_1) ==
*std::static_pointer_cast<RubyState>(state_2);
}
// function calls matched with alphanumeric names followed immediately by !
// or ? or `(` immediately or siwth space or are followed by a non-keyword
// or non-operator (some operators like - for negating and ! for not or {
// for block might be allowed?)
// a word following :: or . is matched as a property
// and any random word is matched as a variable name
// or as a class/module name if it starts with a capital letter
//
// regex are matched as text within / and / as long as
// the first / is not
// following a literal (int/float/string) or variable or brace close
// and is following a keyword or operator liek return /regex/ or x =
// /regex/ . so maybe add feild expecting_expr to state that is true right
// after keyword or some operators like = , =~ , `,` etc?
//
// (left to implement) -
//
// words - breaks up into these submatches
// - Constants that start with a capital letter
// - a word following :: or . is matched as a property
// - function call if ending with ! or ? or ( or are followed by a
// non-keyword or non-operator . ill figure it out
//
// regex (and distinguish between / for division and / for regex) and
// %r{} ones too
//
// Matching brace colors by brace depth
//

View File

@@ -0,0 +1,167 @@
#include "io/knot.h"
#include "main.h"
#include "syntax/langs.h"
#include "syntax/parser.h"
Parser::Parser(Knot *n_root, std::shared_mutex *n_knot_mutex,
std::string n_lang, uint32_t n_scroll_max) {
scroll_max = n_scroll_max;
line_data.reserve(n_root->line_count + 1);
knot_mutex = n_knot_mutex;
lang = n_lang;
auto pair = parsers.find(n_lang);
if (pair != parsers.end()) {
parse_func = std::get<0>(pair->second);
state_match_func = std::get<1>(pair->second);
} else {
assert("unknown lang should be checked by caller" && 0);
}
edit(n_root, 0, 0, n_root->line_count);
}
void Parser::edit(Knot *n_root, uint32_t start_line, uint32_t old_end_line,
uint32_t new_end_line) {
std::lock_guard lock(data_mutex);
root = n_root;
if (((int64_t)old_end_line - (int64_t)start_line) > 0)
line_data.erase(line_data.begin() + start_line,
line_data.begin() + start_line + old_end_line - start_line);
if (((int64_t)new_end_line - (int64_t)old_end_line) > 0)
line_data.insert(line_data.begin() + start_line,
new_end_line - old_end_line, LineData{});
dirty_lines.insert(start_line);
}
void Parser::work() {
std::shared_lock k_lock(*knot_mutex);
k_lock.unlock();
uint32_t capacity = 256;
char *text = (char *)calloc((capacity + 1), sizeof(char));
std::set<uint32_t> tmp_dirty;
std::unique_lock lock_data(data_mutex);
tmp_dirty.swap(dirty_lines);
lock_data.unlock();
std::set<uint32_t> remaining_dirty;
for (uint32_t c_line : tmp_dirty) {
if (c_line > scroll_max) {
remaining_dirty.insert(c_line);
continue;
}
std::unique_lock lock(mutex);
uint32_t line_count = (uint32_t)line_data.size();
std::shared_ptr<void> prev_state =
(c_line > 0) ? line_data[c_line - 1].out_state : nullptr;
lock.unlock();
while (c_line < line_count) {
if (!running.load(std::memory_order_relaxed)) {
free(text);
return;
}
k_lock.lock();
uint32_t r_offset, r_len;
r_offset = line_to_byte(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);
k_lock.unlock();
if (c_line < scroll_max &&
((scroll_max > 100 && c_line > scroll_max - 100) || c_line < 100))
lock.lock();
lock_data.lock();
std::shared_ptr<void> new_state =
parse_func(&line_data[c_line].tokens, prev_state, text, r_len);
lock_data.unlock();
line_data[c_line].in_state = prev_state;
line_data[c_line].out_state = new_state;
if (lock.owns_lock())
lock.unlock();
if (!running.load(std::memory_order_relaxed)) {
free(text);
return;
}
prev_state = new_state;
c_line++;
if (c_line < line_count && c_line > scroll_max + 50) {
if (c_line > 0)
remaining_dirty.insert(c_line - 1);
remaining_dirty.insert(c_line);
break;
}
lock.lock();
if (c_line < line_count &&
state_match_func(prev_state, line_data[c_line].in_state))
break;
lock.unlock();
}
if (!running.load(std::memory_order_relaxed)) {
free(text);
return;
}
}
free(text);
lock_data.lock();
dirty_lines = std::move(remaining_dirty);
}
void Parser::scroll(uint32_t line) {
if (line != scroll_max) {
scroll_max = line;
uint32_t c_line = line > 100 ? line - 100 : 0;
if (line_data.size() < c_line)
return;
if (line_data[c_line].in_state || line_data[c_line].out_state)
return;
std::shared_lock k_lock(*knot_mutex);
k_lock.unlock();
uint32_t capacity = 256;
char *text = (char *)calloc((capacity + 1), sizeof(char));
std::unique_lock lock_data(data_mutex);
lock_data.unlock();
std::unique_lock lock(mutex);
uint32_t line_count = (uint32_t)line_data.size();
std::shared_ptr<void> prev_state =
(c_line > 0) ? line_data[c_line - 1].out_state : nullptr;
lock.unlock();
while (c_line < line_count) {
if (!running.load(std::memory_order_relaxed)) {
free(text);
return;
}
k_lock.lock();
uint32_t r_offset, r_len;
r_offset = line_to_byte(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);
k_lock.unlock();
if (c_line < scroll_max &&
((scroll_max > 100 && c_line > scroll_max - 100) || c_line < 100))
lock.lock();
lock_data.lock();
std::shared_ptr<void> new_state =
parse_func(&line_data[c_line].tokens, prev_state, text, r_len);
lock_data.unlock();
line_data[c_line].in_state = nullptr;
line_data[c_line].out_state = new_state;
if (lock.owns_lock())
lock.unlock();
if (!running.load(std::memory_order_relaxed)) {
free(text);
return;
}
prev_state = new_state;
c_line++;
if (c_line < line_count && c_line > scroll_max + 50)
break;
}
free(text);
} else {
scroll_max = line;
}
}

View File

@@ -1,171 +0,0 @@
#include "ts/ts.h"
#include "editor/editor.h"
#include "io/knot.h"
const char *read_ts(void *payload, uint32_t byte_index, TSPoint,
uint32_t *bytes_read) {
Editor *editor = (Editor *)payload;
if (byte_index >= editor->root->char_count) {
*bytes_read = 0;
return "";
}
return leaf_from_offset(editor->root, byte_index, bytes_read);
}
void ts_collect_spans(Editor *editor) {
static int parse_counter = 64;
if (!editor->ts.parser || !editor->root || !editor->ts.query)
return;
const bool injections_enabled = editor->root->char_count < (1024 * 20);
for (auto &inj : editor->ts.injections)
inj.second.ranges.clear();
TSInput tsinput{
.payload = editor,
.read = read_ts,
.encoding = TSInputEncodingUTF8,
.decode = nullptr,
};
std::vector<TSInputEdit> edits;
TSInputEdit edit;
if (!editor->edit_queue.empty()) {
while (editor->edit_queue.pop(edit))
edits.push_back(edit);
if (editor->ts.tree)
for (auto &e : edits)
ts_tree_edit(editor->ts.tree, &e);
for (auto &inj : editor->ts.injections)
if (inj.second.tree)
for (auto &e : edits)
ts_tree_edit(inj.second.tree, &e);
} else if (editor->ts.tree && parse_counter++ < 64) {
return;
}
parse_counter = 0;
std::shared_lock lock(editor->knot_mtx);
editor->spans.mid_parse = true;
TSTree *tree = ts_parser_parse(editor->ts.parser, editor->ts.tree, tsinput);
if (!tree)
return;
if (editor->ts.tree)
ts_tree_delete(editor->ts.tree);
editor->ts.tree = tree;
lock.unlock();
std::vector<Span> new_spans;
new_spans.reserve(4096);
struct PendingRanges {
std::vector<TSRange> ranges;
TSSet *tsset = nullptr;
};
struct WorkItem {
TSSetBase *tsset;
TSTree *tree;
int depth;
};
const int kMaxInjectionDepth = 4;
std::vector<WorkItem> work;
work.push_back(
{reinterpret_cast<TSSetBase *>(&editor->ts), editor->ts.tree, 0});
auto overlaps = [](const Span &s, const TSRange &r) {
return !(s.end <= r.start_byte || s.start >= r.end_byte);
};
auto remove_overlapping_spans = [&](const std::vector<TSRange> &ranges) {
if (ranges.empty())
return;
new_spans.erase(
std::remove_if(new_spans.begin(), new_spans.end(),
[&](const Span &sp) {
return std::any_of(
ranges.begin(), ranges.end(),
[&](const TSRange &r) { return overlaps(sp, r); });
}),
new_spans.end());
};
while (!work.empty()) {
WorkItem item = work.back();
work.pop_back();
TSQuery *q = item.tsset->query;
if (!q)
continue;
TSQueryCursor *cursor = ts_query_cursor_new();
ts_query_cursor_exec(cursor, q, ts_tree_root_node(item.tsset->tree));
std::unordered_map<std::string, PendingRanges> pending_injections;
TSQueryMatch match;
auto subject_fn = [&](const TSNode *node, uint32_t *len,
bool *allocated) -> char * {
uint32_t start = ts_node_start_byte(*node);
uint32_t end = ts_node_end_byte(*node);
if (start == end || end > editor->root->char_count)
return nullptr;
std::shared_lock lock(editor->knot_mtx);
char *text = read(editor->root, start, end - start);
*len = end - start;
*allocated = true;
return text;
};
while (ts_query_cursor_next_match(cursor, &match)) {
if (!ts_predicate(q, match, subject_fn))
continue;
for (uint32_t i = 0; i < match.capture_count; i++) {
TSQueryCapture cap = match.captures[i];
uint32_t start = ts_node_start_byte(cap.node);
uint32_t end = ts_node_end_byte(cap.node);
if (Highlight *hl = safe_get(item.tsset->query_map, cap.index))
new_spans.push_back({start, end, hl});
if (!injections_enabled)
continue;
if (Language *inj_lang =
safe_get(item.tsset->injection_map, cap.index)) {
auto &pending = pending_injections[inj_lang->name];
TSSet &tsset =
editor->ts.injections.try_emplace(inj_lang->name).first->second;
if (!tsset.parser) {
tsset.lang = inj_lang->name;
tsset.parser = ts_parser_new();
ts_parser_set_language(tsset.parser, inj_lang->fn());
tsset.language = inj_lang->fn();
tsset.query_file =
get_exe_dir() + "/../grammar/" + inj_lang->name + ".scm";
tsset.query = load_query(tsset.query_file.c_str(), &tsset);
}
pending.tsset = &tsset;
pending.ranges.push_back(TSRange{
ts_node_start_point(cap.node),
ts_node_end_point(cap.node),
start,
end,
});
}
}
}
ts_query_cursor_delete(cursor);
if (injections_enabled && item.depth < kMaxInjectionDepth) {
for (auto &[lang_name, pending] : pending_injections) {
TSSet *tsset = pending.tsset;
if (!tsset || pending.ranges.empty() || !tsset->parser || !tsset->query)
continue;
tsset->ranges = std::move(pending.ranges);
remove_overlapping_spans(tsset->ranges);
ts_parser_set_included_ranges(tsset->parser, tsset->ranges.data(),
tsset->ranges.size());
lock.lock();
TSTree *tree = ts_parser_parse(tsset->parser, tsset->tree, tsinput);
if (!tree)
continue;
if (tsset->tree)
ts_tree_delete(tsset->tree);
tsset->tree = tree;
lock.unlock();
work.push_back({reinterpret_cast<TSSetBase *>(tsset), tsset->tree,
item.depth + 1});
}
}
}
lock.lock();
std::pair<uint32_t, int64_t> span_edit;
while (editor->spans.edits.pop(span_edit))
apply_edit(new_spans, span_edit.first, span_edit.second);
std::sort(new_spans.begin(), new_spans.end());
editor->spans.mid_parse = false;
std::unique_lock span_mtx(editor->spans.mtx);
editor->spans.spans.swap(new_spans);
}

View File

@@ -1,167 +0,0 @@
#include "config.h"
#include "io/sysio.h"
#include "ts/ts.h"
#include <cstdint>
std::unordered_map<std::string, pcre2_code *> regex_cache;
static inline const TSNode *find_capture_node(const TSQueryMatch &match,
uint32_t capture_id) {
for (uint32_t i = 0; i < match.capture_count; i++)
if (match.captures[i].index == capture_id)
return &match.captures[i].node;
return nullptr;
}
void clear_regex_cache() {
for (auto &kv : regex_cache)
pcre2_code_free(kv.second);
regex_cache.clear();
}
pcre2_code *get_re(const std::string &pattern) {
auto it = regex_cache.find(pattern);
if (it != regex_cache.end())
return it->second;
int errornum;
PCRE2_SIZE erroffset;
pcre2_code *re =
pcre2_compile((PCRE2_SPTR)pattern.c_str(), PCRE2_ZERO_TERMINATED, 0,
&errornum, &erroffset, nullptr);
regex_cache[pattern] = re;
return re;
}
TSQuery *load_query(const char *query_path, TSSetBase *set) {
const TSLanguage *lang = set->language;
std::ifstream file(query_path, std::ios::in | std::ios::binary);
if (!file.is_open())
return nullptr;
std::string highlight_query((std::istreambuf_iterator<char>(file)),
std::istreambuf_iterator<char>());
int errornumber = 0;
PCRE2_SIZE erroroffset = 0;
pcre2_code *re = pcre2_compile(
(PCRE2_SPTR) R"((@[A-Za-z0-9_.]+)|(;; \#[0-9a-fA-F]{6} \#[0-9a-fA-F]{6} [01] [01] [01] [01] \d+)|(;; !(\w+)))",
PCRE2_ZERO_TERMINATED, 0, &errornumber, &erroroffset, nullptr);
if (!re)
return nullptr;
pcre2_match_data *match_data =
pcre2_match_data_create_from_pattern(re, nullptr);
std::map<std::string, int> capture_name_cache;
Highlight *c_hl = nullptr;
Language c_lang = {"unknown", nullptr, 0};
int i = 0;
PCRE2_SIZE offset = 0;
PCRE2_SIZE subject_length = highlight_query.size();
while (offset < subject_length) {
int rc = pcre2_match(re, (PCRE2_SPTR)highlight_query.c_str(),
subject_length, offset, 0, match_data, nullptr);
if (rc <= 0)
break;
PCRE2_SIZE *ovector = pcre2_get_ovector_pointer(match_data);
std::string mct =
highlight_query.substr(ovector[0], ovector[1] - ovector[0]);
if (!mct.empty() && mct[0] == '@') {
std::string capture_name = mct;
if (!capture_name_cache.count(capture_name)) {
if (c_hl) {
set->query_map[i] = *c_hl;
delete c_hl;
c_hl = nullptr;
}
if (c_lang.fn != nullptr) {
set->injection_map[i] = c_lang;
c_lang = {"unknown", nullptr, 0};
}
capture_name_cache[capture_name] = i;
i++;
}
} else if (mct.substr(0, 4) == ";; #") {
if (c_hl)
delete c_hl;
c_hl = new Highlight();
c_hl->fg = HEX(mct.substr(4, 6));
c_hl->bg = HEX(mct.substr(12, 6));
int bold = std::stoi(mct.substr(19, 1));
int italic = std::stoi(mct.substr(21, 1));
int underline = std::stoi(mct.substr(23, 1));
int strike = std::stoi(mct.substr(25, 1));
c_hl->priority = std::stoi(mct.substr(27));
c_hl->flags = (bold ? CF_BOLD : 0) | (italic ? CF_ITALIC : 0) |
(underline ? CF_UNDERLINE : 0) |
(strike ? CF_STRIKETHROUGH : 0);
} else if (mct.substr(0, 4) == ";; !") {
auto it = kLanguages.find(mct.substr(4));
if (it != kLanguages.end())
c_lang = it->second;
else
c_lang = {"unknown", nullptr, 0};
}
offset = ovector[1];
}
if (c_hl)
delete c_hl;
pcre2_match_data_free(match_data);
pcre2_code_free(re);
uint32_t error_offset = 0;
TSQueryError error_type = (TSQueryError)0;
TSQuery *q = ts_query_new(lang, highlight_query.c_str(),
(uint32_t)highlight_query.length(), &error_offset,
&error_type);
if (!q)
log("Failed to create TSQuery at offset %u, error type %d", error_offset,
(int)error_type);
return q;
}
bool ts_predicate(
TSQuery *query, const TSQueryMatch &match,
std::function<char *(const TSNode *, uint32_t *len, bool *allocated)>
subject_fn) {
uint32_t step_count;
const TSQueryPredicateStep *steps =
ts_query_predicates_for_pattern(query, match.pattern_index, &step_count);
if (!steps || step_count != 4)
return true;
std::string command;
std::string regex_txt;
uint32_t subject_id = 0;
for (uint32_t i = 0; i < step_count; i++) {
const TSQueryPredicateStep *step = &steps[i];
if (step->type == TSQueryPredicateStepTypeDone)
break;
switch (step->type) {
case TSQueryPredicateStepTypeString: {
uint32_t length = 0;
const char *s =
ts_query_string_value_for_id(query, step->value_id, &length);
if (i == 0)
command.assign(s, length);
else
regex_txt.assign(s, length);
break;
}
case TSQueryPredicateStepTypeCapture: {
subject_id = step->value_id;
break;
}
case TSQueryPredicateStepTypeDone:
break;
}
}
const TSNode *node = find_capture_node(match, subject_id);
pcre2_code *re = get_re(regex_txt);
uint32_t len;
bool allocated;
char *subject = subject_fn(node, &len, &allocated);
if (!subject)
return false;
pcre2_match_data *md = pcre2_match_data_create_from_pattern(re, nullptr);
int rc = pcre2_match(re, (PCRE2_SPTR)subject, len, 0, 0, md, nullptr);
pcre2_match_data_free(md);
bool ok = (rc >= 0);
if (allocated)
free(subject);
return (command == "match?" ? ok : !ok);
}

View File

@@ -139,11 +139,11 @@ 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;
}
// if (start_col + size.col > cols) {
// start_col = cols - 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,11 @@ 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;
}
// if (start_col + size.col > cols) {
// start_col = cols - 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

@@ -1,5 +1,5 @@
#include "ui/hover.h"
#include "ts/ts.h"
#include "syntax/decl.h"
void HoverBox::clear() {
text = "";
@@ -24,107 +24,6 @@ void HoverBox::scroll(int32_t number) {
void HoverBox::render_first(bool scroll) {
if (!scroll) {
std::vector<Span> base_spans;
std::vector<Span> injected_spans;
TSSetBase ts = TSSetBase{};
if (is_markup) {
highlights.clear();
highlights.reserve(1024);
base_spans.reserve(1024);
injected_spans.reserve(1024);
hover_spans.clear();
hover_spans.reserve(1024);
std::string query_path = get_exe_dir() + "/../grammar/hover.scm";
ts.language = LANG(markdown)();
ts.query = load_query(query_path.c_str(), &ts);
ts.parser = ts_parser_new();
ts_parser_set_language(ts.parser, ts.language);
ts.tree = ts_parser_parse_string(ts.parser, nullptr, text.c_str(),
text.length());
TSQueryCursor *cursor = ts_query_cursor_new();
ts_query_cursor_exec(cursor, ts.query, ts_tree_root_node(ts.tree));
TSQueryMatch match;
auto subject_fn = [&](const TSNode *node, uint32_t *len,
bool *allocated) -> char * {
uint32_t start = ts_node_start_byte(*node);
uint32_t end = ts_node_end_byte(*node);
*len = end - start;
*allocated = false;
return text.data() + start;
};
while (ts_query_cursor_next_match(cursor, &match)) {
if (!ts_predicate(ts.query, match, subject_fn))
continue;
for (uint32_t i = 0; i < match.capture_count; i++) {
TSQueryCapture cap = match.captures[i];
uint32_t start = ts_node_start_byte(cap.node);
uint32_t end = ts_node_end_byte(cap.node);
if (Language *inj_lang = safe_get(ts.injection_map, cap.index)) {
TSSetBase inj_ts = TSSetBase{};
inj_ts.language = inj_lang->fn();
inj_ts.query_file =
get_exe_dir() + "/../grammar/" + inj_lang->name + ".scm";
inj_ts.query = load_query(inj_ts.query_file.c_str(), &inj_ts);
inj_ts.parser = ts_parser_new();
ts_parser_set_language(inj_ts.parser, inj_ts.language);
TSPoint start_p = ts_node_start_point(cap.node);
TSPoint end_p = ts_node_end_point(cap.node);
std::vector<TSRange> ranges = {{start_p, end_p, start, end}};
ts_parser_set_included_ranges(inj_ts.parser, ranges.data(), 1);
inj_ts.tree = ts_parser_parse_string(inj_ts.parser, nullptr,
text.c_str(), text.length());
TSQueryCursor *inj_cursor = ts_query_cursor_new();
ts_query_cursor_exec(inj_cursor, inj_ts.query,
ts_tree_root_node(inj_ts.tree));
TSQueryMatch inj_match;
while (ts_query_cursor_next_match(inj_cursor, &inj_match)) {
if (!ts_predicate(inj_ts.query, inj_match, subject_fn))
continue;
for (uint32_t i = 0; i < inj_match.capture_count; i++) {
TSQueryCapture inj_cap = inj_match.captures[i];
uint32_t start = ts_node_start_byte(inj_cap.node);
uint32_t end = ts_node_end_byte(inj_cap.node);
if (Highlight *hl = safe_get(inj_ts.query_map, inj_cap.index)) {
if (highlights.size() >= 1000)
continue;
highlights.push_back(*hl);
Highlight *hl_f = &highlights.back();
injected_spans.push_back({start, end, hl_f});
}
}
}
ts_query_cursor_delete(inj_cursor);
ts_tree_delete(inj_ts.tree);
ts_parser_delete(inj_ts.parser);
ts_query_delete(inj_ts.query);
continue;
}
if (Highlight *hl = safe_get(ts.query_map, cap.index)) {
if (highlights.size() >= 1000)
continue;
highlights.push_back(*hl);
Highlight *hl_f = &highlights.back();
base_spans.push_back({start, end, hl_f});
}
}
}
ts_query_cursor_delete(cursor);
ts_query_delete(ts.query);
ts_tree_delete(ts.tree);
ts_parser_delete(ts.parser);
}
for (const auto &inj : injected_spans) {
base_spans.erase(std::remove_if(base_spans.begin(), base_spans.end(),
[&](const Span &base) {
return !(base.end <= inj.start ||
base.start >= inj.end);
}),
base_spans.end());
}
hover_spans.insert(hover_spans.end(), base_spans.begin(), base_spans.end());
hover_spans.insert(hover_spans.end(), injected_spans.begin(),
injected_spans.end());
std::sort(hover_spans.begin(), hover_spans.end());
}
uint32_t longest_line = 0;
uint32_t current_width = 0;
@@ -148,12 +47,8 @@ void HoverBox::render_first(bool scroll) {
lines_skipped++;
i++;
}
Spans spans{};
spans.spans = hover_spans;
uint32_t border_fg = 0x82AAFF;
uint32_t base_bg = 0;
SpanCursor span_cursor(spans);
span_cursor.sync(i);
cells.assign(size.col * 26, ScreenCell{" ", 0, 0, 0, 0, 0});
auto set = [&](uint32_t r, uint32_t c, const char *text, uint32_t fg,
uint32_t bg, uint8_t flags) {
@@ -174,7 +69,8 @@ void HoverBox::render_first(bool scroll) {
int width = display_width(cluster.c_str(), cluster_len);
if (c + width > content_width)
break;
Highlight *hl = span_cursor.get_highlight(i);
// TODO: Use new highlights
Highlight *hl = nullptr;
uint32_t fg = hl ? hl->fg : 0xFFFFFF;
uint32_t bg = hl ? hl->bg : 0;
uint32_t flags = hl ? hl->flags : 0;
@@ -208,11 +104,11 @@ 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;
}
// if (start_col + size.col > cols) {
// start_col = cols - 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

@@ -28,6 +28,18 @@ int display_width(const char *str, size_t len) {
return width;
}
uint8_t utf8_codepoint_width(unsigned char c) {
if ((c & 0x80) == 0x00)
return 1;
if ((c & 0xE0) == 0xC0)
return 2;
if ((c & 0xF0) == 0xE0)
return 3;
if ((c & 0xF8) == 0xF0)
return 4;
return 1;
}
uint32_t get_visual_col_from_bytes(const char *line, uint32_t len,
uint32_t byte_limit) {
if (!line)