From e930cc6bdc7f62befac063d7d9d016ffb0a64f1a Mon Sep 17 00:00:00 2001 From: David Moc Date: Sat, 30 May 2026 21:53:05 +0200 Subject: Added the old repo, refactored it, added the C jit. --- src/ecex.c | 1630 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1630 insertions(+) create mode 100644 src/ecex.c (limited to 'src/ecex.c') diff --git a/src/ecex.c b/src/ecex.c new file mode 100644 index 0000000..2f4c962 --- /dev/null +++ b/src/ecex.c @@ -0,0 +1,1630 @@ +#include "ecex.h" + +#include "ccdjit.h" +#include "common.h" +#include "config.h" +#include "util.h" + +#include +#include +#include + +extern FILE *popen(const char *command, const char *type); +extern int pclose(FILE *stream); + +#define ECEX_INITIAL_BUFFER_CAP 8 +#define ECEX_INITIAL_COMMAND_CAP 32 +#define ECEX_INITIAL_KEYBIND_CAP 32 +#define ECEX_INITIAL_WINDOW_CAP 4 +#define ECEX_INITIAL_MODE_CAP 8 +#define ECEX_INITIAL_MODE_KEYBIND_CAP 16 + +ecex_window_t *ecex_current_window(ecex_t *ed); + +static ecex_color_t ecex_color(float r, float g, float b) { + ecex_color_t c = {r, g, b}; + return c; +} + +static void ecex_theme_set_defaults(ecex_t *ed) { + if (!ed) return; + + ed->theme.font_path = NULL; + ed->theme.font_size = 22.0f; + + ed->theme.bg = ecex_color(0.08f, 0.09f, 0.11f); + ed->theme.fg = ecex_color(0.88f, 0.88f, 0.86f); + + ed->theme.status_bg = ecex_color(0.15f, 0.15f, 0.18f); + ed->theme.status_fg = ecex_color(0.86f, 0.86f, 0.84f); + ed->theme.status_border = ecex_color(0.28f, 0.28f, 0.32f); + + ed->theme.cursor = ecex_color(1.0f, 1.0f, 1.0f); + ed->theme.region_bg = ecex_color(0.22f, 0.34f, 0.48f); + + ed->theme.minibuffer_bg = ecex_color(0.10f, 0.11f, 0.14f); + ed->theme.minibuffer_fg = ecex_color(0.95f, 0.95f, 0.90f); + + ed->theme.completion_fg = ecex_color(0.45f, 0.45f, 0.45f); + ed->theme.completion_enabled = 1; + + ed->theme.interactive_highlight_bg = ecex_color(0.18f, 0.20f, 0.24f); + ed->theme.interactive_highlight_fg = ecex_color(1.0f, 1.0f, 1.0f); + ed->theme.current_line_bg = ecex_color(0.12f, 0.13f, 0.16f); + ed->theme.search_bg = ecex_color(0.55f, 0.42f, 0.12f); + ed->theme.line_numbers_enabled = 1; + ed->theme.current_line_enabled = 1; +} + +#define ECEX_COMMAND(name, fn) \ + do { \ + if (ecex_register_command(ed, (name), (fn)) != ECEX_OK) return ECEX_ERR; \ + } while (0) + +#define ECEX_BIND(key, command) \ + do { \ + if (ecex_bind_key(ed, (key), (command)) != ECEX_OK) return ECEX_ERR; \ + } while (0) + +#define CURRENT_BUFFER_OR_ERR(ed) \ + buffer_t *buf = ecex_current_buffer(ed); \ + if (!buf) return ECEX_ERR + +static int cmd_quit(ecex_t *ed) { + if (!ed) return ECEX_ERR; + ed->should_quit = 1; + return ECEX_OK; +} + +static int cmd_next_buffer(ecex_t *ed) { return ecex_next_buffer(ed); } +static int cmd_previous_buffer(ecex_t *ed) { return ecex_previous_buffer(ed); } +static int cmd_split_window_right(ecex_t *ed) { return ecex_split_window_vertically(ed); } +static int cmd_split_window_below(ecex_t *ed) { return ecex_split_window_horizontally(ed); } +static int cmd_other_window(ecex_t *ed) { return ecex_other_window(ed); } +static int cmd_previous_window(ecex_t *ed) { return ecex_previous_window(ed); } +static int cmd_delete_window(ecex_t *ed) { return ecex_delete_window(ed); } +static int cmd_delete_other_windows(ecex_t *ed) { return ecex_delete_other_windows(ed); } +static int cmd_balance_windows(ecex_t *ed) { return ecex_balance_windows(ed); } + +static int cmd_move_left(ecex_t *ed) { CURRENT_BUFFER_OR_ERR(ed); buffer_move_left(buf); return ECEX_OK; } +static int cmd_move_right(ecex_t *ed) { CURRENT_BUFFER_OR_ERR(ed); buffer_move_right(buf); return ECEX_OK; } +static int cmd_move_up(ecex_t *ed) { CURRENT_BUFFER_OR_ERR(ed); buffer_move_up(buf); return ECEX_OK; } +static int cmd_move_down(ecex_t *ed) { CURRENT_BUFFER_OR_ERR(ed); buffer_move_down(buf); return ECEX_OK; } +static int cmd_move_word_left(ecex_t *ed) { CURRENT_BUFFER_OR_ERR(ed); buffer_move_word_left(buf); return ECEX_OK; } +static int cmd_move_word_right(ecex_t *ed) { CURRENT_BUFFER_OR_ERR(ed); buffer_move_word_right(buf); return ECEX_OK; } +static int cmd_beginning_of_line(ecex_t *ed) { CURRENT_BUFFER_OR_ERR(ed); buffer_move_beginning_of_line(buf); return ECEX_OK; } +static int cmd_end_of_line(ecex_t *ed) { CURRENT_BUFFER_OR_ERR(ed); buffer_move_end_of_line(buf); return ECEX_OK; } +static int cmd_beginning_of_buffer(ecex_t *ed) { CURRENT_BUFFER_OR_ERR(ed); buffer_move_beginning_of_buffer(buf); return ECEX_OK; } +static int cmd_end_of_buffer(ecex_t *ed) { CURRENT_BUFFER_OR_ERR(ed); buffer_move_end_of_buffer(buf); return ECEX_OK; } + +static int cmd_undo(ecex_t *ed) { CURRENT_BUFFER_OR_ERR(ed); return buffer_undo(buf); } +static int cmd_redo(ecex_t *ed) { CURRENT_BUFFER_OR_ERR(ed); return buffer_redo(buf); } +static int cmd_backspace(ecex_t *ed) { CURRENT_BUFFER_OR_ERR(ed); return buffer_backspace(buf); } +static int cmd_delete_forward(ecex_t *ed) { CURRENT_BUFFER_OR_ERR(ed); return buffer_delete_forward(buf); } +static int cmd_newline(ecex_t *ed) { + CURRENT_BUFFER_OR_ERR(ed); + if (buffer_is_interactive(buf)) { + return ecex_interactive_activate_current_line(ed); + } + return buffer_insert_char(buf, '\n'); +} +static int cmd_kill_line(ecex_t *ed) { CURRENT_BUFFER_OR_ERR(ed); return buffer_kill_line(buf); } +static int cmd_clear_buffer(ecex_t *ed) { CURRENT_BUFFER_OR_ERR(ed); return buffer_clear(buf); } +static int cmd_set_mark(ecex_t *ed) { CURRENT_BUFFER_OR_ERR(ed); buffer_set_mark(buf, buf->point); return ECEX_OK; } +static int cmd_clear_mark(ecex_t *ed) { CURRENT_BUFFER_OR_ERR(ed); buffer_clear_mark(buf); return ECEX_OK; } + + +static int cmd_yank(ecex_t *ed) { + CURRENT_BUFFER_OR_ERR(ed); + const char *text = ecex_clipboard_get(ed); + if (!text || !text[0]) return ECEX_OK; + return buffer_replace_selection(buf, text); +} + +static int cmd_copy_region(ecex_t *ed) { + CURRENT_BUFFER_OR_ERR(ed); + if (!buffer_has_selection(buf)) return ECEX_OK; + + size_t start = 0; + size_t end = 0; + buffer_selection_range(buf, &start, &end); + + char *text = buffer_substring(buf, start, end); + if (!text) return ECEX_ERR; + + int result = ecex_clipboard_set(ed, text); + free(text); + return result; +} + +static int cmd_kill_region(ecex_t *ed) { + CURRENT_BUFFER_OR_ERR(ed); + if (!buffer_has_selection(buf)) return ECEX_OK; + + size_t start = 0; + size_t end = 0; + buffer_selection_range(buf, &start, &end); + + char *text = buffer_substring(buf, start, end); + if (!text) return ECEX_ERR; + + int result = ecex_clipboard_set(ed, text); + free(text); + if (result != ECEX_OK) return result; + + return buffer_delete_selection(buf); +} + +static int cmd_list_commands(ecex_t *ed) { return ecex_list_commands(ed); } +static int cmd_list_buffers(ecex_t *ed) { return ecex_list_buffers(ed); } +static int cmd_switch_buffer(ecex_t *ed) { ecex_request_prompt(ed, ECEX_PROMPT_SWITCH_BUFFER, "Switch buffer: "); return ECEX_OK; } +static int cmd_kill_buffer_command(ecex_t *ed) { ecex_request_prompt(ed, ECEX_PROMPT_KILL_BUFFER, "Kill buffer: "); return ECEX_OK; } +static int cmd_compile(ecex_t *ed) { ecex_request_prompt(ed, ECEX_PROMPT_COMPILE, "Compile: "); return ECEX_OK; } +static int cmd_grep(ecex_t *ed) { ecex_request_prompt(ed, ECEX_PROMPT_GREP, "Grep: "); return ECEX_OK; } +static int cmd_recompile(ecex_t *ed) { return ecex_rerun_compile(ed); } +static int cmd_regrep(ecex_t *ed) { return ecex_rerun_grep(ed); } +static int cmd_next_error(ecex_t *ed) { return ecex_next_interactive_action(ed); } +static int cmd_previous_error(ecex_t *ed) { return ecex_previous_interactive_action(ed); } +static int cmd_comment_region(ecex_t *ed) { return ecex_comment_region(ed); } +static int cmd_uncomment_region(ecex_t *ed) { return ecex_uncomment_region(ed); } +static int cmd_toggle_line_numbers(ecex_t *ed) { if (!ed) return ECEX_ERR; ed->theme.line_numbers_enabled = !ed->theme.line_numbers_enabled; return ECEX_OK; } +static int cmd_toggle_current_line(ecex_t *ed) { if (!ed) return ECEX_ERR; ed->theme.current_line_enabled = !ed->theme.current_line_enabled; return ECEX_OK; } +static int cmd_isearch_forward(ecex_t *ed) { ecex_request_prompt(ed, ECEX_PROMPT_ISEARCH_FORWARD, "I-search: "); return ECEX_OK; } +static int cmd_isearch_backward(ecex_t *ed) { ecex_request_prompt(ed, ECEX_PROMPT_ISEARCH_BACKWARD, "I-search backward: "); return ECEX_OK; } + +static int cmd_find_file(ecex_t *ed) { + ecex_request_prompt(ed, ECEX_PROMPT_FIND_FILE, "Find file: "); + return ECEX_OK; +} + +static int cmd_write_file(ecex_t *ed) { + ecex_request_prompt(ed, ECEX_PROMPT_WRITE_FILE, "Write file: "); + return ECEX_OK; +} + +static int cmd_save_buffer(ecex_t *ed) { + if (!ed) return ECEX_ERR; + + buffer_t *buf = ecex_current_buffer(ed); + if (!buf) return ECEX_ERR; + + if (!buf->path) { + ecex_request_prompt(ed, ECEX_PROMPT_WRITE_FILE, "Write file: "); + return ECEX_OK; + } + + return ecex_save_current_buffer(ed); +} + +static int cmd_eval_buffer(ecex_t *ed) { + return ecex_eval_current_buffer(ed); +} + +static int cmd_eval_line(ecex_t *ed) { + return ecex_eval_current_line(ed); +} + +static int cmd_eval_region(ecex_t *ed) { + return ecex_eval_current_region(ed); +} + +static int cmd_eval_file(ecex_t *ed) { + ecex_request_prompt(ed, ECEX_PROMPT_EVAL_FILE, "Eval file: "); + return ECEX_OK; +} + +static int cmd_eval_rerun_last(ecex_t *ed) { + return ecex_eval_rerun_last(ed); +} + +static int cmd_quit_window(ecex_t *ed) { + if (!ed) return ECEX_ERR; + if (ecex_window_count(ed) > 1) return ecex_delete_window(ed); + return ecex_previous_buffer(ed); +} + +static int cmd_reload_config(ecex_t *ed) { + return ecex_reload_config(ed); +} + +static int ecex_register_builtins(ecex_t *ed) { + ECEX_COMMAND("quit", cmd_quit); + ECEX_COMMAND("find-file", cmd_find_file); + ECEX_COMMAND("save-buffer", cmd_save_buffer); + ECEX_COMMAND("write-file", cmd_write_file); + ECEX_COMMAND("eval-buffer", cmd_eval_buffer); + ECEX_COMMAND("eval-line", cmd_eval_line); + ECEX_COMMAND("eval-region", cmd_eval_region); + ECEX_COMMAND("eval-selection", cmd_eval_region); + ECEX_COMMAND("eval-file", cmd_eval_file); + ECEX_COMMAND("eval-rerun-last", cmd_eval_rerun_last); + ECEX_COMMAND("revert-eval-buffer", cmd_eval_rerun_last); + ECEX_COMMAND("quit-window", cmd_quit_window); + ECEX_COMMAND("reload-config", cmd_reload_config); + ECEX_COMMAND("list-commands", cmd_list_commands); + ECEX_COMMAND("list-buffers", cmd_list_buffers); + ECEX_COMMAND("switch-buffer", cmd_switch_buffer); + ECEX_COMMAND("kill-buffer", cmd_kill_buffer_command); + ECEX_COMMAND("compile", cmd_compile); + ECEX_COMMAND("recompile", cmd_recompile); + ECEX_COMMAND("grep", cmd_grep); + ECEX_COMMAND("regrep", cmd_regrep); + ECEX_COMMAND("next-error", cmd_next_error); + ECEX_COMMAND("previous-error", cmd_previous_error); + ECEX_COMMAND("comment-region", cmd_comment_region); + ECEX_COMMAND("uncomment-region", cmd_uncomment_region); + ECEX_COMMAND("toggle-line-numbers", cmd_toggle_line_numbers); + ECEX_COMMAND("toggle-current-line", cmd_toggle_current_line); + ECEX_COMMAND("isearch-forward", cmd_isearch_forward); + ECEX_COMMAND("isearch-backward", cmd_isearch_backward); + + ECEX_COMMAND("next-buffer", cmd_next_buffer); + ECEX_COMMAND("previous-buffer", cmd_previous_buffer); + ECEX_COMMAND("split-window-right", cmd_split_window_right); + ECEX_COMMAND("split-window-vertically", cmd_split_window_right); + ECEX_COMMAND("split-window-below", cmd_split_window_below); + ECEX_COMMAND("split-window-horizontally", cmd_split_window_below); + ECEX_COMMAND("other-window", cmd_other_window); + ECEX_COMMAND("previous-window", cmd_previous_window); + ECEX_COMMAND("delete-window", cmd_delete_window); + ECEX_COMMAND("delete-other-windows", cmd_delete_other_windows); + ECEX_COMMAND("balance-windows", cmd_balance_windows); + + ECEX_COMMAND("move-left", cmd_move_left); + ECEX_COMMAND("move-right", cmd_move_right); + ECEX_COMMAND("move-up", cmd_move_up); + ECEX_COMMAND("move-down", cmd_move_down); + ECEX_COMMAND("move-word-left", cmd_move_word_left); + ECEX_COMMAND("move-word-right", cmd_move_word_right); + ECEX_COMMAND("beginning-of-line", cmd_beginning_of_line); + ECEX_COMMAND("end-of-line", cmd_end_of_line); + ECEX_COMMAND("beginning-of-buffer", cmd_beginning_of_buffer); + ECEX_COMMAND("end-of-buffer", cmd_end_of_buffer); + + ECEX_COMMAND("undo", cmd_undo); + ECEX_COMMAND("redo", cmd_redo); + ECEX_COMMAND("backspace", cmd_backspace); + ECEX_COMMAND("delete-forward", cmd_delete_forward); + ECEX_COMMAND("newline", cmd_newline); + ECEX_COMMAND("kill-line", cmd_kill_line); + ECEX_COMMAND("clear-buffer", cmd_clear_buffer); + ECEX_COMMAND("set-mark", cmd_set_mark); + ECEX_COMMAND("clear-mark", cmd_clear_mark); + ECEX_COMMAND("yank", cmd_yank); + ECEX_COMMAND("paste", cmd_yank); + ECEX_COMMAND("copy-region", cmd_copy_region); + ECEX_COMMAND("copy-region-as-kill", cmd_copy_region); + ECEX_COMMAND("kill-region", cmd_kill_region); + + ECEX_BIND("LEFT", "move-left"); + ECEX_BIND("RIGHT", "move-right"); + ECEX_BIND("UP", "move-up"); + ECEX_BIND("DOWN", "move-down"); + + ECEX_BIND("C-b", "move-left"); + ECEX_BIND("C-f", "move-right"); + ECEX_BIND("C-p", "move-up"); + ECEX_BIND("C-n", "move-down"); + ECEX_BIND("C-a", "beginning-of-line"); + ECEX_BIND("C-e", "end-of-line"); + ECEX_BIND("C-k", "kill-line"); + ECEX_BIND("C-SPC", "set-mark"); + ECEX_BIND("C-y", "yank"); + ECEX_BIND("C-v", "paste"); + ECEX_BIND("M-w", "copy-region-as-kill"); + ECEX_BIND("C-w", "kill-region"); + ECEX_BIND("C-s", "isearch-forward"); + ECEX_BIND("C-r", "isearch-backward"); + ECEX_BIND("C-/", "undo"); + ECEX_BIND("C-z", "undo"); + ECEX_BIND("C-S-z", "redo"); + + ECEX_BIND("C-x C-f", "find-file"); + ECEX_BIND("C-x C-s", "save-buffer"); + ECEX_BIND("C-x C-w", "write-file"); + ECEX_BIND("C-x C-b", "list-buffers"); + ECEX_BIND("C-x b", "switch-buffer"); + ECEX_BIND("C-x k", "kill-buffer"); + ECEX_BIND("C-x 2", "split-window-below"); + ECEX_BIND("C-x 3", "split-window-right"); + ECEX_BIND("C-x o", "other-window"); + ECEX_BIND("C-x O", "previous-window"); + ECEX_BIND("C-x 0", "delete-window"); + ECEX_BIND("C-x 1", "delete-other-windows"); + ECEX_BIND("C-x +", "balance-windows"); + ECEX_BIND("C-x e b", "eval-buffer"); + ECEX_BIND("C-x e l", "eval-line"); + ECEX_BIND("C-x C-e", "eval-line"); + ECEX_BIND("C-x e r", "eval-region"); + ECEX_BIND("C-x e f", "eval-file"); + ECEX_BIND("C-x C-r", "reload-config"); + ECEX_BIND("F5", "reload-config"); + ECEX_BIND("M-g n", "next-error"); + ECEX_BIND("M-g p", "previous-error"); + ECEX_BIND("M-;", "comment-region"); + + ECEX_BIND("BACKSPACE", "backspace"); + ECEX_BIND("DELETE", "delete-forward"); + ECEX_BIND("ENTER", "newline"); + + ecex_define_major_mode(ed, "fundamental-mode"); + ecex_define_major_mode(ed, "c-mode"); + ecex_define_major_mode(ed, "eval-output-mode"); + ecex_define_major_mode(ed, "special-mode"); + ecex_bind_mode_key(ed, "eval-output-mode", "g", "eval-rerun-last"); + ecex_bind_mode_key(ed, "eval-output-mode", "q", "quit-window"); + ecex_bind_mode_key(ed, "eval-output-mode", "r", "eval-rerun-last"); + ecex_bind_mode_key(ed, "special-mode", "q", "quit-window"); + ecex_bind_mode_key(ed, "special-mode", "g", "recompile"); + ecex_bind_mode_key(ed, "special-mode", "n", "next-error"); + ecex_bind_mode_key(ed, "special-mode", "p", "previous-error"); + + return ECEX_OK; +} + +#undef ECEX_COMMAND +#undef ECEX_BIND +#undef CURRENT_BUFFER_OR_ERR + +ecex_t *ecex_new(void) { + ecex_t *ed = calloc(1, sizeof(*ed)); + if (!ed) return NULL; + + ecex_theme_set_defaults(ed); + + buffer_t *scratch = buffer_new("*scratch*", NULL, 0); + if (!scratch || ecex_add_buffer(ed, scratch) != ECEX_OK) { + buffer_free(scratch); + ecex_free(ed); + return NULL; + } + + ed->current_buffer_index = 0; + ed->current_buffer = scratch; + ed->next_major_mode_id = 1; + + ed->window_cap = ECEX_INITIAL_WINDOW_CAP; + ed->windows = calloc(ed->window_cap, sizeof(*ed->windows)); + if (!ed->windows) { + ecex_free(ed); + return NULL; + } + ed->window_count = 1; + ed->current_window_index = 0; + ed->windows[0].buffer = scratch; + ed->windows[0].x = 0.0f; + ed->windows[0].y = 0.0f; + ed->windows[0].w = 1.0f; + ed->windows[0].h = 1.0f; + + if (ecex_register_builtins(ed) != ECEX_OK) { + ecex_free(ed); + return NULL; + } + + ecex_buffer_set_major_mode_by_name(ed, scratch, "fundamental-mode"); + + return ed; +} + +void ecex_free(ecex_t *ed) { + if (!ed) return; + + for (size_t i = 0; i < ed->jit_module_count; i++) { + ccdjit_module_free((ccdjit_module *)ed->jit_modules[i]); + } + + for (size_t i = 0; i < ed->buffer_count; i++) { + buffer_free(ed->buffers[i]); + } + + for (size_t i = 0; i < ed->command_count; i++) { + free(ed->commands[i].name); + } + + for (size_t i = 0; i < ed->keybind_count; i++) { + free(ed->keybinds[i].key); + free(ed->keybinds[i].command); + } + + for (size_t i = 0; i < ed->mode_keybind_count; i++) { + free(ed->mode_keybinds[i].key); + free(ed->mode_keybinds[i].command); + } + + for (size_t i = 0; i < ed->major_mode_count; i++) { + free(ed->major_modes[i].name); + } + + free(ed->jit_modules); + free(ed->windows); + free(ed->buffers); + free(ed->commands); + free(ed->keybinds); + free(ed->mode_keybinds); + free(ed->major_modes); + free(ed->last_eval_source); + free(ed->last_eval_filename); + free(ed->last_compile_command); + free(ed->last_grep_command); + free(ed->config_path); + free(ed->theme.font_path); + free(ed); +} + +int ecex_reserve_buffers(ecex_t *ed, size_t needed) { + ECEX_RETURN_ERR_IF_NULL(ed); + if (needed <= ed->buffer_cap) return ECEX_OK; + + while (ed->buffer_cap < needed) { + if (ECEX_GROW_ARRAY(ed->buffers, + ed->buffer_count, + ed->buffer_cap, + ECEX_INITIAL_BUFFER_CAP) != ECEX_OK) { + return ECEX_ERR; + } + } + + return ECEX_OK; +} + +int ecex_add_buffer(ecex_t *ed, buffer_t *buffer) { + if (!ed || !buffer) return ECEX_ERR; + + if (ecex_reserve_buffers(ed, ed->buffer_count + 1) != ECEX_OK) return ECEX_ERR; + + ed->buffers[ed->buffer_count++] = buffer; + + if (!ed->current_buffer) { + ed->current_buffer_index = 0; + ed->current_buffer = buffer; + } + + return ECEX_OK; +} + +buffer_t *ecex_create_buffer(ecex_t *ed, + const char *name, + const char *path, + int read_only) { + if (!ed || !name) return NULL; + + buffer_t *buffer = buffer_new(name, path, read_only); + if (!buffer) return NULL; + + if (ecex_add_buffer(ed, buffer) != ECEX_OK) { + buffer_free(buffer); + return NULL; + } + + return buffer; +} + +buffer_t *ecex_find_buffer(ecex_t *ed, const char *name) { + if (!ed || !name) return NULL; + + for (size_t i = 0; i < ed->buffer_count; i++) { + if (strcmp(ed->buffers[i]->name, name) == 0) { + return ed->buffers[i]; + } + } + + return NULL; +} + + +static int ecex_set_current_buffer_index(ecex_t *ed, size_t index) { + if (!ed || index >= ed->buffer_count) return ECEX_ERR; + ed->current_buffer_index = index; + ed->current_buffer = ed->buffers[index]; + ecex_window_t *win = ecex_current_window(ed); + if (win) win->buffer = ed->current_buffer; + return ECEX_OK; +} + +int ecex_sync_current_buffer(ecex_t *ed) { + if (!ed) return ECEX_ERR; + ecex_window_t *win = ecex_current_window(ed); + if (!win || !win->buffer) return ECEX_ERR; + + ed->current_buffer = win->buffer; + for (size_t i = 0; i < ed->buffer_count; i++) { + if (ed->buffers[i] == win->buffer) { + ed->current_buffer_index = i; + return ECEX_OK; + } + } + return ECEX_ERR; +} + +int ecex_switch_buffer(ecex_t *ed, const char *name) { + if (!ed || !name) return ECEX_ERR; + + for (size_t i = 0; i < ed->buffer_count; i++) { + if (strcmp(ed->buffers[i]->name, name) == 0) { + return ecex_set_current_buffer_index(ed, i); + } + } + + return ECEX_ERR; +} + +buffer_t *ecex_current_buffer(ecex_t *ed) { + if (!ed) return NULL; + ecex_window_t *win = ecex_current_window(ed); + if (win && win->buffer) return win->buffer; + return ed->current_buffer; +} + +ecex_window_t *ecex_current_window(ecex_t *ed) { + if (!ed || ed->window_count == 0 || ed->current_window_index >= ed->window_count) return NULL; + return &ed->windows[ed->current_window_index]; +} + +size_t ecex_window_count(ecex_t *ed) { + return ed ? ed->window_count : 0; +} + +static int ecex_reserve_windows(ecex_t *ed, size_t needed) { + if (!ed) return ECEX_ERR; + if (needed <= ed->window_cap) return ECEX_OK; + while (ed->window_cap < needed) { + if (ECEX_GROW_ARRAY(ed->windows, + ed->window_count, + ed->window_cap, + ECEX_INITIAL_WINDOW_CAP) != ECEX_OK) { + return ECEX_ERR; + } + } + return ECEX_OK; +} + +static int ecex_add_window(ecex_t *ed, ecex_window_t win) { + if (!ed || !win.buffer) return ECEX_ERR; + if (ecex_reserve_windows(ed, ed->window_count + 1) != ECEX_OK) return ECEX_ERR; + ed->windows[ed->window_count++] = win; + return ECEX_OK; +} + +int ecex_split_window_vertically(ecex_t *ed) { + ecex_window_t *active = ecex_current_window(ed); + if (!active || !active->buffer || active->w < 0.08f) return ECEX_ERR; + + ecex_window_t new_win = *active; + float half = active->w * 0.5f; + active->w = half; + new_win.x = active->x + half; + new_win.w = half; + + if (ecex_add_window(ed, new_win) != ECEX_OK) return ECEX_ERR; + ed->current_window_index = ed->window_count - 1; + return ecex_sync_current_buffer(ed); +} + +int ecex_split_window_horizontally(ecex_t *ed) { + ecex_window_t *active = ecex_current_window(ed); + if (!active || !active->buffer || active->h < 0.08f) return ECEX_ERR; + + ecex_window_t new_win = *active; + float half = active->h * 0.5f; + active->h = half; + new_win.y = active->y + half; + new_win.h = half; + + if (ecex_add_window(ed, new_win) != ECEX_OK) return ECEX_ERR; + ed->current_window_index = ed->window_count - 1; + return ecex_sync_current_buffer(ed); +} + +int ecex_other_window(ecex_t *ed) { + if (!ed || ed->window_count == 0) return ECEX_ERR; + ed->current_window_index = (ed->current_window_index + 1) % ed->window_count; + return ecex_sync_current_buffer(ed); +} + +int ecex_previous_window(ecex_t *ed) { + if (!ed || ed->window_count == 0) return ECEX_ERR; + if (ed->current_window_index == 0) ed->current_window_index = ed->window_count - 1; + else ed->current_window_index--; + return ecex_sync_current_buffer(ed); +} + +int ecex_delete_window(ecex_t *ed) { + if (!ed || ed->window_count <= 1 || ed->current_window_index >= ed->window_count) return ECEX_ERR; + + size_t index = ed->current_window_index; + for (size_t i = index; i + 1 < ed->window_count; i++) { + ed->windows[i] = ed->windows[i + 1]; + } + ed->window_count--; + if (ed->current_window_index >= ed->window_count) ed->current_window_index = ed->window_count - 1; + return ecex_sync_current_buffer(ed); +} + +int ecex_delete_other_windows(ecex_t *ed) { + ecex_window_t *active = ecex_current_window(ed); + if (!ed || !active) return ECEX_ERR; + ecex_window_t keep = *active; + keep.x = 0.0f; keep.y = 0.0f; keep.w = 1.0f; keep.h = 1.0f; + ed->windows[0] = keep; + ed->window_count = 1; + ed->current_window_index = 0; + return ecex_sync_current_buffer(ed); +} + +int ecex_balance_windows(ecex_t *ed) { + if (!ed || ed->window_count == 0) return ECEX_ERR; + size_t n = ed->window_count; + size_t cols = 1; + while (cols * cols < n) cols++; + size_t rows = (n + cols - 1) / cols; + for (size_t i = 0; i < n; i++) { + size_t row = i / cols; + size_t col = i % cols; + ed->windows[i].x = (float)col / (float)cols; + ed->windows[i].y = (float)row / (float)rows; + ed->windows[i].w = 1.0f / (float)cols; + ed->windows[i].h = 1.0f / (float)rows; + } + return ecex_sync_current_buffer(ed); +} + +int ecex_next_buffer(ecex_t *ed) { + if (!ed || ed->buffer_count == 0) return ECEX_ERR; + ecex_sync_current_buffer(ed); + return ecex_set_current_buffer_index(ed, (ed->current_buffer_index + 1) % ed->buffer_count); +} + +int ecex_previous_buffer(ecex_t *ed) { + if (!ed || ed->buffer_count == 0) return ECEX_ERR; + ecex_sync_current_buffer(ed); + + if (ed->current_buffer_index == 0) { + ed->current_buffer_index = ed->buffer_count - 1; + } else { + ed->current_buffer_index--; + } + + return ecex_set_current_buffer_index(ed, ed->current_buffer_index); +} + +int ecex_kill_buffer(ecex_t *ed, const char *name) { + if (!ed || !name || ed->buffer_count == 0) return ECEX_ERR; + + size_t index = ed->buffer_count; + for (size_t i = 0; i < ed->buffer_count; i++) { + if (strcmp(ed->buffers[i]->name, name) == 0) { + index = i; + break; + } + } + + if (index == ed->buffer_count) return ECEX_ERR; + + buffer_t *victim = ed->buffers[index]; + + for (size_t i = index; i + 1 < ed->buffer_count; i++) { + ed->buffers[i] = ed->buffers[i + 1]; + } + + ed->buffer_count--; + + buffer_t *fallback = ed->buffer_count > 0 ? ed->buffers[0] : NULL; + for (size_t i = 0; i < ed->window_count; i++) { + if (ed->windows[i].buffer == victim) { + ed->windows[i].buffer = fallback; + } + } + + buffer_free(victim); + + if (ed->buffer_count == 0) { + ed->current_buffer = NULL; + ed->current_buffer_index = 0; + ed->window_count = 0; + ed->current_window_index = 0; + return ECEX_OK; + } + + if (ed->current_window_index >= ed->window_count) { + ed->current_window_index = ed->window_count ? ed->window_count - 1 : 0; + } + + return ecex_sync_current_buffer(ed); +} + +int ecex_keep_jit_module(ecex_t *ed, void *module) { + if (!ed || !module) return ECEX_ERR; + + if (ECEX_GROW_ARRAY(ed->jit_modules, + ed->jit_module_count, + ed->jit_module_cap, + 8) != ECEX_OK) { + return ECEX_ERR; + } + + ed->jit_modules[ed->jit_module_count++] = module; + return ECEX_OK; +} + +int ecex_set_config_path(ecex_t *ed, const char *path) { + if (!ed) return ECEX_ERR; + + char *copy = NULL; + if (path && path[0]) { + copy = ecex_strdup(path); + if (!copy) return ECEX_ERR; + } + + free(ed->config_path); + ed->config_path = copy; + return ECEX_OK; +} + +const char *ecex_config_path(ecex_t *ed) { + if (!ed) return NULL; + return ed->config_path; +} + +static void ecex_clear_commands(ecex_t *ed) { + if (!ed) return; + + for (size_t i = 0; i < ed->command_count; i++) { + free(ed->commands[i].name); + } + ed->command_count = 0; +} + +static void ecex_clear_keybinds(ecex_t *ed) { + if (!ed) return; + + for (size_t i = 0; i < ed->keybind_count; i++) { + free(ed->keybinds[i].key); + free(ed->keybinds[i].command); + } + ed->keybind_count = 0; +} + +static void ecex_clear_mode_keybinds(ecex_t *ed) { + if (!ed) return; + + for (size_t i = 0; i < ed->mode_keybind_count; i++) { + free(ed->mode_keybinds[i].key); + free(ed->mode_keybinds[i].command); + } + ed->mode_keybind_count = 0; +} + +int ecex_reload_config(ecex_t *ed) { + if (!ed || !ed->config_path || !ed->config_path[0]) { + fprintf(stderr, "ecex: no config file to reload; start with --config path/to/ecexrc.c\n"); + return ECEX_ERR; + } + + char *path = ecex_strdup(ed->config_path); + if (!path) return ECEX_ERR; + + ecex_clear_commands(ed); + ecex_clear_keybinds(ed); + ecex_clear_mode_keybinds(ed); + + if (ecex_register_builtins(ed) != ECEX_OK) { + free(path); + return ECEX_ERR; + } + + int result = ecex_load_c_config(ed, path); + free(path); + return result; +} + +int ecex_register_command(ecex_t *ed, const char *name, ecex_command_fn fn) { + if (!ed || !name || !fn) return ECEX_ERR; + + for (size_t i = 0; i < ed->command_count; i++) { + if (strcmp(ed->commands[i].name, name) == 0) { + ed->commands[i].fn = fn; + return ECEX_OK; + } + } + + if (ECEX_GROW_ARRAY(ed->commands, + ed->command_count, + ed->command_cap, + ECEX_INITIAL_COMMAND_CAP) != ECEX_OK) { + return ECEX_ERR; + } + + char *copy = ecex_strdup(name); + if (!copy) return ECEX_ERR; + + ed->commands[ed->command_count].name = copy; + ed->commands[ed->command_count].fn = fn; + ed->command_count++; + return ECEX_OK; +} + +int ecex_execute_command(ecex_t *ed, const char *name) { + if (!ed || !name) return ECEX_ERR; + + for (size_t i = 0; i < ed->command_count; i++) { + if (strcmp(ed->commands[i].name, name) == 0) { + return ed->commands[i].fn(ed); + } + } + + return ECEX_ERR; +} + +void ecex_set_clipboard_callbacks(ecex_t *ed, + ecex_clipboard_get_fn get_fn, + ecex_clipboard_set_fn set_fn, + void *userdata) { + if (!ed) return; + ed->clipboard_get = get_fn; + ed->clipboard_set = set_fn; + ed->clipboard_userdata = userdata; +} + +const char *ecex_clipboard_get(ecex_t *ed) { + if (!ed || !ed->clipboard_get) return NULL; + return ed->clipboard_get(ed->clipboard_userdata); +} + +int ecex_clipboard_set(ecex_t *ed, const char *text) { + if (!ed || !ed->clipboard_set || !text) return ECEX_ERR; + ed->clipboard_set(ed->clipboard_userdata, text); + return ECEX_OK; +} + +int ecex_bind_key(ecex_t *ed, const char *key, const char *command) { + if (!ed || !key || !command) return ECEX_ERR; + + for (size_t i = 0; i < ed->keybind_count; i++) { + if (strcmp(ed->keybinds[i].key, key) == 0) { + char *new_command = ecex_strdup(command); + if (!new_command) return ECEX_ERR; + + free(ed->keybinds[i].command); + ed->keybinds[i].command = new_command; + return ECEX_OK; + } + } + + if (ECEX_GROW_ARRAY(ed->keybinds, + ed->keybind_count, + ed->keybind_cap, + ECEX_INITIAL_KEYBIND_CAP) != ECEX_OK) { + return ECEX_ERR; + } + + char *key_copy = ecex_strdup(key); + char *command_copy = ecex_strdup(command); + if (!key_copy || !command_copy) { + free(key_copy); + free(command_copy); + return ECEX_ERR; + } + + ed->keybinds[ed->keybind_count].key = key_copy; + ed->keybinds[ed->keybind_count].command = command_copy; + ed->keybind_count++; + return ECEX_OK; +} + +const char *ecex_lookup_key(ecex_t *ed, const char *key) { + if (!ed || !key) return NULL; + + for (size_t i = 0; i < ed->keybind_count; i++) { + if (strcmp(ed->keybinds[i].key, key) == 0) { + return ed->keybinds[i].command; + } + } + + return NULL; +} + +int ecex_define_major_mode(ecex_t *ed, const char *name) { + if (!ed || !name || !name[0]) return 0; + + for (size_t i = 0; i < ed->major_mode_count; i++) { + if (strcmp(ed->major_modes[i].name, name) == 0) { + return ed->major_modes[i].id; + } + } + + if (ECEX_GROW_ARRAY(ed->major_modes, + ed->major_mode_count, + ed->major_mode_cap, + ECEX_INITIAL_MODE_CAP) != ECEX_OK) { + return 0; + } + + char *copy = ecex_strdup(name); + if (!copy) return 0; + + int id = ed->next_major_mode_id++; + ed->major_modes[ed->major_mode_count].id = id; + ed->major_modes[ed->major_mode_count].name = copy; + ed->major_mode_count++; + return id; +} + +int ecex_major_mode_by_name(ecex_t *ed, const char *name) { + if (!ed || !name) return 0; + for (size_t i = 0; i < ed->major_mode_count; i++) { + if (strcmp(ed->major_modes[i].name, name) == 0) return ed->major_modes[i].id; + } + return 0; +} + +const char *ecex_major_mode_name(ecex_t *ed, int mode) { + if (!ed || mode == 0) return "fundamental-mode"; + for (size_t i = 0; i < ed->major_mode_count; i++) { + if (ed->major_modes[i].id == mode) return ed->major_modes[i].name; + } + return "unknown-mode"; +} + +int ecex_buffer_set_major_mode(buffer_t *buffer, int mode) { + if (!buffer) return ECEX_ERR; + buffer->major_mode = mode; + return ECEX_OK; +} + +int ecex_buffer_set_major_mode_by_name(ecex_t *ed, buffer_t *buffer, const char *name) { + if (!ed || !buffer || !name) return ECEX_ERR; + int mode = ecex_define_major_mode(ed, name); + if (!mode) return ECEX_ERR; + return ecex_buffer_set_major_mode(buffer, mode); +} + +const char *ecex_buffer_major_mode_name(ecex_t *ed, buffer_t *buffer) { + if (!buffer) return "fundamental-mode"; + return ecex_major_mode_name(ed, buffer->major_mode); +} + +int ecex_bind_mode_key(ecex_t *ed, const char *mode_name, const char *key, const char *command) { + if (!ed || !mode_name || !key || !command) return ECEX_ERR; + + int mode = ecex_define_major_mode(ed, mode_name); + if (!mode) return ECEX_ERR; + + for (size_t i = 0; i < ed->mode_keybind_count; i++) { + if (ed->mode_keybinds[i].mode == mode && strcmp(ed->mode_keybinds[i].key, key) == 0) { + char *new_command = ecex_strdup(command); + if (!new_command) return ECEX_ERR; + free(ed->mode_keybinds[i].command); + ed->mode_keybinds[i].command = new_command; + return ECEX_OK; + } + } + + if (ECEX_GROW_ARRAY(ed->mode_keybinds, + ed->mode_keybind_count, + ed->mode_keybind_cap, + ECEX_INITIAL_MODE_KEYBIND_CAP) != ECEX_OK) { + return ECEX_ERR; + } + + char *key_copy = ecex_strdup(key); + char *command_copy = ecex_strdup(command); + if (!key_copy || !command_copy) { + free(key_copy); + free(command_copy); + return ECEX_ERR; + } + + ed->mode_keybinds[ed->mode_keybind_count].mode = mode; + ed->mode_keybinds[ed->mode_keybind_count].key = key_copy; + ed->mode_keybinds[ed->mode_keybind_count].command = command_copy; + ed->mode_keybind_count++; + return ECEX_OK; +} + +const char *ecex_lookup_key_for_buffer(ecex_t *ed, buffer_t *buffer, const char *key) { + if (!ed || !key) return NULL; + + int mode = buffer ? buffer->major_mode : 0; + if (mode) { + for (size_t i = 0; i < ed->mode_keybind_count; i++) { + if (ed->mode_keybinds[i].mode == mode && strcmp(ed->mode_keybinds[i].key, key) == 0) { + return ed->mode_keybinds[i].command; + } + } + } + + return ecex_lookup_key(ed, key); +} + +int ecex_key_sequence_has_prefix_for_buffer(ecex_t *ed, buffer_t *buffer, const char *prefix) { + if (!ed || !prefix) return 0; + size_t prefix_len = strlen(prefix); + int mode = buffer ? buffer->major_mode : 0; + + if (mode) { + for (size_t i = 0; i < ed->mode_keybind_count; i++) { + const char *key = ed->mode_keybinds[i].key; + if (ed->mode_keybinds[i].mode == mode && + strncmp(key, prefix, prefix_len) == 0 && key[prefix_len] == ' ') { + return 1; + } + } + } + + for (size_t i = 0; i < ed->keybind_count; i++) { + const char *key = ed->keybinds[i].key; + if (strncmp(key, prefix, prefix_len) == 0 && key[prefix_len] == ' ') return 1; + } + + return 0; +} + +int ecex_auto_set_major_mode(ecex_t *ed, buffer_t *buffer) { + if (!ed || !buffer) return ECEX_ERR; + + const char *name = "fundamental-mode"; + const char *path = buffer->path ? buffer->path : buffer->name; + const char *dot = path ? strrchr(path, '.') : NULL; + + if (dot && (strcmp(dot, ".c") == 0 || strcmp(dot, ".h") == 0 || + strcmp(dot, ".cc") == 0 || strcmp(dot, ".cpp") == 0 || + strcmp(dot, ".hpp") == 0)) { + name = "c-mode"; + } + + if (buffer_is_interactive(buffer) && buffer->name && buffer->name[0] == '*') { + name = "special-mode"; + } + + return ecex_buffer_set_major_mode_by_name(ed, buffer, name); +} + +int ecex_list_commands(ecex_t *ed) { + if (!ed) return ECEX_ERR; + + buffer_t *buf = ecex_find_buffer(ed, "*commands*"); + if (!buf) { + buf = ecex_create_buffer(ed, "*commands*", NULL, 0); + if (!buf) return ECEX_ERR; + } + + if (buffer_clear(buf) != ECEX_OK) return ECEX_ERR; + buffer_append(buf, "Commands:\n\n"); + + for (size_t i = 0; i < ed->command_count; i++) { + const char *command_name = ed->commands[i].name; + int first_key = 1; + + buffer_append(buf, " "); + buffer_append(buf, command_name); + + for (size_t j = 0; j < ed->keybind_count; j++) { + if (strcmp(ed->keybinds[j].command, command_name) == 0) { + buffer_append(buf, first_key ? " [" : ", "); + buffer_append(buf, ed->keybinds[j].key); + first_key = 0; + } + } + + for (size_t j = 0; j < ed->mode_keybind_count; j++) { + if (strcmp(ed->mode_keybinds[j].command, command_name) == 0) { + buffer_append(buf, first_key ? " [" : ", "); + buffer_append(buf, ecex_major_mode_name(ed, ed->mode_keybinds[j].mode)); + buffer_append(buf, ":"); + buffer_append(buf, ed->mode_keybinds[j].key); + first_key = 0; + } + } + + if (!first_key) buffer_append(buf, "]"); + buffer_append(buf, "\n"); + } + + buf->point = 0; + buf->modified = 0; + return ecex_switch_buffer(ed, "*commands*"); +} + + + +static int ecex_interactive_switch_buffer_action(ecex_t *ed, + buffer_t *buffer, + size_t line, + const char *payload, + void *userdata) { + (void)buffer; + (void)line; + (void)userdata; + + if (!ed || !payload) return ECEX_ERR; + return ecex_switch_buffer(ed, payload); +} + +int ecex_list_buffers(ecex_t *ed) { + if (!ed) return ECEX_ERR; + + buffer_t *buf = ecex_find_buffer(ed, "*buffers*"); + if (!buf) { + buf = ecex_create_interactive_buffer(ed, "*buffers*"); + if (!buf) return ECEX_ERR; + } + + buffer_set_interactive(buf, 1); + if (buffer_clear(buf) != ECEX_OK) return ECEX_ERR; + buffer_set_interactive(buf, 1); + + buffer_append(buf, "Buffers:\n"); + buffer_append(buf, "Press ENTER on a buffer to switch to it.\n\n"); + + for (size_t i = 0; i < ed->buffer_count; i++) { + buffer_t *b = ed->buffers[i]; + char line[1024]; + + snprintf(line, + sizeof(line), + "%s %s %-24s %-18s size:%zu%s%s", + b == ed->current_buffer ? "*" : " ", + b->modified ? "+" : " ", + b->name ? b->name : "(unnamed)", + ecex_buffer_major_mode_name(ed, b), + b->len, + b->path ? " " : "", + b->path ? b->path : ""); + + if (ecex_interactive_append_line(ed, + buf, + line, + ecex_interactive_switch_buffer_action, + b->name, + NULL) != ECEX_OK) { + return ECEX_ERR; + } + } + + buf->point = 0; + buf->modified = 0; + return ecex_switch_buffer(ed, "*buffers*"); +} + +buffer_t *ecex_create_interactive_buffer(ecex_t *ed, const char *name) { + if (!ed || !name) return NULL; + + buffer_t *buffer = ecex_find_buffer(ed, name); + if (!buffer) { + buffer = ecex_create_buffer(ed, name, NULL, 0); + if (!buffer) return NULL; + } + + buffer_set_interactive(buffer, 1); + if (buffer->major_mode == 0) ecex_buffer_set_major_mode_by_name(ed, buffer, "special-mode"); + return buffer; +} + +int ecex_interactive_append_line(ecex_t *ed, + buffer_t *buffer, + const char *text, + ecex_interactive_line_fn fn, + const char *payload, + void *userdata) { + (void)ed; + + if (!buffer || !text) return ECEX_ERR; + + size_t line = buffer_line_count(buffer); + if (line > 0) line--; + + if (fn) { + if (buffer_add_interactive_action(buffer, line, fn, payload, userdata) != ECEX_OK) { + return ECEX_ERR; + } + } + + if (buffer_append(buffer, text) != ECEX_OK) return ECEX_ERR; + if (buffer_append(buffer, "\n") != ECEX_OK) return ECEX_ERR; + + buffer_set_interactive(buffer, 1); + return ECEX_OK; +} + +int ecex_interactive_activate_current_line(ecex_t *ed) { + if (!ed) return ECEX_ERR; + + buffer_t *buffer = ecex_current_buffer(ed); + if (!buffer || !buffer_is_interactive(buffer)) return ECEX_ERR; + + size_t line = buffer_current_line_number(buffer); + if (line > 0) line--; + + ecex_interactive_line_action_t *action = + buffer_interactive_action_at_line(buffer, line); + + if (!action || !action->fn) return ECEX_ERR; + return action->fn(ed, buffer, line, action->payload, action->userdata); +} + +static const char *ecex_basename(const char *path) { + if (!path || !path[0]) return "untitled"; + + const char *last_slash = strrchr(path, '/'); + if (last_slash && last_slash[1]) return last_slash + 1; + if (last_slash && last_slash == path) return "/"; + return path; +} + +static int ecex_buffer_path_equal(buffer_t *buffer, const char *path) { + return buffer && buffer->path && path && strcmp(buffer->path, path) == 0; +} + +int ecex_find_file(ecex_t *ed, const char *path) { + if (!ed || !path || !path[0]) return ECEX_ERR; + + for (size_t i = 0; i < ed->buffer_count; i++) { + if (ecex_buffer_path_equal(ed->buffers[i], path)) { + return ecex_set_current_buffer_index(ed, i); + } + } + + const char *name = ecex_basename(path); + buffer_t *buf = ecex_create_buffer(ed, name, NULL, 0); + if (!buf) return ECEX_ERR; + + if (ecex_file_exists(path)) { + if (buffer_load_file(buf, path) != ECEX_OK) { + ecex_kill_buffer(ed, buf->name); + return ECEX_ERR; + } + } else { + char *new_path = ecex_strdup(path); + if (!new_path) { + ecex_kill_buffer(ed, buf->name); + return ECEX_ERR; + } + + free(buf->path); + buf->path = new_path; + buf->modified = 0; + } + + ecex_auto_set_major_mode(ed, buf); + return ecex_set_current_buffer_index(ed, ed->buffer_count ? ed->buffer_count - 1 : 0); +} + +int ecex_save_current_buffer(ecex_t *ed) { + buffer_t *buf = ecex_current_buffer(ed); + if (!buf) return ECEX_ERR; + return buffer_save(buf); +} + +int ecex_write_current_buffer(ecex_t *ed, const char *path) { + buffer_t *buf = ecex_current_buffer(ed); + if (!buf || !path || !path[0]) return ECEX_ERR; + int result = buffer_save_as(buf, path); + if (result == ECEX_OK) ecex_auto_set_major_mode(ed, buf); + return result; +} + +void ecex_request_prompt(ecex_t *ed, + ecex_prompt_request_t request, + const char *message) { + if (!ed) return; + + ed->prompt_request = request; + snprintf(ed->prompt_message, + sizeof(ed->prompt_message), + "%s", + message ? message : ""); +} + +void ecex_clear_prompt_request(ecex_t *ed) { + if (!ed) return; + ed->prompt_request = ECEX_PROMPT_NONE; + ed->prompt_message[0] = '\0'; +} + +static int ecex_fuzzy_score(const char *candidate, const char *query) { + if (!candidate || !query) return -1; + if (query[0] == '\0') return 0; + + int score = 0; + int consecutive = 0; + int last_match = -1; + size_t ci = 0; + size_t qi = 0; + + while (candidate[ci] && query[qi]) { + char c = candidate[ci]; + char q = query[qi]; + + if (c >= 'A' && c <= 'Z') c = (char)(c - 'A' + 'a'); + if (q >= 'A' && q <= 'Z') q = (char)(q - 'A' + 'a'); + + if (c == q) { + score += 10; + + if ((int)ci == last_match + 1) { + consecutive++; + score += 5 * consecutive; + } else { + consecutive = 0; + } + + if (ci == 0) score += 20; + if (ci > 0 && (candidate[ci - 1] == '-' || + candidate[ci - 1] == '_' || + candidate[ci - 1] == ' ')) { + score += 15; + } + + last_match = (int)ci; + qi++; + } + + ci++; + } + + if (query[qi] != '\0') return -1; + + score -= (int)strlen(candidate); + if (strncmp(candidate, query, strlen(query)) == 0) score += 100; + return score; +} + +const char *ecex_complete_command(ecex_t *ed, const char *query) { + if (!ed || !query || !ed->theme.completion_enabled) return NULL; + + const char *best = NULL; + int best_score = -1; + + for (size_t i = 0; i < ed->command_count; i++) { + const char *name = ed->commands[i].name; + int score = ecex_fuzzy_score(name, query); + + if (score > best_score) { + best_score = score; + best = name; + } + } + + return best; +} + + +static int ecex_set_owned_string(char **slot, const char *value) { + if (!slot) return ECEX_ERR; + char *copy = NULL; + if (value && value[0]) { + copy = ecex_strdup(value); + if (!copy) return ECEX_ERR; + } + free(*slot); + *slot = copy; + return ECEX_OK; +} + +static int ecex_parse_file_line(const char *text, char *path, size_t path_size, size_t *out_line) { + if (!text || !path || path_size == 0 || !out_line) return ECEX_ERR; + const char *colon = strchr(text, ':'); + if (!colon || colon == text) return ECEX_ERR; + char *end = NULL; + long line = strtol(colon + 1, &end, 10); + if (line <= 0 || end == colon + 1) return ECEX_ERR; + size_t len = (size_t)(colon - text); + if (len >= path_size) len = path_size - 1; + memcpy(path, text, len); + path[len] = '\0'; + *out_line = (size_t)line; + return ECEX_OK; +} + +static int ecex_goto_file_line_action(ecex_t *ed, buffer_t *buffer, size_t line, const char *payload, void *userdata) { + (void)buffer; + (void)line; + (void)userdata; + if (!ed || !payload) return ECEX_ERR; + char path[1024]; + size_t target_line = 1; + if (ecex_parse_file_line(payload, path, sizeof(path), &target_line) != ECEX_OK) return ECEX_ERR; + if (ecex_find_file(ed, path) != ECEX_OK) return ECEX_ERR; + buffer_t *buf = ecex_current_buffer(ed); + if (!buf) return ECEX_ERR; + size_t pos = 0; + for (size_t l = 1; l < target_line && pos < buf->len; pos++) { + if (buf->data[pos] == '\n') l++; + } + buffer_set_point(buf, pos); + return ECEX_OK; +} + +static int ecex_append_shell_output(ecex_t *ed, const char *name, const char *command) { + if (!ed || !name || !command || !command[0]) return ECEX_ERR; + buffer_t *buf = ecex_create_interactive_buffer(ed, name); + if (!buf) return ECEX_ERR; + buffer_set_interactive(buf, 1); + if (buffer_clear(buf) != ECEX_OK) return ECEX_ERR; + buffer_set_interactive(buf, 1); + ecex_buffer_set_major_mode_by_name(ed, buf, "special-mode"); + + char header[2048]; + snprintf(header, sizeof(header), "%s\n\nKeys: g/r rerun, n/p next/previous result, RET jump, q quit.\n\n", command); + buffer_append(buf, header); + + char shell_command[4096]; + snprintf(shell_command, sizeof(shell_command), "%s 2>&1", command); + FILE *pipe = popen(shell_command, "r"); + if (!pipe) { + buffer_append(buf, "Failed to start command.\n"); + return ecex_switch_buffer(ed, name); + } + + char line[4096]; + while (fgets(line, sizeof(line), pipe)) { + size_t len = strlen(line); + while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r')) line[--len] = '\0'; + char path[1024]; + size_t target = 0; + ecex_interactive_line_fn fn = NULL; + char payload[1200]; + payload[0] = '\0'; + if (ecex_parse_file_line(line, path, sizeof(path), &target) == ECEX_OK) { + snprintf(payload, sizeof(payload), "%s:%zu", path, target); + fn = ecex_goto_file_line_action; + } + ecex_interactive_append_line(ed, buf, line, fn, payload[0] ? payload : NULL, NULL); + } + + int status = pclose(pipe); + char footer[128]; + snprintf(footer, sizeof(footer), "\n[process exited: %d]\n", status); + buffer_append(buf, footer); + buf->point = 0; + buf->modified = 0; + return ecex_switch_buffer(ed, name); +} + +int ecex_compile(ecex_t *ed, const char *command) { + if (!ed || !command || !command[0]) return ECEX_ERR; + if (ecex_set_owned_string(&ed->last_compile_command, command) != ECEX_OK) return ECEX_ERR; + return ecex_append_shell_output(ed, "*compilation*", command); +} + +int ecex_grep(ecex_t *ed, const char *command) { + if (!ed || !command || !command[0]) return ECEX_ERR; + if (ecex_set_owned_string(&ed->last_grep_command, command) != ECEX_OK) return ECEX_ERR; + return ecex_append_shell_output(ed, "*grep*", command); +} + +int ecex_rerun_compile(ecex_t *ed) { + if (!ed || !ed->last_compile_command) return ECEX_ERR; + return ecex_append_shell_output(ed, "*compilation*", ed->last_compile_command); +} + +int ecex_rerun_grep(ecex_t *ed) { + if (!ed || !ed->last_grep_command) return ECEX_ERR; + return ecex_append_shell_output(ed, "*grep*", ed->last_grep_command); +} + +int ecex_next_interactive_action(ecex_t *ed) { + buffer_t *buf = ecex_current_buffer(ed); + if (!buf || !buffer_is_interactive(buf) || buf->interactive_action_count == 0) return ECEX_ERR; + size_t current = buffer_current_line_number(buf); + if (current > 0) current--; + size_t best = (size_t)-1; + for (size_t i = 0; i < buf->interactive_action_count; i++) { + size_t line = buf->interactive_actions[i].line; + if (line > current && (best == (size_t)-1 || line < best)) best = line; + } + if (best == (size_t)-1) best = buf->interactive_actions[0].line; + size_t pos = 0; + for (size_t l = 0; l < best && pos < buf->len; pos++) if (buf->data[pos] == '\n') l++; + buffer_set_point(buf, pos); + return ECEX_OK; +} + +int ecex_previous_interactive_action(ecex_t *ed) { + buffer_t *buf = ecex_current_buffer(ed); + if (!buf || !buffer_is_interactive(buf) || buf->interactive_action_count == 0) return ECEX_ERR; + size_t current = buffer_current_line_number(buf); + if (current > 0) current--; + size_t best = (size_t)-1; + for (size_t i = 0; i < buf->interactive_action_count; i++) { + size_t line = buf->interactive_actions[i].line; + if (line < current && (best == (size_t)-1 || line > best)) best = line; + } + if (best == (size_t)-1) best = buf->interactive_actions[buf->interactive_action_count - 1].line; + size_t pos = 0; + for (size_t l = 0; l < best && pos < buf->len; pos++) if (buf->data[pos] == '\n') l++; + buffer_set_point(buf, pos); + return ECEX_OK; +} + +static int ecex_region_lines(buffer_t *buf, size_t *out_start, size_t *out_end) { + if (!buf || !buffer_has_selection(buf)) return ECEX_ERR; + size_t start = 0, end = 0; + buffer_selection_range(buf, &start, &end); + start = buffer_line_start_at(buf, start); + end = buffer_line_end_at(buf, end); + if (end < buf->len) end++; + if (out_start) *out_start = start; + if (out_end) *out_end = end; + return ECEX_OK; +} + +int ecex_comment_region(ecex_t *ed) { + buffer_t *buf = ecex_current_buffer(ed); + size_t start = 0, end = 0; + if (!buf || ecex_region_lines(buf, &start, &end) != ECEX_OK) return ECEX_ERR; + size_t pos = start; + while (pos <= end && pos <= buf->len) { + if (buffer_insert_at(buf, pos, "// ") != ECEX_OK) return ECEX_ERR; + pos += 3; + end += 3; + size_t eol = buffer_line_end_at(buf, pos); + if (eol >= end || eol >= buf->len) break; + pos = eol + 1; + } + return ECEX_OK; +} + +int ecex_uncomment_region(ecex_t *ed) { + buffer_t *buf = ecex_current_buffer(ed); + size_t start = 0, end = 0; + if (!buf || ecex_region_lines(buf, &start, &end) != ECEX_OK) return ECEX_ERR; + size_t pos = start; + while (pos < end && pos < buf->len) { + if (pos + 2 <= buf->len && memcmp(buf->data + pos, "//", 2) == 0) { + size_t del = (pos + 3 <= buf->len && buf->data[pos + 2] == ' ') ? 3 : 2; + if (buffer_delete_range(buf, pos, pos + del) != ECEX_OK) return ECEX_ERR; + end = end > del ? end - del : 0; + } + size_t eol = buffer_line_end_at(buf, pos); + if (eol >= end || eol >= buf->len) break; + pos = eol + 1; + } + return ECEX_OK; +} + +int ecex_set_font(ecex_t *ed, const char *path) { + if (!ed || !path) return ECEX_ERR; + + char *copy = ecex_strdup(path); + if (!copy) return ECEX_ERR; + + free(ed->theme.font_path); + ed->theme.font_path = copy; + return ECEX_OK; +} + +float ecex_get_font_size(ecex_t *ed) { + if (!ed) return 0.0f; + return ed->theme.font_size; +} + +int ecex_set_font_size(ecex_t *ed, float size) { + if (!ed) return ECEX_ERR; + ed->theme.font_size = ECEX_CLAMP(size, 8.0f, 96.0f); + return ECEX_OK; +} + +int ecex_adjust_font_size(ecex_t *ed, float delta) { + if (!ed) return ECEX_ERR; + return ecex_set_font_size(ed, ed->theme.font_size + delta); +} + +void ecex_set_bg_color(ecex_t *ed, float r, float g, float b) { if (ed) ed->theme.bg = ecex_color(r, g, b); } +void ecex_set_fg_color(ecex_t *ed, float r, float g, float b) { if (ed) ed->theme.fg = ecex_color(r, g, b); } +void ecex_set_status_bg_color(ecex_t *ed, float r, float g, float b) { if (ed) ed->theme.status_bg = ecex_color(r, g, b); } +void ecex_set_status_fg_color(ecex_t *ed, float r, float g, float b) { if (ed) ed->theme.status_fg = ecex_color(r, g, b); } +void ecex_set_status_border_color(ecex_t *ed, float r, float g, float b) { if (ed) ed->theme.status_border = ecex_color(r, g, b); } +void ecex_set_cursor_color(ecex_t *ed, float r, float g, float b) { if (ed) ed->theme.cursor = ecex_color(r, g, b); } +void ecex_set_region_bg_color(ecex_t *ed, float r, float g, float b) { if (ed) ed->theme.region_bg = ecex_color(r, g, b); } +void ecex_set_minibuffer_bg_color(ecex_t *ed, float r, float g, float b) { if (ed) ed->theme.minibuffer_bg = ecex_color(r, g, b); } +void ecex_set_minibuffer_fg_color(ecex_t *ed, float r, float g, float b) { if (ed) ed->theme.minibuffer_fg = ecex_color(r, g, b); } +void ecex_set_completion_fg_color(ecex_t *ed, float r, float g, float b) { if (ed) ed->theme.completion_fg = ecex_color(r, g, b); } +void ecex_set_completion_enabled(ecex_t *ed, int enabled) { if (ed) ed->theme.completion_enabled = enabled ? 1 : 0; } +void ecex_set_interactive_highlight_bg_color(ecex_t *ed, float r, float g, float b) { if (ed) ed->theme.interactive_highlight_bg = ecex_color(r, g, b); } +void ecex_set_interactive_highlight_fg_color(ecex_t *ed, float r, float g, float b) { if (ed) ed->theme.interactive_highlight_fg = ecex_color(r, g, b); } +void ecex_set_current_line_bg_color(ecex_t *ed, float r, float g, float b) { if (ed) ed->theme.current_line_bg = ecex_color(r, g, b); } +void ecex_set_search_bg_color(ecex_t *ed, float r, float g, float b) { if (ed) ed->theme.search_bg = ecex_color(r, g, b); } +void ecex_set_line_numbers_enabled(ecex_t *ed, int enabled) { if (ed) ed->theme.line_numbers_enabled = enabled ? 1 : 0; } +void ecex_set_current_line_enabled(ecex_t *ed, int enabled) { if (ed) ed->theme.current_line_enabled = enabled ? 1 : 0; } -- cgit v1.2.3