aboutsummaryrefslogtreecommitdiff
path: root/src/app.c
diff options
context:
space:
mode:
authorDavid Moc <personal@cdatgoose.org>2026-05-31 03:47:04 +0200
committerDavid Moc <personal@cdatgoose.org>2026-05-31 03:47:04 +0200
commit6aeaa171dc1ca43392f53cbd02097f76e1b1c5a0 (patch)
treeb16f559f5a701123ebe7b15ecebb9325263b4a3c /src/app.c
parente930cc6bdc7f62befac063d7d9d016ffb0a64f1a (diff)
Hardened API, tetris, MD-View
Diffstat (limited to 'src/app.c')
-rw-r--r--src/app.c511
1 files changed, 346 insertions, 165 deletions
diff --git a/src/app.c b/src/app.c
index 3ae368c..9a38e40 100644
--- a/src/app.c
+++ b/src/app.c
@@ -1,5 +1,7 @@
#include "app.h"
#include "common.h"
+#include "completion.h"
+#include "path.h"
#include <dirent.h>
#include <stdio.h>
@@ -60,140 +62,12 @@ static void app_set_prompt_input(app_t *app, const char *text) {
app->dirty = 1;
}
-static char *app_expand_user_path(const char *path) {
- if (!path) return NULL;
-
- if (path[0] != '~' || (path[1] != '\0' && path[1] != '/')) {
- char *copy = malloc(strlen(path) + 1);
- if (!copy) return NULL;
- strcpy(copy, path);
- return copy;
- }
-
- const char *home = getenv("HOME");
- if (!home || !home[0]) {
- char *copy = malloc(strlen(path) + 1);
- if (!copy) return NULL;
- strcpy(copy, path);
- return copy;
- }
-
- size_t home_len = strlen(home);
- size_t rest_len = strlen(path + 1);
-
- char *expanded = malloc(home_len + rest_len + 1);
- if (!expanded) return NULL;
-
- memcpy(expanded, home, home_len);
- memcpy(expanded + home_len, path + 1, rest_len + 1);
- return expanded;
-}
-
typedef struct command_candidate {
const char *name;
int score;
size_t order;
} command_candidate_t;
-static int ascii_lower(int c) {
- if (c >= 'A' && c <= 'Z') return c - 'A' + 'a';
- return c;
-}
-
-static int ascii_strncasecmp_local(const char *a, const char *b, size_t n) {
- for (size_t i = 0; i < n; i++) {
- int ac = ascii_lower((unsigned char)a[i]);
- int bc = ascii_lower((unsigned char)b[i]);
-
- if (ac != bc || ac == '\0' || bc == '\0') {
- return ac - bc;
- }
- }
-
- return 0;
-}
-
-static int ascii_contains_ci(const char *haystack, const char *needle) {
- if (!haystack || !needle) return 0;
- if (needle[0] == '\0') return 1;
-
- size_t needle_len = strlen(needle);
-
- for (size_t i = 0; haystack[i]; i++) {
- if (ascii_strncasecmp_local(haystack + i, needle, needle_len) == 0) {
- return 1;
- }
- }
-
- return 0;
-}
-
-static int command_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;
- }
-
- if (ascii_contains_ci(candidate, query)) {
- score += 75;
- }
-
- return score;
-}
-
static int compare_command_candidates(const void *a, const void *b) {
const command_candidate_t *ca = (const command_candidate_t *)a;
const command_candidate_t *cb = (const command_candidate_t *)b;
@@ -220,7 +94,7 @@ static command_candidate_t *collect_command_candidates(ecex_t *ed,
for (size_t i = 0; i < ed->command_count; i++) {
const char *name = ed->commands[i].name;
- int score = command_fuzzy_score(name, query);
+ int score = ecex_fuzzy_score(name, query);
if (score >= 0) {
items[count].name = name;
@@ -280,14 +154,14 @@ static int compare_path_candidates(const void *a, const void *b) {
const path_candidate_t *pa = (const path_candidate_t *)a;
const path_candidate_t *pb = (const path_candidate_t *)b;
- if (pa->score != pb->score) {
- return pb->score - pa->score;
- }
-
if (pa->is_dir != pb->is_dir) {
return pb->is_dir - pa->is_dir;
}
+ if (pa->score != pb->score) {
+ return pb->score - pa->score;
+ }
+
int cmp = strcmp(pa->path, pb->path);
if (cmp != 0) return cmp;
@@ -323,7 +197,7 @@ static int split_path_query(const char *query,
if (display_dir[0] == '\0') {
snprintf(fs_dir, fs_dir_size, ".");
} else {
- char *expanded = app_expand_user_path(display_dir);
+ char *expanded = ecex_path_expand_user(display_dir);
if (!expanded) return -1;
snprintf(fs_dir, fs_dir_size, "%s", expanded);
@@ -393,7 +267,7 @@ static path_candidate_t *collect_path_candidates(const char *query,
continue;
}
- int score = command_fuzzy_score(name, needle);
+ int score = ecex_fuzzy_score(name, needle);
if (score < 0) {
order++;
continue;
@@ -446,35 +320,151 @@ static path_candidate_t *collect_path_candidates(const char *query,
return items;
}
-static const char *path_candidate_at(const char *query,
- size_t index,
- size_t *out_count) {
- static char candidate[1024];
+typedef ecex_completion_item_t prompt_candidate_t;
+
+static void free_prompt_candidates(prompt_candidate_t *items, size_t count) {
+ ecex_completion_items_free(items, count);
+}
+
+static int compare_prompt_candidates(const void *a, const void *b) {
+ return ecex_completion_item_compare(a, b);
+}
+
+static int prompt_kind_uses_path_completion(ecex_prompt_request_t kind) {
+ return kind == ECEX_PROMPT_FIND_FILE ||
+ kind == ECEX_PROMPT_WRITE_FILE ||
+ kind == ECEX_PROMPT_EVAL_FILE;
+}
+
+static int prompt_kind_uses_buffer_completion(ecex_prompt_request_t kind) {
+ return kind == ECEX_PROMPT_SWITCH_BUFFER ||
+ kind == ECEX_PROMPT_KILL_BUFFER ||
+ kind == ECEX_PROMPT_FORCE_KILL_BUFFER;
+}
+
+static prompt_candidate_t *collect_buffer_candidates(ecex_t *ed,
+ const char *query,
+ size_t *out_count) {
+ if (out_count) *out_count = 0;
+ if (!ed || !query || ed->buffer_count == 0) return NULL;
+
+ prompt_candidate_t *items = calloc(ed->buffer_count, sizeof(*items));
+ if (!items) return NULL;
size_t count = 0;
- path_candidate_t *items = collect_path_candidates(query, &count);
- if (!items || count == 0) {
- free_path_candidates(items, count);
+ for (size_t i = 0; i < ed->buffer_count; i++) {
+ buffer_t *buf = ed->buffers[i];
+ if (!buf || !buf->name) continue;
+
+ int score = ecex_fuzzy_score(buf->name, query);
+ if (score < 0) continue;
+
+ items[count].value = malloc(strlen(buf->name) + 1);
+ if (!items[count].value) continue;
+ strcpy(items[count].value, buf->name);
+ items[count].score = score;
+ items[count].is_dir = 0;
+ items[count].order = i;
+ count++;
+ }
+
+ if (count == 0) {
+ free(items);
return NULL;
}
- snprintf(candidate, sizeof(candidate), "%s", items[index % count].path);
+ qsort(items, count, sizeof(*items), compare_prompt_candidates);
+ if (out_count) *out_count = count;
+ return items;
+}
- if (out_count) {
- *out_count = count;
+static prompt_candidate_t *collect_prompt_candidates(app_t *app,
+ const char *query,
+ size_t *out_count) {
+ if (out_count) *out_count = 0;
+ if (!app || !query) return NULL;
+
+ if (prompt_kind_uses_path_completion(app->prompt_kind)) {
+ size_t path_count = 0;
+ path_candidate_t *paths = collect_path_candidates(query, &path_count);
+ if (!paths || path_count == 0) {
+ free_path_candidates(paths, path_count);
+ return NULL;
+ }
+
+ prompt_candidate_t *items = calloc(path_count, sizeof(*items));
+ if (!items) {
+ free_path_candidates(paths, path_count);
+ return NULL;
+ }
+
+ size_t count = 0;
+ for (size_t i = 0; i < path_count; i++) {
+ items[count].value = malloc(strlen(paths[i].path) + 1);
+ if (!items[count].value) continue;
+ strcpy(items[count].value, paths[i].path);
+ items[count].score = paths[i].score;
+ items[count].is_dir = paths[i].is_dir;
+ items[count].order = paths[i].order;
+ count++;
+ }
+
+ free_path_candidates(paths, path_count);
+ if (count == 0) {
+ free(items);
+ return NULL;
+ }
+ if (out_count) *out_count = count;
+ return items;
+ }
+
+ if (prompt_kind_uses_buffer_completion(app->prompt_kind)) {
+ return collect_buffer_candidates(app->ed, query, out_count);
+ }
+
+ return NULL;
+}
+
+static const char *prompt_candidate_at(app_t *app,
+ const char *query,
+ size_t index,
+ size_t *out_count) {
+ static char candidate[1024];
+
+ size_t count = 0;
+ prompt_candidate_t *items = collect_prompt_candidates(app, query, &count);
+ if (!items || count == 0) {
+ free_prompt_candidates(items, count);
+ return NULL;
}
- free_path_candidates(items, count);
+ snprintf(candidate, sizeof(candidate), "%s", items[index % count].value);
+ if (out_count) *out_count = count;
+
+ free_prompt_candidates(items, count);
return candidate;
}
+static int app_prompt_input_is_directory(app_t *app) {
+ if (!app || !prompt_kind_uses_path_completion(app->prompt_kind)) return 0;
+ if (app->prompt_input_len == 0) return 0;
+
+ char *expanded = ecex_path_expand_user(app->prompt_input);
+ if (!expanded) return 0;
+
+ struct stat st;
+ int is_dir = stat(expanded, &st) == 0 && S_ISDIR(st.st_mode);
+ free(expanded);
+ return is_dir;
+}
+
static void app_update_prompt_completion_preview(app_t *app) {
if (!app || !app->prompt_completion_active) return;
size_t count = 0;
- path_candidate_t *items = collect_path_candidates(app->prompt_completion_query, &count);
+ prompt_candidate_t *items = collect_prompt_candidates(app, app->prompt_completion_query, &count);
if (!items || count == 0) {
- free_path_candidates(items, count);
+ free_prompt_candidates(items, count);
app->prompt_completion_preview_start = 0;
app->prompt_completion_preview_count = 0;
return;
@@ -495,11 +485,11 @@ static void app_update_prompt_completion_preview(app_t *app) {
snprintf(app->prompt_completion_preview[i],
sizeof(app->prompt_completion_preview[i]),
"%s",
- items[start + i].path);
+ items[start + i].value);
app->prompt_completion_preview_count++;
}
- free_path_candidates(items, count);
+ free_prompt_candidates(items, count);
}
static void app_begin_prompt_completion(app_t *app, int direction) {
@@ -511,12 +501,12 @@ static void app_begin_prompt_completion(app_t *app, int direction) {
app->prompt_input);
size_t count = 0;
- path_candidate_t *items = collect_path_candidates(app->prompt_completion_query, &count);
+ prompt_candidate_t *items = collect_prompt_candidates(app, app->prompt_completion_query, &count);
if (!items || count == 0) {
- free_path_candidates(items, count);
+ free_prompt_candidates(items, count);
app_reset_prompt_completion(app);
- app_message(app, "No file completions");
+ app_message(app, prompt_kind_uses_buffer_completion(app->prompt_kind) ? "No buffer completions" : "No file completions");
app->mode = APP_MODE_PROMPT;
return;
}
@@ -525,8 +515,8 @@ static void app_begin_prompt_completion(app_t *app, int direction) {
app->prompt_completion_count = count;
app->prompt_completion_index = direction < 0 ? count - 1 : 0;
- app_set_prompt_input(app, items[app->prompt_completion_index].path);
- free_path_candidates(items, count);
+ app_set_prompt_input(app, items[app->prompt_completion_index].value);
+ free_prompt_candidates(items, count);
app_update_prompt_completion_preview(app);
}
@@ -548,9 +538,10 @@ static void app_cycle_prompt_completion(app_t *app, int delta) {
}
size_t count = 0;
- const char *candidate = path_candidate_at(app->prompt_completion_query,
- app->prompt_completion_index,
- &count);
+ const char *candidate = prompt_candidate_at(app,
+ app->prompt_completion_query,
+ app->prompt_completion_index,
+ &count);
if (!candidate || count == 0) {
app_reset_prompt_completion(app);
@@ -562,6 +553,18 @@ static void app_cycle_prompt_completion(app_t *app, int delta) {
app_update_prompt_completion_preview(app);
}
+static void app_complete_prompt(app_t *app) {
+ if (!app) return;
+
+ if (app->prompt_completion_active && app_prompt_input_is_directory(app)) {
+ app_reset_prompt_completion(app);
+ app_begin_prompt_completion(app, 1);
+ return;
+ }
+
+ app_cycle_prompt_completion(app, 1);
+}
+
static void app_begin_completion(app_t *app, int direction) {
if (!app) return;
@@ -819,25 +822,47 @@ static void app_cancel_prompt(app_t *app) {
app_message(app, "Prompt cancelled");
}
+static const char *app_default_prompt_input(app_t *app, ecex_prompt_request_t kind) {
+ if (!app || !app->ed) return NULL;
+
+ if (kind == ECEX_PROMPT_SWITCH_BUFFER) {
+ buffer_t *other = ecex_other_buffer(app->ed);
+ return other ? other->name : NULL;
+ }
+
+ if (kind == ECEX_PROMPT_KILL_BUFFER ||
+ kind == ECEX_PROMPT_FORCE_KILL_BUFFER) {
+ buffer_t *current = ecex_current_buffer(app->ed);
+ return current ? current->name : NULL;
+ }
+
+ return NULL;
+}
+
static void app_submit_prompt(app_t *app) {
if (!app || !app->ed) return;
- if (app->prompt_input_len == 0 && app->prompt_kind != ECEX_PROMPT_COMPILE) {
+ ecex_prompt_request_t kind = app->prompt_kind;
+ const char *default_input = app_default_prompt_input(app, kind);
+
+ if (app->prompt_input_len == 0 &&
+ kind != ECEX_PROMPT_COMPILE &&
+ (!default_input || default_input[0] == '\0')) {
app_cancel_prompt(app);
return;
}
char input[sizeof(app->prompt_input)];
- snprintf(input, sizeof(input), "%s", app->prompt_input);
+ snprintf(input, sizeof(input), "%s",
+ app->prompt_input_len > 0 ? app->prompt_input : (default_input ? default_input : ""));
- ecex_prompt_request_t kind = app->prompt_kind;
app->mode = APP_MODE_EDIT;
app->prompt_kind = ECEX_PROMPT_NONE;
app->prompt_label[0] = '\0';
app->prompt_input[0] = '\0';
app->prompt_input_len = 0;
- char *expanded_input = app_expand_user_path(input);
+ char *expanded_input = ecex_path_expand_user(input);
const char *path = expanded_input ? expanded_input : input;
int result = -1;
@@ -869,6 +894,11 @@ static void app_submit_prompt(app_t *app) {
verb = "Killed";
break;
+ case ECEX_PROMPT_FORCE_KILL_BUFFER:
+ result = ecex_kill_buffer_force(app->ed, input);
+ verb = "Force killed";
+ break;
+
case ECEX_PROMPT_COMPILE:
result = ecex_compile(app->ed, input[0] ? input : "make");
verb = "Compiled";
@@ -1049,6 +1079,11 @@ static float app_mono_cell_width(app_t *app) {
return w > 1.0f ? w : app->font.size_px * 0.6f;
}
+static int app_trace_callbacks_enabled(void) {
+ const char *v = getenv("ECEX_TRACE_CALLBACKS");
+ return v && v[0] && v[0] != '0';
+}
+
static float app_status_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;
@@ -1117,15 +1152,151 @@ static void app_move_point_to_pixel(app_t *app, double px, double py) {
app->dirty = 1;
}
+static int app_window_rect_at(app_t *app,
+ double px,
+ double py,
+ size_t *out_index,
+ float *out_x,
+ float *out_y,
+ float *out_w,
+ float *out_h) {
+ if (!app || !app->ed || app->ed->window_count == 0) return 0;
+ float editor_h = (float)app->height - app_status_height(app);
+ if (app_minibuffer_visible(app)) editor_h -= app_minibuffer_height(app);
+ if (editor_h < 1.0f) editor_h = 1.0f;
+ for (size_t i = 0; i < app->ed->window_count; i++) {
+ ecex_window_t *w = &app->ed->windows[i];
+ float x = w->x * (float)app->width;
+ float y = w->y * editor_h;
+ float ww = w->w * (float)app->width;
+ float hh = w->h * editor_h;
+ if (px >= x && px < x + ww && py >= y && py < y + hh) {
+ if (out_index) *out_index = i;
+ if (out_x) *out_x = x;
+ if (out_y) *out_y = y;
+ if (out_w) *out_w = ww;
+ if (out_h) *out_h = hh;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static int app_dispatch_buffer_mouse(app_t *app, int event, double px, double py, int button) {
+ if (!app || !app->ed) return 0;
+
+ buffer_t *buf = NULL;
+ float wx = 0.0f;
+ float wy = 0.0f;
+ float ww = 0.0f;
+ float wh = 0.0f;
+
+ if (app->mouse_capture_active && app->mouse_capture_buffer) {
+ buf = app->mouse_capture_buffer;
+ for (size_t i = 0; i < app->ed->window_count; i++) {
+ if (app->ed->windows[i].buffer != buf) continue;
+ float editor_h = (float)app->height - app_status_height(app);
+ if (app_minibuffer_visible(app)) editor_h -= app_minibuffer_height(app);
+ if (editor_h < 1.0f) editor_h = 1.0f;
+ wx = app->ed->windows[i].x * (float)app->width;
+ wy = app->ed->windows[i].y * editor_h;
+ ww = app->ed->windows[i].w * (float)app->width;
+ wh = app->ed->windows[i].h * editor_h;
+ break;
+ }
+ } else {
+ size_t wi = 0;
+ if (!app_window_rect_at(app, px, py, &wi, &wx, &wy, &ww, &wh)) return 0;
+ if (wi >= app->ed->window_count) return 0;
+ buf = app->ed->windows[wi].buffer;
+ }
+
+ (void)ww;
+ (void)wh;
+ if (!buf || !buf->mouse_fn) return 0;
+
+ int local_x = (int)(px - (double)wx);
+ int local_y = (int)(py - (double)wy);
+ if (local_x < -32768) local_x = -32768;
+ 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);
+ }
+
+ int result = buf->mouse_fn(app->ed, buf, event, local_x, local_y, button, buf->mouse_userdata);
+ if (result) app->dirty = 1;
+ return result != 0;
+}
+
+static int app_mouse_button_id(int glfw_button) {
+ if (glfw_button == GLFW_MOUSE_BUTTON_LEFT) return ECEX_MOUSE_BUTTON_LEFT;
+ if (glfw_button == GLFW_MOUSE_BUTTON_RIGHT) return ECEX_MOUSE_BUTTON_RIGHT;
+ if (glfw_button == GLFW_MOUSE_BUTTON_MIDDLE) return ECEX_MOUSE_BUTTON_MIDDLE;
+ return glfw_button;
+}
+
static void mouse_button_callback(GLFWwindow *window, int button, int action, int mods) {
(void)mods;
- if (button != GLFW_MOUSE_BUTTON_LEFT || action != GLFW_PRESS) return;
app_t *app = glfwGetWindowUserPointer(window);
if (!app) return;
glfwGetFramebufferSize(window, &app->width, &app->height);
double x = 0.0, y = 0.0;
glfwGetCursorPos(window, &x, &y);
- app_move_point_to_pixel(app, x, y);
+ int ebutton = app_mouse_button_id(button);
+
+ if (action == GLFW_PRESS) {
+ size_t capture_wi = 0;
+ float capture_wx = 0.0f, capture_wy = 0.0f, capture_ww = 0.0f, capture_wh = 0.0f;
+ int have_capture_window = app_window_rect_at(app, x, y, &capture_wi,
+ &capture_wx, &capture_wy, &capture_ww, &capture_wh);
+
+ if (app_dispatch_buffer_mouse(app, ECEX_MOUSE_PRESS, x, y, ebutton)) {
+ /* Start capture from the window resolved before dispatch. Do not
+ * re-hit-test after the plugin callback; the callback may mark the
+ * app dirty or mutate buffer/window state, and a second lookup can
+ * fail or select a different target. Without capture, GLFW cursor
+ * motion is delivered as plain ECEX_MOUSE_MOVE events, so rendered
+ * widgets never receive ECEX_MOUSE_DRAG. */
+ if (have_capture_window && capture_wi < app->ed->window_count) {
+ app->mouse_capture_buffer = app->ed->windows[capture_wi].buffer;
+ 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);
+ }
+ }
+ return;
+ }
+ if (button == GLFW_MOUSE_BUTTON_LEFT) app_move_point_to_pixel(app, x, y);
+ } else if (action == GLFW_RELEASE) {
+ 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);
+ }
+ app->mouse_capture_active = 0;
+ app->mouse_capture_buffer = NULL;
+ app->mouse_capture_button = 0;
+ }
+ }
+}
+
+static void cursor_pos_callback(GLFWwindow *window, double x, double y) {
+ app_t *app = glfwGetWindowUserPointer(window);
+ if (!app) return;
+ glfwGetFramebufferSize(window, &app->width, &app->height);
+ if (app->mouse_capture_active) {
+ app_dispatch_buffer_mouse(app, ECEX_MOUSE_DRAG, x, y, app->mouse_capture_button);
+ } else {
+ app_dispatch_buffer_mouse(app, ECEX_MOUSE_MOVE, x, y, ECEX_MOUSE_BUTTON_LEFT);
+ }
}
static void scroll_callback(GLFWwindow *window, double xoffset, double yoffset) {
@@ -1329,6 +1500,7 @@ static void char_callback(GLFWwindow *window, unsigned int codepoint) {
if (app->mode == APP_MODE_PROMPT) {
if (codepoint >= 32 && codepoint <= 126) {
if (app->prompt_input_len + 1 < sizeof(app->prompt_input)) {
+ app_reset_prompt_completion(app);
app->prompt_input[app->prompt_input_len++] = (char)codepoint;
app->prompt_input[app->prompt_input_len] = '\0';
app->dirty = 1;
@@ -1470,7 +1642,7 @@ static void key_callback(GLFWwindow *window,
return;
case GLFW_KEY_TAB:
- app_cycle_prompt_completion(app, 1);
+ app_complete_prompt(app);
return;
case GLFW_KEY_DOWN:
@@ -1531,6 +1703,14 @@ static void key_callback(GLFWwindow *window,
try_execute_keybind(app, key_name);
return;
}
+
+ if (key_sequence_has_prefix(app, key_name)) {
+ if (key_produces_text(key, scancode)) {
+ app->suppress_next_char = 1;
+ }
+ app_enter_prefix(app, key_name);
+ return;
+ }
}
if (key == GLFW_KEY_ESCAPE) {
@@ -1574,5 +1754,6 @@ void app_install_callbacks(app_t *app) {
glfwSetFramebufferSizeCallback(app->window, framebuffer_size_callback);
glfwSetWindowRefreshCallback(app->window, window_refresh_callback);
glfwSetMouseButtonCallback(app->window, mouse_button_callback);
+ glfwSetCursorPosCallback(app->window, cursor_pos_callback);
glfwSetScrollCallback(app->window, scroll_callback);
}