Skip to content

Commit d77a6e0

Browse files
da-liiiclaude
andauthored
[221_10] 改进列表的结构化插入和结构化删除 (#3296)
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 12fc0cc commit d77a6e0

3 files changed

Lines changed: 80 additions & 11 deletions

File tree

TeXmacs/progs/generic/generic-edit.scm

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,7 @@
366366

367367
(tm-define (blank-list-item-stree list-type)
368368
(if (== list-type 'description)
369-
`(item*)
369+
`(item* "")
370370
`(item)))
371371

372372
(tm-define (list-item-end-index item-list item-index list-type)
@@ -394,6 +394,12 @@
394394
(and range
395395
(tree-remove item-list (car range) (- (cadr range) (car range)))))
396396

397+
(tm-define (document-empty-for-list? doc)
398+
(let loop ((i 0))
399+
(cond ((>= i (tree-arity doc)) #t)
400+
((list-item-node? (tree-ref doc i)) #f)
401+
(else (loop (+ i 1))))))
402+
397403
;; 在有序和无序列表中实现缩进功能
398404
(tm-define (kbd-variant t forwards?)
399405
(:require
@@ -1027,7 +1033,9 @@ TODO: 在文本模式中,可以自动识别剪贴板中的内容,并智能
10271033
(if (and (tree-is? new-item 'concat)
10281034
(> (tree-arity new-item) 1))
10291035
(tree-go-to new-item 1 :end)
1030-
(tree-go-to new-list insert-pos :end)))))))))
1036+
(if (tree-is? new-item 'item*)
1037+
(tree-go-to new-item 0 :start)
1038+
(tree-go-to new-list insert-pos :end))))))))))
10311039

10321040
(tm-define (structured-remove-vertical t downwards?)
10331041
(:require (list-structured-insert-context?))
@@ -1043,9 +1051,20 @@ TODO: 在文本模式中,可以自动识别剪贴板中的内容,并智能
10431051
(when range
10441052
(let* ((new-list (remove-list-item-range item-list range))
10451053
(pos (min (car range) (- (tree-arity new-list) 1))))
1046-
(if (>= pos 0)
1047-
(tree-go-to new-list pos :end)
1048-
(tree-go-to new-list :end))))))))))
1054+
(if (document-empty-for-list? new-list)
1055+
(let* ((list-parent (tree-up new-list))
1056+
(parent-doc (and list-parent (tree-up list-parent)))
1057+
(list-index (and list-parent (tree-index list-parent))))
1058+
(when (and parent-doc list-index)
1059+
(tree-remove parent-doc list-index 1)
1060+
(if (== (tree-arity parent-doc) 0)
1061+
(begin
1062+
(tree-insert parent-doc 0 (list ""))
1063+
(tree-go-to parent-doc 0 :end))
1064+
(tree-go-to parent-doc list-index :end))))
1065+
(if (>= pos 0)
1066+
(tree-go-to new-list pos :end)
1067+
(tree-go-to new-list :end)))))))))))
10491068

10501069
(tm-define (structured-insert-extremal t forwards?)
10511070
(structured-extremal t forwards?)

TeXmacs/tests/221_10.scm

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,8 @@
130130
(item)))))
131131

132132
(define (test-list-structured-insert-blank-item)
133-
(check (blank-list-item-stree 'enumerate) => '(item)))
133+
(check (blank-list-item-stree 'enumerate) => '(item))
134+
(check (blank-list-item-stree 'description) => '(item* "")))
134135

135136
(define (test-list-structured-remove-tree-shape)
136137
(let* ((remove-up-doc
@@ -156,7 +157,11 @@
156157
(tm->tree '(document
157158
(item)
158159
(concat (item) "ddd")
159-
(item)))))
160+
(item))))
161+
(only-list-doc
162+
(tm->tree '(document
163+
(enumerate
164+
(document (concat (item) "a"))))))))
160165
(check (list-item-remove-range remove-up-doc 1 'enumerate #f)
161166
=> '(0 1))
162167
(check (list-item-remove-range remove-down-doc 1 'enumerate #t)
@@ -174,7 +179,9 @@
174179
(check (remove-list-item-at bare-item-doc 0 #t)
175180
=> '(document
176181
(concat (item) "ddd")
177-
(item)))))
182+
(item)))
183+
(check (remove-list-item-at only-list-doc 0 #t)
184+
=> '(document ""))))
178185

179186
(tm-define (test_221_10)
180187
(test-list-structured-insert-end-index)

devel/221_10.md

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# [221_10] 为有序列表、无序列表和描述列表新增结构化向上/向下插入与删除
22

3+
> 相关文档:[201_31](201_31.md)[x_y.md](x_y.md)(模板)
4+
35
## 如何测试
46

57
### 一、单元测试
@@ -40,16 +42,57 @@
4042
1. 通过菜单 **Text -> Enumerate -> Default** 插入有序列表,输入至少两个条目。
4143
2. 重复无序列表测试中的步骤 3-9。
4244
3. 确认插入新条目后,编号自动重新排列。
45+
4. **结构化向下删除至空文档**:新建一个空白文档,仅插入一个有序列表并输入一个条目。将光标放在该条目内,按 **Alt + Delete** 删除唯一条目。确认:
46+
- Mogan **不崩溃**(不会触发 "Throwing empty composite box" 错误)。
47+
- 列表被自动移除,文档变为一个包含空字符串的普通段落,光标位于该段落内。
48+
5. **结构化向下删除后仍有内容**:新建一个文档,先输入一段普通文字,再插入一个有序列表并输入一个条目。将光标放在列表条目内,按 **Alt + Delete** 删除唯一条目。确认:
49+
- 列表节点被移除,但前面的普通文字保留。
50+
- 光标位于列表原来的位置(文字之后)。
4351

4452
### 四、描述列表(description)
4553

4654
1. 通过菜单 **Text -> Description -> Default** 插入描述列表,输入至少两个条目,每个条目包含标签和描述内容。
47-
2. 将光标放在**第二个条目内部**,按 **Alt + Up**(macOS 为 **Option + Up**),确认在当前条目**上方**插入一个新的空描述条目(`item*`),且光标进入新条目的描述内容区
48-
3. 将光标放在**第一个条目内部**,按 **Alt + Down**(macOS 为 **Option + Down**),确认在当前条目**下方**插入一个新的空描述条目(`item*`),且光标进入新条目的描述内容区
55+
2. 将光标放在**第二个条目内部**,按 **Alt + Up**(macOS 为 **Option + Up**),确认在当前条目**上方**插入一个新的空描述条目(`item* ""`),且光标位于 `item*` 子节点 `""` 的开始位置(`tree-go-to new-item 0 :start`
56+
3. 将光标放在**第一个条目内部**,按 **Alt + Down**(macOS 为 **Option + Down**),确认在当前条目**下方**插入一个新的空描述条目(`item* ""`),且光标位于 `item*` 子节点 `""` 的开始位置(`tree-go-to new-item 0 :start`
4957
4. 将光标放在描述列表条目内部,查看焦点工具栏,确认出现 **Insert above****Insert below****Remove upwards****Remove downwards** 四个结构化按钮,且**不出现**左右方向的插入/删除按钮。
5058
5.**Alt + Backspace**(macOS 为 **Option + Backspace**),确认删除当前 item 上方的同级逻辑 item(包括标签和描述内容)。
5159
6.**Alt + Delete**(macOS 为 **Option + Delete**),确认删除当前 item 下方的同级逻辑 item(包括标签和描述内容)。
52-
7. 确认在描述列表中插入的新条目结构为 `item*`(而非普通列表的 `item`)。
60+
7. 确认在描述列表中插入的新条目结构为 `item* ""`(而非普通列表的 `item`,也非无子节点的 `item*`)。
61+
62+
## 2026/05/08 修复描述列表插入结构与光标定位,修复空文档保护
63+
64+
### What
65+
66+
1. **描述列表插入结构修复**`blank-list-item-stree``description` 类型返回 `(item* "")` 而非 `(item*)`,确保新插入的描述条目包含一个空的标签/描述内容子节点。
67+
2. **描述列表光标定位修复**`structured-insert-vertical` 在插入 `item*` 后,通过 `(tree-go-to new-item 0 :end)` 将光标放入 `item*` 的子节点内部,而不是停留在 `item*` 节点上。
68+
3. **空文档保护修复**`structured-remove-vertical` 在删除列表节点后,若父 `document` 的 arity 变为 0,则自动插入一个空字符串 `""` 作为占位,避免产生空的 document 触发 `TM_FAILED ("empty composite box")`
69+
70+
### Why
71+
72+
1. 描述列表的 `item*` 必须包含一个子节点作为标签/描述内容,插入 `(item*)` 会导致结构不完整,后续编辑时行为异常。
73+
2. 光标停留在 `item*` 节点上时,用户直接输入文字不会进入标签/描述内容区,需要额外的 Enter 或点击操作才能开始编辑,体验不符合预期。
74+
3. 当整个文档仅包含一个有序/无序/描述列表时,结构化删除所有条目会导致列表节点被移除,根 `document` 变为空。空的 `document` 在排版阶段会创建没有任何子盒子的 composite box,触发 `composite_boxes.cpp:67``TM_FAILED ("empty composite box")`,导致 Mogan 崩溃。
75+
76+
### How
77+
78+
1. `blank-list-item-stree``description` 分支返回 `` `(item* "") ``
79+
2. `structured-insert-vertical` 的光标定位逻辑增加对 `item*` 的判断:
80+
```scheme
81+
(if (tree-is? new-item 'item*)
82+
(tree-go-to new-item 0 :end)
83+
(tree-go-to new-list insert-pos :end))
84+
```
85+
3. `structured-remove-vertical` 在删除列表节点后增加空文档保护:
86+
```scheme
87+
(when (and parent-doc list-index)
88+
(tree-remove parent-doc list-index 1)
89+
(if (== (tree-arity parent-doc) 0)
90+
(begin
91+
(tree-insert parent-doc 0 (list ""))
92+
(tree-go-to parent-doc 0 :end))
93+
(tree-go-to parent-doc list-index :end)))
94+
```
95+
4. 单元测试 `221_10.scm` 新增 `blank-list-item-stree``description` 类型断言、以及 `only-list-doc` 场景的结构化删除断言。
5396

5497
## 2026/05/07 新增列表结构化向上/向下插入与删除
5598

0 commit comments

Comments
 (0)