#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; }