aboutsummaryrefslogtreecommitdiff
path: root/src/app.c
diff options
context:
space:
mode:
authorDavid Moc <personal@cdatgoose.org>2026-06-02 13:50:21 +0200
committerDavid Moc <personal@cdatgoose.org>2026-06-02 13:50:21 +0200
commita15cb041654ae307add0b998b526c87c3f42bf5f (patch)
tree225bb4b70e9fa05aa5f4d2722a1a9cf5fc6fca7f /src/app.c
parent6aeaa171dc1ca43392f53cbd02097f76e1b1c5a0 (diff)
Add plugin hooks and mode plugins
Diffstat (limited to 'src/app.c')
-rw-r--r--src/app.c223
1 files changed, 202 insertions, 21 deletions
diff --git a/src/app.c b/src/app.c
index 9a38e40..b9840de 100644
--- a/src/app.c
+++ b/src/app.c
@@ -770,6 +770,36 @@ static void app_reload_font_if_needed(app_t *app) {
app->dirty = 1;
}
+void app_sync_editor_ui(app_t *app) {
+ if (!app || !app->ed) return;
+
+ const char *path = app->ed->theme.font_path && app->ed->theme.font_path[0]
+ ? app->ed->theme.font_path
+ : app->font_path;
+ int font_path_mismatch =
+ path && path[0] &&
+ (!app->font_path[0] || strcmp(app->font_path, path) != 0);
+ int font_size_mismatch = app->font.size_px != app->ed->theme.font_size;
+
+ if (app->font_revision_seen != app->ed->font_revision ||
+ font_path_mismatch ||
+ font_size_mismatch) {
+ app_reload_font_if_needed(app);
+ app->font_revision_seen = app->ed->font_revision;
+ }
+
+ if (app->ui_revision_seen != app->ed->ui_revision) {
+ app->ui_revision_seen = app->ed->ui_revision;
+ app->dirty = 1;
+ }
+
+ if (app->message_revision_seen != app->ed->message_revision) {
+ app->message_revision_seen = app->ed->message_revision;
+ snprintf(app->message, sizeof(app->message), "%s", app->ed->message);
+ app->dirty = 1;
+ }
+}
+
static int app_handle_prompt_request(app_t *app) {
if (!app || !app->ed) return 0;
@@ -797,7 +827,7 @@ static int app_execute_command(app_t *app, const char *command) {
int result = ecex_execute_command(app->ed, command);
if (result == 0) {
- app_reload_font_if_needed(app);
+ app_sync_editor_ui(app);
app_handle_prompt_request(app);
}
@@ -915,9 +945,7 @@ static void app_submit_prompt(app_t *app) {
break;
}
- if (result == 0) {
- app_reload_font_if_needed(app);
- }
+ if (result == 0) app_sync_editor_ui(app);
char msg[ECEX_MINIBUFFER_SIZE];
if (result == 0) {
@@ -989,12 +1017,15 @@ static void app_enter_prefix(app_t *app, const char *first_key) {
app->prefix_len = strlen(app->prefix);
app->message[0] = '\0';
+ ecex_notify_prefix_hooks(app->ed, app->prefix, ECEX_PREFIX_HOOK_BEGIN);
+ app_sync_editor_ui(app);
app->dirty = 1;
}
static void app_cancel_prefix(app_t *app) {
if (!app) return;
+ ecex_notify_prefix_hooks(app->ed, app->prefix, ECEX_PREFIX_HOOK_CANCEL);
app->mode = APP_MODE_EDIT;
app->prefix[0] = '\0';
app->prefix_len = 0;
@@ -1032,6 +1063,8 @@ static void app_finish_prefix(app_t *app) {
const char *command = ecex_lookup_key_for_buffer(app->ed, ecex_current_buffer(app->ed), app->prefix);
if (command) {
+ ecex_notify_prefix_hooks(app->ed, app->prefix, ECEX_PREFIX_HOOK_FINISH);
+ app_sync_editor_ui(app);
if (app_execute_command(app, command) == 0) {
if (app->mode == APP_MODE_EDIT) {
char msg[ECEX_MINIBUFFER_SIZE];
@@ -1059,6 +1092,8 @@ static void app_finish_prefix(app_t *app) {
}
if (key_sequence_has_prefix(app, app->prefix)) {
+ ecex_notify_prefix_hooks(app->ed, app->prefix, ECEX_PREFIX_HOOK_UPDATE);
+ app_sync_editor_ui(app);
app->dirty = 1;
return;
}
@@ -1066,6 +1101,7 @@ static void app_finish_prefix(app_t *app) {
char msg[ECEX_MINIBUFFER_SIZE];
snprintf(msg, sizeof(msg), "Undefined key: %s", app->prefix);
+ ecex_notify_prefix_hooks(app->ed, app->prefix, ECEX_PREFIX_HOOK_UNDEFINED);
app->mode = APP_MODE_EDIT;
app->prefix[0] = '\0';
app->prefix_len = 0;
@@ -1091,7 +1127,24 @@ static float app_status_height(app_t *app) {
return line_h + pad_y * 2.0f;
}
-static float app_minibuffer_height(app_t *app) { return app_status_height(app); }
+static size_t app_minibuffer_row_count(app_t *app) {
+ if (!app || app->mode != APP_MODE_PREFIX || !app->message[0]) return 1;
+
+ size_t rows = 1;
+ for (const char *p = app->message; *p; ++p) {
+ if (*p != '\n') continue;
+ rows++;
+ if (rows >= ECEX_MINIBUFFER_MAX_ROWS) return ECEX_MINIBUFFER_MAX_ROWS;
+ }
+ return rows;
+}
+
+static float app_minibuffer_height(app_t *app) {
+ float line_h = app->font.line_height > 1.0f ? app->font.line_height : app->font.size_px * 1.2f;
+ float pad_y = app->font.size_px * 0.35f;
+ if (pad_y < 4.0f) pad_y = 4.0f;
+ return line_h * (float)app_minibuffer_row_count(app) + pad_y * 2.0f;
+}
static int app_minibuffer_visible(app_t *app) {
return app->mode == APP_MODE_MX || app->mode == APP_MODE_PREFIX ||
@@ -1182,6 +1235,29 @@ static int app_window_rect_at(app_t *app,
return 0;
}
+static int app_page_scroll_rendered_buffer(app_t *app, int direction) {
+ if (!app || !app->ed || direction == 0) return 0;
+
+ ecex_window_t *win = ecex_current_window(app->ed);
+ buffer_t *buf = win && win->buffer ? win->buffer : ecex_current_buffer(app->ed);
+ if (!buf || !buf->render_fn || !(buf->render_flags & ECEX_RENDER_REPLACE_CONTENT)) {
+ return 0;
+ }
+
+ size_t step = 1;
+
+ if (direction > 0) {
+ size_t line_count = buffer_line_count(buf);
+ size_t max_scroll = line_count > 0 ? line_count - 1 : 0;
+ buf->scroll_line = buf->scroll_line + step > max_scroll ? max_scroll : buf->scroll_line + step;
+ } else {
+ buf->scroll_line = buf->scroll_line > step ? buf->scroll_line - step : 0;
+ }
+
+ app->dirty = 1;
+ return 1;
+}
+
static int app_dispatch_buffer_mouse(app_t *app, int event, double px, double py, int button) {
if (!app || !app->ed) return 0;
@@ -1221,9 +1297,8 @@ static int app_dispatch_buffer_mouse(app_t *app, int event, double px, double py
if (local_y < -32768) local_y = -32768;
if (app_trace_callbacks_enabled()) {
- fprintf(stderr, "ecex-log: mouse_callback buffer=%p fn=%p event=%d x=%d y=%d button=%d userdata=%p\n",
- (void *)buf, (void *)buf->mouse_fn, event, local_x, local_y, button, buf->mouse_userdata);
- fflush(stderr);
+ ecex_logf("mouse_callback buffer=%p fn=%p event=%d x=%d y=%d button=%d userdata=%p",
+ (void *)buf, (void *)buf->mouse_fn, event, local_x, local_y, button, buf->mouse_userdata);
}
int result = buf->mouse_fn(app->ed, buf, event, local_x, local_y, button, buf->mouse_userdata);
@@ -1265,9 +1340,8 @@ static void mouse_button_callback(GLFWwindow *window, int button, int action, in
app->mouse_capture_button = ebutton;
app->mouse_capture_active = 1;
if (app_trace_callbacks_enabled()) {
- fprintf(stderr, "ecex-log: mouse_capture_start buffer=%p button=%d\n",
- (void *)app->mouse_capture_buffer, ebutton);
- fflush(stderr);
+ ecex_logf("mouse_capture_start buffer=%p button=%d",
+ (void *)app->mouse_capture_buffer, ebutton);
}
}
return;
@@ -1277,9 +1351,8 @@ static void mouse_button_callback(GLFWwindow *window, int button, int action, in
app_dispatch_buffer_mouse(app, ECEX_MOUSE_RELEASE, x, y, ebutton);
if (!app->mouse_capture_active || app->mouse_capture_button == ebutton) {
if (app_trace_callbacks_enabled() && app->mouse_capture_active) {
- fprintf(stderr, "ecex-log: mouse_capture_end buffer=%p button=%d\n",
- (void *)app->mouse_capture_buffer, ebutton);
- fflush(stderr);
+ ecex_logf("mouse_capture_end buffer=%p button=%d",
+ (void *)app->mouse_capture_buffer, ebutton);
}
app->mouse_capture_active = 0;
app->mouse_capture_buffer = NULL;
@@ -1303,7 +1376,25 @@ static void scroll_callback(GLFWwindow *window, double xoffset, double yoffset)
(void)xoffset;
app_t *app = glfwGetWindowUserPointer(window);
if (!app || !app->ed) return;
- buffer_t *buf = ecex_current_buffer(app->ed);
+
+ glfwGetFramebufferSize(window, &app->width, &app->height);
+
+ double x = 0.0;
+ double y = 0.0;
+ glfwGetCursorPos(window, &x, &y);
+
+ buffer_t *buf = NULL;
+ size_t wi = 0;
+ if (app_window_rect_at(app, x, y, &wi, NULL, NULL, NULL, NULL) &&
+ wi < app->ed->window_count) {
+ buf = app->ed->windows[wi].buffer;
+ }
+
+ if (!buf) {
+ ecex_window_t *win = ecex_current_window(app->ed);
+ buf = win && win->buffer ? win->buffer : ecex_current_buffer(app->ed);
+ }
+
if (!buf) return;
if (yoffset < 0.0) buf->scroll_line += 3;
else if (yoffset > 0.0) buf->scroll_line = buf->scroll_line > 3 ? buf->scroll_line - 3 : 0;
@@ -1407,8 +1498,47 @@ static int key_event_to_string(int key,
strncat(prefix, "M-", sizeof(prefix) - strlen(prefix) - 1);
}
+ if (mods & GLFW_MOD_SHIFT) {
+ if (base[0] >= 'a' && base[0] <= 'z' && base[1] == '\0') {
+ if (mods & (GLFW_MOD_CONTROL | GLFW_MOD_ALT | GLFW_MOD_SUPER)) {
+ strncat(prefix, "S-", sizeof(prefix) - strlen(prefix) - 1);
+ } else {
+ base[0] = (char)(base[0] - 'a' + 'A');
+ }
+ } else if (base[1] == '\0') {
+ switch (base[0]) {
+ case '`': base[0] = '~'; break;
+ case '1': base[0] = '!'; break;
+ case '2': base[0] = '@'; break;
+ case '3': base[0] = '#'; break;
+ case '4': base[0] = '$'; break;
+ case '5': base[0] = '%'; break;
+ case '6': base[0] = '^'; break;
+ case '7': base[0] = '&'; break;
+ case '8': base[0] = '*'; break;
+ case '9': base[0] = '('; break;
+ case '0': base[0] = ')'; break;
+ case '-': base[0] = '_'; break;
+ case '=': base[0] = '+'; break;
+ case '[': base[0] = '{'; break;
+ case ']': base[0] = '}'; break;
+ case '\\': base[0] = '|'; break;
+ case ';': base[0] = ':'; break;
+ case '\'': base[0] = '"'; break;
+ case ',': base[0] = '<'; break;
+ case '.': base[0] = '>'; break;
+ case '/': base[0] = '?'; break;
+ default:
+ strncat(prefix, "S-", sizeof(prefix) - strlen(prefix) - 1);
+ break;
+ }
+ } else {
+ strncat(prefix, "S-", sizeof(prefix) - strlen(prefix) - 1);
+ }
+ }
+
if (mods & GLFW_MOD_SUPER) {
- strncat(prefix, "S-", sizeof(prefix) - strlen(prefix) - 1);
+ strncat(prefix, "Super-", sizeof(prefix) - strlen(prefix) - 1);
}
snprintf(out, out_size, "%s%s", prefix, base);
@@ -1440,6 +1570,19 @@ static int key_produces_text(int key, int scancode) {
return name && name[0] && name[1] == '\0';
}
+static int is_modifier_key(int key) {
+ return key == GLFW_KEY_LEFT_SHIFT ||
+ key == GLFW_KEY_RIGHT_SHIFT ||
+ key == GLFW_KEY_LEFT_CONTROL ||
+ key == GLFW_KEY_RIGHT_CONTROL ||
+ key == GLFW_KEY_LEFT_ALT ||
+ key == GLFW_KEY_RIGHT_ALT ||
+ key == GLFW_KEY_LEFT_SUPER ||
+ key == GLFW_KEY_RIGHT_SUPER ||
+ key == GLFW_KEY_CAPS_LOCK ||
+ key == GLFW_KEY_NUM_LOCK;
+}
+
static void try_execute_keybind(app_t *app, const char *key_name) {
const char *command = ecex_lookup_key_for_buffer(app->ed, ecex_current_buffer(app->ed), key_name);
if (!command) return;
@@ -1541,13 +1684,46 @@ static void key_callback(GLFWwindow *window,
app->current_mods = mods;
- if (app->mode == APP_MODE_EDIT && key == GLFW_KEY_F1) {
+ if (is_modifier_key(key)) {
+ return;
+ }
+
+ if ((mods & GLFW_MOD_CONTROL) &&
+ !(mods & (GLFW_MOD_ALT | GLFW_MOD_SHIFT | GLFW_MOD_SUPER)) &&
+ is_layout_char_key(key, scancode, 'g')) {
+ if (app->mode == APP_MODE_MX) {
+ app_cancel_mx(app);
+ } else if (app->mode == APP_MODE_PROMPT) {
+ app_cancel_prompt(app);
+ } else if (app->mode == APP_MODE_ISEARCH) {
+ app_cancel_isearch(app);
+ } else if (app->mode == APP_MODE_PREFIX) {
+ app_cancel_prefix(app);
+ } else {
+ buffer_t *buf = ecex_current_buffer(app->ed);
+ if (buf) buffer_clear_mark(buf);
+ app_message(app, "Quit");
+ }
+ if (key_produces_text(key, scancode)) {
+ app->suppress_next_char = 1;
+ }
+ return;
+ }
+
+ if (app->mode == APP_MODE_EDIT &&
+ (mods & GLFW_MOD_ALT) &&
+ !(mods & (GLFW_MOD_CONTROL | GLFW_MOD_SHIFT | GLFW_MOD_SUPER)) &&
+ is_layout_char_key(key, scancode, 'x')) {
+ if (key_produces_text(key, scancode)) {
+ app->suppress_next_char = 1;
+ }
app_enter_mx(app);
return;
}
if (app->mode == APP_MODE_EDIT &&
(mods & GLFW_MOD_ALT) &&
+ !(mods & (GLFW_MOD_CONTROL | GLFW_MOD_SHIFT | GLFW_MOD_SUPER)) &&
is_layout_char_key(key, scancode, 'g')) {
app_enter_prefix(app, "M-g");
return;
@@ -1555,6 +1731,7 @@ static void key_callback(GLFWwindow *window,
if (app->mode == APP_MODE_EDIT &&
(mods & GLFW_MOD_CONTROL) &&
+ !(mods & (GLFW_MOD_ALT | GLFW_MOD_SHIFT | GLFW_MOD_SUPER)) &&
is_layout_char_key(key, scancode, 'x')) {
app_enter_prefix(app, "C-x");
return;
@@ -1713,11 +1890,15 @@ static void key_callback(GLFWwindow *window,
}
}
- if (key == GLFW_KEY_ESCAPE) {
- glfwSetWindowShouldClose(window, GLFW_TRUE);
- glfwPostEmptyEvent();
- return;
+ if (key == GLFW_KEY_PAGE_DOWN) {
+ if (app_page_scroll_rendered_buffer(app, 1)) return;
+ }
+
+ if (key == GLFW_KEY_PAGE_UP) {
+ if (app_page_scroll_rendered_buffer(app, -1)) return;
}
+
+ if (key == GLFW_KEY_ESCAPE) return;
}
void app_init(app_t *app, ecex_t *ed) {