From 6fb16e1cc2dc3fbbc19d9f4e0a055a4c6ba261d9 Mon Sep 17 00:00:00 2001 From: Charlie Gordon Date: Tue, 23 Jun 2026 16:24:02 +0200 Subject: [PATCH] Simplify and improve `qe_check_buffer_file` * pass `QEmacsState` to key grab functions * clear `grab_key_opaque` when freeing windows, buffers, mode_data * add checks on window and buffer pointers in key grab functions * this should fix a crash bug when deleting unsynched buffers from bufed * fix `reload_buffer` to update buffer `file_size` and `mtime` fields * check buffer file before invoking commands that modify the buffer * check buffer file after command that changes the current window or the current window's buffer --- TODO.md | 3 +- buffer.c | 17 +++------- modes/bufed.c | 1 - modes/dired.c | 2 -- modes/shell.c | 7 ++-- qe.c | 92 ++++++++++++++++++++++++++++++--------------------- qe.h | 4 +-- search.c | 33 ++++++++++++------ 8 files changed, 88 insertions(+), 71 deletions(-) diff --git a/TODO.md b/TODO.md index 299a5bd..77ac349 100644 --- a/TODO.md +++ b/TODO.md @@ -11,8 +11,6 @@ This is the TODO list for qemacs * fix warning: `int (*eb_printf_fun)(EditBuffer *b, const char *fmt, ...) = (void*)eb_printf;` * fix crash bug on `C-X C-B C-X C-F xxx RET` * fix cursor positioning beyond end of screen -* crash bug on reloading file contents in `qe_check_buffer_file()` -* fix file change detection on buffer change (`C-x b`) # Core - Buffers - Files @@ -64,6 +62,7 @@ This is the TODO list for qemacs ## Files +* show popup window with explanation and diff in `qe_check_buffer_file()` * fix corner cases in `qe_check_buffer_file()` * minimize edit list in `qe_check_buffer_file()` when new version is loaded * remanent annotations on files and buffers diff --git a/buffer.c b/buffer.c index 05a01c3..19e5771 100644 --- a/buffer.c +++ b/buffer.c @@ -134,9 +134,6 @@ int eb_write(EditBuffer *b, int offset, const void *buf, int size) if (b->flags & BF_READONLY) return 0; - if (qe_check_buffer_file(b, CBF_MODIFY) == CBF_PROMPT) - return 0; - /* We carefully clip the request, avoiding integer overflow */ if (offset < 0 || size <= 0 || offset > b->total_size) return 0; @@ -320,9 +317,6 @@ int eb_insert_buffer(EditBuffer *dest, int dest_offset, if (dest->flags & BF_READONLY) return 0; - if (qe_check_buffer_file(dest, CBF_MODIFY) == CBF_PROMPT) - return 0; - /* Assert parameter consistency */ if (dest_offset < 0 || src_offset < 0 || src_offset >= src->total_size) return 0; @@ -461,9 +455,6 @@ int eb_insert(EditBuffer *b, int offset, const void *buf, int size) if (b->flags & BF_READONLY) return 0; - if (qe_check_buffer_file(b, CBF_MODIFY) == CBF_PROMPT) - return 0; - /* sanity checks */ if (offset > b->total_size) offset = b->total_size; @@ -488,9 +479,6 @@ int eb_delete(EditBuffer *b, int offset, int size) if (b->flags & BF_READONLY) return 0; - if (qe_check_buffer_file(b, CBF_MODIFY) == CBF_PROMPT) - return 0; - if (offset < 0 || offset >= b->total_size || size <= 0) return 0; @@ -816,6 +804,7 @@ void eb_free(EditBuffer **bp) EditBuffer **pb; EditBuffer *b1; + // XXX should use qe_free_mode_data() /* free b->mode_data_list by calling destructors */ while (b->mode_data_list) { QEModeData *md = b->mode_data_list; @@ -823,6 +812,8 @@ void eb_free(EditBuffer **bp) md->next = NULL; if (md->mode && md->mode->mode_free) md->mode->mode_free(b, md); + if (qs->key_ctx.grab_key_opaque == md) + qs->key_ctx.grab_key_opaque = NULL; qe_free(&md); } @@ -870,6 +861,8 @@ void eb_free(EditBuffer **bp) eb_free_style_buffer(b); qe_free(&b->saved_data); + if (qs->key_ctx.grab_key_opaque == b) + qs->key_ctx.grab_key_opaque = NULL; // XXX: cannot use qe_free(bp) because *bp may have been set to NULL // already, eg: eb_free_log_buffer() and eb_free_style_buffer() qe_free(&b); diff --git a/modes/bufed.c b/modes/bufed.c index de79254..dd50ad6 100644 --- a/modes/bufed.c +++ b/modes/bufed.c @@ -309,7 +309,6 @@ static void bufed_select(EditState *s, int temp) do_delete_window(s, 1); if (e) { qs->active_window = e; - qe_check_buffer_file(e->b, CBF_CHECK); } } } else { diff --git a/modes/dired.c b/modes/dired.c index 552b75a..06697ac 100644 --- a/modes/dired.c +++ b/modes/dired.c @@ -1458,7 +1458,6 @@ static void dired_select(EditState *s, int mode) if (e) { #if 1 s->qs->active_window = e; - qe_check_buffer_file(e->b, CBF_CHECK); if (mode == 1) { /* XXX: should keep BF_PREVIEW flag and set pager-mode */ e->b->flags &= ~BF_PREVIEW; @@ -1544,7 +1543,6 @@ static void dired_parent(EditState *s, int collapse) EditState *e = find_window(s, KEY_LEFT, NULL); if (e && (e->flags & WF_FILELIST)) { s->qs->active_window = e; - qe_check_buffer_file(e->b, CBF_CHECK); return; } } diff --git a/modes/shell.c b/modes/shell.c index 73f71f7..c562af0 100644 --- a/modes/shell.c +++ b/modes/shell.c @@ -1368,7 +1368,7 @@ static void shell_display_hook(EditState *e) } } -static void shell_key(void *opaque, int key) +static void shell_key(QEmacsState *qs, void *opaque, int key) { ShellState *s = opaque; char buf[10]; @@ -1379,8 +1379,8 @@ static void shell_key(void *opaque, int key) return; if (key == KEY_CTRL('o') && s->last_key_grabbed == KEY_CTRL(' ')) { - qe_ungrab_keys(s->base.qs); - qe_unget_key(s->base.qs, key); + qe_ungrab_keys(qs); + qe_unget_key(qs, key); s->last_key_grabbed = key; return; } @@ -2878,7 +2878,6 @@ static EditBuffer *try_show_buffer(EditState **sp, const char *bufname) e = eb_find_window(b, NULL); if (e) { qs->active_window = *sp = e; - qe_check_buffer_file(e->b, CBF_CHECK); } else { switch_to_buffer(s, b); } diff --git a/qe.c b/qe.c index 7d4c598..b0935fa 100644 --- a/qe.c +++ b/qe.c @@ -1783,7 +1783,6 @@ void text_mouse_goto(EditState *s, int x, int y, QEEvent *ev) if (!(curw && (curw->flags & (WF_POPUP | WF_MINIBUF))) && !qs->key_ctx.grab_key_cb) { qs->active_window = s; - qe_check_buffer_file(s->b, CBF_CHECK); } if (s->mouse_force_highlight) s->force_highlight = 1; @@ -2021,14 +2020,13 @@ struct QuoteKeyArgument { }; /* XXX: may be better to move it into qe_key_process() */ -static void quote_key(void *opaque, int key) +static void quote_key(QEmacsState *qs, void *opaque, int key) { // XXX: emacs supports octal input followed by RET // and f1 for context sensitive help // qemacs supports special keys and inserts the keyboard sequence struct QuoteKeyArgument *qa = opaque; - EditState *s = qa->s; - QEmacsState *qs = s->qs; + EditState *s = qe_check_window(qs, &qa->s); int repeat = qa->argval; put_status(s, ""); /* erase "Quote: " message */ @@ -2656,7 +2654,7 @@ static int reload_buffer(EditState *s, EditBuffer *b, int reload) } return -1; } else { - if (stat(b->filename, &st) < 0) { + if (!stat(b->filename, &st)) { b->file_mtime = st.st_mtime; b->file_size = st.st_size; } @@ -2783,6 +2781,8 @@ int qe_free_mode_data(QEModeData *md) } if (rc == 0) { /* mode data was found, OK to free */ + if (md->qs->key_ctx.grab_key_opaque == md) + md->qs->key_ctx.grab_key_opaque = NULL; qe_free(&md); } return rc; @@ -5644,6 +5644,9 @@ void exec_command(EditState *s, const CmdDef *d, int argval, int key) put_error(s, "Buffer is read only"); return; } + if (qe_check_buffer_file(s->b, CBF_MODIFY) == CBF_PROMPT) { + return; + } } else if (*argdesc == '#') { argdesc++; @@ -6626,7 +6629,7 @@ void do_prefix_argument(EditState *s, int key) { /* * All typed keys are sent to the callback. Previous grab is aborted */ -void qe_grab_keys(QEmacsState *qs, void (*cb)(void *opaque, int key), void *opaque) +void qe_grab_keys(QEmacsState *qs, void (*cb)(QEmacsState *qs, void *opaque, int key), void *opaque) { QEKeyContext *c = &qs->key_ctx; @@ -6722,7 +6725,7 @@ static void qe_key_process(QEmacsState *qs, int key) // XXX: shound test for help-popup if (c->grab_key_cb) { /* grabber should return codes for quit / fall thru / ungrab */ - c->grab_key_cb(c->grab_key_opaque, key); + c->grab_key_cb(qs, c->grab_key_opaque, key); /* allow key_grabber to quit and unget last key */ if (c->grab_key_cb || qs->ungot_key == -1) return; @@ -6835,6 +6838,8 @@ static void qe_key_process(QEmacsState *qs, int key) } else { int argval = c->argval; int multi_cursor_active = s->multi_cursor_active; + EditBuffer *this_buffer = s->b; + if (c->has_arg & HAS_ARG_NEGATIVE) argval = -argval; else @@ -6853,12 +6858,18 @@ static void qe_key_process(QEmacsState *qs, int key) qs->last_key = key; } exec_command(s, d, argval, key); - if (multi_cursor_active && qe_check_window(qs, &s)) { - int i; - if (s != qs->active_window) { - /* end multi-cursor session upto window change */ + if (s != qs->active_window || s->b != this_buffer) { + if (qe_check_window(qs, &s)) { + /* end multi-cursor session upto window or buffer change */ s->multi_cursor_active = 0; } + if (!qs->key_ctx.grab_key_cb && qs->active_window) { + qe_check_buffer_file(qs->active_window->b, CBF_CHECK); + } + } else + if (multi_cursor_active) { + // dispatch the command to all cursors if multi_cursor was already active + int i; s->multi_cursor[0].offset = s->offset; for (i = 1; s->multi_cursor_active && i < s->multi_cursor_len; i++) { swap_int(&s->b->mark, &s->multi_cursor[i].mark); @@ -7206,8 +7217,6 @@ static void edit_detach(EditState *s) /* if window was active, activate target window or default window */ if (qs->active_window == s) { qs->active_window = s->target_window ? s->target_window : qs->first_window; - if (qs->active_window) - qe_check_buffer_file(qs->active_window->b, CBF_CHECK); } } @@ -7345,6 +7354,8 @@ void edit_close(EditState **sp) /* save current state for later window reattachment */ switch_to_buffer(s, NULL); edit_detach(s); + if (s->qs->key_ctx.grab_key_opaque == s) + s->qs->key_ctx.grab_key_opaque = NULL; /* closing the window mode should have freed it already */ qe_free_mode_data(s->mode_data); qe_free(&s->prompt); @@ -9102,7 +9113,6 @@ int qe_load_file(EditState *s, const char *filename1, int lflags, int bflags) b = qe_find_buffer_filename(qs, filename); if (b != NULL) { switch_to_buffer(s, b); - qe_check_buffer_file(b, CBF_CHECK); return 0; } @@ -9368,14 +9378,14 @@ static void put_save_message(EditState *s, const char *filename, int nb) void do_save_buffer(EditState *s) { + if (qe_check_buffer_file(s->b, CBF_SAVE) == CBF_PROMPT) + return; + if (!s->b->modified) { /* CG: This behaviour bugs me! */ put_status(s, "(No changes need to be saved)"); return; } - if (qe_check_buffer_file(s->b, CBF_SAVE) == CBF_PROMPT) - return; - put_save_message(s, s->b->filename, eb_save_buffer(s->b)); } @@ -9399,11 +9409,16 @@ void do_write_region(EditState *s, const char *filename) eb_write_buffer(s->b, s->b->mark, s->offset, filename)); } -static void qe_check_buffer_file_key(EditBuffer *b, int ch, int save) +static void qe_check_buffer_file_key(QEmacsState *qs, EditBuffer *b, int ch, int save) { - QEmacsState *qs = b->qs; EditState *s = qs->active_window; + if (!b) { + qe_ungrab_keys(qs); + put_error(s, "&Deleted"); + return; + } + switch (ch) { case KEY_CTRL('g'): /* abort */ qe_ungrab_keys(qs); @@ -9447,6 +9462,7 @@ static void qe_check_buffer_file_key(EditBuffer *b, int ch, int save) put_error(s, "Diff not supported"); #else qe_ungrab_keys(qs); + put_status(s, ""); qe_diff_buffer_with_file(s, b); #endif break; @@ -9477,14 +9493,14 @@ static void qe_check_buffer_file_key(EditBuffer *b, int ch, int save) qe_display(qs); } -static void qe_check_buffer_file_key_read(void *opaque, int ch) +static void qe_check_buffer_file_key_read(QEmacsState *qs, void *opaque, int ch) { - qe_check_buffer_file_key(opaque, ch, FALSE); + qe_check_buffer_file_key(qs, opaque, ch, FALSE); } -static void qe_check_buffer_file_key_save(void *opaque, int ch) +static void qe_check_buffer_file_key_save(QEmacsState *qs, void *opaque, int ch) { - qe_check_buffer_file_key(opaque, ch, TRUE); + qe_check_buffer_file_key(qs, opaque, ch, TRUE); } int qe_check_buffer_file(EditBuffer *b, int mode) @@ -9595,8 +9611,8 @@ typedef struct QuitState { QEmacsState *qs; } QuitState; -static void quit_examine_buffers(QuitState *is); -static void quit_key(void *opaque, int ch); +static void quit_examine_buffers(QEmacsState *qs, QuitState *is); +static void quit_key(QEmacsState *qs, void *opaque, int ch); static void quit_confirm_cb(void *opaque, char *reply, CompletionDef *completion); static void do_suspend_qemacs(EditState *s, int argval) @@ -9628,13 +9644,12 @@ void do_exit_qemacs(EditState *s, int argval) qe_stop_macro(qs); qe_grab_keys(qs, quit_key, is); - quit_examine_buffers(is); + quit_examine_buffers(qs, is); } /* analyse next buffer and ask question if needed */ -static void quit_examine_buffers(QuitState *is) +static void quit_examine_buffers(QEmacsState *qs, QuitState *is) { - QEmacsState *qs = is->qs; EditBuffer *b; while (is->b != NULL) { @@ -9677,11 +9692,17 @@ static void quit_examine_buffers(QuitState *is) qe_free(&is); } -static void quit_key(void *opaque, int ch) +static void quit_key(QEmacsState *qs, void *opaque, int ch) { QuitState *is = opaque; EditBuffer *b; + if (!is || !(b = qe_check_buffer(qs, &is->b))) { + qe_ungrab_keys(qs); + put_error(qs->active_window, "&Deleted"); + return; + } + switch (ch) { case 'y': case ' ': @@ -9705,20 +9726,19 @@ static void quit_key(void *opaque, int ch) /* save current and exit */ is->state = QS_NOSAVE; do_save: - b = is->b; eb_save_buffer(b); break; case KEY_CTRL('g'): /* abort */ - qe_ungrab_keys(is->qs); - put_error(is->qs->active_window, "&Quit"); + qe_ungrab_keys(qs); + put_error(qs->active_window, "&Quit"); return; default: /* get another key */ return; } - is->b = is->b->next; - quit_examine_buffers(is); + is->b = b->next; + quit_examine_buffers(qs, is); } static void quit_confirm_cb(qe__unused__ void *opaque, @@ -9972,7 +9992,6 @@ void do_other_window(EditState *s) if (e->flags & WF_MINIBUF) edit_invalidate(e, 0); s->qs->active_window = e; - qe_check_buffer_file(e->b, CBF_CHECK); } } @@ -9981,7 +10000,6 @@ void do_previous_window(EditState *s) EditState *e = get_previous_window(s, 0, 0); if (e) { s->qs->active_window = e; - qe_check_buffer_file(e->b, CBF_CHECK); } } @@ -10056,8 +10074,6 @@ void do_delete_window(EditState *s, int force) } if (qs->active_window == s) { qs->active_window = e1 ? e1 : qs->first_window; - if (qs->active_window) - qe_check_buffer_file(qs->active_window->b, CBF_CHECK); } edit_close(&s); if (qs->first_window) diff --git a/qe.h b/qe.h index 36a6cbc..ce77ac7 100644 --- a/qe.h +++ b/qe.h @@ -249,7 +249,7 @@ static inline QEEvent *qe_event_clear(QEEvent *ev) { void qe_handle_event(QEmacsState *qs, QEEvent *ev); /* CG: Should optionally attach grab to a window */ /* CG: Should deal with opaque object life cycle */ -void qe_grab_keys(QEmacsState *qs, void (*cb)(void *opaque, int key), void *opaque); +void qe_grab_keys(QEmacsState *qs, void (*cb)(QEmacsState *qs, void *opaque, int key), void *opaque); void qe_ungrab_keys(QEmacsState *qs); KeyDef *qe_find_binding(unsigned int *keys, int nb_keys, KeyDef *kd, int exact); KeyDef *qe_find_current_binding(QEmacsState *qs, unsigned int *keys, int nb_keys, ModeDef *m, int exact); @@ -985,7 +985,7 @@ typedef struct QEKeyContext { int is_escape; int nb_keys; int describe_key; /* if true, the following command is only displayed */ - void (*grab_key_cb)(void *opaque, int key); + void (*grab_key_cb)(QEmacsState *qs, void *opaque, int key); void *grab_key_opaque; unsigned int keys[MAX_KEYS]; char buf[128]; diff --git a/search.c b/search.c index 53f1802..ac73606 100644 --- a/search.c +++ b/search.c @@ -2,7 +2,7 @@ * Incremental search and replace for QEmacs. * * Copyright (c) 2000-2002 Fabrice Bellard. - * Copyright (c) 2000-2024 Charlie Gordon. + * Copyright (c) 2000-2026 Charlie Gordon. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -809,25 +809,31 @@ static void isearch_exit(EditState *s, int key) { } } -static void isearch_key(void *opaque, int key) { +static void isearch_key(QEmacsState *qs, void *opaque, int key) { ISearchState *is = opaque; - QEmacsState *qs = is->s->qs; + EditState *s; unsigned int keys[1] = { key }; + if (!is || !(s = qe_check_window(qs, &is->s))) { + qe_ungrab_keys(qs); + put_error(qs->active_window, "&Deleted"); + return; + } + if (is->quoting) { is->quoting = 0; if (!KEY_IS_SPECIAL(key)) { - isearch_printing_char(is->s, key); + isearch_printing_char(s, key); } } else { KeyDef *kd = qe_find_binding(keys, 1, isearch_mode.first_key, 1); if (kd) { - exec_command(is->s, kd->cmd, NO_ARG, key); + exec_command(s, kd->cmd, NO_ARG, key); } else { if (KEY_IS_SPECIAL(key) || KEY_IS_CONTROL(key)) { - isearch_exit(is->s, key); + isearch_exit(s, key); } else { - isearch_printing_char(is->s, key); + isearch_printing_char(s, key); } } } @@ -1123,11 +1129,18 @@ static void query_replace_run(QueryReplaceState *is) put_status(s, "&%s", out->buf); } -static void query_replace_key(void *opaque, int key) +static void query_replace_key(QEmacsState *qs, void *opaque, int key) { QueryReplaceState *is = opaque; - EditState *s = is->s; - QEmacsState *qs = s->qs; + EditState *s; + + if (!is || !(s = qe_check_window(qs, &is->s))) { + qe_ungrab_keys(qs); + qe_free(&is); + qe_display(qs); + put_error(qs->active_window, "&Deleted"); + return; + } if (qe_check_window(qs, &is->help_window)) { do_delete_window(is->help_window, 0);