Compare commits

...

2 Commits

Author SHA1 Message Date
4e25b28bbe Add more todoists 2025-12-10 22:53:24 +00:00
24c5b70af1 Major code cleanup. 2025-12-10 22:51:19 +00:00
14 changed files with 108 additions and 107 deletions

View File

@@ -6,7 +6,11 @@ A TUI IDE.
# TODO # TODO
- [ ] Add support for text selection. - [ ] Add support for text selection (slect range / all).
- [ ] Add support for delete key.
- [ ] Add support for copy/cut/paste.
- [ ] Add mouse support.
- [ ] Add modes for editing - insert, select, normal, etc.
- [ ] Add folding support. - [ ] Add folding support.
- [ ] Add feature where doing enter uses tree-sitter to add newline with indentation. - [ ] Add feature where doing enter uses tree-sitter to add newline with indentation.
1. it should also put stuff like `}` on the next line. 1. it should also put stuff like `}` on the next line.

View File

@@ -31,7 +31,7 @@
"&" "&"
] @punctuation.delimiter ] @punctuation.delimiter
;; #ffffff #000000 0 0 0 1 ;; #ffffff #000000 0 1 0 1
[ [
">" ">"
">>" ">>"

View File

@@ -2,7 +2,7 @@
#define EDITOR_H #define EDITOR_H
#include "../libs/tree-sitter/lib/include/tree_sitter/api.h" #include "../libs/tree-sitter/lib/include/tree_sitter/api.h"
#include "./rope.h" #include "./knot.h"
#include "./ui.h" #include "./ui.h"
#include "./utils.h" #include "./utils.h"
#include <algorithm> #include <algorithm>
@@ -82,26 +82,25 @@ struct SpanCursor {
}; };
struct Editor { struct Editor {
const char *filename; // Filename of the editor const char *filename;
Knot *root; // A rope Knot *root;
std::shared_mutex knot_mtx; // A mutex std::shared_mutex knot_mtx;
Coord cursor; // position of the cursor Coord cursor;
uint32_t cursor_preffered; // preffered visual column uint32_t cursor_preffered;
Coord selection; // position of the selection Coord selection;
bool selection_active; // true if there is a selection bool selection_active;
Coord position; // Position of the editor Coord position;
Coord size; // Size of the editor Coord size;
Coord scroll; // Position of the scroll Coord scroll;
TSTree *tree; // Tree-sitter tree TSTree *tree;
TSParser *parser; // Tree-sitter parser TSParser *parser;
TSQuery *query; // Tree-sitter query TSQuery *query;
const TSLanguage *language; // Tree-sitter language const TSLanguage *language;
Queue<TSInputEdit> edit_queue; // Tree-sitter edit queue Queue<TSInputEdit> edit_queue;
std::vector<Highlight> query_map; // Tree-sitter query map std::vector<Highlight> query_map;
std::vector<int8_t> folded; // folded lines indexed by line number std::vector<int8_t> folded;
Spans spans; // Highlighted spans Spans spans;
std::map<uint32_t, bool> folded_node; // maps content hash to fold state std::map<uint32_t, bool> folded_node;
// - built by tree-sitter helpers
}; };
Editor *new_editor(const char *filename, Coord position, Coord size); Editor *new_editor(const char *filename, Coord position, Coord size);

View File

@@ -1,6 +1,7 @@
#ifndef ROPE_H #ifndef ROPE_H
#define ROPE_H #define ROPE_H
#include "./utils.h"
#include <cstdint> #include <cstdint>
#include <vector> #include <vector>
@@ -10,9 +11,6 @@
#define MIN(a, b) ((a) < (b) ? (a) : (b)) #define MIN(a, b) ((a) < (b) ? (a) : (b))
#define DEPTH(n) ((n) ? (n)->depth : 0) #define DEPTH(n) ((n) ? (n)->depth : 0)
#define PCRE2_CODE_UNIT_WIDTH 8
#define PCRE_WORKSPACE_SIZE 512
// Rope node definition // Rope node definition
typedef struct Knot { typedef struct Knot {
Knot *left; Knot *left;

View File

@@ -2,6 +2,7 @@
#define TS_H #define TS_H
#include "./editor.h" #include "./editor.h"
#include "./utils.h"
#include <pcre2.h> #include <pcre2.h>
#define HEX(s) (static_cast<uint32_t>(std::stoul(s, nullptr, 16))) #define HEX(s) (static_cast<uint32_t>(std::stoul(s, nullptr, 16)))

View File

@@ -7,18 +7,18 @@ struct Language {
}; };
extern "C" { extern "C" {
const TSLanguage *tree_sitter_bash(void); const TSLanguage *tree_sitter_bash();
const TSLanguage *tree_sitter_c(void); const TSLanguage *tree_sitter_c();
const TSLanguage *tree_sitter_cpp(void); const TSLanguage *tree_sitter_cpp();
const TSLanguage *tree_sitter_css(void); const TSLanguage *tree_sitter_css();
const TSLanguage *tree_sitter_fish(void); const TSLanguage *tree_sitter_fish();
const TSLanguage *tree_sitter_go(void); const TSLanguage *tree_sitter_go();
const TSLanguage *tree_sitter_haskell(void); const TSLanguage *tree_sitter_haskell();
const TSLanguage *tree_sitter_html(void); const TSLanguage *tree_sitter_html();
const TSLanguage *tree_sitter_javascript(void); const TSLanguage *tree_sitter_javascript();
const TSLanguage *tree_sitter_json(void); const TSLanguage *tree_sitter_json();
const TSLanguage *tree_sitter_lua(void); const TSLanguage *tree_sitter_lua();
const TSLanguage *tree_sitter_make(void); const TSLanguage *tree_sitter_make();
const TSLanguage *tree_sitter_python(void); const TSLanguage *tree_sitter_python();
const TSLanguage *tree_sitter_ruby(void); const TSLanguage *tree_sitter_ruby();
} }

View File

@@ -1,6 +1,7 @@
#ifndef UI_H #ifndef UI_H
#define UI_H #define UI_H
#include "./utils.h"
#include <atomic> #include <atomic>
#include <cstdint> #include <cstdint>
#include <mutex> #include <mutex>
@@ -63,11 +64,6 @@ struct ScreenCell {
uint8_t flags = CF_NONE; uint8_t flags = CF_NONE;
}; };
struct Coord {
uint32_t row;
uint32_t col;
};
struct KeyEvent { struct KeyEvent {
uint8_t key_type; uint8_t key_type;
@@ -91,7 +87,6 @@ extern std::mutex screen_mutex;
extern std::atomic<bool> running; extern std::atomic<bool> running;
void get_terminal_size(); void get_terminal_size();
void die(const char *s);
void enable_raw_mode(); void enable_raw_mode();
void disable_raw_mode(); void disable_raw_mode();
Coord start_screen(); Coord start_screen();

View File

@@ -6,6 +6,9 @@
#include <queue> #include <queue>
#include <string> #include <string>
#define PCRE2_CODE_UNIT_WIDTH 8
#define PCRE_WORKSPACE_SIZE 512
template <typename T> struct Queue { template <typename T> struct Queue {
std::queue<T> q; std::queue<T> q;
std::mutex m; std::mutex m;
@@ -28,6 +31,11 @@ template <typename T> struct Queue {
} }
}; };
struct Coord {
uint32_t row;
uint32_t col;
};
uint32_t grapheme_strlen(const char *s); uint32_t grapheme_strlen(const char *s);
uint32_t get_visual_col_from_bytes(const char *line, uint32_t byte_limit); uint32_t get_visual_col_from_bytes(const char *line, uint32_t byte_limit);
uint32_t get_bytes_from_visual_col(const char *line, uint32_t get_bytes_from_visual_col(const char *line,

View File

@@ -14,7 +14,6 @@ Editor *new_editor(const char *filename, Coord position, Coord size) {
char *str = load_file(filename, &len); char *str = load_file(filename, &len);
if (!str) { if (!str) {
free_editor(editor); free_editor(editor);
log("me?");
return nullptr; return nullptr;
} }
editor->filename = filename; editor->filename = filename;
@@ -438,8 +437,7 @@ void apply_edit(std::vector<Span> &spans, uint32_t x, int64_t y) {
} }
void render_editor(Editor *editor) { void render_editor(Editor *editor) {
uint32_t screen_rows = editor->size.row; Coord cursor = {UINT32_MAX, UINT32_MAX};
uint32_t screen_cols = editor->size.col;
uint32_t line_index = editor->scroll.row; uint32_t line_index = editor->scroll.row;
SpanCursor span_cursor(editor->spans); SpanCursor span_cursor(editor->spans);
std::shared_lock knot_lock(editor->knot_mtx); std::shared_lock knot_lock(editor->knot_mtx);
@@ -449,10 +447,10 @@ void render_editor(Editor *editor) {
uint32_t rendered_rows = 0; uint32_t rendered_rows = 0;
uint32_t global_byte_offset = line_to_byte(editor->root, line_index, nullptr); uint32_t global_byte_offset = line_to_byte(editor->root, line_index, nullptr);
span_cursor.sync(global_byte_offset); span_cursor.sync(global_byte_offset);
while (rendered_rows < screen_rows) { while (rendered_rows < editor->size.row) {
if (editor->folded[line_index]) { if (editor->folded[line_index]) {
if (editor->folded[line_index] == 2) { if (editor->folded[line_index] == 2) {
update_render_fold_marker(rendered_rows, screen_cols); update_render_fold_marker(rendered_rows, editor->size.col);
rendered_rows++; rendered_rows++;
} }
do { do {
@@ -486,11 +484,11 @@ void render_editor(Editor *editor) {
skipped_cols++; skipped_cols++;
} }
} }
while (current_byte_offset < line_len && rendered_rows < screen_rows) { while (current_byte_offset < line_len && rendered_rows < editor->size.row) {
uint32_t slice_byte_len = 0; uint32_t slice_byte_len = 0;
uint32_t slice_visual_cols = 0; uint32_t slice_visual_cols = 0;
uint32_t probe_offset = current_byte_offset; uint32_t probe_offset = current_byte_offset;
while (slice_visual_cols < screen_cols && probe_offset < line_len) { while (slice_visual_cols < editor->size.col && probe_offset < line_len) {
uint32_t len = grapheme_next_character_break_utf8( uint32_t len = grapheme_next_character_break_utf8(
line + probe_offset, line_len - probe_offset); line + probe_offset, line_len - probe_offset);
slice_byte_len += len; slice_byte_len += len;
@@ -501,9 +499,10 @@ void render_editor(Editor *editor) {
uint32_t local_render_offset = 0; uint32_t local_render_offset = 0;
while (local_render_offset < slice_byte_len) { while (local_render_offset < slice_byte_len) {
if (line_index == editor->cursor.row && if (line_index == editor->cursor.row &&
editor->cursor.col == (current_byte_offset + local_render_offset)) editor->cursor.col == (current_byte_offset + local_render_offset)) {
set_cursor(editor->position.row + rendered_rows, cursor.row = editor->position.row + rendered_rows;
editor->position.col + col, 1); cursor.col = editor->position.col + col;
}
uint32_t absolute_byte_pos = uint32_t absolute_byte_pos =
global_byte_offset + current_byte_offset + local_render_offset; global_byte_offset + current_byte_offset + local_render_offset;
Highlight *hl = span_cursor.get_highlight(absolute_byte_pos); Highlight *hl = span_cursor.get_highlight(absolute_byte_pos);
@@ -523,10 +522,11 @@ void render_editor(Editor *editor) {
col++; col++;
} }
if (line_index == editor->cursor.row && if (line_index == editor->cursor.row &&
editor->cursor.col == (current_byte_offset + slice_byte_len)) editor->cursor.col == (current_byte_offset + slice_byte_len)) {
set_cursor(editor->position.row + rendered_rows, cursor.row = editor->position.row + rendered_rows;
editor->position.col + col, 1); cursor.col = editor->position.col + col;
while (col < screen_cols) { }
while (col < editor->size.col) {
update(editor->position.row + rendered_rows, editor->position.col + col, update(editor->position.row + rendered_rows, editor->position.col + col,
" ", 0xFFFFFF, 0, 0); " ", 0xFFFFFF, 0, 0);
col++; col++;
@@ -536,11 +536,12 @@ void render_editor(Editor *editor) {
} }
if (line_len == 0 || if (line_len == 0 ||
(current_byte_offset >= line_len && rendered_rows == 0)) { (current_byte_offset >= line_len && rendered_rows == 0)) {
if (editor->cursor.row == line_index) if (editor->cursor.row == line_index) {
set_cursor(editor->position.row + rendered_rows, editor->position.col, cursor.row = editor->position.row + rendered_rows;
1); cursor.col = editor->position.col;
}
uint32_t col = 0; uint32_t col = 0;
while (col < screen_cols) { while (col < editor->size.col) {
update(editor->position.row + rendered_rows, editor->position.col + col, update(editor->position.row + rendered_rows, editor->position.col + col,
" ", 0xFFFFFF, 0, 0); " ", 0xFFFFFF, 0, 0);
col++; col++;
@@ -551,8 +552,10 @@ void render_editor(Editor *editor) {
line_index++; line_index++;
free(line); free(line);
} }
while (rendered_rows < screen_rows) { if (cursor.row != UINT32_MAX && cursor.col != UINT32_MAX)
for (uint32_t col = 0; col < screen_cols; col++) set_cursor(cursor.row, cursor.col, 1);
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, update(editor->position.row + rendered_rows, editor->position.col + col,
" ", 0xFFFFFF, 0, 0); " ", 0xFFFFFF, 0, 0);
rendered_rows++; rendered_rows++;

View File

@@ -46,7 +46,7 @@ void capture_mouse(char *buf, KeyEvent *ret) {
} }
} }
KeyEvent read_key_nonblock() { KeyEvent read_key() {
KeyEvent ret; KeyEvent ret;
char buf[7]; char buf[7];
int n = read_input(buf, sizeof(buf)); int n = read_input(buf, sizeof(buf));
@@ -113,11 +113,3 @@ KeyEvent read_key_nonblock() {
} }
return ret; return ret;
} }
KeyEvent read_key() {
while (true) {
KeyEvent ret = read_key_nonblock();
if (ret.key_type != KEY_NONE)
return ret;
}
}

View File

@@ -1,4 +1,4 @@
#include "../include/rope.h" #include "../include/knot.h"
#include <assert.h> #include <assert.h>
#include <cmath> #include <cmath>
#include <cstdint> #include <cstdint>

View File

@@ -22,6 +22,8 @@ void background_worker(Editor *editor) {
void input_listener() { void input_listener() {
while (running) { while (running) {
KeyEvent event = read_key(); KeyEvent event = read_key();
if (event.key_type == KEY_NONE)
continue;
if (event.key_type == KEY_CHAR && event.c == CTRL('q')) if (event.key_type == KEY_CHAR && event.c == CTRL('q'))
running = false; running = false;
event_queue.push(event); event_queue.push(event);

View File

@@ -5,18 +5,15 @@ extern "C" {
} }
#include "../include/ui.h" #include "../include/ui.h"
termios orig_termios;
uint32_t rows, cols; uint32_t rows, cols;
int show_cursor = 0; int show_cursor = 0;
std::vector<ScreenCell> screen; std::vector<ScreenCell> screen;
std::vector<ScreenCell> old_screen; std::vector<ScreenCell> old_screen;
std::mutex screen_mutex; std::mutex screen_mutex;
termios orig_termios;
int display_width(const char *str) { int display_width(const char *str) {
if (!str) if (!str || !*str)
return 0;
if (!strlen(str))
return 0; return 0;
if (str[0] == '\t') if (str[0] == '\t')
return 4; return 4;
@@ -40,7 +37,6 @@ int display_width(const char *str) {
} }
} }
} }
return width; return width;
} }
@@ -51,15 +47,9 @@ void get_terminal_size() {
cols = w.ws_col; cols = w.ws_col;
} }
void die(const char *s) {
perror(s);
disable_raw_mode();
exit(EXIT_FAILURE);
}
void enable_raw_mode() { void enable_raw_mode() {
if (tcgetattr(STDIN_FILENO, &orig_termios) == -1) if (tcgetattr(STDIN_FILENO, &orig_termios) == -1)
die("tcgetattr"); exit(EXIT_FAILURE);
atexit(disable_raw_mode); atexit(disable_raw_mode);
struct termios raw = orig_termios; struct termios raw = orig_termios;
@@ -72,7 +62,7 @@ void enable_raw_mode() {
raw.c_cc[VTIME] = 0; raw.c_cc[VTIME] = 0;
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1) if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1)
die("tcsetattr"); exit(EXIT_FAILURE);
std::string os = "\x1b[?1049h\x1b[2 q\x1b[?1002h\x1b[?25l"; std::string os = "\x1b[?1049h\x1b[2 q\x1b[?1002h\x1b[?25l";
write(STDOUT_FILENO, os.c_str(), os.size()); write(STDOUT_FILENO, os.c_str(), os.size());
@@ -246,7 +236,7 @@ void render() {
} else if (written == -1) { } else if (written == -1) {
if (errno == EINTR) if (errno == EINTR)
continue; continue;
die("write failed"); exit(EXIT_FAILURE);
break; break;
} else { } else {
ptr += written; ptr += written;

View File

@@ -1,10 +1,9 @@
#include "../include/ts.h" #include "../include/ts.h"
#include "../include/editor.h" #include "../include/editor.h"
#include "../include/rope.h" #include "../include/knot.h"
#include <algorithm> #include <algorithm>
#include <cstdint> #include <cstdint>
#include <fstream> #include <fstream>
#include <regex>
#include <string> #include <string>
#include <unordered_map> #include <unordered_map>
@@ -30,9 +29,6 @@ pcre2_code *get_re(const std::string &pattern) {
return re; return re;
} }
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) { TSQuery *load_query(const char *query_path, Editor *editor) {
const TSLanguage *lang = editor->language; const TSLanguage *lang = editor->language;
std::ifstream file(query_path, std::ios::in | std::ios::binary); std::ifstream file(query_path, std::ios::in | std::ios::binary);
@@ -40,17 +36,31 @@ TSQuery *load_query(const char *query_path, Editor *editor) {
return nullptr; return nullptr;
std::string highlight_query((std::istreambuf_iterator<char>(file)), std::string highlight_query((std::istreambuf_iterator<char>(file)),
std::istreambuf_iterator<char>()); std::istreambuf_iterator<char>());
std::smatch match; 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] \d+))",
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; std::map<std::string, int> capture_name_cache;
Highlight *c_hl = nullptr; Highlight *c_hl = nullptr;
int i = 0; int i = 0;
int limit = 20; int limit = 20;
editor->query_map.resize(limit); editor->query_map.resize(limit);
std::string::const_iterator searchStart(highlight_query.cbegin()); PCRE2_SIZE offset = 0;
while (std::regex_search(searchStart, highlight_query.cend(), match, PCRE2_SIZE subject_length = highlight_query.size();
scm_regex)) { while (offset < subject_length) {
std::string mct = match.str(); int rc = pcre2_match(re, (PCRE2_SPTR)highlight_query.c_str(),
if (mct.substr(0, 1) == "@") { 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; std::string capture_name = mct;
if (!capture_name_cache.count(capture_name)) { if (!capture_name_cache.count(capture_name)) {
if (c_hl) { if (c_hl) {
@@ -65,7 +75,7 @@ TSQuery *load_query(const char *query_path, Editor *editor) {
capture_name_cache[capture_name] = i; capture_name_cache[capture_name] = i;
i++; i++;
} }
} else if (mct.substr(0, 2) == ";;") { } else if (mct.size() >= 2 && mct[0] == ';' && mct[1] == ';') {
if (c_hl) if (c_hl)
delete c_hl; delete c_hl;
c_hl = new Highlight(); c_hl = new Highlight();
@@ -78,10 +88,12 @@ TSQuery *load_query(const char *query_path, Editor *editor) {
c_hl->flags = (bold ? CF_BOLD : 0) | (italic ? CF_ITALIC : 0) | c_hl->flags = (bold ? CF_BOLD : 0) | (italic ? CF_ITALIC : 0) |
(underline ? CF_UNDERLINE : 0); (underline ? CF_UNDERLINE : 0);
} }
searchStart = match.suffix().first; offset = ovector[1];
} }
if (c_hl) if (c_hl)
delete c_hl; delete c_hl;
pcre2_match_data_free(match_data);
pcre2_code_free(re);
uint32_t error_offset = 0; uint32_t error_offset = 0;
TSQueryError error_type = (TSQueryError)0; TSQueryError error_type = (TSQueryError)0;
TSQuery *q = ts_query_new(lang, highlight_query.c_str(), TSQuery *q = ts_query_new(lang, highlight_query.c_str(),
@@ -202,10 +214,7 @@ void ts_collect_spans(Editor *editor) {
} }
parse_counter = 0; parse_counter = 0;
editor->spans.mid_parse = true; editor->spans.mid_parse = true;
// TODO: Remove this lock and replace with an index
// modifier based on edits made in the `read_ts` function.
std::shared_lock lock(editor->knot_mtx); std::shared_lock lock(editor->knot_mtx);
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tree = ts_parser_parse(editor->parser, copy, tsinput); tree = ts_parser_parse(editor->parser, copy, tsinput);
lock.unlock(); lock.unlock();
if (copy) if (copy)