Add custom syntax highlighter and optimize
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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
730
src/syntax/ruby.cc
Normal 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
|
||||
//
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
171
src/ts/ts.cc
171
src/ts/ts.cc
@@ -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);
|
||||
}
|
||||
167
src/ts/utils.cc
167
src/ts/utils.cc
@@ -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);
|
||||
}
|
||||
@@ -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++)
|
||||
|
||||
@@ -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,
|
||||
|
||||
120
src/ui/hover.cc
120
src/ui/hover.cc
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user