Skip to content

Commit 301c79e

Browse files
authored
[222_65] 修复 right footer 的快捷键显示 (#3085)
1 parent 7fee068 commit 301c79e

File tree

4 files changed

+297
-10
lines changed

4 files changed

+297
-10
lines changed

TeXmacs/progs/kernel/gui/menu-widget.scm

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,10 @@
150150
;; (display* what " -> " r " -> " (kbd-system r menu-flag?) "\n"))
151151
(kbd-system r menu-flag?)))
152152

153+
(tm-define (kbd-find-shortcut-export what)
154+
(:synopsis "Find display shortcut string for command @what")
155+
(kbd-find-shortcut what #f))
156+
153157
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
154158
;; Menu labels
155159
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

devel/222_65.md

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
[222_65] 修复右侧 footer 快捷键显示错误,并补齐 operation footer 的快捷键提示
2+
3+
## 如何测试
4+
5+
测试项一:数学符号在右侧 footer 中显示对应快捷键
6+
1. 打开任意文档,进入数学模式
7+
2. 输入 `a tab`,得到 `<alpha>`
8+
3. 将光标移动到 `<alpha>` 附近
9+
4. 右侧 footer 应显示 `<alpha>` 以及对应的快捷键提示,不应只显示符号本身
10+
11+
测试项二:不同数学符号都能实时显示对应快捷键
12+
1. 打开任意文档,进入数学模式
13+
2. 输入 `- >`,得到 `<rightarrow>`
14+
3. 将光标移走,再移动到 `<rightarrow>` 附近
15+
4. 右侧 footer 应显示 `<rightarrow>` 以及对应的快捷键提示
16+
5. 再输入 `@ @`,得到 `<infty>`
17+
6. 将光标移走,再移动到 `<infty>` 附近
18+
7. 右侧 footer 应更新为 `<infty>` 的快捷键提示,不应继续停留在 `<rightarrow>` 的结果
19+
20+
测试项三:同一个符号在不同上下文中切换时,footer 应按当前上下文实时更新
21+
1. 打开任意文档,先进入一个可以显示符号快捷键的上下文
22+
2. 将光标移动到某个带快捷键提示的符号附近,记录右侧 footer 内容
23+
3. 切换到另一个编辑上下文,再将光标移动到相同符号附近
24+
4. 右侧 footer 应按当前上下文重新计算结果,不应复用上一次上下文中的旧显示结果
25+
26+
测试项四:光标连续移动时,footer 快捷键提示应持续更新
27+
1. 打开任意包含多个符号和 operation 的文档
28+
2. 使用方向键连续移动光标,依次经过普通字符、数学符号、结构性节点
29+
3. 右侧 footer 应随着光标位置实时切换显示内容
30+
4. 不应出现已经离开的符号快捷键残留在 footer 中的情况
31+
32+
测试项五:根号、分式、上下标等结构在插入后应显示 operation 快捷键
33+
1. 打开任意文档,进入数学模式
34+
2. 依次执行以下操作:
35+
输入根号
36+
输入分式
37+
输入右下标 / 右上标
38+
3. 每次插入后,光标都会落在结构内部空槽中
39+
4. 右侧 footer 应显示对应的 operation 文案和快捷键,不应退化为普通 text footer
40+
5. 至少验证以下几类:
41+
`square root`
42+
`fraction`
43+
`subscript` / `superscript`
44+
45+
测试项六:大运算符应显示对应快捷键
46+
1. 打开任意文档,进入数学模式
47+
2. 插入 `sum``prod``int` 等大运算符
48+
3. 将光标移动到这些结构附近
49+
4. 右侧 footer 应显示对应 operation 文案和快捷键
50+
5. 不应继续走默认 `(make 'op)` fallback 而丢失快捷键
51+
52+
测试项七:定界符结构的快捷键显示行为
53+
1. 打开任意文档,进入数学模式
54+
2. 分别插入 `()`, `[]`, `{}` 等定界符结构
55+
3. 将光标移动到对应结构附近,观察右侧 footer 中的快捷键提示
56+
4. 再打开数学工具栏中的大型定界符菜单,将鼠标悬浮到对应符号上,观察工具栏菜单的快捷键提示
57+
5. 对比两者是否一致
58+
6. 当前已知结论:
59+
对定界符这类结构,footer 的反查结果可能与菜单 tooltip 不一致
60+
这是由于 footer 只能从结果树逆向推回 source,而菜单直接持有原始 source
61+
62+
## 2026/04/01 修复右侧 footer 快捷键缓存导致的错误显示
63+
64+
### What
65+
移除 `src/Edit/Interface/edit_footer.cpp` 中按符号字符串做 key 的全局快捷键缓存,改为每次 footer 更新时按当前上下文实时调用 `kbd-find-inv-binding` 反查快捷键。
66+
67+
### Why
68+
此前右侧 footer 的快捷键显示使用了进程级静态缓存,只以 `<alpha>``<rightarrow>` 这类符号字符串作为缓存 key。
69+
`kbd-find-inv-binding` 实际是上下文相关的反查接口,键盘绑定本身可能带有 mode 和 require 条件。
70+
这样会导致第一次查到的结果被缓存下来,之后即使切换上下文,footer 仍然可能显示旧的快捷键结果,不能满足“实时显示当前快捷键”的需求。
71+
72+
### How
73+
src/Edit/Interface/edit_footer.cpp 删除 `shortcut_cache`
74+
src/Edit/Interface/edit_footer.cpp :143 `get_shortcut_suffix` 改为直接调用 `kbd-find-inv-binding`
75+
src/Edit/Interface/edit_footer.cpp :161 `compute_text_footer` 继续复用 `get_shortcut_suffix`,但结果不再受旧缓存污染
76+
77+
## 2026/04/01 补齐 operation footer 的快捷键提示
78+
79+
### What
80+
`compute_operation_footer()` 增加快捷键后缀逻辑,使 operation 类型的 footer 也能显示对应快捷键。
81+
82+
### Why
83+
此前实现只在 `compute_text_footer()` 中为 `<symbol>` 追加快捷键提示,而 `compute_operation_footer()` 只返回 `square root``long arrow``hat` 这类文本描述,没有追加快捷键。
84+
这会导致 symbol path 能显示快捷键,但 operation path 不能显示,实际效果与原功能说明不一致。
85+
86+
### How
87+
src/Edit/Interface/edit_footer.cpp :222 `compute_operation_footer` 新增 `suffix`
88+
src/Edit/Interface/edit_footer.cpp :355 使用 `drd->get_name (L (st))` 组装 `"(make '<op>)"`,并通过 `get_shortcut_suffix` 统一反查快捷键
89+
src/Edit/Interface/edit_footer.cpp :356 当存在绑定时,将快捷键后缀追加到 operation footer 文本后
90+
91+
## 2026/04/01 结构命令反查需要传 command object,而不是字符串
92+
93+
### What
94+
为 footer 的结构类快捷键反查补齐 command object 转换,确认 `kbd-find-inv-binding` 对不同参数形式的行为差异。
95+
96+
### Why
97+
排查发现 `<alpha>` 这类 symbol 可以直接用字符串反查,但 `(make-sqrt)``(make-fraction)``(make-script #f #t)` 这类结构命令如果直接以字符串传给 `kbd-find-inv-binding`,会返回空结果。
98+
原因在于逆向绑定表里存储的是命令对象,而不是命令源码字符串。
99+
100+
- `"<alpha>"` 作为字符串可以命中
101+
- `"(make-sqrt)"` / `"make-sqrt"` 作为字符串不能命中
102+
- `'(make-sqrt)``(string->object "(make-sqrt)")` 可以命中
103+
- 返回结果是内部 binding 形式,例如 `A-s``A-f`,而不一定是源码里的 `"math s"``"math f"`
104+
105+
### How
106+
src/Edit/Interface/edit_footer.cpp :144 `get_shortcut_suffix` 改为:
107+
- 普通 symbol 继续使用 `object (cmd_s)`
108+
-`(` 开头的命令字符串先 `string_to_object (cmd_s)`,再传给 `kbd-find-inv-binding`
109+
110+
## 2026/04/01 operation footer 在空占位原子节点中提升到父结构节点
111+
112+
### What
113+
修复刚插入根号、分式、上下标、上下附标等结构时,右侧 footer 只走 text path、不显示 operation shortcut 的问题。
114+
115+
### Why
116+
这些结构在插入后会立刻把光标放进内部空原子节点中,例如 `SQRT("")``RSUB("")`
117+
此时 `set_right_footer()` 中的
118+
119+
`subtree (et, path_up (tp))`
120+
121+
读到的是空原子子节点,而不是外层结构节点,导致逻辑总是进入 `compute_text_footer()`,根本不会调用 `compute_operation_footer()`
122+
123+
### How
124+
src/Edit/Interface/edit_footer.cpp :512 `set_right_footer()` 修改分发逻辑:
125+
- 当当前节点是原子节点时,先查看 `path_up (tp, 2)` 对应的父节点
126+
- 若父节点标签属于 `LSUB``LSUP``RSUB``RSUP``FRAC``SQRT``ABOVE``BELOW``WIDE``VAR_WIDE`
127+
- 则直接对父节点调用 `compute_operation_footer(parent)`
128+
- 其他情况仍保持原有 text path
129+
130+
## 2026/04/01 get_operation_shortcut_suffix 补齐结构类命令映射
131+
132+
### What
133+
新增 `get_operation_shortcut_suffix(tree st)`,把 operation footer 的快捷键反查从统一的 `(make 'op)` fallback 提升为“按结构类别选择命令对象”。
134+
135+
### Why
136+
结构类节点的实际插入命令并不统一:
137+
- `FRAC` 对应 `(make-fraction)`
138+
- `SQRT` 对应 `(make-sqrt)` / `(make-var-sqrt)`
139+
- `RSUB/RSUP/LSUB/LSUP` 对应 `(make-script ...)`
140+
- `ABOVE/BELOW` 对应 `(make-above)` / `(make-below)`
141+
- `BIG(sum)``BIG(prod)``BIG(int)` 对应 `(math-big-operator "...")`
142+
- `WIDE/VAR_WIDE` 对应 `(make-wide "...")` / `(make-wide-under "...")`
143+
144+
如果继续统一走 `(make 'op)`,这些结构大都无法正确反查。
145+
146+
### How
147+
src/Edit/Interface/edit_interface.hpp 新增 `get_operation_shortcut_suffix (tree st)` 声明。
148+
149+
src/Edit/Interface/edit_footer.cpp :156 新增 `get_operation_shortcut_suffix`
150+
- `FRAC` -> `(make-fraction)`
151+
- `SQRT` -> `(make-sqrt)` / `(make-var-sqrt)`
152+
- `LSUB/LSUP/RSUB/RSUP` -> `(make-script ...)`
153+
- `ABOVE/BELOW` -> `(make-above)` / `(make-below)`
154+
- `BIG` -> 根据 `st[0]` 中的 `<sum>` / `<prod>` / `<int>` 等内容,组装 `(math-big-operator "...")`
155+
- `WIDE/VAR_WIDE` -> `(make-wide "...")` / `(make-wide-under "...")`
156+
- 其他结构保留 `(make 'op)` fallback
157+
158+
`compute_operation_footer()` 改为统一调用这个 helper。
159+
160+
## 2026/04/01 定界符结构的逆向结果与菜单提示不一致
161+
162+
### What
163+
尝试为 `AROUND` / `VAR_AROUND` 结构补齐快捷键显示,并排查为什么 footer 中会出现 `Alt+L [`,而数学工具栏大型定界符菜单悬浮提示显示的是 `[`
164+
165+
### Why
166+
大型定界符菜单里的每个按钮都带有原始命令 source,例如:
167+
168+
`(math-bracket-open "[" "]" 'default)`
169+
170+
菜单系统在 `TeXmacs/progs/kernel/gui/menu-widget.scm` 中直接对这个 source 调用 `kbd-find-shortcut`,因此能稳定返回按钮自身对应的快捷键。
171+
172+
footer 则不同:
173+
- footer 拿到的是结果树,如 `around` / `around*`
174+
- 然后再从结果树反推命令 source
175+
- 对括号结构来说,同一个结果树可能对应多条合法绑定:
176+
- 普通绑定:`"[" -> (make-bracket-open "[" "]")`
177+
- 数学默认括号:`(math-bracket-open "[" "]" 'default)`
178+
- large 定界符:`"math:left [" -> (math-bracket-open "[" "]" #t)`
179+
180+
逆向系统在多条候选里可能返回 large 这条,因此 footer 中会看到 `Alt+L [` 这样的前缀式结果。
181+
这并不代表 `Alt+L` 是一个完整快捷键,而是 `math:left` 这个前缀组的显示形式。
182+
183+
### 结论
184+
对于括号这类结构,问题的根源不在 footer 拼接,而在逆向系统本身:
185+
- 菜单是 `source -> shortcut`,一对一,结果稳定
186+
- footer 是 `tree -> source -> shortcut`,一对多,容易命中“合法但不符合直觉”的候选
187+
188+
本轮代码里为复用菜单逻辑,新增了:
189+
- `TeXmacs/progs/kernel/gui/menu-widget.scm` : `kbd-find-shortcut-export`
190+
- `src/Edit/Interface/edit_footer.cpp` : `get_display_shortcut_suffix`
191+

src/Edit/Interface/edit_footer.cpp

Lines changed: 100 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
#include "cork.hpp"
1616
#include "dictionary.hpp"
1717
#include "edit_interface.hpp"
18+
#include "moebius/tree_label.hpp"
19+
#include "tree_helper.hpp"
1820

1921
using namespace moebius;
2022

@@ -139,24 +141,87 @@ edit_interface_rep::set_middle_footer () {
139141
* Set right footer with information about cursor position
140142
******************************************************************************/
141143

142-
// Cache for keyboard shortcuts
143-
static hashmap<string, tree> shortcut_cache ("");
144-
145144
tree
146145
edit_interface_rep::get_shortcut_suffix (string cmd_s) {
147-
if (shortcut_cache->contains (cmd_s)) return shortcut_cache[cmd_s];
148-
149-
object result_obj= call ("kbd-find-inv-binding", object (cmd_s));
146+
object query= starts (cmd_s, "(") ? string_to_object (cmd_s) : object (cmd_s);
147+
object result_obj= call ("kbd-find-inv-binding", query);
150148
string binding = as_string (result_obj);
151149
tree result = "";
152150
if (binding != "" && binding != "#f") {
153151
tree shortcut_tree= sv->kbd_system_rewrite (binding);
154-
result = concat (" [", shortcut_tree, "]");
152+
result = concat (" [ ", shortcut_tree, " ]");
155153
}
156-
shortcut_cache (cmd_s)= result;
157154
return result;
158155
}
159156

157+
tree
158+
edit_interface_rep::get_display_shortcut_suffix (string cmd_s) {
159+
object query= starts (cmd_s, "(") ? string_to_object (cmd_s) : object (cmd_s);
160+
string sh = as_string (call ("kbd-find-shortcut-export", query));
161+
if (sh == "" || sh == "#f") return "";
162+
return concat (" [ ", sh, " ]");
163+
}
164+
165+
tree
166+
edit_interface_rep::get_operation_shortcut_suffix (tree st) {
167+
switch (L (st)) {
168+
case FRAC:
169+
return get_shortcut_suffix ("(make-fraction)");
170+
case SQRT:
171+
if (N (st) == 1) return get_shortcut_suffix ("(make-sqrt)");
172+
return get_shortcut_suffix ("(make-var-sqrt)");
173+
case LSUB:
174+
return get_shortcut_suffix ("(make-script #f #f)");
175+
case LSUP:
176+
return get_shortcut_suffix ("(make-script #t #f)");
177+
case RSUB:
178+
return get_shortcut_suffix ("(make-script #f #t)");
179+
case RSUP:
180+
return get_shortcut_suffix ("(make-script #t #t)");
181+
case BELOW:
182+
return get_shortcut_suffix ("(make-below)");
183+
case ABOVE:
184+
return get_shortcut_suffix ("(make-above)");
185+
case BIG:
186+
if (N (st) >= 1 && is_atomic (st[0])) {
187+
string op= as_string (st[0]);
188+
if (starts (op, "<") && ends (op, ">")) op= op (1, N (op) - 1);
189+
return get_shortcut_suffix ("(math-big-operator " *
190+
object_to_string (object (op)) * ")");
191+
}
192+
return "";
193+
case AROUND:
194+
case VAR_AROUND:
195+
if (N (st) >= 3 && is_atomic (st[0]) && is_atomic (st[2])) {
196+
string lb = object_to_string (object (as_string (st[0])));
197+
string rb = object_to_string (object (as_string (st[2])));
198+
string large= (L (st) == VAR_AROUND) ? "#t" : "'default";
199+
tree ret= get_display_shortcut_suffix ("(math-bracket-open " * lb * " " *
200+
rb * " " * large * ")");
201+
if (ret != "") return ret;
202+
return get_display_shortcut_suffix ("(math-bracket-close " * rb * " " *
203+
lb * " " * large * ")");
204+
}
205+
return "";
206+
case WIDE:
207+
if (N (st) >= 2)
208+
return get_shortcut_suffix (
209+
"(make-wide " * object_to_string (object (as_string (st[1]))) * ")");
210+
return "";
211+
case VAR_WIDE:
212+
if (N (st) >= 2)
213+
return get_shortcut_suffix (
214+
"(make-wide-under " * object_to_string (object (as_string (st[1]))) *
215+
")");
216+
return "";
217+
default: {
218+
string op= drd->get_name (L (st));
219+
if (op != "") return get_shortcut_suffix ("(make '" * op * ")");
220+
return "";
221+
}
222+
}
223+
}
224+
160225
void
161226
edit_interface_rep::set_right_footer (tree r) {
162227
SERVER (
@@ -226,7 +291,8 @@ get_with_text (tree t) {
226291

227292
tree
228293
edit_interface_rep::compute_operation_footer (tree st) {
229-
tree r= "";
294+
tree r = "";
295+
tree suffix= "";
230296
if (N (st) >= 2) {
231297
switch (L (st)) {
232298
case VAR_WIDE:
@@ -356,6 +422,8 @@ edit_interface_rep::compute_operation_footer (tree st) {
356422
r= drd->get_name (L (st));
357423
}
358424
}
425+
suffix= get_operation_shortcut_suffix (st);
426+
if (suffix != "") r= concat (r, suffix);
359427
if (last_item (tp) == 0) r= concat ("before ", r);
360428
return r;
361429
}
@@ -482,7 +550,29 @@ edit_interface_rep::set_right_footer () {
482550
tree cf= compute_compound_footer (et, path_up (tp));
483551
tree st= subtree (et, path_up (tp));
484552
tree lf;
485-
if (is_atomic (st)) lf= compute_text_footer (st);
553+
if (is_atomic (st) && N (tp) >= 2) {
554+
tree parent= subtree (et, path_up (tp, 2));
555+
switch (L (parent)) {
556+
case LSUB:
557+
case LSUP:
558+
case RSUB:
559+
case RSUP:
560+
case FRAC:
561+
case SQRT:
562+
case ABOVE:
563+
case BELOW:
564+
case WIDE:
565+
case VAR_WIDE:
566+
case AROUND:
567+
case VAR_AROUND:
568+
lf= compute_operation_footer (parent);
569+
break;
570+
default:
571+
lf= compute_text_footer (st);
572+
break;
573+
}
574+
}
575+
else if (is_atomic (st)) lf= compute_text_footer (st);
486576
else lf= compute_operation_footer (st);
487577
if (N (focus_get (false)) + 1 >= N (tp)) cf= concat (cf, lf);
488578
set_right_footer (cf);

src/Edit/Interface/edit_interface.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,8 @@ class edit_interface_rep : virtual public editor_rep {
272272

273273
/* the footer */
274274
tree get_shortcut_suffix (string cmd_s);
275+
tree get_display_shortcut_suffix (string cmd_s);
276+
tree get_operation_shortcut_suffix (tree st);
275277
tree compute_text_footer (tree st);
276278
tree compute_operation_footer (tree st);
277279
tree compute_compound_footer (tree t, path p);

0 commit comments

Comments
 (0)