Switch to c++
This commit is contained in:
508
src/editor.cc
Normal file
508
src/editor.cc
Normal file
@@ -0,0 +1,508 @@
|
||||
extern "C" {
|
||||
#include "../libs/libgrapheme/grapheme.h"
|
||||
}
|
||||
#include "../include/editor.h"
|
||||
#include "../include/ts.h"
|
||||
#include "../libs/tree-sitter-ruby/bindings/c/tree-sitter-ruby.h"
|
||||
#include <cstdint>
|
||||
#include <fstream>
|
||||
|
||||
char *load_file(const char *path, uint32_t *out_len) {
|
||||
std::ifstream file(path, std::ios::in | std::ios::binary | std::ios::ate);
|
||||
if (!file.is_open())
|
||||
return nullptr;
|
||||
std::streamsize len = file.tellg();
|
||||
if (len < 0 || (std::uint32_t)len > 0xFFFFFFFF)
|
||||
return nullptr;
|
||||
file.seekg(0, std::ios::beg);
|
||||
char *buf = (char *)malloc(static_cast<std::uint32_t>(len));
|
||||
if (!buf)
|
||||
return nullptr;
|
||||
if (file.read(buf, len)) {
|
||||
*out_len = static_cast<uint32_t>(len);
|
||||
return buf;
|
||||
} else {
|
||||
free(buf);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
Editor *new_editor(const char *filename, Coord position, Coord size) {
|
||||
Editor *editor = new Editor();
|
||||
if (!editor)
|
||||
return nullptr;
|
||||
uint32_t len = 0;
|
||||
char *str = load_file(filename, &len);
|
||||
if (!str)
|
||||
return nullptr;
|
||||
editor->filename = filename;
|
||||
editor->position = position;
|
||||
editor->size = size;
|
||||
editor->tree = nullptr;
|
||||
editor->cursor = {0, 0};
|
||||
editor->selection_active = false;
|
||||
editor->selection = {0, 0};
|
||||
editor->scroll = {0, 0};
|
||||
editor->root = load(str, len, optimal_chunk_size(len));
|
||||
editor->folded = (int *)calloc(editor->root->line_count + 2, sizeof(int));
|
||||
std::string query = get_exe_dir() + "/../grammar/ruby.scm";
|
||||
if (!(len > (1024 * 1024))) {
|
||||
editor->parser = ts_parser_new();
|
||||
editor->language = tree_sitter_ruby();
|
||||
ts_parser_set_language(editor->parser, editor->language);
|
||||
editor->query = load_query(query.c_str(), editor);
|
||||
}
|
||||
free(str);
|
||||
return editor;
|
||||
}
|
||||
|
||||
void free_editor(Editor *editor) {
|
||||
if (editor->folded)
|
||||
free(editor->folded);
|
||||
ts_parser_delete(editor->parser);
|
||||
if (editor->tree)
|
||||
ts_tree_delete(editor->tree);
|
||||
if (editor->query)
|
||||
ts_query_delete(editor->query);
|
||||
free_rope(editor->root);
|
||||
delete editor;
|
||||
}
|
||||
|
||||
void scroll_up(Editor *editor, uint32_t number) {
|
||||
if (!editor || !editor->root || number == 0)
|
||||
return;
|
||||
uint32_t count = 0;
|
||||
uint32_t visible_lines_checked = 0;
|
||||
int32_t current_check_row = editor->scroll.row;
|
||||
while (visible_lines_checked < number + 1 && current_check_row >= 0) {
|
||||
if (editor->folded[current_check_row] != 1)
|
||||
visible_lines_checked++;
|
||||
count++;
|
||||
current_check_row--;
|
||||
}
|
||||
if (current_check_row < 0)
|
||||
count = editor->scroll.row;
|
||||
LineIterator *it = begin_l_iter(editor->root, editor->scroll.row - count + 1);
|
||||
std::vector<std::pair<uint32_t, uint32_t>> stack;
|
||||
stack.reserve(count);
|
||||
uint32_t lines_iterated = 0;
|
||||
uint32_t start_row = editor->scroll.row - count + 1;
|
||||
while (lines_iterated < count) {
|
||||
char *line_content = next_line(it);
|
||||
uint32_t current_idx = start_row + lines_iterated;
|
||||
int fold_state = editor->folded[current_idx];
|
||||
if (fold_state == 2) {
|
||||
stack.push_back({1, current_idx});
|
||||
} else if (fold_state == 0) {
|
||||
uint32_t len =
|
||||
(line_content != nullptr) ? grapheme_strlen(line_content) : 0;
|
||||
stack.push_back({len, current_idx});
|
||||
}
|
||||
if (line_content)
|
||||
free(line_content);
|
||||
lines_iterated++;
|
||||
}
|
||||
uint32_t ln = 0;
|
||||
uint32_t wrap_limit = editor->size.col;
|
||||
for (int i = stack.size() - 1; i >= 0; i--) {
|
||||
uint32_t len = stack[i].first;
|
||||
uint32_t row_idx = stack[i].second;
|
||||
uint32_t segments =
|
||||
(wrap_limit > 0 && len > 0) ? (len + wrap_limit - 1) / wrap_limit : 1;
|
||||
if (len == 0)
|
||||
segments = 1;
|
||||
for (int seg = (row_idx == editor->scroll.row)
|
||||
? editor->scroll.col / wrap_limit
|
||||
: segments - 1;
|
||||
seg >= 0; seg--) {
|
||||
ln++;
|
||||
if (ln == number + 1) {
|
||||
editor->scroll.row = row_idx;
|
||||
editor->scroll.col = seg * wrap_limit;
|
||||
free(it);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ln < number + 1) {
|
||||
editor->scroll.row = 0;
|
||||
editor->scroll.col = 0;
|
||||
}
|
||||
free(it);
|
||||
}
|
||||
|
||||
void scroll_down(Editor *editor, uint32_t number) {
|
||||
if (!editor || !editor->root || number == 0)
|
||||
return;
|
||||
LineIterator *it = begin_l_iter(editor->root, editor->scroll.row);
|
||||
uint32_t current_row = editor->scroll.row;
|
||||
uint32_t lines_moved = 0;
|
||||
uint32_t wrap_limit = editor->size.col;
|
||||
if (wrap_limit == 0)
|
||||
wrap_limit = 1;
|
||||
while (lines_moved < number) {
|
||||
char *line_content = next_line(it);
|
||||
if (line_content == nullptr)
|
||||
break;
|
||||
int fold_state = editor->folded[current_row];
|
||||
if (fold_state == 1) {
|
||||
free(line_content);
|
||||
current_row++;
|
||||
continue;
|
||||
}
|
||||
uint32_t segments = 1;
|
||||
if (fold_state == 2) {
|
||||
segments = 1;
|
||||
} else {
|
||||
uint32_t len = grapheme_strlen(line_content);
|
||||
segments = (len > 0) ? (len + wrap_limit - 1) / wrap_limit : 1;
|
||||
}
|
||||
uint32_t start_seg = (current_row == editor->scroll.row)
|
||||
? (editor->scroll.col / wrap_limit)
|
||||
: 0;
|
||||
for (uint32_t seg = start_seg; seg < segments; seg++) {
|
||||
if (current_row == editor->scroll.row && seg == start_seg)
|
||||
continue;
|
||||
lines_moved++;
|
||||
if (lines_moved == number) {
|
||||
editor->scroll.row = current_row;
|
||||
editor->scroll.col = seg * wrap_limit;
|
||||
free(line_content);
|
||||
free(it);
|
||||
return;
|
||||
}
|
||||
}
|
||||
free(line_content);
|
||||
current_row++;
|
||||
}
|
||||
free(it);
|
||||
}
|
||||
|
||||
void cursor_down(Editor *editor, uint32_t number) {
|
||||
if (!editor || !editor->root || number == 0)
|
||||
return;
|
||||
LineIterator *it = begin_l_iter(editor->root, editor->cursor.row);
|
||||
char *line_content = next_line(it);
|
||||
if (line_content == nullptr)
|
||||
return;
|
||||
uint32_t visual_col =
|
||||
get_visual_col_from_bytes(line_content, editor->cursor.col);
|
||||
do {
|
||||
free(line_content);
|
||||
line_content = next_line(it);
|
||||
editor->cursor.row += 1;
|
||||
if (editor->cursor.row >= editor->root->line_count) {
|
||||
editor->cursor.row = editor->root->line_count - 1;
|
||||
break;
|
||||
};
|
||||
if (editor->folded && editor->folded[editor->cursor.row] != 0)
|
||||
number++;
|
||||
} while (--number > 0);
|
||||
free(it);
|
||||
if (line_content == nullptr)
|
||||
return;
|
||||
editor->cursor.col = get_bytes_from_visual_col(line_content, visual_col);
|
||||
free(line_content);
|
||||
}
|
||||
|
||||
void cursor_up(Editor *editor, uint32_t number) {
|
||||
if (!editor || !editor->root || number == 0)
|
||||
return;
|
||||
LineIterator *it = begin_l_iter(editor->root, editor->cursor.row);
|
||||
char *line_content = next_line(it);
|
||||
if (!line_content) {
|
||||
free(it);
|
||||
return;
|
||||
}
|
||||
uint32_t visual_col =
|
||||
get_visual_col_from_bytes(line_content, editor->cursor.col);
|
||||
free(line_content);
|
||||
while (number > 0 && editor->cursor.row > 0) {
|
||||
editor->cursor.row--;
|
||||
if (editor->folded && editor->folded[editor->cursor.row] != 0)
|
||||
continue;
|
||||
number--;
|
||||
}
|
||||
free(it);
|
||||
it = begin_l_iter(editor->root, editor->cursor.row);
|
||||
line_content = next_line(it);
|
||||
if (!line_content) {
|
||||
free(it);
|
||||
return;
|
||||
}
|
||||
editor->cursor.col = get_bytes_from_visual_col(line_content, visual_col);
|
||||
free(line_content);
|
||||
free(it);
|
||||
}
|
||||
|
||||
void cursor_right(Editor *editor, uint32_t number) {
|
||||
if (!editor || !editor->root || number == 0)
|
||||
return;
|
||||
LineIterator *it = begin_l_iter(editor->root, editor->cursor.row);
|
||||
char *line = next_line(it);
|
||||
free(it);
|
||||
if (!line)
|
||||
return;
|
||||
uint32_t line_len = strlen(line);
|
||||
if (line[line_len - 1] == '\n')
|
||||
line[--line_len] = '\0';
|
||||
while (number > 0) {
|
||||
if (editor->cursor.col >= line_len) {
|
||||
free(line);
|
||||
line = nullptr;
|
||||
uint32_t next_row = editor->cursor.row + 1;
|
||||
while (editor->folded && next_row < editor->root->line_count &&
|
||||
editor->folded[next_row] != 0)
|
||||
next_row++;
|
||||
if (next_row >= editor->root->line_count) {
|
||||
editor->cursor.col = line_len;
|
||||
break;
|
||||
}
|
||||
editor->cursor.row = next_row;
|
||||
editor->cursor.col = 0;
|
||||
it = begin_l_iter(editor->root, editor->cursor.row);
|
||||
line = next_line(it);
|
||||
free(it);
|
||||
if (!line)
|
||||
break;
|
||||
line_len = strlen(line);
|
||||
if (line[line_len - 1] == '\n')
|
||||
line[--line_len] = '\0';
|
||||
} else {
|
||||
uint32_t inc = grapheme_next_character_break_utf8(
|
||||
line + editor->cursor.col, line_len - editor->cursor.col);
|
||||
if (inc == 0)
|
||||
break;
|
||||
editor->cursor.col += inc;
|
||||
}
|
||||
number--;
|
||||
}
|
||||
if (line)
|
||||
free(line);
|
||||
}
|
||||
|
||||
void cursor_left(Editor *editor, uint32_t number) {
|
||||
if (!editor || !editor->root || number == 0)
|
||||
return;
|
||||
LineIterator *it = begin_l_iter(editor->root, editor->cursor.row);
|
||||
char *line = next_line(it);
|
||||
free(it);
|
||||
if (!line)
|
||||
return;
|
||||
uint32_t len = strlen(line);
|
||||
if (line[len - 1] == '\n')
|
||||
line[--len] = '\0';
|
||||
while (number > 0) {
|
||||
if (editor->cursor.col == 0) {
|
||||
free(line);
|
||||
line = nullptr;
|
||||
if (editor->cursor.row == 0)
|
||||
break;
|
||||
int32_t prev_row = editor->cursor.row - 1;
|
||||
while (editor->folded && prev_row >= 0 && editor->folded[prev_row] != 0)
|
||||
prev_row--;
|
||||
if (prev_row < 0)
|
||||
break;
|
||||
editor->cursor.row = prev_row;
|
||||
it = begin_l_iter(editor->root, editor->cursor.row);
|
||||
line = next_line(it);
|
||||
free(it);
|
||||
if (!line)
|
||||
break;
|
||||
uint32_t len = strlen(line);
|
||||
if (line[len - 1] == '\n')
|
||||
line[--len] = '\0';
|
||||
editor->cursor.col = len;
|
||||
} else {
|
||||
uint32_t col = editor->cursor.col;
|
||||
uint32_t new_col = 0;
|
||||
uint32_t visual_col = 0;
|
||||
uint32_t len = strlen(line);
|
||||
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;
|
||||
visual_col++;
|
||||
}
|
||||
editor->cursor.col = new_col;
|
||||
}
|
||||
number--;
|
||||
}
|
||||
if (line)
|
||||
free(line);
|
||||
}
|
||||
|
||||
void ensure_scroll(Editor *editor) {
|
||||
LineIterator *it = begin_l_iter(editor->root, editor->scroll.row);
|
||||
uint32_t rendered_lines = 0;
|
||||
uint32_t line_index = editor->scroll.row;
|
||||
char *line_content = nullptr;
|
||||
while (rendered_lines <= editor->size.row) {
|
||||
line_content = next_line(it);
|
||||
if (!line_content)
|
||||
break;
|
||||
char *line_content_t = line_content;
|
||||
if (rendered_lines == 0)
|
||||
line_content_t = line_content + editor->scroll.col;
|
||||
uint32_t len = grapheme_strlen(line_content_t);
|
||||
free(line_content);
|
||||
uint32_t wrapped_lines = (len + editor->size.col - 1) / editor->size.col;
|
||||
rendered_lines += wrapped_lines;
|
||||
line_index++;
|
||||
}
|
||||
line_index -= 2;
|
||||
free(it);
|
||||
if (editor->cursor.row >= line_index && line_content)
|
||||
scroll_down(editor, editor->cursor.row - line_index);
|
||||
if (editor->cursor.row < editor->scroll.row)
|
||||
scroll_up(editor, editor->scroll.row - editor->cursor.row);
|
||||
}
|
||||
|
||||
void fold(Editor *editor, uint32_t start_line, uint32_t end_line) {
|
||||
if (!editor)
|
||||
return;
|
||||
if (!editor->folded) {
|
||||
editor->folded = (int *)calloc(editor->root->line_count + 2, sizeof(int));
|
||||
if (!editor->folded)
|
||||
return;
|
||||
}
|
||||
for (uint32_t i = start_line; i <= end_line && i < editor->size.row; i++)
|
||||
editor->folded[i] = 1;
|
||||
editor->folded[start_line] = 2;
|
||||
}
|
||||
|
||||
void update_render_fold_marker(uint32_t row, uint32_t cols) {
|
||||
const char *marker = "... folded ...";
|
||||
uint32_t len = strlen(marker);
|
||||
uint32_t i = 0;
|
||||
for (; i < len && i < cols; i++)
|
||||
update(row, i, (char[2]){marker[i], 0}, 0xc6c6c6, 0, 0);
|
||||
for (; i < cols; i++)
|
||||
update(row, i, " ", 0xc6c6c6, 0, 0);
|
||||
}
|
||||
|
||||
void render_editor(Editor *editor) {
|
||||
uint32_t screen_rows = editor->size.row;
|
||||
uint32_t screen_cols = editor->size.col;
|
||||
uint32_t line_index = editor->scroll.row;
|
||||
SpanCursor span_cursor(editor->spans);
|
||||
std::shared_lock knot_lock(editor->knot_mtx);
|
||||
std::shared_lock span_lock(editor->span_mtx);
|
||||
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);
|
||||
while (rendered_rows < screen_rows) {
|
||||
if (editor->folded && editor->folded[line_index]) {
|
||||
if (editor->folded[line_index] == 2) {
|
||||
update_render_fold_marker(rendered_rows, screen_cols);
|
||||
rendered_rows++;
|
||||
}
|
||||
do {
|
||||
char *line = next_line(it);
|
||||
if (!line)
|
||||
break;
|
||||
global_byte_offset += strlen(line);
|
||||
if (line[strlen(line) - 1] == '\n')
|
||||
global_byte_offset--;
|
||||
global_byte_offset++;
|
||||
free(line);
|
||||
line_index++;
|
||||
} while (line_index < editor->size.row &&
|
||||
editor->folded[line_index] == 1);
|
||||
continue;
|
||||
}
|
||||
char *line = next_line(it);
|
||||
if (!line)
|
||||
break;
|
||||
uint32_t line_len = strlen(line);
|
||||
if (line_len > 0 && line[line_len - 1] == '\n')
|
||||
line_len--;
|
||||
uint32_t current_byte_offset = 0;
|
||||
if (rendered_rows == 0 && editor->scroll.col > 0) {
|
||||
uint32_t skipped_cols = 0;
|
||||
while (skipped_cols < editor->scroll.col &&
|
||||
current_byte_offset < line_len) {
|
||||
uint32_t len = grapheme_next_character_break_utf8(
|
||||
line + current_byte_offset, line_len - current_byte_offset);
|
||||
current_byte_offset += len;
|
||||
skipped_cols++;
|
||||
}
|
||||
}
|
||||
while (current_byte_offset < line_len && rendered_rows < screen_rows) {
|
||||
uint32_t slice_byte_len = 0;
|
||||
uint32_t slice_visual_cols = 0;
|
||||
uint32_t probe_offset = current_byte_offset;
|
||||
while (slice_visual_cols < screen_cols && probe_offset < line_len) {
|
||||
uint32_t len = grapheme_next_character_break_utf8(
|
||||
line + probe_offset, line_len - probe_offset);
|
||||
slice_byte_len += len;
|
||||
probe_offset += len;
|
||||
slice_visual_cols++;
|
||||
}
|
||||
uint32_t col = 0;
|
||||
uint32_t local_render_offset = 0;
|
||||
while (local_render_offset < slice_byte_len) {
|
||||
if (line_index == editor->cursor.row &&
|
||||
editor->cursor.col == (current_byte_offset + local_render_offset))
|
||||
set_cursor(editor->position.row + rendered_rows,
|
||||
editor->position.col + col, 1);
|
||||
uint32_t absolute_byte_pos =
|
||||
global_byte_offset + current_byte_offset + local_render_offset;
|
||||
Highlight *hl = span_cursor.get_highlight(absolute_byte_pos);
|
||||
uint32_t fg = hl ? hl->fg : 0xFFFFFF;
|
||||
uint32_t bg = hl ? hl->bg : 0;
|
||||
uint8_t fl = hl ? hl->flags : 0;
|
||||
uint32_t cluster_len = grapheme_next_character_break_utf8(
|
||||
line + current_byte_offset + local_render_offset,
|
||||
slice_byte_len - local_render_offset);
|
||||
if (cluster_len == 0)
|
||||
cluster_len = 1;
|
||||
std::string cluster(line + current_byte_offset + local_render_offset,
|
||||
cluster_len);
|
||||
update(editor->position.row + rendered_rows, editor->position.col + col,
|
||||
cluster.c_str(), fg, bg, fl);
|
||||
local_render_offset += cluster_len;
|
||||
col++;
|
||||
}
|
||||
if (line_index == editor->cursor.row &&
|
||||
editor->cursor.col == (current_byte_offset + slice_byte_len))
|
||||
set_cursor(editor->position.row + rendered_rows,
|
||||
editor->position.col + col, 1);
|
||||
while (col < screen_cols) {
|
||||
update(editor->position.row + rendered_rows, editor->position.col + col,
|
||||
" ", 0xFFFFFF, 0, 0);
|
||||
col++;
|
||||
}
|
||||
rendered_rows++;
|
||||
current_byte_offset += slice_byte_len;
|
||||
}
|
||||
if (line_len == 0 ||
|
||||
(current_byte_offset >= line_len && rendered_rows == 0)) {
|
||||
if (editor->cursor.row == line_index)
|
||||
set_cursor(editor->position.row + rendered_rows, editor->position.col,
|
||||
1);
|
||||
uint32_t col = 0;
|
||||
while (col < screen_cols) {
|
||||
update(editor->position.row + rendered_rows, editor->position.col + col,
|
||||
" ", 0xFFFFFF, 0, 0);
|
||||
col++;
|
||||
}
|
||||
rendered_rows++;
|
||||
}
|
||||
global_byte_offset += line_len + 1;
|
||||
line_index++;
|
||||
free(line);
|
||||
}
|
||||
while (rendered_rows < screen_rows) {
|
||||
for (uint32_t col = 0; col < screen_cols; col++)
|
||||
update(editor->position.row + rendered_rows, editor->position.col + col,
|
||||
" ", 0xFFFFFF, 0, 0);
|
||||
rendered_rows++;
|
||||
}
|
||||
free(it);
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <sys/ioctl.h>
|
||||
#include <termios.h>
|
||||
#include <unistd.h>
|
||||
#include <vector>
|
||||
|
||||
#define KEY_CHAR 0
|
||||
#define KEY_SPECIAL 1
|
||||
#define KEY_MOUSE 2
|
||||
#define KEY_NONE 3
|
||||
|
||||
#define KEY_UP 0
|
||||
#define KEY_DOWN 1
|
||||
#define KEY_LEFT 2
|
||||
#define KEY_RIGHT 3
|
||||
#define KEY_DELETE 4
|
||||
|
||||
#define KEY_ESC '\x1b'
|
||||
|
||||
#define PRESS 0
|
||||
#define RELEASE 1
|
||||
#define DRAG 2
|
||||
#define SCROLL 3
|
||||
|
||||
#define LEFT_BTN 0
|
||||
#define MIDDLE_BTN 1
|
||||
#define RIGHT_BTN 2
|
||||
#define SCROLL_BTN 3
|
||||
#define NONE_BTN 4
|
||||
|
||||
#define SCROLL_UP 0
|
||||
#define SCROLL_DOWN 1
|
||||
#define SCROLL_LEFT 2
|
||||
#define SCROLL_RIGHT 3
|
||||
|
||||
#define ALT 1
|
||||
#define CNTRL 2
|
||||
#define CNTRL_ALT 3
|
||||
#define SHIFT 4
|
||||
|
||||
enum CellFlags : uint8_t {
|
||||
CF_NONE = 0,
|
||||
CF_ITALIC = 1 << 0,
|
||||
CF_BOLD = 1 << 1,
|
||||
CF_UNDERLINE = 1 << 2,
|
||||
};
|
||||
|
||||
struct ScreenCell {
|
||||
std::string utf8; // empty => no content
|
||||
uint32_t fg = 0;
|
||||
uint32_t bg = 0;
|
||||
uint8_t flags = CF_NONE;
|
||||
};
|
||||
|
||||
struct KeyEvent {
|
||||
uint8_t key_type;
|
||||
|
||||
char c;
|
||||
|
||||
uint8_t special_key;
|
||||
uint8_t special_modifier;
|
||||
|
||||
uint8_t mouse_x;
|
||||
uint8_t mouse_y;
|
||||
uint8_t mouse_button;
|
||||
uint8_t mouse_state;
|
||||
uint8_t mouse_direction;
|
||||
uint8_t mouse_modifier;
|
||||
};
|
||||
|
||||
extern int rows, cols;
|
||||
extern std::vector<ScreenCell> screen; // size rows*cols
|
||||
extern std::vector<ScreenCell> old_screen;
|
||||
extern std::mutex screen_mutex;
|
||||
|
||||
struct coords {
|
||||
int row;
|
||||
int col;
|
||||
};
|
||||
|
||||
extern "C" {
|
||||
void get_terminal_size();
|
||||
void die(const char *s);
|
||||
void enable_raw_mode();
|
||||
void disable_raw_mode();
|
||||
void start_screen();
|
||||
void end_screen();
|
||||
void update(int row, int col, const char *utf8, uint32_t fg, uint32_t bg,
|
||||
uint8_t flags);
|
||||
void set_cursor(int row, int col, bool show_cursor_param);
|
||||
void render();
|
||||
coords get_size();
|
||||
|
||||
int real_width(std::string str);
|
||||
|
||||
int read_input(char *buf, size_t buflen);
|
||||
KeyEvent read_key_nonblock();
|
||||
KeyEvent read_key();
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "../headers/header.hpp"
|
||||
#include "../include/ui.h"
|
||||
|
||||
int read_input(char *buf, size_t buflen) {
|
||||
size_t i = 0;
|
||||
@@ -115,11 +115,9 @@ KeyEvent read_key_nonblock() {
|
||||
}
|
||||
|
||||
KeyEvent read_key() {
|
||||
KeyEvent ret;
|
||||
while (1) {
|
||||
ret = read_key_nonblock();
|
||||
while (true) {
|
||||
KeyEvent ret = read_key_nonblock();
|
||||
if (ret.key_type != KEY_NONE)
|
||||
return ret;
|
||||
usleep(2500);
|
||||
}
|
||||
}
|
||||
199
src/main.cc
Normal file
199
src/main.cc
Normal file
@@ -0,0 +1,199 @@
|
||||
#include "../include/editor.h"
|
||||
#include "../include/ts.h"
|
||||
#include "../include/ui.h"
|
||||
#include "../libs/tree-sitter/lib/include/tree_sitter/api.h"
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <sys/ioctl.h>
|
||||
#include <thread>
|
||||
|
||||
std::atomic<bool> running{true};
|
||||
Queue<KeyEvent> event_queue;
|
||||
|
||||
std::atomic<uint64_t> render_frames{0};
|
||||
std::atomic<uint64_t> worker_frames{0};
|
||||
|
||||
auto start_time = std::chrono::high_resolution_clock::now();
|
||||
|
||||
void background_worker(Editor *editor) {
|
||||
while (running) {
|
||||
worker_frames++;
|
||||
|
||||
ts_collect_spans(editor);
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(16));
|
||||
}
|
||||
}
|
||||
|
||||
void input_listener() {
|
||||
while (running) {
|
||||
KeyEvent event = read_key();
|
||||
if (event.key_type == KEY_CHAR && event.c == CTRL('q'))
|
||||
running = false;
|
||||
event_queue.push(event);
|
||||
}
|
||||
}
|
||||
|
||||
void handle_editor_event(Editor *editor, KeyEvent event) {
|
||||
if (event.key_type == KEY_SPECIAL && event.special_key == KEY_DOWN)
|
||||
cursor_down(editor, 1);
|
||||
if (event.key_type == KEY_SPECIAL && event.special_key == KEY_UP)
|
||||
cursor_up(editor, 1);
|
||||
if (event.key_type == KEY_SPECIAL && event.special_key == KEY_LEFT)
|
||||
cursor_left(editor, 1);
|
||||
if (event.key_type == KEY_SPECIAL && event.special_key == KEY_RIGHT)
|
||||
cursor_right(editor, 1);
|
||||
if (event.key_type == KEY_CHAR &&
|
||||
((event.c >= 'a' && event.c <= 'z') ||
|
||||
(event.c >= 'A' && event.c <= 'Z') ||
|
||||
(event.c >= '0' && event.c <= '9') || event.c == ' ' || event.c == '!' ||
|
||||
event.c == '@' || event.c == '#' || event.c == '$' || event.c == '%' ||
|
||||
event.c == '^' || event.c == '&' || event.c == '*' || event.c == '(' ||
|
||||
event.c == ')' || event.c == '-' || event.c == '_' || event.c == '=' ||
|
||||
event.c == '+' || event.c == '[' || event.c == ']' || event.c == '{' ||
|
||||
event.c == '}' || event.c == '\\' || event.c == '|' || event.c == ';' ||
|
||||
event.c == ':' || event.c == '\'' || event.c == '"' || event.c == ',' ||
|
||||
event.c == '.' || event.c == '<' || event.c == '>' || event.c == '/' ||
|
||||
event.c == '?' || event.c == '`' || event.c == '~')) {
|
||||
std::shared_lock lock_1(editor->knot_mtx);
|
||||
uint32_t pos = line_to_byte(editor->root, editor->cursor.row, nullptr) +
|
||||
editor->cursor.col;
|
||||
lock_1.unlock();
|
||||
std::unique_lock lock_2(editor->knot_mtx);
|
||||
editor->root = insert(editor->root, pos, &event.c, 1);
|
||||
lock_2.unlock();
|
||||
if (editor->tree) {
|
||||
TSInputEdit edit = {
|
||||
.start_byte = pos,
|
||||
.old_end_byte = pos,
|
||||
.new_end_byte = pos + 1,
|
||||
.start_point = {editor->cursor.row, editor->cursor.col},
|
||||
.old_end_point = {editor->cursor.row, editor->cursor.col},
|
||||
.new_end_point = {editor->cursor.row, editor->cursor.col + 1},
|
||||
};
|
||||
editor->edit_queue.push(edit);
|
||||
}
|
||||
cursor_right(editor, 1);
|
||||
}
|
||||
if (event.key_type == KEY_CHAR && event.c == '\t') {
|
||||
std::shared_lock lock_1(editor->knot_mtx);
|
||||
uint32_t pos = line_to_byte(editor->root, editor->cursor.row, nullptr) +
|
||||
editor->cursor.col;
|
||||
lock_1.unlock();
|
||||
std::unique_lock lock_2(editor->knot_mtx);
|
||||
editor->root = insert(editor->root, pos, (char *)" ", 2);
|
||||
lock_2.unlock();
|
||||
if (editor->tree) {
|
||||
TSInputEdit edit = {
|
||||
.start_byte = pos,
|
||||
.old_end_byte = pos,
|
||||
.new_end_byte = pos + 2,
|
||||
.start_point = {editor->cursor.row, editor->cursor.col},
|
||||
.old_end_point = {editor->cursor.row, editor->cursor.col},
|
||||
.new_end_point = {editor->cursor.row, editor->cursor.col + 2},
|
||||
};
|
||||
editor->edit_queue.push(edit);
|
||||
}
|
||||
cursor_right(editor, 2);
|
||||
}
|
||||
if (event.key_type == KEY_CHAR && (event.c == '\n' || event.c == '\r')) {
|
||||
std::shared_lock lock_1(editor->knot_mtx);
|
||||
uint32_t pos = line_to_byte(editor->root, editor->cursor.row, nullptr) +
|
||||
editor->cursor.col;
|
||||
lock_1.unlock();
|
||||
std::unique_lock lock_2(editor->knot_mtx);
|
||||
editor->root = insert(editor->root, pos, (char *)"\n", 1);
|
||||
lock_2.unlock();
|
||||
if (editor->tree) {
|
||||
TSInputEdit edit = {
|
||||
.start_byte = pos,
|
||||
.old_end_byte = pos,
|
||||
.new_end_byte = pos + 1,
|
||||
.start_point = {editor->cursor.row, editor->cursor.col},
|
||||
.old_end_point = {editor->cursor.row, editor->cursor.col},
|
||||
.new_end_point = {editor->cursor.row, editor->cursor.col + 1},
|
||||
};
|
||||
editor->edit_queue.push(edit);
|
||||
}
|
||||
cursor_right(editor, 1);
|
||||
}
|
||||
if (event.key_type == KEY_CHAR && event.c == 0x7F) {
|
||||
std::shared_lock lock_1(editor->knot_mtx);
|
||||
uint32_t pos = line_to_byte(editor->root, editor->cursor.row, nullptr) +
|
||||
editor->cursor.col;
|
||||
TSPoint old_point = {editor->cursor.row, editor->cursor.col};
|
||||
cursor_left(editor, 1);
|
||||
uint32_t start = line_to_byte(editor->root, editor->cursor.row, nullptr) +
|
||||
editor->cursor.col;
|
||||
lock_1.unlock();
|
||||
std::unique_lock lock_2(editor->knot_mtx);
|
||||
editor->root = erase(editor->root, start, pos - start);
|
||||
lock_2.unlock();
|
||||
if (editor->tree) {
|
||||
TSInputEdit edit = {
|
||||
.start_byte = start,
|
||||
.old_end_byte = pos,
|
||||
.new_end_byte = start,
|
||||
.start_point = {editor->cursor.row, editor->cursor.col},
|
||||
.old_end_point = old_point,
|
||||
.new_end_point = {editor->cursor.row, editor->cursor.col},
|
||||
};
|
||||
editor->edit_queue.push(edit);
|
||||
}
|
||||
}
|
||||
ensure_scroll(editor);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
Coord screen = start_screen();
|
||||
const char *filename = (argc > 1) ? argv[1] : "ts.cpp";
|
||||
|
||||
Editor *editor = new_editor(filename, {0, 0}, {screen.row, screen.col});
|
||||
if (!editor) {
|
||||
end_screen();
|
||||
fprintf(stderr, "Failed to load editor\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
start_time = std::chrono::high_resolution_clock::now();
|
||||
|
||||
std::thread input_thread(input_listener);
|
||||
std::thread work_thread(background_worker, editor);
|
||||
|
||||
while (running) {
|
||||
render_frames++;
|
||||
|
||||
KeyEvent event;
|
||||
while (event_queue.pop(event))
|
||||
handle_editor_event(editor, event);
|
||||
|
||||
render_editor(editor);
|
||||
render();
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(16));
|
||||
}
|
||||
|
||||
input_thread.detach();
|
||||
|
||||
if (work_thread.joinable())
|
||||
work_thread.join();
|
||||
|
||||
auto end_time = std::chrono::high_resolution_clock::now();
|
||||
double seconds = std::chrono::duration<double>(end_time - start_time).count();
|
||||
|
||||
double render_fps = render_frames / seconds;
|
||||
double worker_fps = worker_frames / seconds;
|
||||
|
||||
end_screen();
|
||||
|
||||
std::cout << "\n======= Performance Summary =======\n";
|
||||
std::cout << "Runtime: " << seconds << "s\n";
|
||||
std::cout << "Render loop FPS: " << render_fps << "Hz\n";
|
||||
std::cout << "Worker loop FPS: " << worker_fps << "Hz\n";
|
||||
std::cout << "===================================\n";
|
||||
|
||||
free_editor(editor);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,26 +1,29 @@
|
||||
// includes
|
||||
#include "../../libs/libgrapheme/grapheme.h"
|
||||
#include "../../libs/unicode_width/unicode_width.h"
|
||||
#include "../headers/header.hpp"
|
||||
extern "C" {
|
||||
#include "../libs/libgrapheme/grapheme.h"
|
||||
#include "../libs/unicode_width/unicode_width.h"
|
||||
}
|
||||
#include "../include/ui.h"
|
||||
|
||||
struct termios orig_termios;
|
||||
termios orig_termios;
|
||||
|
||||
int rows, cols;
|
||||
bool show_cursor = false;
|
||||
uint32_t rows, cols;
|
||||
int show_cursor = 0;
|
||||
std::vector<ScreenCell> screen;
|
||||
std::vector<ScreenCell> old_screen;
|
||||
std::mutex screen_mutex;
|
||||
|
||||
int real_width(std::string str) {
|
||||
if (!str.size())
|
||||
int display_width(const char *str) {
|
||||
if (!str)
|
||||
return 0;
|
||||
if (!strlen(str))
|
||||
return 0;
|
||||
const char *p = str.c_str();
|
||||
if (str[0] == '\t')
|
||||
return 4;
|
||||
unicode_width_state_t state;
|
||||
unicode_width_init(&state);
|
||||
int width = 0;
|
||||
for (size_t j = 0; j < str.size(); j++) {
|
||||
for (size_t j = 0; j < strlen(str); j++) {
|
||||
unsigned char c = str[j];
|
||||
if (c < 128) {
|
||||
int char_width = unicode_width_process(&state, c);
|
||||
@@ -28,7 +31,7 @@ int real_width(std::string str) {
|
||||
width += char_width;
|
||||
} else {
|
||||
uint_least32_t cp;
|
||||
size_t bytes = grapheme_decode_utf8(p + j, str.size() - j, &cp);
|
||||
size_t bytes = grapheme_decode_utf8(str + j, strlen(str) - j, &cp);
|
||||
if (bytes > 1) {
|
||||
int char_width = unicode_width_process(&state, cp);
|
||||
if (char_width > 0)
|
||||
@@ -37,6 +40,7 @@ int real_width(std::string str) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return width;
|
||||
}
|
||||
|
||||
@@ -63,6 +67,7 @@ void enable_raw_mode() {
|
||||
raw.c_oflag &= ~(OPOST);
|
||||
raw.c_cflag |= (CS8);
|
||||
raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
|
||||
raw.c_lflag |= ISIG;
|
||||
raw.c_cc[VMIN] = 0;
|
||||
raw.c_cc[VTIME] = 0;
|
||||
|
||||
@@ -74,7 +79,7 @@ void enable_raw_mode() {
|
||||
}
|
||||
|
||||
void disable_raw_mode() {
|
||||
std::string os = "\x1b[?1049l\x1b[?25h";
|
||||
std::string os = "\x1b[?1049l\x1b[2 q\x1b[?1002l\x1b[?25h";
|
||||
write(STDOUT_FILENO, os.c_str(), os.size());
|
||||
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios) == -1) {
|
||||
perror("tcsetattr");
|
||||
@@ -82,30 +87,40 @@ void disable_raw_mode() {
|
||||
}
|
||||
}
|
||||
|
||||
void start_screen() {
|
||||
Coord start_screen() {
|
||||
enable_raw_mode();
|
||||
get_terminal_size();
|
||||
screen.assign(rows * cols, {}); // allocate & zero-init
|
||||
old_screen.assign(rows * cols, {}); // allocate & zero-init
|
||||
return {rows, cols};
|
||||
}
|
||||
|
||||
void end_screen() { disable_raw_mode(); }
|
||||
|
||||
void update(int row, int col, const char *utf8, uint32_t fg, uint32_t bg,
|
||||
uint8_t flags) {
|
||||
if (row < 0 || row >= rows || col < 0 || col >= cols)
|
||||
Coord get_size() { return {rows, cols}; }
|
||||
|
||||
void update(uint32_t row, uint32_t col, const char *utf8, uint32_t fg,
|
||||
uint32_t bg, uint8_t flags) {
|
||||
if (row >= rows || col >= cols)
|
||||
return;
|
||||
|
||||
int idx = row * cols + col;
|
||||
uint32_t idx = row * cols + col;
|
||||
std::lock_guard<std::mutex> lock(screen_mutex);
|
||||
|
||||
screen[idx].utf8 = utf8 ? utf8 : ""; // nullptr => empty string
|
||||
screen[idx].utf8 = utf8 ? utf8 : "";
|
||||
screen[idx].fg = fg;
|
||||
screen[idx].bg = bg;
|
||||
screen[idx].flags = flags;
|
||||
}
|
||||
|
||||
coords get_size() { return {rows, cols}; }
|
||||
bool ends_with(const std::string &string_to_check) {
|
||||
size_t len = string_to_check.size();
|
||||
if (len < 3)
|
||||
return false;
|
||||
return (string_to_check[len - 3] == VS16_BYTE_A) &&
|
||||
(string_to_check[len - 2] == VS16_BYTE_B) &&
|
||||
(string_to_check[len - 1] == VS16_BYTE_C);
|
||||
}
|
||||
|
||||
void render() {
|
||||
static bool first_render = true;
|
||||
@@ -114,147 +129,135 @@ void render() {
|
||||
bool current_italic = false;
|
||||
bool current_bold = false;
|
||||
bool current_underline = false;
|
||||
|
||||
std::lock_guard<std::mutex> lock(screen_mutex);
|
||||
|
||||
std::string out;
|
||||
// reserve a conservative amount to avoid repeated reallocs
|
||||
out.reserve(static_cast<size_t>(rows) * static_cast<size_t>(cols) * 4 + 256);
|
||||
|
||||
// save cursor + hide
|
||||
out += "\x1b[s\x1b[?25l";
|
||||
|
||||
if (first_render) {
|
||||
out += "\x1b[2J\x1b[H";
|
||||
first_render = false;
|
||||
}
|
||||
|
||||
for (int row = 0; row < rows; ++row) {
|
||||
for (uint32_t row = 0; row < rows; ++row) {
|
||||
int first_change_col = -1;
|
||||
int last_change_col = -1;
|
||||
|
||||
// detect change span in this row
|
||||
for (int col = 0; col < cols; ++col) {
|
||||
int idx = row * cols + col;
|
||||
for (uint32_t col = 0; col < cols; ++col) {
|
||||
uint32_t idx = row * cols + col;
|
||||
ScreenCell &old_cell = old_screen[idx];
|
||||
ScreenCell &new_cell = screen[idx];
|
||||
|
||||
bool old_empty = old_cell.utf8.empty();
|
||||
bool new_empty = new_cell.utf8.empty();
|
||||
|
||||
bool content_changed =
|
||||
(old_empty && !new_empty) || (!old_empty && new_empty) ||
|
||||
(!old_empty && !new_empty && old_cell.utf8 != new_cell.utf8);
|
||||
|
||||
bool content_changed = old_cell.utf8 != new_cell.utf8;
|
||||
bool style_changed =
|
||||
(old_cell.fg != new_cell.fg) || (old_cell.bg != new_cell.bg) ||
|
||||
((old_cell.flags & CF_ITALIC) != (new_cell.flags & CF_ITALIC)) ||
|
||||
((old_cell.flags & CF_BOLD) != (new_cell.flags & CF_BOLD)) ||
|
||||
((old_cell.flags & CF_UNDERLINE) != (new_cell.flags & CF_UNDERLINE));
|
||||
|
||||
if (content_changed || style_changed) {
|
||||
if (first_change_col == -1)
|
||||
first_change_col = col;
|
||||
last_change_col = col;
|
||||
}
|
||||
}
|
||||
|
||||
if (first_change_col == -1)
|
||||
continue;
|
||||
|
||||
// move cursor once to the start of change region
|
||||
char buf[32];
|
||||
int n = snprintf(buf, sizeof(buf), "\x1b[%d;%dH", row + 1,
|
||||
first_change_col + 1);
|
||||
out.append(buf, n);
|
||||
|
||||
// render changed region
|
||||
char buf[64];
|
||||
snprintf(buf, sizeof(buf), "\x1b[%d;%dH", row + 1, first_change_col + 1);
|
||||
out.append(buf);
|
||||
for (int col = first_change_col; col <= last_change_col; ++col) {
|
||||
int idx = row * cols + col;
|
||||
ScreenCell &old_cell = old_screen[idx];
|
||||
ScreenCell &new_cell = screen[idx];
|
||||
|
||||
// foreground change
|
||||
if (current_fg != new_cell.fg) {
|
||||
if (new_cell.fg) {
|
||||
char fb[64];
|
||||
int m = snprintf(
|
||||
fb, sizeof(fb), "\x1b[38;2;%d;%d;%dm", (new_cell.fg >> 16) & 0xFF,
|
||||
(new_cell.fg >> 8) & 0xFF, (new_cell.fg >> 0) & 0xFF);
|
||||
out.append(fb, m);
|
||||
snprintf(fb, sizeof(fb), "\x1b[38;2;%d;%d;%dm",
|
||||
(new_cell.fg >> 16) & 0xFF, (new_cell.fg >> 8) & 0xFF,
|
||||
(new_cell.fg >> 0) & 0xFF);
|
||||
out.append(fb);
|
||||
} else {
|
||||
out += "\x1b[39m";
|
||||
}
|
||||
current_fg = new_cell.fg;
|
||||
}
|
||||
|
||||
// background change
|
||||
if (current_bg != new_cell.bg) {
|
||||
if (new_cell.bg) {
|
||||
char bb[64];
|
||||
int m = snprintf(
|
||||
bb, sizeof(bb), "\x1b[48;2;%d;%d;%dm", (new_cell.bg >> 16) & 0xFF,
|
||||
(new_cell.bg >> 8) & 0xFF, (new_cell.bg >> 0) & 0xFF);
|
||||
out.append(bb, m);
|
||||
snprintf(bb, sizeof(bb), "\x1b[48;2;%d;%d;%dm",
|
||||
(new_cell.bg >> 16) & 0xFF, (new_cell.bg >> 8) & 0xFF,
|
||||
(new_cell.bg >> 0) & 0xFF);
|
||||
out.append(bb);
|
||||
} else {
|
||||
out += "\x1b[49m";
|
||||
}
|
||||
current_bg = new_cell.bg;
|
||||
}
|
||||
|
||||
// italic
|
||||
bool italic = (new_cell.flags & CF_ITALIC) != 0;
|
||||
if (italic != current_italic) {
|
||||
out += italic ? "\x1b[3m" : "\x1b[23m";
|
||||
current_italic = italic;
|
||||
}
|
||||
|
||||
// bold
|
||||
bool bold = (new_cell.flags & CF_BOLD) != 0;
|
||||
if (bold != current_bold) {
|
||||
out += bold ? "\x1b[1m" : "\x1b[22m";
|
||||
current_bold = bold;
|
||||
}
|
||||
|
||||
// underline
|
||||
bool underline = (new_cell.flags & CF_UNDERLINE) != 0;
|
||||
if (underline != current_underline) {
|
||||
out += underline ? "\x1b[4m" : "\x1b[24m";
|
||||
current_underline = underline;
|
||||
}
|
||||
|
||||
// content
|
||||
if (!new_cell.utf8.empty()) {
|
||||
if (new_cell.utf8[0] == '\t')
|
||||
if (new_cell.utf8[0] == '\t') {
|
||||
out.append(" ");
|
||||
else
|
||||
out.append(new_cell.utf8);
|
||||
} else {
|
||||
// HACK: This is a hack to work around the fact that emojis should be
|
||||
// double width but handling them as so requires a lot of
|
||||
// calculations for word wrapping so eventually have to do that
|
||||
// and render them as the 2 wide they should be.
|
||||
if (new_cell.utf8.size() > 1) {
|
||||
if (new_cell.utf8.size() >= 3 && ends_with(new_cell.utf8)) {
|
||||
out.append(new_cell.utf8.substr(0, new_cell.utf8.size() - 3));
|
||||
out.append("\xEF\xB8\x8E");
|
||||
} else {
|
||||
out.append(new_cell.utf8);
|
||||
out.append("\xEF\xB8\x8E");
|
||||
}
|
||||
} else {
|
||||
out.append(new_cell.utf8);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
out.append(1, ' ');
|
||||
}
|
||||
|
||||
// copy new -> old (std::string assignment, no strdup/free)
|
||||
old_cell.utf8 = new_cell.utf8;
|
||||
old_cell.fg = new_cell.fg;
|
||||
old_cell.bg = new_cell.bg;
|
||||
old_cell.flags = new_cell.flags;
|
||||
}
|
||||
}
|
||||
|
||||
// final reset + restore cursor + show cursor
|
||||
out += "\x1b[0m";
|
||||
out += "\x1b[u";
|
||||
if (show_cursor)
|
||||
out += "\x1b[?25h";
|
||||
|
||||
// single syscall to write the whole frame
|
||||
ssize_t written = write(STDOUT_FILENO, out.data(), out.size());
|
||||
(void)written; // you may check for errors in debug builds
|
||||
const char *ptr = out.data();
|
||||
size_t remaining = out.size();
|
||||
while (remaining > 0) {
|
||||
ssize_t written = write(STDOUT_FILENO, ptr, remaining);
|
||||
if (written == 0) {
|
||||
break;
|
||||
} else if (written == -1) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
die("write failed");
|
||||
break;
|
||||
} else {
|
||||
ptr += written;
|
||||
remaining -= written;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void set_cursor(int row, int col, bool show_cursor_param) {
|
||||
void set_cursor(int row, int col, int show_cursor_param) {
|
||||
char buf[32];
|
||||
int n = snprintf(buf, sizeof(buf), "\x1b[%d;%dH", row + 1, col + 1);
|
||||
int n = snprintf(buf, sizeof(buf), "\x1b[%d;%dH\x1b[5 q", row + 1, col + 1);
|
||||
show_cursor = show_cursor_param;
|
||||
write(STDOUT_FILENO, buf, n);
|
||||
}
|
||||
858
src/rope.cc
Normal file
858
src/rope.cc
Normal file
@@ -0,0 +1,858 @@
|
||||
#include "../include/rope.h"
|
||||
#include <assert.h>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <pcre2.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
static void update(Knot *n) {
|
||||
if (!n)
|
||||
return;
|
||||
if (!n->depth || n->depth == 0)
|
||||
return;
|
||||
uint32_t left_chars = n->left ? n->left->char_count : 0;
|
||||
uint32_t right_chars = n->right ? n->right->char_count : 0;
|
||||
n->char_count = left_chars + right_chars;
|
||||
uint32_t left_lines = n->left ? n->left->line_count : 0;
|
||||
uint32_t right_lines = n->right ? n->right->line_count : 0;
|
||||
n->line_count = left_lines + right_lines;
|
||||
uint8_t left_depth = n->left ? n->left->depth : 0;
|
||||
uint8_t right_depth = n->right ? n->right->depth : 0;
|
||||
n->depth = MAX(left_depth, right_depth) + 1;
|
||||
n->chunk_size = n->left ? n->left->chunk_size : n->right->chunk_size;
|
||||
}
|
||||
|
||||
// str is not consumed and \0 is not handled
|
||||
// So if str is null terminated then len must be strlen(str)
|
||||
// and freed by caller
|
||||
Knot *load(char *str, uint32_t len, uint32_t chunk_size) {
|
||||
if (len > (uint32_t)(chunk_size - (chunk_size / 16))) {
|
||||
Knot *left = load(str, len / 2, chunk_size);
|
||||
Knot *right = load(str + len / 2, len - len / 2, chunk_size);
|
||||
Knot *node = (Knot *)malloc(sizeof(Knot));
|
||||
if (!node)
|
||||
return nullptr;
|
||||
node->left = left;
|
||||
node->right = right;
|
||||
node->chunk_size = chunk_size;
|
||||
node->depth = MAX(left->depth, right->depth) + 1;
|
||||
node->char_count = left->char_count + right->char_count;
|
||||
node->line_count = left->line_count + right->line_count;
|
||||
return node;
|
||||
} else {
|
||||
Knot *node = (Knot *)malloc(sizeof(Knot) + chunk_size);
|
||||
if (!node)
|
||||
return nullptr;
|
||||
node->left = nullptr;
|
||||
node->right = nullptr;
|
||||
node->chunk_size = chunk_size;
|
||||
node->depth = 0;
|
||||
node->char_count = len;
|
||||
uint32_t newline_count = 0;
|
||||
for (uint32_t i = 0; i < len; i++) {
|
||||
char c = str[i];
|
||||
node->data[i] = c;
|
||||
if (c == '\n')
|
||||
newline_count++;
|
||||
}
|
||||
node->line_count = newline_count;
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
// leaf if consumed and freed (so dont use or free it after)
|
||||
// left and right are the new nodes
|
||||
static void split_leaf(Knot *leaf, uint32_t k, Knot **left, Knot **right) {
|
||||
Knot *left_node = (Knot *)malloc(sizeof(Knot) + leaf->chunk_size);
|
||||
left_node->left = nullptr;
|
||||
left_node->right = nullptr;
|
||||
left_node->chunk_size = leaf->chunk_size;
|
||||
left_node->depth = 0;
|
||||
left_node->char_count = k;
|
||||
uint32_t newline_count = 0;
|
||||
for (uint32_t i = 0; i < k; i++) {
|
||||
char c = leaf->data[i];
|
||||
left_node->data[i] = c;
|
||||
if (c == '\n')
|
||||
newline_count++;
|
||||
}
|
||||
left_node->line_count = newline_count;
|
||||
uint16_t right_line_count = leaf->line_count - newline_count;
|
||||
*left = left_node;
|
||||
Knot *right_node = (Knot *)malloc(sizeof(Knot) + leaf->chunk_size);
|
||||
right_node->left = nullptr;
|
||||
right_node->right = nullptr;
|
||||
right_node->chunk_size = leaf->chunk_size;
|
||||
right_node->depth = 0;
|
||||
right_node->char_count = leaf->char_count - k;
|
||||
right_node->line_count = right_line_count;
|
||||
for (uint32_t i = k; i < leaf->char_count; i++) {
|
||||
char c = leaf->data[i];
|
||||
right_node->data[i - k] = c;
|
||||
}
|
||||
*right = right_node;
|
||||
free(leaf);
|
||||
}
|
||||
|
||||
// This makes node nonsensical, so dont use or free it after
|
||||
void split(Knot *node, uint32_t offset, Knot **left, Knot **right) {
|
||||
if (!node) {
|
||||
*left = nullptr;
|
||||
*right = nullptr;
|
||||
return;
|
||||
}
|
||||
if (node->depth == 0) {
|
||||
split_leaf(node, offset, left, right);
|
||||
return;
|
||||
}
|
||||
uint32_t left_size = node->left ? node->left->char_count : 0;
|
||||
if (offset < left_size) {
|
||||
Knot *L = nullptr, *R = nullptr;
|
||||
split(node->left, offset, &L, &R);
|
||||
node->left = R;
|
||||
update(node);
|
||||
*right = node;
|
||||
*left = L;
|
||||
} else {
|
||||
uint32_t new_offset = offset - left_size;
|
||||
Knot *L = nullptr, *R = nullptr;
|
||||
split(node->right, new_offset, &L, &R);
|
||||
node->right = L;
|
||||
update(node);
|
||||
*left = node;
|
||||
*right = R;
|
||||
}
|
||||
}
|
||||
|
||||
static inline int get_balance_factor(Knot *n) {
|
||||
if (!n)
|
||||
return 0;
|
||||
return (int)DEPTH(n->left) - (int)DEPTH(n->right);
|
||||
}
|
||||
|
||||
static inline Knot *rotate_right(Knot *y) {
|
||||
Knot *x = y->left;
|
||||
Knot *T2 = x->right;
|
||||
x->right = y;
|
||||
y->left = T2;
|
||||
update(y);
|
||||
update(x);
|
||||
return x;
|
||||
}
|
||||
|
||||
static inline Knot *rotate_left(Knot *x) {
|
||||
Knot *y = x->right;
|
||||
Knot *T2 = y->left;
|
||||
y->left = x;
|
||||
x->right = T2;
|
||||
update(x);
|
||||
update(y);
|
||||
return y;
|
||||
}
|
||||
|
||||
// Technically n can be used after calling
|
||||
// but use return value instead
|
||||
Knot *balance(Knot *n) {
|
||||
update(n);
|
||||
int bal = get_balance_factor(n);
|
||||
if (bal > 1) {
|
||||
if (get_balance_factor(n->left) < 0)
|
||||
n->left = rotate_left(n->left);
|
||||
return rotate_right(n);
|
||||
}
|
||||
if (bal < -1) {
|
||||
if (get_balance_factor(n->right) > 0)
|
||||
n->right = rotate_right(n->right);
|
||||
return rotate_left(n);
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
// Dont free left or right after calling (only free return value)
|
||||
// Assumes both ropes have equal chunk sizes
|
||||
Knot *concat(Knot *left, Knot *right) {
|
||||
if (!left)
|
||||
return right;
|
||||
if (!right)
|
||||
return left;
|
||||
if (!left || left->char_count == 0) {
|
||||
if (left)
|
||||
free_rope(left);
|
||||
return right;
|
||||
}
|
||||
if (!right || right->char_count == 0) {
|
||||
if (right)
|
||||
free_rope(right);
|
||||
return left;
|
||||
}
|
||||
if (left->depth == 0 && right->depth == 0) {
|
||||
if (left->char_count + right->char_count <= left->chunk_size) {
|
||||
Knot *node = (Knot *)malloc(sizeof(Knot) + left->chunk_size);
|
||||
node->left = nullptr;
|
||||
node->right = nullptr;
|
||||
node->chunk_size = left->chunk_size;
|
||||
node->depth = 0;
|
||||
node->char_count = left->char_count + right->char_count;
|
||||
node->line_count = left->line_count + right->line_count;
|
||||
memcpy(node->data, left->data, left->char_count);
|
||||
memcpy(node->data + left->char_count, right->data, right->char_count);
|
||||
free(left);
|
||||
free(right);
|
||||
return node;
|
||||
}
|
||||
}
|
||||
uint16_t d_left = left->depth;
|
||||
uint16_t d_right = right->depth;
|
||||
if (d_left > d_right + 1) {
|
||||
left->right = concat(left->right, right);
|
||||
return balance(left);
|
||||
}
|
||||
if (d_right > d_left + 1) {
|
||||
right->left = concat(left, right->left);
|
||||
return balance(right);
|
||||
}
|
||||
Knot *node = (Knot *)malloc(sizeof(Knot));
|
||||
if (!node)
|
||||
return nullptr;
|
||||
node->left = left;
|
||||
node->right = right;
|
||||
node->chunk_size = left->chunk_size;
|
||||
node->depth = MAX(d_left, d_right) + 1;
|
||||
update(node);
|
||||
return node;
|
||||
}
|
||||
|
||||
// This makes node nonsensical, so dont use or free it after
|
||||
// Instead, free the return value or use it in node's place
|
||||
Knot *insert(Knot *node, uint32_t offset, char *str, uint32_t len) {
|
||||
if (!node)
|
||||
return nullptr;
|
||||
if (len == 0)
|
||||
return node;
|
||||
if (node->depth == 0 && node->char_count + len <= node->chunk_size) {
|
||||
if (offset < node->char_count)
|
||||
memmove(node->data + offset + len, node->data + offset,
|
||||
node->char_count - offset);
|
||||
memcpy(node->data + offset, str, len);
|
||||
node->char_count += len;
|
||||
for (uint32_t i = 0; i < len; i++)
|
||||
if (str[i] == '\n')
|
||||
node->line_count++;
|
||||
return node;
|
||||
}
|
||||
if (node->depth > 0) {
|
||||
uint32_t left_count = node->left ? node->left->char_count : 0;
|
||||
if (offset < left_count) {
|
||||
Knot *new_left = insert(node->left, offset, str, len);
|
||||
node->left = new_left;
|
||||
update(node);
|
||||
return balance(node);
|
||||
} else {
|
||||
Knot *new_right = insert(node->right, offset - left_count, str, len);
|
||||
node->right = new_right;
|
||||
update(node);
|
||||
return balance(node);
|
||||
}
|
||||
}
|
||||
Knot *left_part = nullptr;
|
||||
Knot *right_part = nullptr;
|
||||
split(node, offset, &left_part, &right_part);
|
||||
Knot *middle_part = load(str, len, node->chunk_size);
|
||||
return concat(concat(left_part, middle_part), right_part);
|
||||
}
|
||||
|
||||
// This makes node nonsensical, so dont use or free it after
|
||||
// Instead, free the return value or use it in node's place
|
||||
Knot *erase(Knot *node, uint32_t offset, uint32_t len) {
|
||||
if (!node || len == 0 || offset >= node->char_count)
|
||||
return node;
|
||||
if (offset + len > node->char_count)
|
||||
len = node->char_count - offset;
|
||||
if (node->depth == 0) {
|
||||
uint32_t deleted_newlines = 0;
|
||||
for (uint32_t i = offset; i < offset + len; i++)
|
||||
if (node->data[i] == '\n')
|
||||
deleted_newlines++;
|
||||
node->line_count -= deleted_newlines;
|
||||
if (offset + len < node->char_count)
|
||||
memmove(node->data + offset, node->data + offset + len,
|
||||
node->char_count - (offset + len));
|
||||
node->char_count -= len;
|
||||
return node;
|
||||
}
|
||||
uint32_t left_count = node->left ? node->left->char_count : 0;
|
||||
if (offset + len <= left_count) {
|
||||
node->left = erase(node->left, offset, len);
|
||||
} else if (offset >= left_count) {
|
||||
node->right = erase(node->right, offset - left_count, len);
|
||||
} else {
|
||||
Knot *left = nullptr, *middle = nullptr, *right = nullptr;
|
||||
split(node, offset, &left, &right);
|
||||
split(right, len, &middle, &right);
|
||||
free_rope(middle);
|
||||
return concat(left, right);
|
||||
}
|
||||
update(node);
|
||||
return balance(node);
|
||||
}
|
||||
|
||||
static void _read_into(Knot *node, uint32_t offset, uint32_t len, char *dest) {
|
||||
if (!node || len == 0)
|
||||
return;
|
||||
if (node->depth == 0) {
|
||||
memcpy(dest, node->data + offset, len);
|
||||
return;
|
||||
}
|
||||
Knot *left = node->left;
|
||||
uint32_t left_count = left ? left->char_count : 0;
|
||||
if (offset < left_count) {
|
||||
uint32_t chunk_len = left_count - offset;
|
||||
if (chunk_len > len)
|
||||
chunk_len = len;
|
||||
_read_into(left, offset, chunk_len, dest);
|
||||
dest += chunk_len;
|
||||
len -= chunk_len;
|
||||
offset = 0;
|
||||
} else {
|
||||
offset -= left_count;
|
||||
}
|
||||
if (len > 0 && node->right)
|
||||
_read_into(node->right, offset, len, dest);
|
||||
}
|
||||
|
||||
char *read(Knot *root, uint32_t offset, uint32_t len) {
|
||||
if (!root)
|
||||
return nullptr;
|
||||
if (offset >= root->char_count) {
|
||||
char *empty = (char *)malloc(1);
|
||||
if (empty)
|
||||
empty[0] = '\0';
|
||||
return empty;
|
||||
}
|
||||
if (offset + len > root->char_count) {
|
||||
len = root->char_count - offset;
|
||||
}
|
||||
char *buffer = (char *)malloc((len + 1) * sizeof(char));
|
||||
if (!buffer)
|
||||
return nullptr;
|
||||
_read_into(root, offset, len, buffer);
|
||||
buffer[len] = '\0';
|
||||
return buffer;
|
||||
}
|
||||
|
||||
// Hopefully free the tree only once at the end of its use using the pointer
|
||||
// from the last insert or concat or erase call.
|
||||
// (or use twice if last call was split - for both left and right).
|
||||
void free_rope(Knot *root) {
|
||||
if (!root)
|
||||
return;
|
||||
free_rope(root->left);
|
||||
free_rope(root->right);
|
||||
free(root);
|
||||
}
|
||||
|
||||
static uint32_t find_nth_newline_offset(Knot *node, uint32_t n) {
|
||||
if (!node || n > node->line_count)
|
||||
return UINT32_MAX;
|
||||
if (node->depth == 0) {
|
||||
uint32_t count = 0;
|
||||
for (uint32_t i = 0; i < node->char_count; i++) {
|
||||
if (node->data[i] == '\n') {
|
||||
if (count == n)
|
||||
return i;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return UINT32_MAX;
|
||||
}
|
||||
uint32_t left_lines = node->left ? node->left->line_count : 0;
|
||||
if (n < left_lines) {
|
||||
return find_nth_newline_offset(node->left, n);
|
||||
} else {
|
||||
uint32_t right_offset =
|
||||
find_nth_newline_offset(node->right, n - left_lines);
|
||||
if (right_offset == UINT32_MAX)
|
||||
return UINT32_MAX;
|
||||
uint32_t left_chars = node->left ? node->left->char_count : 0;
|
||||
return left_chars + right_offset;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t byte_to_line(Knot *node, uint32_t offset) {
|
||||
if (!node)
|
||||
return 0;
|
||||
if (offset >= node->char_count)
|
||||
return node->line_count;
|
||||
if (node->depth == 0) {
|
||||
uint32_t lines_before = 0;
|
||||
uint32_t limit = (offset < node->char_count) ? offset : node->char_count;
|
||||
for (uint32_t i = 0; i < limit; i++)
|
||||
if (node->data[i] == '\n')
|
||||
lines_before++;
|
||||
return lines_before;
|
||||
}
|
||||
uint32_t left_chars = node->left ? node->left->char_count : 0;
|
||||
if (offset < left_chars) {
|
||||
return byte_to_line(node->left, offset);
|
||||
} else {
|
||||
uint32_t left_lines = node->left ? node->left->line_count : 0;
|
||||
return left_lines + byte_to_line(node->right, offset - left_chars);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t line_to_byte(Knot *node, uint32_t line, uint32_t *out_len) {
|
||||
if (!node) {
|
||||
if (out_len)
|
||||
*out_len = 0;
|
||||
return 0;
|
||||
}
|
||||
uint32_t start_offset = 0;
|
||||
uint32_t end_offset = 0;
|
||||
if (line == 0) {
|
||||
start_offset = 0;
|
||||
} else {
|
||||
uint32_t prev_newline = find_nth_newline_offset(node, line - 1);
|
||||
if (prev_newline == UINT32_MAX)
|
||||
start_offset = node->char_count;
|
||||
else
|
||||
start_offset = prev_newline + 1;
|
||||
}
|
||||
uint32_t current_newline = find_nth_newline_offset(node, line);
|
||||
if (current_newline == UINT32_MAX)
|
||||
end_offset = node->char_count;
|
||||
else
|
||||
end_offset = current_newline + 1;
|
||||
if (out_len) {
|
||||
if (end_offset > start_offset)
|
||||
*out_len = end_offset - start_offset;
|
||||
else
|
||||
*out_len = 0;
|
||||
}
|
||||
return start_offset;
|
||||
}
|
||||
|
||||
LineIterator *begin_l_iter(Knot *root, uint32_t start_line) {
|
||||
if (!root)
|
||||
return nullptr;
|
||||
if (start_line > root->line_count)
|
||||
return nullptr;
|
||||
LineIterator *it = (LineIterator *)malloc(sizeof(LineIterator));
|
||||
if (!it)
|
||||
return nullptr;
|
||||
it->top = 0;
|
||||
it->line = start_line;
|
||||
it->node = nullptr;
|
||||
if (start_line == 0) {
|
||||
it->offset = 0;
|
||||
while (root->left) {
|
||||
it->stack[it->top++] = root;
|
||||
root = root->left;
|
||||
}
|
||||
if (!root->left && !root->right)
|
||||
it->node = root;
|
||||
it->stack[it->top++] = root;
|
||||
return it;
|
||||
}
|
||||
Knot *curr = root;
|
||||
uint32_t relative_line = --start_line;
|
||||
it->line = start_line;
|
||||
while (curr) {
|
||||
it->stack[it->top++] = curr;
|
||||
if (!curr->left && !curr->right) {
|
||||
it->node = curr;
|
||||
break;
|
||||
}
|
||||
uint32_t left_lines = (curr->left) ? curr->left->line_count : 0;
|
||||
if (relative_line < left_lines) {
|
||||
curr = curr->left;
|
||||
} else {
|
||||
relative_line -= left_lines;
|
||||
curr = curr->right;
|
||||
}
|
||||
}
|
||||
if (!it->node) {
|
||||
free(it);
|
||||
return nullptr;
|
||||
}
|
||||
it->offset = 0;
|
||||
if (relative_line > 0) {
|
||||
uint32_t found_newlines = 0;
|
||||
uint32_t i = 0;
|
||||
for (i = 0; i < it->node->char_count; i++) {
|
||||
if (it->node->data[i] == '\n') {
|
||||
found_newlines++;
|
||||
if (found_newlines == relative_line) {
|
||||
it->offset = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
free(next_line(it));
|
||||
return it;
|
||||
}
|
||||
|
||||
static inline void iter_advance_leaf(LineIterator *it) {
|
||||
if (it->top == 0) {
|
||||
it->node = nullptr;
|
||||
return;
|
||||
}
|
||||
Knot *prev = it->stack[--it->top];
|
||||
while (it->top > 0) {
|
||||
Knot *parent = it->stack[it->top - 1];
|
||||
if (parent->left == prev && parent->right) {
|
||||
Knot *curr = parent->right;
|
||||
while (curr) {
|
||||
it->stack[it->top++] = curr;
|
||||
if (!curr->left && !curr->right) {
|
||||
it->node = curr;
|
||||
it->offset = 0;
|
||||
return;
|
||||
}
|
||||
curr = (curr->left) ? curr->left : curr->right;
|
||||
}
|
||||
}
|
||||
prev = it->stack[--it->top];
|
||||
}
|
||||
it->node = nullptr;
|
||||
}
|
||||
|
||||
char *next_line(LineIterator *it) {
|
||||
if (!it || !it->node)
|
||||
return nullptr;
|
||||
size_t capacity = 128;
|
||||
size_t len = 0;
|
||||
char *buffer = (char *)malloc(capacity);
|
||||
if (!buffer)
|
||||
return nullptr;
|
||||
while (it->node) {
|
||||
if (it->offset >= it->node->char_count) {
|
||||
iter_advance_leaf(it);
|
||||
if (!it->node)
|
||||
break;
|
||||
}
|
||||
char *start = it->node->data + it->offset;
|
||||
char *end = it->node->data + it->node->char_count;
|
||||
char *newline_ptr = (char *)memchr(start, '\n', end - start);
|
||||
size_t chunk_len;
|
||||
bool found_newline = false;
|
||||
if (newline_ptr) {
|
||||
chunk_len = (newline_ptr - start) + 1;
|
||||
found_newline = true;
|
||||
} else {
|
||||
chunk_len = end - start;
|
||||
}
|
||||
if (len + chunk_len + 1 > capacity) {
|
||||
capacity = (capacity * 2) + chunk_len;
|
||||
char *new_buf = (char *)realloc(buffer, capacity);
|
||||
if (!new_buf) {
|
||||
free(buffer);
|
||||
return nullptr;
|
||||
}
|
||||
buffer = new_buf;
|
||||
}
|
||||
memcpy(buffer + len, start, chunk_len);
|
||||
len += chunk_len;
|
||||
it->offset += chunk_len;
|
||||
if (found_newline) {
|
||||
buffer[len] = '\0';
|
||||
it->line++;
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
if (len > 0) {
|
||||
buffer[len] = '\0';
|
||||
it->line++;
|
||||
return buffer;
|
||||
}
|
||||
free(buffer);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
LeafIterator *begin_k_iter(Knot *root) {
|
||||
if (!root)
|
||||
return nullptr;
|
||||
LeafIterator *it = (LeafIterator *)malloc(sizeof(LeafIterator));
|
||||
if (!it)
|
||||
return nullptr;
|
||||
it->top = 0;
|
||||
Knot *curr = root;
|
||||
while (curr) {
|
||||
it->stack[it->top++] = curr;
|
||||
if (!curr->left && !curr->right) {
|
||||
it->node = curr;
|
||||
return it;
|
||||
}
|
||||
curr = curr->left;
|
||||
if (!curr) {
|
||||
curr = it->stack[--it->top]->right;
|
||||
Knot *temp = it->stack[it->top];
|
||||
it->stack[it->top++] = temp;
|
||||
curr = temp->left ? temp->left : temp->right;
|
||||
Knot *parent = it->stack[it->top - 1];
|
||||
curr = parent->left;
|
||||
if (!curr) {
|
||||
curr = parent->right;
|
||||
}
|
||||
}
|
||||
}
|
||||
free(it);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Caller must never free the returned string
|
||||
char *next_leaf(LeafIterator *it) {
|
||||
if (!it || !it->node)
|
||||
return nullptr;
|
||||
char *data_to_return = it->node->data;
|
||||
data_to_return[it->node->char_count] = '\0';
|
||||
Knot *prev_leaf = it->node;
|
||||
Knot *parent = nullptr;
|
||||
while (it->top > 0) {
|
||||
parent = it->stack[--it->top];
|
||||
if (parent->right && parent->right != prev_leaf) {
|
||||
Knot *curr = parent->right;
|
||||
while (curr) {
|
||||
it->stack[it->top++] = curr;
|
||||
if (!curr->left && !curr->right) {
|
||||
it->node = curr;
|
||||
return data_to_return;
|
||||
}
|
||||
curr = curr->left;
|
||||
if (!curr)
|
||||
curr = it->stack[it->top - 1]->right;
|
||||
}
|
||||
}
|
||||
prev_leaf = parent;
|
||||
}
|
||||
it->node = nullptr;
|
||||
return data_to_return;
|
||||
}
|
||||
|
||||
ByteIterator *begin_b_iter(Knot *root) {
|
||||
ByteIterator *b_it = (ByteIterator *)malloc(sizeof(ByteIterator));
|
||||
LeafIterator *l_it = begin_k_iter(root);
|
||||
b_it->it = l_it;
|
||||
b_it->offset_g = 0;
|
||||
b_it->offset_l = 0;
|
||||
b_it->char_count = 0;
|
||||
b_it->data = nullptr;
|
||||
return b_it;
|
||||
}
|
||||
|
||||
char next_byte(ByteIterator *it) {
|
||||
if (it->data && it->offset_l < it->char_count) {
|
||||
return it->data[it->offset_l++];
|
||||
} else {
|
||||
it->offset_g += it->offset_l;
|
||||
it->offset_l = 1;
|
||||
char *data = next_leaf(it->it);
|
||||
it->char_count = strlen(data);
|
||||
it->data = data;
|
||||
if (it->data)
|
||||
return *it->data;
|
||||
else
|
||||
return '\0';
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::pair<size_t, size_t>> search_rope(Knot *root,
|
||||
const char *pattern) {
|
||||
std::vector<std::pair<size_t, size_t>> results;
|
||||
int errorcode;
|
||||
PCRE2_SIZE erroffset;
|
||||
pcre2_code *re = pcre2_compile((PCRE2_SPTR)pattern, PCRE2_ZERO_TERMINATED, 0,
|
||||
&errorcode, &erroffset, nullptr);
|
||||
if (!re) {
|
||||
fprintf(stderr, "PCRE2 compile error: %d\n", errorcode);
|
||||
return results;
|
||||
}
|
||||
pcre2_match_data *mdata = pcre2_match_data_create(128, nullptr);
|
||||
int workspace[PCRE_WORKSPACE_SIZE];
|
||||
LeafIterator *it = begin_k_iter(root);
|
||||
if (!it) {
|
||||
pcre2_code_free(re);
|
||||
pcre2_match_data_free(mdata);
|
||||
return results;
|
||||
}
|
||||
size_t chunk_abs_offset = 0;
|
||||
size_t saved_match_start = 0;
|
||||
bool match_in_progress = false;
|
||||
int flags = PCRE2_PARTIAL_SOFT;
|
||||
while (1) {
|
||||
const char *chunk_start = next_leaf(it);
|
||||
if (!chunk_start)
|
||||
break;
|
||||
size_t chunk_len = strlen(chunk_start);
|
||||
const char *current_ptr = chunk_start;
|
||||
size_t remaining_len = chunk_len;
|
||||
while (remaining_len > 0) {
|
||||
int rc =
|
||||
pcre2_dfa_match(re, (PCRE2_SPTR)current_ptr, remaining_len, 0, flags,
|
||||
mdata, nullptr, workspace, PCRE_WORKSPACE_SIZE);
|
||||
if (rc >= 0) {
|
||||
PCRE2_SIZE *ov = pcre2_get_ovector_pointer(mdata);
|
||||
size_t match_start_abs;
|
||||
size_t match_end_abs;
|
||||
if (match_in_progress) {
|
||||
match_start_abs = saved_match_start;
|
||||
match_end_abs =
|
||||
chunk_abs_offset + (current_ptr - chunk_start) + ov[1];
|
||||
} else {
|
||||
match_start_abs =
|
||||
chunk_abs_offset + (current_ptr - chunk_start) + ov[0];
|
||||
match_end_abs =
|
||||
chunk_abs_offset + (current_ptr - chunk_start) + ov[1];
|
||||
}
|
||||
size_t total_len = match_end_abs - match_start_abs;
|
||||
results.push_back(std::make_pair(match_start_abs, total_len));
|
||||
size_t consumed = ov[1];
|
||||
if (consumed == 0)
|
||||
consumed = 1;
|
||||
current_ptr += consumed;
|
||||
if (consumed > remaining_len)
|
||||
remaining_len = 0;
|
||||
else
|
||||
remaining_len -= consumed;
|
||||
match_in_progress = false;
|
||||
flags = PCRE2_PARTIAL_SOFT | PCRE2_NOTBOL;
|
||||
continue;
|
||||
} else if (rc == PCRE2_ERROR_PARTIAL) {
|
||||
PCRE2_SIZE *ov = pcre2_get_ovector_pointer(mdata);
|
||||
if (!match_in_progress) {
|
||||
saved_match_start =
|
||||
chunk_abs_offset + (current_ptr - chunk_start) + ov[0];
|
||||
match_in_progress = true;
|
||||
}
|
||||
flags |= PCRE2_DFA_RESTART;
|
||||
flags |= PCRE2_NOTBOL;
|
||||
break;
|
||||
} else {
|
||||
if (match_in_progress) {
|
||||
match_in_progress = false;
|
||||
flags = PCRE2_PARTIAL_SOFT | PCRE2_NOTBOL;
|
||||
current_ptr++;
|
||||
remaining_len--;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
// if (rc != PCRE2_ERROR_NOMATCH) {} // handle error
|
||||
}
|
||||
}
|
||||
chunk_abs_offset += chunk_len;
|
||||
if (!match_in_progress)
|
||||
flags = PCRE2_PARTIAL_SOFT | PCRE2_NOTBOL;
|
||||
}
|
||||
pcre2_match_data_free(mdata);
|
||||
pcre2_code_free(re);
|
||||
free(it);
|
||||
return results;
|
||||
}
|
||||
|
||||
uint32_t optimal_chunk_size(uint64_t length) {
|
||||
if (length <= MIN_CHUNK_SIZE)
|
||||
return MIN_CHUNK_SIZE;
|
||||
double target_exponent = MIN(std::log2((double)MAX_CHUNK_SIZE),
|
||||
7.0 + (std::log2((double)length) - 10.0) * 0.25);
|
||||
uint32_t final_chunk_size =
|
||||
MAX((uint32_t)MIN_CHUNK_SIZE, (uint32_t)std::pow(2.0, target_exponent));
|
||||
final_chunk_size = MIN(final_chunk_size, (uint32_t)MAX_CHUNK_SIZE);
|
||||
final_chunk_size = 1U << (32 - __builtin_clz(final_chunk_size - 1));
|
||||
return final_chunk_size;
|
||||
}
|
||||
|
||||
// Basic correctness test & usage example
|
||||
/*
|
||||
int _main() {
|
||||
char *buffer = (char *)malloc(44 * 4 + 5);
|
||||
strcpy(buffer, "The quick brown fox jumps over the lazy dog.\n\
|
||||
The quick brown fox jumps over the lazy dog.\n\
|
||||
The quick brown fox jumps over the lazy dog.\n\
|
||||
The quick brown fox jumps over the lazy dog.");
|
||||
// This loads all (excluding \0 put in by strcpy)
|
||||
Knot *root = load(buffer, 44 * 4 + 3, optimal_chunk_size(44 * 4 + 3));
|
||||
Knot *left = nullptr, *right = nullptr;
|
||||
// Splits root into left and right (root is no longer valid)
|
||||
split(root, 5, &left, &right);
|
||||
// simple read based on byte offset and length
|
||||
char *s1 = read(left, 0, 100);
|
||||
printf("%s\n:\n", s1);
|
||||
char *s2 = read(right, 0, 100);
|
||||
printf("%s\n;\n", s2);
|
||||
free(s1);
|
||||
free(s2);
|
||||
// Recombines left and right into root (both can
|
||||
// be valid or invalid in optimized cases)
|
||||
// they are to not be used after concat
|
||||
root = concat(left, right);
|
||||
// root should be set to return value from insert always
|
||||
root = insert(root, 5, buffer, 5);
|
||||
free(buffer);
|
||||
char *s3 = read(root, 0, 100);
|
||||
printf("%s\n,\n", s3);
|
||||
// Similar to insert but for erase
|
||||
root = erase(root, 5, 5);
|
||||
char *s4 = read(root, 0, 100);
|
||||
printf("%s\n.\n", s4);
|
||||
free(s3);
|
||||
free(s4);
|
||||
uint32_t byte_offset;
|
||||
uint32_t len;
|
||||
// Byte offset given reltive to how it would
|
||||
// be in a file offset + len includes the \n
|
||||
// at the end of the line (or nothing is EOF)
|
||||
byte_offset = line_to_byte(root, 2, &len);
|
||||
char *s5 = read(root, byte_offset, len);
|
||||
printf("%s\n'\n", s5);
|
||||
free(s5);
|
||||
// returns line number of which line that
|
||||
// byte position would be in.
|
||||
// the ending \n position is included in this
|
||||
uint32_t line = byte_to_line(root, byte_offset + len - 1);
|
||||
printf("%u\n:\n", line);
|
||||
// From second line onwards (0 indexed)
|
||||
LineIterator *it = begin_l_iter(root, 0);
|
||||
char *c = nullptr;
|
||||
while ((c = next_line(it)) != nullptr) {
|
||||
printf("%s :wow:\n", c);
|
||||
free(c);
|
||||
}
|
||||
free(it);
|
||||
printf("\n/\n");
|
||||
// Starts at first byte (to be used for regex search)
|
||||
ByteIterator *it2 = begin_b_iter(root);
|
||||
|
||||
uint32_t saved[40];
|
||||
|
||||
for (int i = 0; i < 40; i++)
|
||||
saved[i] = 0;
|
||||
|
||||
// std::string pattern = "f.x";
|
||||
|
||||
// Inst *program = compile_regex(pattern);
|
||||
//
|
||||
// bool result;
|
||||
// while ((result = next_match(program, it2, saved))) {
|
||||
// printf("\nRES: %d\n", result);
|
||||
// for (int i = 0; i < 40; i++)
|
||||
// printf("%d, ", saved[i]);
|
||||
// }
|
||||
|
||||
// char c2 = ' ';
|
||||
// while ((c2 = next_byte(it2)) != '\0')
|
||||
// printf("%c :wow!:\n", c2);
|
||||
// free(it2);
|
||||
// search // uses leaf iterator internally // PCRE2 based
|
||||
std::vector<std::pair<size_t, size_t>> matches = search_rope(root, "f.x");
|
||||
for (size_t i = 0; i < matches.size(); i++)
|
||||
printf("\n%lu %lu", matches[i].first, matches[i].second);
|
||||
// A rope needs to be freed only once if last action on the rope is
|
||||
// insert or concat or erase.
|
||||
// for splits we need to free both left and right separately
|
||||
free_rope(root);
|
||||
return 0;
|
||||
}
|
||||
*/
|
||||
@@ -1,70 +0,0 @@
|
||||
class Buffer
|
||||
# Simple structs for clarity
|
||||
Diagnostic = Struct.new(:x0, :y0, :x1, :y1, :message)
|
||||
Highlight = Struct.new(:x0, :y0, :x1, :y1, :fg, :bg)
|
||||
VirtualText = Struct.new(:x, :y, :lines)
|
||||
Cursor = Struct.new(:x, :y)
|
||||
|
||||
attr_accessor :text, :cursor, :selection_start,
|
||||
:diagnostics, :highlights, :virtual_texts
|
||||
|
||||
def initialize(initial_text = "")
|
||||
@text = initial_text
|
||||
@cursor = Cursor.new(0, 0)
|
||||
@selection_start = Cursor.new(0, 0)
|
||||
@diagnostics = []
|
||||
@highlights = []
|
||||
@virtual_texts = []
|
||||
end
|
||||
|
||||
# Utility methods
|
||||
def lines
|
||||
@text.split("\n")
|
||||
end
|
||||
|
||||
def line_count
|
||||
lines.size
|
||||
end
|
||||
|
||||
def line(y)
|
||||
lines[y] || ""
|
||||
end
|
||||
|
||||
def insert(x, y, str)
|
||||
current_line = lines[y] || ""
|
||||
before = current_line[0...x] || ""
|
||||
after = current_line[x..-1] || ""
|
||||
lines_arr = lines
|
||||
lines_arr[y] = before + str + after
|
||||
@text = lines_arr.join("\n")
|
||||
end
|
||||
|
||||
def erase(x0, y0, x1, y1)
|
||||
lines_arr = lines
|
||||
if y0 == y1
|
||||
line = lines_arr[y0] || ""
|
||||
lines_arr[y0] = line[0...x0] + line[x1..-1].to_s
|
||||
else
|
||||
first = lines_arr[y0][0...x0]
|
||||
last = lines_arr[y1][x1..-1].to_s
|
||||
lines_arr[y0..y1] = [first + last]
|
||||
end
|
||||
@text = lines_arr.join("\n")
|
||||
end
|
||||
|
||||
# Add overlays
|
||||
def add_diagnostic(x0, y0, x1, y1, message)
|
||||
@diagnostics << Diagnostic.new(x0, y0, x1, y1, message)
|
||||
end
|
||||
|
||||
def add_highlight(x0, y0, x1, y1, fg: 0xFFFFFF, bg: 0x000000)
|
||||
@highlights << Highlight.new(x0, y0, x1, y1, fg, bg)
|
||||
end
|
||||
|
||||
def add_virtual_text(x, y, lines)
|
||||
@virtual_texts << VirtualText.new(x, y, lines)
|
||||
end
|
||||
|
||||
def render()
|
||||
end
|
||||
end
|
||||
@@ -1,57 +0,0 @@
|
||||
require "ffi"
|
||||
require_relative "utils"
|
||||
|
||||
module C
|
||||
extend FFI::Library
|
||||
ffi_lib File.join(__dir__, "../../builds/C-crib.so")
|
||||
|
||||
class KeyEvent < FFI::Struct
|
||||
layout :key_type, :uint8,
|
||||
:c, :char,
|
||||
:special_key, :uint8,
|
||||
:special_modifier, :uint8,
|
||||
:mouse_x, :uint8,
|
||||
:mouse_y, :uint8,
|
||||
:mouse_button, :uint8,
|
||||
:mouse_state, :uint8,
|
||||
:mouse_direction, :uint8,
|
||||
:mouse_modifier, :uint8
|
||||
|
||||
def to_s
|
||||
case KEY_TYPE[self[:key_type]]
|
||||
when :char
|
||||
"#<KeyEvent char=#{self[:c].inspect}>"
|
||||
when :special
|
||||
"#<KeyEvent special key=#{SPECIAL_KEY[self[:special_key]]} mod=#{MODIFIER[self[:special_modifier]]}>"
|
||||
when :mouse
|
||||
"#<KeyEvent mouse x=#{self[:mouse_x]} y=#{self[:mouse_y]} " \
|
||||
"btn=#{MOUSE_BUTTON[self[:mouse_button]]} state=#{MOUSE_STATE[self[:mouse_state]]} " \
|
||||
"scroll_dir=#{SCROLL_DIR[self[:mouse_direction]]} mod=#{MODIFIER[self[:mouse_modifier]]}>"
|
||||
else
|
||||
"#<KeyEvent type=#{self[:key_type]} unknown=true>"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Coords < FFI::Struct
|
||||
layout :x, :int,
|
||||
:y, :int
|
||||
|
||||
def to_ary
|
||||
[self[:x], self[:y]]
|
||||
end
|
||||
|
||||
def to_s
|
||||
"#<Coords x=#{self[:x]} y=#{self[:y]}>"
|
||||
end
|
||||
end
|
||||
|
||||
attach_function :start_screen, [], :void
|
||||
attach_function :end_screen, [], :void
|
||||
attach_function :update, [:int, :int, :string, :uint32, :uint32, :uint8], :void
|
||||
attach_function :render, [], :void
|
||||
attach_function :set_cursor, [:int, :int, :int], :void
|
||||
attach_function :read_key, [], KeyEvent.by_value
|
||||
attach_function :get_size, [], Coords.by_value
|
||||
attach_function :real_width, [:string], :int
|
||||
end
|
||||
@@ -1,56 +0,0 @@
|
||||
def ctrl_key(k)
|
||||
k.ord & 0x1F
|
||||
end
|
||||
|
||||
# Key types
|
||||
KEY_TYPE = {
|
||||
0 => :char,
|
||||
1 => :special,
|
||||
2 => :mouse
|
||||
}
|
||||
|
||||
# Special keys
|
||||
SPECIAL_KEY = {
|
||||
0 => :up,
|
||||
1 => :down,
|
||||
2 => :left,
|
||||
3 => :right,
|
||||
4 => :delete
|
||||
}
|
||||
|
||||
# Control key
|
||||
KEY_ESC = "\x1b"
|
||||
|
||||
# Mouse states
|
||||
MOUSE_STATE = {
|
||||
0 => :press,
|
||||
1 => :release,
|
||||
2 => :drag,
|
||||
3 => :scroll
|
||||
}
|
||||
|
||||
# Mouse buttons
|
||||
MOUSE_BUTTON = {
|
||||
0 => :left,
|
||||
1 => :middle,
|
||||
2 => :right,
|
||||
3 => :scroll,
|
||||
4 => :none
|
||||
}
|
||||
|
||||
# Scroll directions
|
||||
SCROLL_DIR = {
|
||||
0 => :up,
|
||||
1 => :down,
|
||||
2 => :left,
|
||||
3 => :right,
|
||||
4 => :none
|
||||
}
|
||||
|
||||
# Modifiers
|
||||
MODIFIER = {
|
||||
1 => :alt,
|
||||
2 => :cntrl,
|
||||
3 => :cntrl_alt,
|
||||
4 => :shift
|
||||
}
|
||||
243
src/ts.cc
Normal file
243
src/ts.cc
Normal file
@@ -0,0 +1,243 @@
|
||||
#include "../include/ts.h"
|
||||
#include "../include/editor.h"
|
||||
#include "../include/rope.h"
|
||||
#include <cstdint>
|
||||
#include <fstream>
|
||||
#include <regex>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
static std::unordered_map<std::string, std::regex> regex_cache;
|
||||
|
||||
static const std::regex scm_regex(
|
||||
R"((@[A-Za-z0-9_.]+)|(;; \#[0-9a-fA-F]{6} \#[0-9a-fA-F]{6} [01] [01] [01] \d+))");
|
||||
|
||||
TSQuery *load_query(const char *query_path, Editor *editor) {
|
||||
const TSLanguage *lang = editor->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>());
|
||||
std::smatch match;
|
||||
std::map<std::string, int> capture_name_cache;
|
||||
Highlight *c_hl = nullptr;
|
||||
int i = 0;
|
||||
int limit = 20;
|
||||
editor->query_map.resize(limit);
|
||||
std::string::const_iterator searchStart(highlight_query.cbegin());
|
||||
while (std::regex_search(searchStart, highlight_query.cend(), match,
|
||||
scm_regex)) {
|
||||
std::string mct = match.str();
|
||||
if (mct.substr(0, 1) == "@") {
|
||||
std::string capture_name = mct;
|
||||
if (!capture_name_cache.count(capture_name)) {
|
||||
if (c_hl) {
|
||||
if (i >= limit) {
|
||||
limit += 20;
|
||||
editor->query_map.resize(limit);
|
||||
}
|
||||
editor->query_map[i] = *c_hl;
|
||||
delete c_hl;
|
||||
c_hl = nullptr;
|
||||
}
|
||||
capture_name_cache[capture_name] = i;
|
||||
i++;
|
||||
}
|
||||
} else if (mct.substr(0, 2) == ";;") {
|
||||
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));
|
||||
c_hl->priority = std::stoi(mct.substr(25));
|
||||
c_hl->flags = (bold ? CF_BOLD : 0) | (italic ? CF_ITALIC : 0) |
|
||||
(underline ? CF_UNDERLINE : 0);
|
||||
}
|
||||
searchStart = match.suffix().first;
|
||||
}
|
||||
if (c_hl)
|
||||
delete c_hl;
|
||||
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);
|
||||
return q;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
static inline std::string node_text(const TSNode &node, Knot *source) {
|
||||
uint32_t start = ts_node_start_byte(node);
|
||||
uint32_t end = ts_node_end_byte(node);
|
||||
char *text = read(source, start, end - start);
|
||||
std::string final = std::string(text, end - start);
|
||||
free(text);
|
||||
return final;
|
||||
}
|
||||
|
||||
static inline bool ts_predicate(TSQuery *query, const TSQueryMatch &match,
|
||||
Knot *source) {
|
||||
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;
|
||||
if (source->char_count >= (64 * 1024))
|
||||
return false;
|
||||
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);
|
||||
std::string subject = node_text(*node, source);
|
||||
auto it = regex_cache.find(regex_txt);
|
||||
if (it == regex_cache.end())
|
||||
it = regex_cache.emplace(regex_txt, std::regex(regex_txt)).first;
|
||||
const std::regex &re = it->second;
|
||||
if (command == "match?")
|
||||
return std::regex_match(subject, re);
|
||||
else if (command == "not-match?")
|
||||
return !std::regex_match(subject, re);
|
||||
return false;
|
||||
}
|
||||
|
||||
const char *read_ts(void *payload, uint32_t byte_index, TSPoint,
|
||||
uint32_t *bytes_read) {
|
||||
if (!running) {
|
||||
*bytes_read = 0;
|
||||
return "";
|
||||
}
|
||||
TSLoad *load = (TSLoad *)payload;
|
||||
Knot *root = load->editor->root;
|
||||
if (load->prev)
|
||||
free(load->prev);
|
||||
if (byte_index >= root->char_count) {
|
||||
*bytes_read = 0;
|
||||
load->prev = nullptr;
|
||||
return "";
|
||||
}
|
||||
uint32_t chunk_size = 4096;
|
||||
uint32_t remaining = root->char_count - byte_index;
|
||||
uint32_t len_to_read = remaining > chunk_size ? chunk_size : remaining;
|
||||
std::shared_lock lock(load->editor->knot_mtx);
|
||||
char *buffer = read(root, byte_index, len_to_read);
|
||||
lock.unlock();
|
||||
load->prev = buffer;
|
||||
*bytes_read = len_to_read;
|
||||
return buffer;
|
||||
}
|
||||
|
||||
static inline Highlight *safe_get(std::vector<Highlight> &vec, size_t index) {
|
||||
if (index >= vec.size())
|
||||
return nullptr;
|
||||
return &vec[index];
|
||||
}
|
||||
|
||||
void ts_collect_spans(Editor *editor) {
|
||||
if (!editor->parser || !editor->root || !editor->query)
|
||||
return;
|
||||
TSLoad load = {editor, nullptr};
|
||||
TSInput tsinput = {
|
||||
.payload = &load,
|
||||
.read = read_ts,
|
||||
.encoding = TSInputEncodingUTF8,
|
||||
.decode = nullptr,
|
||||
};
|
||||
TSTree *tree, *copy = nullptr;
|
||||
std::unique_lock lock_0(editor->knot_mtx);
|
||||
if (editor->tree)
|
||||
copy = ts_tree_copy(editor->tree);
|
||||
lock_0.unlock();
|
||||
if (!running)
|
||||
return;
|
||||
std::vector<TSInputEdit> edits;
|
||||
TSInputEdit edit;
|
||||
if (copy) {
|
||||
while (editor->edit_queue.pop(edit)) {
|
||||
edits.push_back(edit);
|
||||
ts_tree_edit(copy, &edits.back());
|
||||
}
|
||||
}
|
||||
tree = ts_parser_parse(editor->parser, copy, tsinput);
|
||||
ts_tree_delete(copy);
|
||||
while (editor->edit_queue.pop(edit)) {
|
||||
edits.push_back(edit);
|
||||
ts_tree_edit(tree, &edits.back());
|
||||
}
|
||||
lock_0.lock();
|
||||
if (editor->tree)
|
||||
ts_tree_delete(editor->tree);
|
||||
editor->tree = tree;
|
||||
copy = ts_tree_copy(tree);
|
||||
lock_0.unlock();
|
||||
std::shared_lock lock_1(editor->span_mtx);
|
||||
TSQueryCursor *cursor = ts_query_cursor_new();
|
||||
ts_query_cursor_exec(cursor, editor->query, ts_tree_root_node(copy));
|
||||
std::vector<Span> new_spans;
|
||||
new_spans.reserve(4096);
|
||||
TSQueryMatch match;
|
||||
while (ts_query_cursor_next_match(cursor, &match)) {
|
||||
if (!running)
|
||||
break;
|
||||
if (!ts_predicate(editor->query, match, editor->root))
|
||||
continue;
|
||||
for (uint32_t i = 0; i < match.capture_count; i++) {
|
||||
if (!running)
|
||||
break;
|
||||
TSQueryCapture cap = match.captures[i];
|
||||
uint32_t start = ts_node_start_byte(cap.node);
|
||||
uint32_t end = ts_node_end_byte(cap.node);
|
||||
Highlight *hl = safe_get(editor->query_map, cap.index);
|
||||
if (hl)
|
||||
new_spans.push_back({start, end, hl});
|
||||
}
|
||||
}
|
||||
if (!running) {
|
||||
ts_tree_delete(copy);
|
||||
ts_query_cursor_delete(cursor);
|
||||
if (load.prev)
|
||||
free(load.prev);
|
||||
return;
|
||||
}
|
||||
lock_1.unlock();
|
||||
std::sort(new_spans.begin(), new_spans.end());
|
||||
std::unique_lock lock_2(editor->span_mtx);
|
||||
editor->spans.swap(new_spans);
|
||||
lock_2.unlock();
|
||||
ts_query_cursor_delete(cursor);
|
||||
if (load.prev)
|
||||
free(load.prev);
|
||||
}
|
||||
87
src/utils.cc
Normal file
87
src/utils.cc
Normal file
@@ -0,0 +1,87 @@
|
||||
extern "C" {
|
||||
#include "../libs/libgrapheme/grapheme.h"
|
||||
}
|
||||
#include "../include/utils.h"
|
||||
#include <cstdarg>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <limits.h>
|
||||
#include <string.h>
|
||||
#include <string>
|
||||
#include <unistd.h>
|
||||
|
||||
std::string get_exe_dir() {
|
||||
char exe_path[PATH_MAX];
|
||||
ssize_t count = readlink("/proc/self/exe", exe_path, PATH_MAX);
|
||||
if (count == -1)
|
||||
return "";
|
||||
exe_path[count] = '\0';
|
||||
std::string path(exe_path);
|
||||
return path.substr(0, path.find_last_of('/'));
|
||||
}
|
||||
|
||||
uint32_t grapheme_strlen(const char *s) {
|
||||
if (!s)
|
||||
return 0;
|
||||
uint32_t count = 0;
|
||||
const char *p = s;
|
||||
while (*p) {
|
||||
uint32_t next = grapheme_next_character_break_utf8(p, UINT32_MAX);
|
||||
if (!next)
|
||||
next = 1;
|
||||
p += next;
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
uint32_t get_visual_col_from_bytes(const char *line, uint32_t byte_limit) {
|
||||
if (!line)
|
||||
return 0;
|
||||
uint32_t visual_col = 0;
|
||||
uint32_t current_byte = 0;
|
||||
uint32_t len = strlen(line);
|
||||
if (len > 0 && line[len - 1] == '\n')
|
||||
len--;
|
||||
while (current_byte < byte_limit && current_byte < len) {
|
||||
uint32_t inc = grapheme_next_character_break_utf8(line + current_byte,
|
||||
len - current_byte);
|
||||
if (current_byte + inc > byte_limit)
|
||||
break;
|
||||
current_byte += inc;
|
||||
visual_col++;
|
||||
}
|
||||
return visual_col;
|
||||
}
|
||||
|
||||
uint32_t get_bytes_from_visual_col(const char *line,
|
||||
uint32_t target_visual_col) {
|
||||
if (!line)
|
||||
return 0;
|
||||
uint32_t visual_col = 0;
|
||||
uint32_t current_byte = 0;
|
||||
uint32_t len = strlen(line);
|
||||
if (len > 0 && line[len - 1] == '\n')
|
||||
len--;
|
||||
while (visual_col < target_visual_col && current_byte < len) {
|
||||
uint32_t inc = grapheme_next_character_break_utf8(line + current_byte,
|
||||
len - current_byte);
|
||||
current_byte += inc;
|
||||
visual_col++;
|
||||
}
|
||||
return current_byte;
|
||||
}
|
||||
|
||||
void log(const char *fmt, ...) {
|
||||
FILE *fp = fopen("/tmp/log.txt", "a");
|
||||
if (!fp)
|
||||
return;
|
||||
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
vfprintf(fp, fmt, args);
|
||||
va_end(args);
|
||||
|
||||
fputc('\n', fp);
|
||||
fclose(fp);
|
||||
}
|
||||
Reference in New Issue
Block a user