From 2baa14add6d9976c29b27cf4470bb458a0198694 Mon Sep 17 00:00:00 2001 From: Robin Haberkorn Date: Sun, 6 Jul 2025 06:37:07 +0200 Subject: supports a numeric buffer id now * ED hooks are not executed in this case * is now allowed even when editing a Q-Reg, unless you try to close the current buffer --- TODO | 4 +- doc/tedoc.tes | 4 +- src/core-commands.c | 2 +- src/ring.c | 119 +++++++++++++++++++++++++++++++++------------------- src/ring.h | 2 +- tests/testsuite.at | 6 +++ 6 files changed, 88 insertions(+), 49 deletions(-) diff --git a/TODO b/TODO index 697dd10..8178671 100644 --- a/TODO +++ b/TODO @@ -318,7 +318,6 @@ Features: rubbed out command line). * some missing useful VideoTECO/TECO-11 commands and unnecessary incompatibilities: - * EF with buffer id * ER command: read file(s) into current buffer at dot. Video TECO accepts a glob pattern here. * nEW to save a buffer by id @@ -701,6 +700,9 @@ Features: Allows us to do CI on FreeBSD and potentially other more obscure systems. Also, this will allow us to migrate to another hoster like Savannah or Sourcehut later on. + * nEF is currently disallowed when editing a Q-Reg as long as + n == Q* (default). Perhaps it should always be allowed without + switching from the Q-Reg view. Optimizations: * Use SC_DOCUMENTOPTION_STYLES_NONE in batch mode. diff --git a/doc/tedoc.tes b/doc/tedoc.tes index b148691..885f41a 100755 --- a/doc/tedoc.tes +++ b/doc/tedoc.tes @@ -97,11 +97,11 @@ I\# \#^J !* find insertion point *! -FS^J.TEDOC^J^J +:FS^J.TEDOC^J^J"F(0/0)' EJ-1< < - 2U* EU.#scQ* + 2U* [*]#sc !* extract comment *! SQ[comment.start]$; diff --git a/src/core-commands.c b/src/core-commands.c index 819b1aa..0ff2b81 100644 --- a/src/core-commands.c +++ b/src/core-commands.c @@ -2003,7 +2003,7 @@ teco_state_ecommand_flags(teco_machine_main_t *ctx, GError **error) * The current user interface: 1 for Curses, 2 for GTK * (\fBread-only\fP) * .IP 1: - * The current numbfer of buffers: Also the numeric id + * The current number of buffers: Also the numeric id * of the last buffer in the ring. This is implied if * no argument is given, so \(lqEJ\(rq returns the number * of buffers in the ring. diff --git a/src/ring.c b/src/ring.c index b7a75d1..1c9a2cd 100644 --- a/src/ring.c +++ b/src/ring.c @@ -151,7 +151,7 @@ teco_ring_last(void) } static void -teco_undo_ring_edit_action(teco_buffer_t **buffer, gboolean run) +teco_undo_ring_reinsert_action(teco_buffer_t **buffer, gboolean run) { if (run) { /* @@ -162,22 +162,19 @@ teco_undo_ring_edit_action(teco_buffer_t **buffer, gboolean run) teco_tailq_insert_before((*buffer)->entry.next, &(*buffer)->entry); else teco_tailq_insert_tail(&teco_ring_head, &(*buffer)->entry); - - teco_ring_current = *buffer; - teco_buffer_edit(*buffer); } else { teco_buffer_free(*buffer); } } -/* - * Emitted after a buffer close - * The pointer is the only remaining reference to the buffer! +/** + * Insert buffer during undo (for closing buffers). + * Ownership of the buffer is passed to the undo token. */ static void -teco_undo_ring_edit(teco_buffer_t *buffer) +teco_undo_ring_reinsert(teco_buffer_t *buffer) { - teco_buffer_t **ctx = teco_undo_push_size((teco_undo_action_t)teco_undo_ring_edit_action, + teco_buffer_t **ctx = teco_undo_push_size((teco_undo_action_t)teco_undo_ring_reinsert_action, sizeof(buffer)); if (ctx) *ctx = buffer; @@ -320,7 +317,7 @@ teco_ring_edit_by_id(teco_int_t id, GError **error) } static void -teco_ring_close_buffer(teco_buffer_t *buffer) +teco_ring_remove_buffer(teco_buffer_t *buffer) { teco_tailq_remove(&teco_ring_head, &buffer->entry); @@ -333,32 +330,48 @@ teco_ring_close_buffer(teco_buffer_t *buffer) "Removed unnamed file from the ring."); } -TECO_DEFINE_UNDO_CALL(teco_ring_close_buffer, teco_buffer_t *); +TECO_DEFINE_UNDO_CALL(teco_ring_remove_buffer, teco_buffer_t *); +/** + * Close the given buffer. + * Executes close hooks and changes the current buffer if necessary. + * It already pushes undo tokens. + */ gboolean -teco_ring_close(GError **error) +teco_ring_close(teco_buffer_t *buffer, GError **error) { - teco_buffer_t *buffer = teco_ring_current; + if (buffer == teco_ring_current) { + if (!teco_ed_hook(TECO_ED_HOOK_CLOSE, error)) + return FALSE; - if (!teco_ed_hook(TECO_ED_HOOK_CLOSE, error)) - return FALSE; - teco_ring_close_buffer(buffer); - teco_ring_current = teco_buffer_next(buffer) ? : teco_buffer_prev(buffer); - /* Transfer responsibility to the undo token object. */ - teco_undo_ring_edit(buffer); + teco_ring_undo_edit(); + teco_ring_remove_buffer(buffer); + + teco_ring_current = teco_buffer_next(buffer) ? : teco_buffer_prev(buffer); + if (!teco_ring_current) { + /* edit new unnamed buffer */ + if (!teco_ring_edit_by_name(NULL, error)) + return FALSE; + } else { + teco_buffer_edit(teco_ring_current); + if (!teco_ed_hook(TECO_ED_HOOK_EDIT, error)) + return FALSE; + } + } else { + teco_ring_remove_buffer(buffer); + } - if (!teco_ring_current) - return teco_ring_edit_by_name(NULL, error); + /* transfer responsibility to the undo token object */ + teco_undo_ring_reinsert(buffer); - teco_buffer_edit(teco_ring_current); - return teco_ed_hook(TECO_ED_HOOK_EDIT, error); + return TRUE; } void teco_ring_undo_close(void) { undo__teco_buffer_free(teco_ring_current); - undo__teco_ring_close_buffer(teco_ring_current); + undo__teco_ring_remove_buffer(teco_ring_current); } void @@ -583,38 +596,59 @@ teco_state_save_file_done(teco_machine_main_t *ctx, const teco_string_t *str, GE TECO_DEFINE_STATE_EXPECTFILE(teco_state_save_file); /*$ EF close - * [bool]EF -- Remove buffer from ring + * [n]EF -- Remove buffer from ring * -EF - * :EF + * [n]:EF * * Removes buffer from buffer ring, effectively * closing it. - * If the buffer is dirty (modified), EF will yield + * The optional argument specifies the id of the buffer + * to close -- by default the current buffer will be closed. + * If the selected buffer is dirty (modified), \fBEF\fP will yield * an error. - * may be a specified to enforce closing dirty - * buffers. - * If it is a Failure condition boolean (negative), - * the buffer will be closed unconditionally. - * If is absent, the sign prefix (1 or -1) will - * be implied, so \(lq-EF\(rq will always close the buffer. + * If is negative (success boolean), buffer <-n> will be closed + * even if it is dirty. + * \(lq-EF\(rq will force-close the current buffer. * - * When colon-modified, is ignored and \fBEF\fP - * will save the buffer before closing. + * When colon-modified, the selected buffer is saved before closing. * The file is always written, unlike \(lq:EX\(rq which * saves only dirty buffers. * This can fail of course, e.g. when called on the unnamed * buffer. * - * It is noteworthy that EF will be executed immediately in + * It is noteworthy that \fBEF\fP will be executed immediately in * interactive mode but can be rubbed out at a later time * to reopen the file. * Closed files are kept in memory until the command line * is terminated. + * + * Close and edit hooks are only executed when closing the current buffer. */ void teco_state_ecommand_close(teco_machine_main_t *ctx, GError **error) { - if (teco_qreg_current) { + if (!teco_expressions_eval(FALSE, error)) + return; + + /* + * This is like implying teco_num_sign*teco_ring_get_id(teco_ring_current) + * but avoids the O(n) ring iterations. + */ + teco_buffer_t *buffer; + gboolean force; + if (teco_expressions_args() > 0) { + teco_int_t id; + if (!teco_expressions_pop_num_calc(&id, 0, error)) + return; + buffer = teco_ring_find(ABS(id)); + force = id < 0; + } else { + buffer = teco_ring_current; + force = teco_num_sign < 0; + teco_set_num_sign(1); + } + + if (buffer == teco_ring_current && teco_qreg_current) { const teco_string_t *name = &teco_qreg_current->head.name; g_autofree gchar *name_printable = teco_string_echo(name->data, name->len); g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED, @@ -623,19 +657,16 @@ teco_state_ecommand_close(teco_machine_main_t *ctx, GError **error) } if (teco_machine_main_eval_colon(ctx) > 0) { - if (!teco_buffer_save(teco_ring_current, NULL, error)) + if (!teco_buffer_save(buffer, NULL, error)) return; } else { - teco_int_t v; - if (!teco_expressions_pop_num_calc(&v, teco_num_sign, error)) - return; - if (teco_is_failure(v) && teco_ring_current->dirty) { + if (!force && buffer->dirty) { g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED, "Buffer \"%s\" is dirty", - teco_ring_current->filename ? : "(Unnamed)"); + buffer->filename ? : "(Unnamed)"); return; } } - teco_ring_close(error); + teco_ring_close(buffer, error); } diff --git a/src/ring.h b/src/ring.h index 34293b9..b75846a 100644 --- a/src/ring.h +++ b/src/ring.h @@ -86,7 +86,7 @@ teco_ring_undo_edit(void) teco_buffer_undo_edit(teco_ring_current); } -gboolean teco_ring_close(GError **error); +gboolean teco_ring_close(teco_buffer_t *buffer, GError **error); void teco_ring_undo_close(void); void teco_ring_set_scintilla_undo(gboolean state); diff --git a/tests/testsuite.at b/tests/testsuite.at index a1b8ada..b0db94d 100644 --- a/tests/testsuite.at +++ b/tests/testsuite.at @@ -239,6 +239,12 @@ TE_CHECK([[@^Ua/test/ @E%a/saveqreg.txt/ @EB/saveqreg.txt/ Z-4"N(0/0)']], 0, ign TE_CHECK([[@E%$/saveqreg.txt/ @EB/saveqreg.txt/ Z-:Q$"N(0/0)']], 0, ignore, ignore) AT_CLEANUP +AT_SETUP([Opening/closing buffers]) +TE_CHECK([[@EB/foo/ @I/XXX/ -EF :Q*"N(0/0)']], 0, ignore, ignore) +TE_CHECK([[@EB/foo/ @I/XXX/ :EF :Q*"N(0/0)' @EB/foo/ Z-3"N(0/0)']], 0, ignore, ignore) +TE_CHECK([[@EB/foo/ 1EF :Q*"=(0/0)']], 0, ignore, ignore) +AT_CLEANUP + AT_SETUP([8-bit cleanliness]) TE_CHECK([[0@I//J 0A"N(0/0)' :@S/^@/"F(0/0)']], 0, ignore, ignore) TE_CHECK([[@EQa//0EE 1U*0EE 0:@EUa/f^@^@/ :Qa-4"N(0/0)' Ga Z-4"N(0/0)']], 0, ignore, ignore) -- cgit v1.2.3