Compare commits

...

2 Commits

Author SHA1 Message Date
d82389bc42 Add jumping hooks for faster navigation 2025-12-14 12:17:38 +00:00
5fae2f3bb6 Add alt+arrow support 2025-12-14 10:52:56 +00:00
6 changed files with 179 additions and 22 deletions

View File

@@ -6,8 +6,6 @@ A TUI IDE.
# TODO
- [ ] Add alt+arrows to move line/block up/down.
- [ ] Add `hooks` in files that can be set/unset/jumped to.
- [ ] Add folding support at tree-sitter level (basic folding is done).
- [ ] Add feature where doing enter uses tree-sitter to add newline with indentation.
- it should also put stuff like `}` on the next line.
@@ -25,3 +23,4 @@ A TUI IDE.
- [ ] Add codeium/copilot support.
- [ ] Normalize / validate unicode on file open.
- [ ] Add git stuff.
- [ ] Fix bug where alt+up at eof adds extra line.

View File

@@ -15,6 +15,8 @@
#define WORD 1
#define LINE 2
#define EXTRA_META 3
struct Highlight {
uint32_t fg;
uint32_t bg;
@@ -107,8 +109,11 @@ struct Editor {
Spans spans;
Spans def_spans;
std::map<uint32_t, bool> folded_node;
uint32_t hooks[94];
bool jumper_set;
};
void apply_edit(std::vector<Span> &spans, uint32_t x, int64_t y);
Editor *new_editor(const char *filename, Coord position, Coord size);
void free_editor(Editor *editor);
void render_editor(Editor *editor);
@@ -124,12 +129,13 @@ void scroll_down(Editor *editor, uint32_t number);
void ensure_cursor(Editor *editor);
void ensure_scroll(Editor *editor);
void handle_editor_event(Editor *editor, KeyEvent event);
void apply_edit(std::vector<Span> &spans, uint32_t x, int64_t y);
void edit_erase(Editor *editor, Coord pos, int64_t len);
void edit_insert(Editor *editor, Coord pos, char *data, uint32_t len);
Coord editor_hit_test(Editor *editor, uint32_t x, uint32_t y);
char *get_selection(Editor *editor, uint32_t *out_len);
void editor_worker(Editor *editor);
void move_line_down(Editor *editor);
void move_line_up(Editor *editor);
void word_boundaries(Editor *editor, Coord coord, uint32_t *prev_col,
uint32_t *next_col, uint32_t *prev_clusters,
uint32_t *next_clusters);

View File

@@ -8,6 +8,7 @@
#define INSERT 1
#define SELECT 2
#define RUNNER 3
#define JUMPER 4
extern std::atomic<bool> running;
extern uint8_t mode;

View File

@@ -20,12 +20,7 @@ Editor *new_editor(const char *filename, Coord position, Coord size) {
editor->filename = filename;
editor->position = position;
editor->size = size;
editor->tree = nullptr;
editor->cursor = {0, 0};
editor->cursor_preffered = UINT32_MAX;
editor->selection_active = false;
editor->selection = {0, 0};
editor->scroll = {0, 0};
editor->root = load(str, len, optimal_chunk_size(len));
free(str);
editor->folded.resize(editor->root->line_count + 2);
@@ -71,9 +66,14 @@ void update_render_fold_marker(uint32_t row, uint32_t cols) {
void render_editor(Editor *editor) {
uint32_t sel_start = 0, sel_end = 0;
uint32_t numlen =
2 + static_cast<int>(std::log10(editor->root->line_count + 1));
EXTRA_META + static_cast<int>(std::log10(editor->root->line_count + 1));
uint32_t render_width = editor->size.col - numlen;
uint32_t render_x = editor->position.col + numlen;
std::vector<std::pair<uint32_t, char>> v;
for (size_t i = 0; i < 94; ++i)
if (editor->hooks[i] != 0)
v.push_back({editor->hooks[i], '!' + i});
std::sort(v.begin(), v.end());
std::shared_lock knot_lock(editor->knot_mtx);
if (editor->selection_active) {
Coord start, end;
@@ -167,8 +167,16 @@ void render_editor(Editor *editor) {
while (current_byte_offset < line_len && rendered_rows < editor->size.row) {
uint32_t color = editor->cursor.row == line_index ? 0x222222 : 0;
if (current_byte_offset == 0 || rendered_rows == 0) {
char buf[16];
int len = snprintf(buf, sizeof(buf), "%*u", numlen - 1, line_index + 1);
char buf[EXTRA_META + 16];
char hook = ' ';
for (auto &p : v) {
if (p.first == line_index + 1) {
hook = p.second;
break;
}
}
int len = snprintf(buf, sizeof(buf), "%c%*u", hook, numlen - 2,
line_index + 1);
uint32_t num_color =
editor->cursor.row == line_index ? 0xFFFFFF : 0x555555;
for (int i = 0; i < len; i++)
@@ -244,8 +252,16 @@ void render_editor(Editor *editor) {
if (line_len == 0 ||
(current_byte_offset >= line_len && rendered_rows == 0)) {
uint32_t color = editor->cursor.row == line_index ? 0x222222 : 0;
char buf[16];
int len = snprintf(buf, sizeof(buf), "%*u", numlen - 1, line_index + 1);
char buf[EXTRA_META + 16];
char hook = ' ';
for (auto &p : v) {
if (p.first == line_index + 1) {
hook = p.second;
break;
}
}
int len =
snprintf(buf, sizeof(buf), "%c%*u", hook, numlen - 2, line_index + 1);
uint32_t num_color =
editor->cursor.row == line_index ? 0xFFFFFF : 0x555555;
for (int i = 0; i < len; i++)
@@ -283,6 +299,7 @@ void render_editor(Editor *editor) {
case INSERT:
type = CURSOR;
break;
case JUMPER:
case SELECT:
type = UNDERLINE;
break;

View File

@@ -165,10 +165,10 @@ void handle_editor_event(Editor *editor, KeyEvent event) {
nullptr);
switch (event.special_key) {
case KEY_DOWN:
cursor_down(editor, 1);
cursor_down(editor, 5);
break;
case KEY_UP:
cursor_up(editor, 1);
cursor_up(editor, 5);
break;
case KEY_LEFT:
editor->cursor_preffered = UINT32_MAX;
@@ -187,8 +187,20 @@ void handle_editor_event(Editor *editor, KeyEvent event) {
}
break;
case ALT:
// TODO: For up/down in insert/normal move line and in select move lines
// overlapping selection up/down. right/left are normal
switch (event.special_key) {
case KEY_DOWN:
move_line_down(editor);
break;
case KEY_UP:
move_line_up(editor);
break;
case KEY_LEFT:
cursor_left(editor, 8);
break;
case KEY_RIGHT:
cursor_right(editor, 8);
break;
}
break;
}
}
@@ -203,6 +215,21 @@ void handle_editor_event(Editor *editor, KeyEvent event) {
case 'i':
mode = INSERT;
break;
case 'n':
mode = JUMPER;
editor->jumper_set = true;
break;
case 'm':
mode = JUMPER;
editor->jumper_set = false;
break;
case 'N':
for (uint8_t i = 0; i < 94; i++)
if (editor->hooks[i] == editor->cursor.row + 1) {
editor->hooks[i] = 0;
break;
}
break;
case 's':
case 'v':
mode = SELECT;
@@ -347,6 +374,26 @@ void handle_editor_event(Editor *editor, KeyEvent event) {
}
}
break;
case JUMPER:
if (event.key_type == KEY_CHAR && event.len == 1 &&
(event.c[0] > '!' && event.c[0] < '~')) {
if (editor->jumper_set) {
for (uint8_t i = 0; i < 94; i++)
if (editor->hooks[i] == editor->cursor.row + 1) {
editor->hooks[i] = 0;
break;
}
editor->hooks[event.c[0] - '!'] = editor->cursor.row + 1;
} else {
uint32_t line = editor->hooks[event.c[0] - '!'];
if (line > 0) {
editor->cursor = {line - 1, 0};
editor->cursor_preffered = UINT32_MAX;
}
}
}
mode = NORMAL;
break;
case RUNNER:
if (event.key_type == KEY_CHAR && event.len == 1) {
switch (event.c[0]) {
@@ -516,7 +563,7 @@ Coord editor_hit_test(Editor *editor, uint32_t x, uint32_t y) {
if (mode == INSERT)
x++;
uint32_t numlen =
2 + static_cast<int>(std::log10(editor->root->line_count + 1));
EXTRA_META + static_cast<int>(std::log10(editor->root->line_count + 1));
uint32_t render_width = editor->size.col - numlen;
x = MAX(x, numlen) - numlen;
uint32_t target_visual_row = y;
@@ -777,6 +824,93 @@ void cursor_left(Editor *editor, uint32_t number) {
editor->cursor_preffered = UINT32_MAX;
}
void move_line_up(Editor *editor) {
if (!editor || !editor->root || editor->cursor.row == 0)
return;
if (mode == NORMAL || mode == INSERT) {
uint32_t line_len, line_cluster_len;
std::shared_lock lock(editor->knot_mtx);
LineIterator *it = begin_l_iter(editor->root, editor->cursor.row);
char *line = next_line(it, &line_len);
free(it);
if (!line) {
lock.unlock();
return;
}
line_cluster_len = count_clusters(line, line_len, 0, line_len);
lock.unlock();
Coord cursor = editor->cursor;
edit_erase(editor, {cursor.row, 0}, line_cluster_len);
edit_insert(editor, {cursor.row - 1, 0}, line, line_len);
free(line);
editor->cursor = {cursor.row - 1, cursor.col};
} else if (mode == SELECT) {
uint32_t start_row = MIN(editor->cursor.row, editor->selection.row);
uint32_t end_row = MAX(editor->cursor.row, editor->selection.row);
uint32_t start_byte = line_to_byte(editor->root, start_row, nullptr);
uint32_t end_byte = line_to_byte(editor->root, end_row + 1, nullptr);
char *selected_text = read(editor->root, start_byte, end_byte - start_byte);
if (!selected_text)
return;
uint32_t selected_len = count_clusters(selected_text, end_byte - start_byte,
0, end_byte - start_byte);
Coord cursor = editor->cursor;
Coord selection = editor->selection;
edit_erase(editor, {start_row, 0}, selected_len);
edit_insert(editor, {start_row - 1, 0}, selected_text,
end_byte - start_byte);
free(selected_text);
editor->cursor = {cursor.row - 1, cursor.col};
editor->selection = {selection.row - 1, selection.col};
}
}
void move_line_down(Editor *editor) {
if (!editor || !editor->root)
return;
if (mode == NORMAL || mode == INSERT) {
if (editor->cursor.row >= editor->root->line_count - 1)
return;
uint32_t line_len, line_cluster_len;
std::shared_lock lock(editor->knot_mtx);
LineIterator *it = begin_l_iter(editor->root, editor->cursor.row);
char *line = next_line(it, &line_len);
free(it);
if (!line) {
lock.unlock();
return;
}
line_cluster_len = count_clusters(line, line_len, 0, line_len);
lock.unlock();
Coord cursor = editor->cursor;
edit_erase(editor, {cursor.row, 0}, line_cluster_len);
edit_insert(editor, {cursor.row + 1, 0}, line, line_len);
free(line);
editor->cursor = {cursor.row + 1, cursor.col};
} else if (mode == SELECT) {
if (editor->cursor.row >= editor->root->line_count - 1 ||
editor->selection.row >= editor->root->line_count - 1)
return;
uint32_t start_row = MIN(editor->cursor.row, editor->selection.row);
uint32_t end_row = MAX(editor->cursor.row, editor->selection.row);
uint32_t start_byte = line_to_byte(editor->root, start_row, nullptr);
uint32_t end_byte = line_to_byte(editor->root, end_row + 1, nullptr);
char *selected_text = read(editor->root, start_byte, end_byte - start_byte);
if (!selected_text)
return;
uint32_t selected_len = count_clusters(selected_text, end_byte - start_byte,
0, end_byte - start_byte);
Coord cursor = editor->cursor;
Coord selection = editor->selection;
edit_erase(editor, {start_row, 0}, selected_len);
edit_insert(editor, {start_row + 1, 0}, selected_text,
end_byte - start_byte);
free(selected_text);
editor->cursor = {cursor.row + 1, cursor.col};
editor->selection = {selection.row + 1, selection.col};
}
}
void edit_erase(Editor *editor, Coord pos, int64_t len) {
if (len == 0)
return;

View File

@@ -9,7 +9,7 @@ extern "C" {
void scroll_up(Editor *editor, uint32_t number) {
number++;
uint32_t numlen =
2 + static_cast<int>(std::log10(editor->root->line_count + 1));
EXTRA_META + static_cast<int>(std::log10(editor->root->line_count + 1));
uint32_t render_width = editor->size.col - numlen;
Coord *scroll_queue = (Coord *)malloc(sizeof(Coord) * number);
uint32_t q_head = 0;
@@ -79,7 +79,7 @@ void scroll_down(Editor *editor, uint32_t number) {
if (!editor || number == 0)
return;
uint32_t numlen =
2 + static_cast<int>(std::log10(editor->root->line_count + 1));
EXTRA_META + static_cast<int>(std::log10(editor->root->line_count + 1));
uint32_t render_width = editor->size.col - numlen;
uint32_t line_index = editor->scroll.row;
LineIterator *it = begin_l_iter(editor->root, line_index);
@@ -185,7 +185,7 @@ void ensure_cursor(Editor *editor) {
return;
}
uint32_t numlen =
2 + static_cast<int>(std::log10(editor->root->line_count + 1));
EXTRA_META + static_cast<int>(std::log10(editor->root->line_count + 1));
uint32_t render_width = editor->size.col - numlen;
uint32_t visual_rows = 0;
uint32_t line_index = editor->scroll.row;
@@ -267,7 +267,7 @@ void ensure_cursor(Editor *editor) {
void ensure_scroll(Editor *editor) {
std::shared_lock knot_lock(editor->knot_mtx);
uint32_t numlen =
2 + static_cast<int>(std::log10(editor->root->line_count + 1));
EXTRA_META + static_cast<int>(std::log10(editor->root->line_count + 1));
uint32_t render_width = editor->size.col - numlen;
if (editor->cursor < editor->scroll) {
LineIterator *it = begin_l_iter(editor->root, editor->cursor.row);