|
168 | 168 |
|
169 | 169 | ;; 辅助函数:定义 enumerate-tag-list |
170 | 170 | (define (enumerate-tag-list) |
171 | | - '(enumerate enumerate-1 enumerate-2 enumerate-3 enumerate-4)) |
| 171 | + '(enumerate enumerate-numeric enumerate-roman |
| 172 | + enumerate-Roman enumerate-alpha enumerate-Alpha |
| 173 | + enumerate-circle enumerate-hanzi enumerate-numeric-paren)) |
172 | 174 |
|
173 | 175 | ;; 辅助函数:定义 itemize-tag-list |
174 | 176 | (define (itemize-tag-list) |
|
190 | 192 | (define (in-description-context?) |
191 | 193 | (not (not (tree-search-upwards (focus-tree) (lambda (node) (tree-in? node (description-tag-list))))))) |
192 | 194 |
|
193 | | -;; 辅助函数:获取当前列表的类型 |
194 | | -(define (get-list-type) |
195 | | - (cond |
196 | | - ((in-description-context?) 'description) |
197 | | - ((in-itemize-context?) 'itemize) |
198 | | - ((in-enumerate-context?) 'enumerate) |
199 | | - (else #f))) |
| 195 | +;; 辅助函数:获取当前实际列表的精确标签,保留 itemize-dot 等变体样式 |
| 196 | +(define (get-current-list-label item) |
| 197 | + (and-with list-node (tree-search-upwards item list-node?) |
| 198 | + (tree-label list-node))) |
200 | 199 |
|
201 | 200 | ;; 辅助函数:查找包含 item 的 concat 包装和真正的 item list |
202 | 201 | (define (find-item-wrapper-and-list item) |
|
226 | 225 | (let ((item-index (tree-index item))) |
227 | 226 | (tree-remove! item-list item-index 1)))) |
228 | 227 |
|
| 228 | +(define (list-item-node? t) |
| 229 | + (or (tree-is? t 'item) |
| 230 | + (tree-is? t 'item*) |
| 231 | + (and (tree-is? t 'concat) |
| 232 | + (> (tree-arity t) 0) |
| 233 | + (or (tree-is? (tree-ref t 0) 'item) |
| 234 | + (tree-is? (tree-ref t 0) 'item*))))) |
| 235 | + |
| 236 | +(define (list-node? t) |
| 237 | + (and t (tree-in? t (list-tag-list)))) |
| 238 | + |
| 239 | +(define (list-family label) |
| 240 | + (cond ((in? label (enumerate-tag-list)) 'enumerate) |
| 241 | + ((in? label (itemize-tag-list)) 'itemize) |
| 242 | + ((in? label (description-tag-list)) 'description) |
| 243 | + (else label))) |
| 244 | + |
| 245 | +(define (same-list-family? lhs rhs) |
| 246 | + (and lhs rhs (== (list-family lhs) (list-family rhs)))) |
| 247 | + |
| 248 | +(define (find-previous-item-index item-list start) |
| 249 | + (let loop ((i (- start 1))) |
| 250 | + (cond ((< i 0) #f) |
| 251 | + ((list-item-node? (tree-ref item-list i)) i) |
| 252 | + (else (loop (- i 1)))))) |
| 253 | + |
| 254 | +(define (find-following-list-index item-list start end list-type) |
| 255 | + (let loop ((i (+ start 1))) |
| 256 | + (cond ((>= i end) #f) |
| 257 | + ((and (list-node? (tree-ref item-list i)) |
| 258 | + (same-list-family? (tree-label (tree-ref item-list i)) list-type)) |
| 259 | + i) |
| 260 | + (else (loop (+ i 1)))))) |
| 261 | + |
| 262 | +(define (append-strees-to-document doc strees) |
| 263 | + (if (null? strees) doc |
| 264 | + (tree-insert doc (tree-arity doc) strees))) |
| 265 | + |
229 | 266 | ;; 在有序和无序列表中实现缩进功能 |
230 | 267 | (tm-define (kbd-variant t forwards?) |
231 | 268 | (:require |
232 | | - (or |
233 | | - ;; 有序列表或者无序列表 |
234 | | - (and (or in-enumerate-context? in-itemize-context?) (tree-is? (focus-tree) 'item)) |
235 | | - ;; 描述列表 |
236 | | - (and in-description-context? (tree-is? (focus-tree) 'item*)))) |
| 269 | + (and forwards? |
| 270 | + (or |
| 271 | + ;; 有序列表或者无序列表 |
| 272 | + (and (or in-enumerate-context? in-itemize-context?) (tree-is? (focus-tree) 'item)) |
| 273 | + ;; 描述列表 |
| 274 | + (and in-description-context? (tree-is? (focus-tree) 'item*))))) |
237 | 275 |
|
238 | 276 | (let ((item (focus-tree))) |
239 | | - ;; 步骤 1: 查找包装和列表 |
| 277 | + ;; 查找包装和列表 |
240 | 278 | (call-with-values (lambda () (find-item-wrapper-and-list item)) |
241 | 279 | (lambda (wrapper item-list) |
242 | 280 | (if (and item item-list) |
243 | | - (let ((item-index (if wrapper (tree-index wrapper) (tree-index item)))) |
| 281 | + (let* ((item-index (if wrapper (tree-index wrapper) (tree-index item))) |
| 282 | + (list-type (list-family (or (get-current-list-label item) 'enumerate))) |
| 283 | + (item-stree (tree->stree (if wrapper wrapper item))) |
| 284 | + (next-index (+ item-index 1)) |
| 285 | + (attached-sublist-idx |
| 286 | + (and (< next-index (tree-arity item-list)) |
| 287 | + (let ((next-node (tree-ref item-list next-index))) |
| 288 | + ;; 当前 item 后面如果紧跟同一大类的子列表,缩进时一并并入目标子列表。 |
| 289 | + (and (list-node? next-node) |
| 290 | + (same-list-family? (tree-label next-node) list-type) |
| 291 | + next-index))))) |
244 | 292 | (if (> item-index 0) |
245 | | - (let ((prev-item (tree-ref item-list (- item-index 1)))) |
246 | | - ;; 步骤 2: 提取内容 |
247 | | - (let ((item-content (extract-item-content wrapper))) |
248 | | - ;; 步骤 3: 创建子列表并移动内容 |
249 | | - (tree-go-to prev-item :end) |
250 | | - (insert-return) |
251 | | - (let ((list-type (get-list-type))) |
252 | | - (if list-type |
253 | | - (make-tmlist list-type) |
254 | | - (make-tmlist 'enumerate))) ; 默认使用有序列表 |
255 | | - ;; 步骤4:拷贝内容 |
256 | | - (if item-content |
257 | | - (let ((new-item (focus-tree)) |
258 | | - (list-type (get-list-type))) |
259 | | - |
260 | | - (let ((content-stree (tree->stree item-content))) |
261 | | - (if (eq? list-type 'description) |
262 | | - (tree-set! new-item `(concat (item*), content-stree)) |
263 | | - (tree-set! new-item `(concat (item), content-stree))) |
264 | | - (tree-go-to new-item)))) |
265 | | - ;; 步骤 5: 从原列表中移除 |
266 | | - (remove-item-from-list item wrapper item-list)))))))))) |
| 293 | + (let* ((prev-item-index (find-previous-item-index item-list item-index)) |
| 294 | + (target-sublist-idx |
| 295 | + (and prev-item-index |
| 296 | + ;; 优先复用前一个 item 已有的同一大类子列表,避免制造相邻碎片列表。 |
| 297 | + (find-following-list-index item-list prev-item-index |
| 298 | + item-index list-type)))) |
| 299 | + (when prev-item-index |
| 300 | + ;; 当前一个 item 还没有子列表时,才在当前 item 前插入一个空子列表。 |
| 301 | + (when (not target-sublist-idx) |
| 302 | + (set! item-list |
| 303 | + (tree-insert item-list item-index |
| 304 | + (list `(,list-type (document))))) |
| 305 | + (set! target-sublist-idx item-index) |
| 306 | + (set! item-index (+ item-index 1)) |
| 307 | + ;; 新插入的空子列表会让原 item 和其后置子列表整体右移一位。 |
| 308 | + (if attached-sublist-idx |
| 309 | + (set! attached-sublist-idx (+ attached-sublist-idx 1)))) |
| 310 | + |
| 311 | + (let* ((target-sublist (tree-ref item-list target-sublist-idx)) |
| 312 | + (target-doc (tree-ref target-sublist 0)) |
| 313 | + (target-pos (tree-arity target-doc)) |
| 314 | + (attached-items |
| 315 | + (if attached-sublist-idx |
| 316 | + (map (lambda (i) |
| 317 | + (tree->stree |
| 318 | + (tree-copy |
| 319 | + (tree-ref (tree-ref (tree-ref item-list attached-sublist-idx) 0) i)))) |
| 320 | + (iota (tree-arity (tree-ref (tree-ref item-list attached-sublist-idx) 0)))) |
| 321 | + '()))) |
| 322 | + ;; 目标子列表依次接收:当前 item,以及它后面原来挂着的同类型子列表内容。 |
| 323 | + (set! target-doc |
| 324 | + (append-strees-to-document target-doc |
| 325 | + (append (list item-stree) attached-items))) |
| 326 | + ;; 从右往左删除,避免索引漂移。 |
| 327 | + (when attached-sublist-idx |
| 328 | + (set! item-list (tree-remove! item-list attached-sublist-idx 1))) |
| 329 | + (set! item-list (tree-remove! item-list item-index 1)) |
| 330 | + ;; concat 包装的 item 需要落到第 0 个子元素末尾,普通 item 直接定位。 |
| 331 | + (let ((moved-item (tree-ref target-doc target-pos))) |
| 332 | + (if (tree-is? moved-item 'concat) |
| 333 | + (tree-go-to moved-item 0 :end) |
| 334 | + (tree-go-to target-doc target-pos :end))))))))))))) |
| 335 | + |
| 336 | +;; 在有序和无序列表中实现反缩进功能 |
| 337 | +;; |
| 338 | +;; 处理逻辑: |
| 339 | +;; 当用户按 Shift+Tab 时,将当前 item 从当前子列表中移出到外层列表。 |
| 340 | +;; 如果当前列表已经是最外层列表,则不再继续反缩进。 |
| 341 | +;; |
| 342 | +;; 两种 Case: |
| 343 | +;; 1. NOT first item: 当前 item 前面还有其他 items |
| 344 | +;; - 保留原 sublist(因为前面还有 items) |
| 345 | +;; - 从当前 sublist 的 document 中移除当前 item 和后续 items |
| 346 | +;; - 在 parent-list 中 sublist 之后插入当前 item |
| 347 | +;; - 如有后续 items,在当前 item 后面重建一个同类型 sublist |
| 348 | +;; |
| 349 | +;; 2. FIRST item: 当前 item 是第一个,后面可能有 items |
| 350 | +;; - 从当前 sublist 的 document 中移除当前 item(保留后续 items) |
| 351 | +;; - 从 parent-list 中移除整个 sublist |
| 352 | +;; - 在 parent-list 中原 sublist 位置插入当前 item |
| 353 | +;; - 如有后续 items,在当前 item 后面重建一个同类型 sublist |
| 354 | +;; |
| 355 | +(tm-define (kbd-variant t forwards?) |
| 356 | + (:require |
| 357 | + (and (not forwards?) |
| 358 | + (or |
| 359 | + (and (or in-enumerate-context? in-itemize-context?) (tree-is? (focus-tree) 'item)) |
| 360 | + (and in-description-context? (tree-is? (focus-tree) 'item*))))) |
| 361 | + |
| 362 | + (let* ((item (focus-tree)) |
| 363 | + (item-stree (tree->stree item)) |
| 364 | + (wrapper #f) |
| 365 | + (doc (tree-outer item))) |
| 366 | + |
| 367 | + ;; 处理 concat 包装 |
| 368 | + (when (tree-is? doc 'concat) |
| 369 | + (set! wrapper doc) |
| 370 | + (set! doc (tree-outer wrapper)) |
| 371 | + (set! item-stree (tree->stree wrapper))) |
| 372 | + |
| 373 | + ;; 仅在 item 直接位于列表 document 中时才执行反缩进。 |
| 374 | + (when (tree-is? doc 'document) |
| 375 | + (let* ((sublist (tree-outer doc)) |
| 376 | + (parent-list (if sublist (tree-outer sublist) #f)) |
| 377 | + ;; 只有在当前子列表外面还能找到另一层列表时,才允许继续反缩进; |
| 378 | + ;; 这样最外层列表会直接止住。 |
| 379 | + (outer-list (and parent-list |
| 380 | + (tree-search-upwards parent-list list-node?)))) |
| 381 | + |
| 382 | + (when (and parent-list outer-list) |
| 383 | + (let* ((sublist-idx (tree-index sublist)) |
| 384 | + (doc-arity (tree-arity doc)) |
| 385 | + (item-idx (if wrapper (tree-index wrapper) (tree-index item))) |
| 386 | + (items-before-count item-idx) |
| 387 | + (items-after-count (- doc-arity item-idx 1))) |
| 388 | + |
| 389 | + ;; 先保存当前 item 后面的所有兄弟节点,后面需要重建 trailing sublist。 |
| 390 | + (with items-after-stree |
| 391 | + (if (> items-after-count 0) |
| 392 | + (map (lambda (i) |
| 393 | + (tree->stree (tree-copy (tree-ref doc (+ item-idx 1 i))))) |
| 394 | + (iota items-after-count)) |
| 395 | + '()) |
| 396 | + |
| 397 | + ;; 两个 case 都会得到同样的结果:更新后的 parent-list 和插入位置。 |
| 398 | + (let* ((current-doc (tree-ref sublist 0)) |
| 399 | + (current-parent (tree-outer sublist)) |
| 400 | + (item-insert-pos #f)) |
| 401 | + |
| 402 | + ;; 先处理“原 sublist 要保留还是删除”这部分差异。 |
| 403 | + (if (> items-before-count 0) |
| 404 | + ;; Case 1: 不是第一个 item,保留 sublist |
| 405 | + (begin |
| 406 | + ;; 从 doc 中移除当前 item 和后续 items |
| 407 | + (let loop ((i (- doc-arity 1)) |
| 408 | + (cd current-doc)) |
| 409 | + (when (>= i item-idx) |
| 410 | + (set! cd (tree-remove! cd i 1)) |
| 411 | + (loop (- i 1) cd))) |
| 412 | + ;; 重新获取 current-doc(修改后) |
| 413 | + (set! current-doc (tree-ref sublist 0)) |
| 414 | + ;; item 插入位置:sublist 之后 |
| 415 | + (set! item-insert-pos (+ sublist-idx 1))) |
| 416 | + |
| 417 | + ;; Case 2: 是第一个 item,删除 sublist |
| 418 | + (begin |
| 419 | + ;; 从 doc 中移除当前 item |
| 420 | + (set! current-doc (tree-remove! current-doc item-idx 1)) |
| 421 | + ;; 从 parent-list 中移除 sublist |
| 422 | + (set! current-parent (tree-remove! current-parent sublist-idx 1)) |
| 423 | + ;; item 插入位置:原 sublist 位置 |
| 424 | + (set! item-insert-pos sublist-idx))) |
| 425 | + |
| 426 | + ;; 共同逻辑:在 parent-list 中插入当前 item |
| 427 | + (set! current-parent (tree-insert current-parent item-insert-pos (list item-stree))) |
| 428 | + |
| 429 | + ;; 如有后续 items,则在当前 item 后面重建一个同类型 sublist。 |
| 430 | + (when (> (length items-after-stree) 0) |
| 431 | + (let ((new-sublist-stree `(,(tree-label sublist) (document ,@items-after-stree))) |
| 432 | + (sublist-pos (+ item-insert-pos 1))) |
| 433 | + (set! current-parent (tree-insert current-parent sublist-pos (list new-sublist-stree))))) |
| 434 | + |
| 435 | + ;; 共同逻辑:移动光标到新插入的 item |
| 436 | + (with moved-item (tree-ref current-parent item-insert-pos) |
| 437 | + ;; 根据 moved-item 类型决定光标位置 |
| 438 | + (if (tree-is? moved-item 'concat) |
| 439 | + ;; concat 包装:定位到第0个子元素(document)的结束位置 |
| 440 | + (tree-go-to moved-item 0 :end) |
| 441 | + ;; item: 从 parent 定位 |
| 442 | + (begin |
| 443 | + (tree-go-to current-parent item-insert-pos :end)))))))))))) |
| 444 | + |
267 | 445 |
|
268 | 446 | (tm-define (kbd-variant t forwards?) |
269 | 447 | (:require (and (tree-in? t '(label reference pageref eqref smart-ref)) |
|
0 commit comments