Skip to content

Commit 0b3279d

Browse files
201_85: Ctrl+G interrupt for ongoing commands (#315)
Add Ctrl+G as a universal interrupt/cancel for search, replace-all, and spell-check operations. Blocking C++ loops now check gui_interrupted() to allow breaking out when events arrive. - Add editor_interrupted flag and methods on edit_interface_rep - Add gui_interrupted(true) checks to next_match, replace-all, spell_next - Expose editor-interrupt/editor-interrupted?/editor-clear-interrupt to Scheme - Add general-cancel command dispatching to the right cancel handler - Fix C-g in toolbar search/replace to cancel instead of next-match - Add missing escape handler in old-style search_keypress - Update C-g bindings in Emacs/macOS profiles to use general-cancel
1 parent 1505e98 commit 0b3279d

11 files changed

Lines changed: 144 additions & 7 deletions

File tree

TeXmacs/progs/generic/generic-edit.scm

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,23 @@
230230
(tm-define (kbd-cancel)
231231
(clipboard-clear "primary"))
232232

233+
(tm-define (general-cancel)
234+
(:synopsis "Cancel the current ongoing operation or selection")
235+
(cond
236+
((or (and (defined? 'toolbar-search-active?) toolbar-search-active?)
237+
(and (defined? 'toolbar-replace-active?) toolbar-replace-active?))
238+
(toolbar-search-end))
239+
((and (defined? 'toolbar-spell-active?) toolbar-spell-active?)
240+
(toolbar-spell-end))
241+
((in-search-mode?)
242+
(key-press-search "C-g"))
243+
((in-replace-mode?)
244+
(key-press-replace "C-g"))
245+
((in-spell-mode?)
246+
(key-press-spell "C-g"))
247+
(else
248+
(selection-cancel))))
249+
233250
#|
234251
ocr-paste
235252
剪贴板中的内容是图像时,OCR并插入已识别的内容到当前光标处

TeXmacs/progs/generic/generic-kbd.scm

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,7 @@
254254
("emacs d" (kbd-delete))
255255
("emacs e" (kbd-end-line))
256256
("emacs f" (kbd-right))
257-
("emacs g" (selection-cancel))
257+
("emacs g" (general-cancel))
258258
("emacs j" (insert-return))
259259
("emacs k" (kill-paragraph))
260260
("emacs l" (refresh-window))
@@ -620,7 +620,7 @@
620620
("C-e" (kbd-end-line))
621621
("C-b" (kbd-left))
622622
("C-f" (kbd-right))
623-
("C-g" (selection-cancel))
623+
("C-g" (general-cancel))
624624
("C-k" (kill-paragraph))
625625
("C-l" (refresh-window))
626626
("C-y" (yank-paragraph))

TeXmacs/progs/generic/search-widgets.scm

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -696,9 +696,10 @@ tree 或 #f
696696
(let ((u (if (null? args) (master-buffer) (car args)))
697697
(raux (if (null? args) (replace-buffer) (cadr args))))
698698
(and-with by (or (by-tree raux) current-replace)
699+
(editor-clear-interrupt)
699700
(with-buffer u
700701
(start-editing)
701-
(while (replace-next by)
702+
(while (and (not (editor-interrupted?)) (replace-next by))
702703
(perform-search*))
703704
(end-editing))
704705
(perform-search*)
@@ -1043,10 +1044,11 @@ tree 或 #f
10431044
((== key "pagedown") (search-next-match #t))
10441045
((== key "S-F3") (search-next-match #f))
10451046
((== key "F3") (search-next-match #t))
1046-
((in? key '("C-F" "A-F" "M-F" "C-G" "A-G" "M-G" "C-r" "A-r" "M-r"))
1047+
((in? key '("C-F" "A-F" "M-F" "A-G" "M-G" "C-r" "A-r" "M-r"))
10471048
(search-next-match #f))
1048-
((in? key '("C-f" "A-f" "M-f" "C-g" "A-g" "M-g" "C-s" "A-s" "M-s"))
1049+
((in? key '("C-f" "A-f" "M-f" "A-g" "M-g" "C-s" "A-s" "M-s"))
10491050
(search-next-match #t))
1051+
((in? key '("C-g" "C-G")) (toolbar-search-end))
10501052
((and r? (in? key (list "tab" "S-tab" "return")))
10511053
(search-toolbar-search what)
10521054
(keyboard-focus-on "replace-by"))
@@ -1149,7 +1151,7 @@ tree 或 #f
11491151
((== key "return") (replace-toolbar-replace by))
11501152
((== key "S-return") (undo 0) (perform-search*))
11511153
((== key "C-return") (replace-all))
1152-
((== key "escape") (toolbar-search-end))
1154+
((in? key '("escape" "C-g")) (toolbar-search-end))
11531155
(else (perform-search*)))))
11541156

11551157
(tm-widget (replace-toolbar)

devel/201_85.md

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# [201_85] Ctrl+G interrupt for ongoing commands
2+
3+
## Issue
4+
#315 — Ctrl+G should interrupt ongoing commands such as find-replace and search.
5+
6+
## Problem
7+
There was no general-purpose mechanism to interrupt long-running editor operations (find, replace-all, spell-check) via Ctrl+G. Several C++ loops (`next_match`, replace-all, `spell_next`) run unbounded `while(true)` with no way to break out once started. Additionally, Ctrl+G behavior was inconsistent: it cancelled selection in normal mode but triggered "next match" in toolbar search.
8+
9+
## Solution
10+
### 1. C++ interrupt infrastructure
11+
Added an `editor_interrupted` flag on `edit_interface_rep` with methods `interrupt_editor()`, `is_editor_interrupted()`, and `clear_editor_interrupt()`. The flag is reset when returning to normal input mode.
12+
13+
### 2. Interruptible blocking loops
14+
Added `gui_interrupted(true)` checks to all unbounded C++ loops:
15+
- `next_match()` — exits cleanly with "Search interrupted" message
16+
- Replace-all loop (`a`/`!` key) — stops mid-replace with partial count
17+
- `search_previous_compound()` / `search_next_compound()`
18+
- `spell_next()` — ends spell check early with "Spell check interrupted"
19+
20+
### 3. Scheme glue
21+
Exposed `editor-interrupt`, `editor-interrupted?`, `editor-clear-interrupt` to Scheme via `glue_editor.lua`.
22+
23+
### 4. Unified `general-cancel` command
24+
Added `general-cancel` in `generic-edit.scm` that dispatches to the right cancel handler based on current mode: toolbar search/replace end, old-style search/replace/spell stop, or `selection-cancel` as fallback.
25+
26+
### 5. Consistent Ctrl+G behavior
27+
- Toolbar search: Changed C-g/C-G from "next match" to `toolbar-search-end`
28+
- Toolbar replace: Added C-g as cancel alongside escape
29+
- Scheme replace-all: Made interruptible via `editor-interrupted?` check
30+
- Emacs/macOS profiles: Updated C-g binding to use `general-cancel`
31+
- Old-style search: Added missing `escape` handler to `search_keypress`
32+
33+
## Changed Files
34+
- `src/Edit/Interface/edit_interface.hpp``editor_interrupted` flag and methods
35+
- `src/Edit/Interface/edit_interface.cpp` — Initialize flag in constructor
36+
- `src/Edit/Interface/edit_keyboard.cpp` — Implement interrupt methods, reset in `set_input_normal`
37+
- `src/Edit/editor.hpp` — Pure virtual declarations
38+
- `src/Edit/Replace/edit_search.cpp` — Interrupt checks in loops, escape in search_keypress
39+
- `src/Edit/Replace/edit_spell.cpp` — Interrupt check in `spell_next`
40+
- `src/Scheme/Glue/glue_editor.lua` — Expose interrupt methods to Scheme
41+
- `TeXmacs/progs/generic/generic-edit.scm``general-cancel` command
42+
- `TeXmacs/progs/generic/generic-kbd.scm` — C-g bindings use `general-cancel`
43+
- `TeXmacs/progs/generic/search-widgets.scm` — C-g cancels toolbar search/replace
44+
45+
## How to Test
46+
1. Open a large document
47+
2. Use Ctrl+H (or menu Edit > Replace) to open find-replace
48+
3. Enter a common pattern and press Ctrl+Return (replace-all)
49+
4. While replacement is running, press Ctrl+G — it should stop
50+
5. Verify partial replacements can be undone with Ctrl+Z
51+
6. In toolbar search (Ctrl+F), press Ctrl+G — toolbar should close
52+
7. In normal mode, press Ctrl+G — selection should be cancelled
53+
54+
## 2026/02/26
55+
### What
56+
Implement Ctrl+G as a universal interrupt/cancel command for ongoing operations.

src/Edit/Interface/edit_interface.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ edit_interface_rep::edit_interface_rep ()
7373
shadow (NULL), stored (NULL), cur_sb (2), cur_wb (2) {
7474
user_active= false;
7575
input_mode = INPUT_NORMAL;
76+
editor_interrupted= false;
7677
gui_root_extents (cur_wx, cur_wy);
7778
}
7879

src/Edit/Interface/edit_interface.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ class edit_interface_rep : virtual public editor_rep {
5858
SI zpixel; // pixel multiplied by zoom factor
5959
rectangles copy_always; // for wiping out cursor
6060
int input_mode; // INPUT_NORMAL, INPUT_SEARCH, INPUT_REPLACE
61+
bool editor_interrupted; // set by Ctrl+G, checked by long-running loops
6162

6263
protected:
6364
SI last_x, last_y;
@@ -218,6 +219,9 @@ class edit_interface_rep : virtual public editor_rep {
218219
bool in_spell_mode ();
219220
bool kbd_get_command (string which, string& help, command& cmd);
220221
void interrupt_shortcut ();
222+
void interrupt_editor ();
223+
bool is_editor_interrupted ();
224+
void clear_editor_interrupt ();
221225
bool try_shortcut (string comb);
222226
tree kbd (string s);
223227
tree kbd_shortcut (string s);

src/Edit/Interface/edit_keyboard.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ edit_interface_rep::set_input_normal () {
6161
}
6262
prev_math_comb= "";
6363
hide_math_completion_popup ();
64+
editor_interrupted= false;
6465
}
6566

6667
bool
@@ -99,6 +100,21 @@ edit_interface_rep::interrupt_shortcut () {
99100
sh_mark= 0;
100101
}
101102

103+
void
104+
edit_interface_rep::interrupt_editor () {
105+
editor_interrupted= true;
106+
}
107+
108+
bool
109+
edit_interface_rep::is_editor_interrupted () {
110+
return editor_interrupted;
111+
}
112+
113+
void
114+
edit_interface_rep::clear_editor_interrupt () {
115+
editor_interrupted= false;
116+
}
117+
102118
bool
103119
edit_interface_rep::try_shortcut (string comb) {
104120
int status;

src/Edit/Replace/edit_search.cpp

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include "Interface/edit_interface.hpp"
1313
#include "Replace/edit_replace.hpp"
1414
#include "analyze.hpp"
15+
#include "gui.hpp"
1516

1617
#include <moebius/drd/drd_mode.hpp>
1718
#include <moebius/drd/drd_std.hpp>
@@ -130,6 +131,7 @@ path
130131
edit_replace_rep::search_previous_compound (path init, string which) {
131132
path p= init;
132133
while (true) {
134+
if (gui_interrupted (true)) return init;
133135
if (p == rp) return init;
134136
if (last_item (p) == 0) p= path_up (p);
135137
else {
@@ -149,6 +151,7 @@ path
149151
edit_replace_rep::search_next_compound (path init, string which) {
150152
path p= init;
151153
while (true) {
154+
if (gui_interrupted (true)) return init;
152155
if (p == rp) return init;
153156
if (last_item (p) == (N (subtree (et, path_up (p))) - 1)) p= path_up (p);
154157
else {
@@ -344,6 +347,13 @@ void
344347
edit_replace_rep::next_match (bool forward) {
345348
// cout << "Next match at " << search_at << "\n";
346349
while (true) {
350+
if (gui_interrupted (true)) {
351+
search_at= rp;
352+
set_selection (tp, tp);
353+
notify_change (THE_SELECTION);
354+
set_message ("Search interrupted", "");
355+
return;
356+
}
347357
if (search_at == rp) {
348358
set_selection (tp, tp);
349359
notify_change (THE_SELECTION);
@@ -438,7 +448,7 @@ edit_replace_rep::search_keypress (string s) {
438448
search_stop ();
439449
return false;
440450
}
441-
else if ((s == "C-c") || (s == "C-g")) search_stop ();
451+
else if ((s == "C-c") || (s == "C-g") || (s == "escape")) search_stop ();
442452
else if ((s == "next") || (s == "previous")) {
443453
if (search_what == "") {
444454
tree t= selection_raw_get ("search");
@@ -549,6 +559,13 @@ edit_replace_rep::replace_keypress (string s) {
549559
}
550560
else if (s == "a" || s == "!") {
551561
while (search_at != rp) {
562+
if (gui_interrupted (true)) {
563+
set_message (concat ("Replaced ", as_string (nr_replaced),
564+
" occurrences (interrupted)"),
565+
"replace");
566+
set_input_normal ();
567+
return true;
568+
}
552569
nr_replaced++;
553570
go_to (copy (search_end));
554571
cut (search_at, search_end);

src/Edit/Replace/edit_spell.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include "Interface/edit_interface.hpp"
1313
#include "Replace/edit_replace.hpp"
1414
#include "analyze.hpp"
15+
#include "gui.hpp"
1516

1617
#ifdef USE_PLUGIN_ISPELL
1718
#ifdef MACOSX_EXTENSIONS
@@ -124,6 +125,11 @@ message_ispell (tree t) {
124125
void
125126
edit_replace_rep::spell_next () {
126127
while (true) {
128+
if (gui_interrupted (true)) {
129+
spell_end ();
130+
set_message ("Spell check interrupted", "correct text");
131+
return;
132+
}
127133
if (path_inf (spell_end_p, search_at)) search_at= rp;
128134
if (search_at == rp) {
129135
spell_end ();

src/Edit/editor.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,9 @@ class editor_rep : public simple_widget_rep {
180180
virtual bool in_replace_mode () = 0;
181181
virtual bool in_spell_mode () = 0;
182182
virtual void interrupt_shortcut () = 0;
183+
virtual void interrupt_editor () = 0;
184+
virtual bool is_editor_interrupted () = 0;
185+
virtual void clear_editor_interrupt () = 0;
183186
virtual bool kbd_get_command (string cmd_s, string& help, command& cmd)= 0;
184187
virtual void key_press (string key) = 0;
185188
virtual void emulate_keyboard (string keys, string action= "") = 0;

0 commit comments

Comments
 (0)