From b0b6934c74f274155218f2a91a80d5a407ac6e9d Mon Sep 17 00:00:00 2001 From: Yuki Date: Wed, 25 Mar 2026 15:09:11 +0800 Subject: [PATCH 1/7] nit --- TeXmacs/progs/text/text-menu.scm | 197 ++++++++++++++++++++++++++++--- 1 file changed, 182 insertions(+), 15 deletions(-) diff --git a/TeXmacs/progs/text/text-menu.scm b/TeXmacs/progs/text/text-menu.scm index ba6ccc20f9..4f7f408e2c 100644 --- a/TeXmacs/progs/text/text-menu.scm +++ b/TeXmacs/progs/text/text-menu.scm @@ -859,10 +859,174 @@ (define (is-section-top-level t) (in? (tree-label t) '(section))) -(define (get-verbatim-section-title s indent?) - (if (is-current-tree s) - `(verbatim ,(string-append (tm/section-get-title-string s indent?) " <=")) - `(verbatim ,(tm/section-get-title-string s indent?)))) +(define (symbol-ends-char? s ch) + "检查符号s是否以字符ch结尾" + (let* ((str (symbol->string s)) + (len (string-length str))) + (and (> len 0) + (char=? (string-ref str (- len 1)) ch)))) + +(define (short-style?) + "检查是否为短样式(section作为顶层章节)" + (!= (get-init-tree "sectional-short-style") (tree 'macro "false"))) + +(define (section-type label) + "获取章节类型,无编号章节去除*后缀" + (let ((label-str (symbol->string label))) + (if (symbol-ends-char? label #\*) + (string->symbol (string-drop-right label-str 1)) + label))) + +(define (section-numbered? label) + "检查章节是否为编号章节" + (not (symbol-ends-char? label #\*))) + +(define (has-chapter-in-doc? sections) + "检查文档中是否有 chapter 类型的章节" + (list-any (lambda (s) (eq? (section-type (tree-label s)) 'chapter)) sections)) + + +(define (section-parent-type label sections) + "获取父章节类型,根据文档实际结构决定" + (let ((type (section-type label))) + (cond ((eq? type 'subparagraph) 'paragraph) + ((eq? type 'paragraph) 'subsubsection) + ((eq? type 'subsubsection) 'subsection) + ((eq? type 'subsection) 'section) + ((eq? type 'section) + ;; section 的父是最近的 chapter 或 appendix + 'chapter-or-appendix) + ((eq? type 'appendix) + ;; appendix 在非短样式中父是 part,短样式中无父 + (if (short-style?) #f 'part)) + ((eq? type 'chapter) 'part) + (else #f)))) + +(define (number->letter n) + "将数字转换为字母 (1->A, 2->B, ...)" + (string (integer->char (+ 64 n)))) + +(define (section-get-number s sections parent-section) + "计算章节在父章节范围内的编号" + (let* ((label (tree-label s)) + (type (section-type label)) + (s-path (tree->path s))) + (define (count-iter secs acc) + (cond ((null? secs) acc) + ;; 到达当前章节,返回计数 + ((equal? (tree->path (car secs)) s-path) (+ acc 1)) + ;; 如果遇到父章节的起始,重置计数 + ((and parent-section + (equal? (tree->path (car secs)) (tree->path parent-section))) + (count-iter (cdr secs) 0)) + ;; 同类型编号章节,增加计数 + ((and (eq? (section-type (tree-label (car secs))) type) + (section-numbered? (tree-label (car secs)))) + (count-iter (cdr secs) (+ acc 1))) + (else (count-iter (cdr secs) acc)))) + (count-iter sections 0))) + +(define (section-get-number-display s sections parent-section) + "获取章节的显示编号(数字或字母)" + (let* ((label (tree-label s)) + (type (section-type label)) + (num (section-get-number s sections parent-section))) + (cond ((eq? type 'appendix) (number->letter num)) + ;; 如果父是 appendix,section 也使用字母编号(基于 appendix 的字母) + ((and parent-section + (eq? (section-type (tree-label parent-section)) 'appendix)) + (number->string num)) + (else (number->string num))))) + +(define (find-nearest-parent s sections parent-type) + "在当前章节之前查找最近的父类型章节" + (let ((s-path (tree->path s))) + ;; 在 sections 中找出所有在当前章节之前、类型匹配的章节,返回最后一个 + (define (iter secs best) + (cond ((null? secs) best) + ;; 如果找到当前章节,停止搜索 + ((equal? (tree->path (car secs)) s-path) best) + ;; 如果当前章节在当前章节之前且类型匹配,更新 best + ((and (pathpath (car secs)) s-path) + (eq? (section-type (tree-label (car secs))) parent-type)) + (iter (cdr secs) (car secs))) + (else (iter (cdr secs) best)))) + (iter sections #f))) + +(define (find-nearest-parent-or-appendix s sections) + "在当前章节之前查找最近的父类型章节(chapter 或 appendix)" + (let ((s-path (tree->path s))) + (define (iter secs best) + (cond ((null? secs) best) + ((equal? (tree->path (car secs)) s-path) best) + ((and (pathpath (car secs)) s-path) + (or (eq? (section-type (tree-label (car secs))) 'chapter) + (eq? (section-type (tree-label (car secs))) 'appendix))) + (iter (cdr secs) (car secs))) + (else (iter (cdr secs) best)))) + (iter sections #f))) + +(define (section-get-full-number-rec s sections) + "递归计算章节的完整编号" + (let* ((label (tree-label s)) + (type (section-type label)) + (parent-type (section-parent-type label sections))) + (if (not (section-numbered? label)) + #f + (let* ((parent-section + (cond ((eq? parent-type 'chapter-or-appendix) + ;; section 的父是最近的 chapter 或 appendix + (find-nearest-parent-or-appendix s sections)) + (parent-type + (find-nearest-parent s sections parent-type)) + (else #f)))) + (if parent-section + (let ((parent-num (section-get-full-number-rec parent-section sections)) + (display-num (section-get-number-display s sections parent-section))) + (if parent-num + (string-append parent-num "." display-num) + display-num)) + (section-get-number-display s sections #f)))))) + +(define (path (car p1) (car p2)) #f) + (else (path (string-length full-number) 0) + (string-append prefix full-number " " title) + (string-append prefix title)))) + (if (is-current-tree s) + `(verbatim ,(string-append display-title " <=")) + `(verbatim ,display-title)))) (define (filter-sections l f-is-current-tree f-is-top-level) (define (section-list->nested l result) @@ -914,10 +1078,11 @@ (filter-sections main-sections is-current-tree is-book-top-level))))) (tm-menu (focus-section-menu) - (for (s (all-sections)) - ((eval (get-verbatim-section-title s #t)) - (when (and (tree->path s) (section-context? s)) - (tree-go-to s 0 :end))))) + (let ((all-secs (all-sections))) + (for (s all-secs) + ((eval (get-verbatim-section-title s all-secs #t)) + (when (and (tree->path s) (section-context? s)) + (tree-go-to s 0 :end)))))) (tm-menu (focus-document-extra-menu t) (:require (previous-section)) @@ -925,9 +1090,10 @@ (tm-menu (focus-document-extra-icons t) (:require (previous-section)) - (mini #t - (=> (eval (get-verbatim-section-title (previous-section) #f)) - (link focus-section-menu)))) + (let ((all-secs (all-sections))) + (mini #t + (=> (eval (get-verbatim-section-title (previous-section) all-secs #f)) + (link focus-section-menu))))) (tm-menu (focus-extra-menu t) (:require (section-context? t)) @@ -937,10 +1103,11 @@ (tm-menu (focus-extra-icons t) (:require (section-context? t)) - (mini #t - // - (=> (eval (get-verbatim-section-title t #f)) - (link focus-section-menu)))) + (let ((all-secs (all-sections))) + (mini #t + // + (=> (eval (get-verbatim-section-title t all-secs #f)) + (link focus-section-menu))))) (tm-define (child-proposals t i) (:require (and (tree-in? t '(bibliography bibliography*)) (<= i 1))) From 59766da8b78738b52c33e2029fb650fa0723d82c Mon Sep 17 00:00:00 2001 From: Yuki Date: Wed, 25 Mar 2026 15:35:59 +0800 Subject: [PATCH 2/7] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TeXmacs/progs/text/text-menu.scm | 132 +++++++++++++++---------------- 1 file changed, 64 insertions(+), 68 deletions(-) diff --git a/TeXmacs/progs/text/text-menu.scm b/TeXmacs/progs/text/text-menu.scm index 4f7f408e2c..7d6b2f8d16 100644 --- a/TeXmacs/progs/text/text-menu.scm +++ b/TeXmacs/progs/text/text-menu.scm @@ -859,48 +859,52 @@ (define (is-section-top-level t) (in? (tree-label t) '(section))) -(define (symbol-ends-char? s ch) - "检查符号s是否以字符ch结尾" - (let* ((str (symbol->string s)) - (len (string-length str))) - (and (> len 0) - (char=? (string-ref str (- len 1)) ch)))) - -(define (short-style?) - "检查是否为短样式(section作为顶层章节)" - (!= (get-init-tree "sectional-short-style") (tree 'macro "false"))) - -(define (section-type label) - "获取章节类型,无编号章节去除*后缀" - (let ((label-str (symbol->string label))) - (if (symbol-ends-char? label #\*) - (string->symbol (string-drop-right label-str 1)) +;; String utilities +(define (string-suffix? s suffix) + "检查字符串是否以指定后缀结尾" + (let ((s-len (string-length s)) + (suffix-len (string-length suffix))) + (and (>= s-len suffix-len) + (string=? (substring s (- s-len suffix-len) s-len) suffix)))) + +(define (string-multiply s n) + "将字符串重复n次" + (if (<= n 0) + "" + (string-append s (string-multiply s (- n 1))))) + +;; Section type utilities +(define (section-base-type label) + "获取章节基础类型(去除*后缀)" + (let ((s (symbol->string label))) + (if (string-suffix? s "*") + (string->symbol (string-drop-right s 1)) label))) (define (section-numbered? label) "检查章节是否为编号章节" - (not (symbol-ends-char? label #\*))) - -(define (has-chapter-in-doc? sections) - "检查文档中是否有 chapter 类型的章节" - (list-any (lambda (s) (eq? (section-type (tree-label s)) 'chapter)) sections)) + (not (string-suffix? (symbol->string label) "*"))) + +;; Section hierarchy: child -> parent +(define section-hierarchy + '((subparagraph . paragraph) + (paragraph . subsubsection) + (subsubsection . subsection) + (subsection . section) + (section . chapter-or-appendix) ; special handling needed + (chapter . part) + (appendix . part))) +(define (short-style?) + "检查是否为短样式(section作为顶层章节)" + (!= (get-init-tree "sectional-short-style") (tree 'macro "false"))) (define (section-parent-type label sections) - "获取父章节类型,根据文档实际结构决定" - (let ((type (section-type label))) - (cond ((eq? type 'subparagraph) 'paragraph) - ((eq? type 'paragraph) 'subsubsection) - ((eq? type 'subsubsection) 'subsection) - ((eq? type 'subsection) 'section) - ((eq? type 'section) - ;; section 的父是最近的 chapter 或 appendix - 'chapter-or-appendix) - ((eq? type 'appendix) - ;; appendix 在非短样式中父是 part,短样式中无父 - (if (short-style?) #f 'part)) - ((eq? type 'chapter) 'part) - (else #f)))) + "获取父章节类型" + (let ((base (section-base-type label))) + (cond ((eq? base 'appendix) (if (short-style?) #f 'part)) + ((eq? base 'section) 'chapter-or-appendix) + (else (assoc-ref section-hierarchy base))))) (define (number->letter n) "将数字转换为字母 (1->A, 2->B, ...)" @@ -909,7 +913,7 @@ (define (section-get-number s sections parent-section) "计算章节在父章节范围内的编号" (let* ((label (tree-label s)) - (type (section-type label)) + (type (section-base-type label)) (s-path (tree->path s))) (define (count-iter secs acc) (cond ((null? secs) acc) @@ -920,7 +924,7 @@ (equal? (tree->path (car secs)) (tree->path parent-section))) (count-iter (cdr secs) 0)) ;; 同类型编号章节,增加计数 - ((and (eq? (section-type (tree-label (car secs))) type) + ((and (eq? (section-base-type (tree-label (car secs))) type) (section-numbered? (tree-label (car secs)))) (count-iter (cdr secs) (+ acc 1))) (else (count-iter (cdr secs) acc)))) @@ -929,26 +933,23 @@ (define (section-get-number-display s sections parent-section) "获取章节的显示编号(数字或字母)" (let* ((label (tree-label s)) - (type (section-type label)) + (type (section-base-type label)) (num (section-get-number s sections parent-section))) (cond ((eq? type 'appendix) (number->letter num)) ;; 如果父是 appendix,section 也使用字母编号(基于 appendix 的字母) ((and parent-section - (eq? (section-type (tree-label parent-section)) 'appendix)) + (eq? (section-base-type (tree-label parent-section)) 'appendix)) (number->string num)) (else (number->string num))))) (define (find-nearest-parent s sections parent-type) "在当前章节之前查找最近的父类型章节" (let ((s-path (tree->path s))) - ;; 在 sections 中找出所有在当前章节之前、类型匹配的章节,返回最后一个 (define (iter secs best) (cond ((null? secs) best) - ;; 如果找到当前章节,停止搜索 ((equal? (tree->path (car secs)) s-path) best) - ;; 如果当前章节在当前章节之前且类型匹配,更新 best ((and (pathpath (car secs)) s-path) - (eq? (section-type (tree-label (car secs))) parent-type)) + (eq? (section-base-type (tree-label (car secs))) parent-type)) (iter (cdr secs) (car secs))) (else (iter (cdr secs) best)))) (iter sections #f))) @@ -960,8 +961,8 @@ (cond ((null? secs) best) ((equal? (tree->path (car secs)) s-path) best) ((and (pathpath (car secs)) s-path) - (or (eq? (section-type (tree-label (car secs))) 'chapter) - (eq? (section-type (tree-label (car secs))) 'appendix))) + (let ((t (section-base-type (tree-label (car secs))))) + (or (eq? t 'chapter) (eq? t 'appendix)))) (iter (cdr secs) (car secs))) (else (iter (cdr secs) best)))) (iter sections #f))) @@ -969,17 +970,14 @@ (define (section-get-full-number-rec s sections) "递归计算章节的完整编号" (let* ((label (tree-label s)) - (type (section-type label)) (parent-type (section-parent-type label sections))) (if (not (section-numbered? label)) #f - (let* ((parent-section - (cond ((eq? parent-type 'chapter-or-appendix) - ;; section 的父是最近的 chapter 或 appendix - (find-nearest-parent-or-appendix s sections)) - (parent-type - (find-nearest-parent s sections parent-type)) - (else #f)))) + (let ((parent-section + (if (eq? parent-type 'chapter-or-appendix) + (find-nearest-parent-or-appendix s sections) + (and parent-type + (find-nearest-parent s sections parent-type))))) (if parent-section (let ((parent-num (section-get-full-number-rec parent-section sections)) (display-num (section-get-number-display s sections parent-section))) @@ -996,26 +994,24 @@ ((> (car p1) (car p2)) #f) (else (path Date: Wed, 25 Mar 2026 18:18:20 +0800 Subject: [PATCH 3/7] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TeXmacs/progs/text/text-menu.scm | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/TeXmacs/progs/text/text-menu.scm b/TeXmacs/progs/text/text-menu.scm index 7d6b2f8d16..2b750df02a 100644 --- a/TeXmacs/progs/text/text-menu.scm +++ b/TeXmacs/progs/text/text-menu.scm @@ -869,9 +869,10 @@ (define (string-multiply s n) "将字符串重复n次" - (if (<= n 0) - "" - (string-append s (string-multiply s (- n 1))))) + (let loop ((i n) (acc "")) + (if (<= i 0) + acc + (loop (- i 1) (string-append acc s))))) ;; Section type utilities (define (section-base-type label) @@ -908,7 +909,9 @@ (define (number->letter n) "将数字转换为字母 (1->A, 2->B, ...)" - (string (integer->char (+ 64 n)))) + (if (and (>= n 1) (<= n 26)) + (string (integer->char (+ 64 n))) + (number->string n))) (define (section-get-number s sections parent-section) "计算章节在父章节范围内的编号" From f53c8d70d75365fb2612d318b533d3a87cdc7cae Mon Sep 17 00:00:00 2001 From: Yuki Date: Wed, 25 Mar 2026 18:24:41 +0800 Subject: [PATCH 4/7] cache --- TeXmacs/progs/text/text-menu.scm | 77 ++++++++++++++++++-------------- 1 file changed, 44 insertions(+), 33 deletions(-) diff --git a/TeXmacs/progs/text/text-menu.scm b/TeXmacs/progs/text/text-menu.scm index 2b750df02a..0a7abbd241 100644 --- a/TeXmacs/progs/text/text-menu.scm +++ b/TeXmacs/progs/text/text-menu.scm @@ -970,24 +970,32 @@ (else (iter (cdr secs) best)))) (iter sections #f))) -(define (section-get-full-number-rec s sections) - "递归计算章节的完整编号" - (let* ((label (tree-label s)) - (parent-type (section-parent-type label sections))) - (if (not (section-numbered? label)) - #f - (let ((parent-section - (if (eq? parent-type 'chapter-or-appendix) - (find-nearest-parent-or-appendix s sections) - (and parent-type - (find-nearest-parent s sections parent-type))))) - (if parent-section - (let ((parent-num (section-get-full-number-rec parent-section sections)) - (display-num (section-get-number-display s sections parent-section))) - (if parent-num - (string-append parent-num "." display-num) - display-num)) - (section-get-number-display s sections #f)))))) +(define (section-get-full-number-rec/cached s sections cache) + "递归计算章节完整编号(带缓存)" + (let* ((s-path (tree->path s)) + (cached (and s-path (ahash-ref cache s-path)))) + (if cached + cached + (let* ((label (tree-label s)) + (parent-type (section-parent-type label sections)) + (result + (if (not (section-numbered? label)) + "" + (let ((parent-section + (if (eq? parent-type 'chapter-or-appendix) + (find-nearest-parent-or-appendix s sections) + (and parent-type + (find-nearest-parent s sections parent-type))))) + (if parent-section + (let* ((parent-num + (section-get-full-number-rec/cached parent-section sections cache)) + (display-num (section-get-number-display s sections parent-section))) + (if (> (string-length parent-num) 0) + (string-append parent-num "." display-num) + display-num)) + (section-get-number-display s sections #f)))))) + (when s-path (ahash-set! cache s-path result)) + result)))) (define (path (car p1) (car p2)) #f) (else (path (string-length full-number) 0) (string-append prefix full-number " " title) @@ -1078,10 +1086,11 @@ (tm-menu (focus-section-menu) (let ((all-secs (all-sections))) - (for (s all-secs) - ((eval (get-verbatim-section-title s all-secs #t)) - (when (and (tree->path s) (section-context? s)) - (tree-go-to s 0 :end)))))) + (let ((number-cache (make-ahash-table))) + (for (s all-secs) + ((eval (get-verbatim-section-title s all-secs number-cache #t)) + (when (and (tree->path s) (section-context? s)) + (tree-go-to s 0 :end))))))) (tm-menu (focus-document-extra-menu t) (:require (previous-section)) @@ -1090,9 +1099,10 @@ (tm-menu (focus-document-extra-icons t) (:require (previous-section)) (let ((all-secs (all-sections))) - (mini #t - (=> (eval (get-verbatim-section-title (previous-section) all-secs #f)) - (link focus-section-menu))))) + (let ((number-cache (make-ahash-table))) + (mini #t + (=> (eval (get-verbatim-section-title (previous-section) all-secs number-cache #f)) + (link focus-section-menu)))))) (tm-menu (focus-extra-menu t) (:require (section-context? t)) @@ -1103,10 +1113,11 @@ (tm-menu (focus-extra-icons t) (:require (section-context? t)) (let ((all-secs (all-sections))) - (mini #t - // - (=> (eval (get-verbatim-section-title t all-secs #f)) - (link focus-section-menu))))) + (let ((number-cache (make-ahash-table))) + (mini #t + // + (=> (eval (get-verbatim-section-title t all-secs number-cache #f)) + (link focus-section-menu)))))) (tm-define (child-proposals t i) (:require (and (tree-in? t '(bibliography bibliography*)) (<= i 1))) From e656606080c2725a5d2e212c2284dcfa18138a54 Mon Sep 17 00:00:00 2001 From: Yuki Date: Wed, 25 Mar 2026 18:36:04 +0800 Subject: [PATCH 5/7] =?UTF-8?q?=E5=A4=A7=E6=96=87=E4=BB=B6=E6=80=A7?= =?UTF-8?q?=E8=83=BD=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TeXmacs/progs/text/text-menu.scm | 165 +++++++++++++++---------------- 1 file changed, 81 insertions(+), 84 deletions(-) diff --git a/TeXmacs/progs/text/text-menu.scm b/TeXmacs/progs/text/text-menu.scm index 0a7abbd241..b398d2a413 100644 --- a/TeXmacs/progs/text/text-menu.scm +++ b/TeXmacs/progs/text/text-menu.scm @@ -900,7 +900,7 @@ "检查是否为短样式(section作为顶层章节)" (!= (get-init-tree "sectional-short-style") (tree 'macro "false"))) -(define (section-parent-type label sections) +(define (section-parent-type label) "获取父章节类型" (let ((base (section-base-type label))) (cond ((eq? base 'appendix) (if (short-style?) #f 'part)) @@ -945,69 +945,61 @@ (number->string num)) (else (number->string num))))) -(define (find-nearest-parent s sections parent-type) - "在当前章节之前查找最近的父类型章节" - (let ((s-path (tree->path s))) - (define (iter secs best) - (cond ((null? secs) best) - ((equal? (tree->path (car secs)) s-path) best) - ((and (pathpath (car secs)) s-path) - (eq? (section-base-type (tree-label (car secs))) parent-type)) - (iter (cdr secs) (car secs))) - (else (iter (cdr secs) best)))) - (iter sections #f))) - -(define (find-nearest-parent-or-appendix s sections) - "在当前章节之前查找最近的父类型章节(chapter 或 appendix)" - (let ((s-path (tree->path s))) - (define (iter secs best) - (cond ((null? secs) best) - ((equal? (tree->path (car secs)) s-path) best) - ((and (pathpath (car secs)) s-path) - (let ((t (section-base-type (tree-label (car secs))))) - (or (eq? t 'chapter) (eq? t 'appendix)))) - (iter (cdr secs) (car secs))) - (else (iter (cdr secs) best)))) - (iter sections #f))) - -(define (section-get-full-number-rec/cached s sections cache) - "递归计算章节完整编号(带缓存)" - (let* ((s-path (tree->path s)) - (cached (and s-path (ahash-ref cache s-path)))) - (if cached - cached - (let* ((label (tree-label s)) - (parent-type (section-parent-type label sections)) - (result - (if (not (section-numbered? label)) - "" - (let ((parent-section - (if (eq? parent-type 'chapter-or-appendix) - (find-nearest-parent-or-appendix s sections) - (and parent-type - (find-nearest-parent s sections parent-type))))) - (if parent-section - (let* ((parent-num - (section-get-full-number-rec/cached parent-section sections cache)) - (display-num (section-get-number-display s sections parent-section))) - (if (> (string-length parent-num) 0) - (string-append parent-num "." display-num) - display-num)) - (section-get-number-display s sections #f)))))) - (when s-path (ahash-set! cache s-path result)) - result)))) - -(define (path (car p1) (car p2)) #f) - (else (pathpath s))) sections)) + +(define focus-section-cache-signature #f) +(define focus-section-cache-numbers #f) +(define focus-section-cache-titles #f) + +(define (rebuild-focus-section-cache sections) + "一次遍历预计算章节编号与标题缓存" + (let ((latest-by-type (make-ahash-table)) + (latest-chapter-or-appendix #f) + (counters (make-ahash-table)) + (number-map (make-ahash-table)) + (title-map (make-ahash-table))) + (for (s sections) + (let* ((path (tree->path s)) + (label (tree-label s)) + (base (section-base-type label)) + (parent-type (section-parent-type label)) + (parent-section + (cond ((eq? parent-type 'chapter-or-appendix) latest-chapter-or-appendix) + (parent-type (ahash-ref latest-by-type parent-type)) + (else #f))) + (parent-path (and parent-section (tree->path parent-section))) + (counter-key (section-counter-key base parent-path)) + (current-count (or (ahash-ref counters counter-key) 0)) + (num (if (section-numbered? label) (+ current-count 1) current-count)) + (display-num (if (section-numbered? label) + (if (eq? base 'appendix) + (number->letter num) + (number->string num)) + "")) + (parent-num (if parent-path (or (ahash-ref number-map parent-path) "") "")) + (full-num (if (section-numbered? label) + (if (> (string-length parent-num) 0) + (string-append parent-num "." display-num) + display-num) + ""))) + (ahash-set! counters counter-key num) + (ahash-set! number-map path full-num) + (ahash-set! title-map path (tm/section-get-title-string s #f)) + (ahash-set! latest-by-type base s) + (when (or (eq? base 'chapter) (eq? base 'appendix)) + (set! latest-chapter-or-appendix s)))) + (set! focus-section-cache-signature (section-structure-signature sections)) + (set! focus-section-cache-numbers number-map) + (set! focus-section-cache-titles title-map))) + +(define (ensure-focus-section-cache sections) + (let ((signature (section-structure-signature sections))) + (if (not (equal? signature focus-section-cache-signature)) + (rebuild-focus-section-cache sections)))) ;; Section indent prefixes (define section-indent-levels @@ -1024,9 +1016,14 @@ (let ((level (assoc-ref section-indent-levels (tree-label s)))) (if level (string-multiply " " level) ""))) -(define (get-verbatim-section-title s sections cache indent?) - (let* ((title (tm/section-get-title-string s #f)) - (full-number (section-get-full-number s sections cache)) +(define (get-verbatim-section-title s indent?) + (let* ((path (tree->path s)) + (title (or (and path focus-section-cache-titles + (ahash-ref focus-section-cache-titles path)) + (tm/section-get-title-string s #f))) + (full-number (or (and path focus-section-cache-numbers + (ahash-ref focus-section-cache-numbers path)) + "")) (prefix (if indent? (get-indent-prefix s) "")) (display-title (if (> (string-length full-number) 0) (string-append prefix full-number " " title) @@ -1085,12 +1082,12 @@ (filter-sections main-sections is-current-tree is-book-top-level))))) (tm-menu (focus-section-menu) - (let ((all-secs (all-sections))) - (let ((number-cache (make-ahash-table))) - (for (s all-secs) - ((eval (get-verbatim-section-title s all-secs number-cache #t)) - (when (and (tree->path s) (section-context? s)) - (tree-go-to s 0 :end))))))) + (let* ((all-secs (all-sections)) + (_ (ensure-focus-section-cache all-secs))) + (for (s all-secs) + ((eval (get-verbatim-section-title s #t)) + (when (and (tree->path s) (section-context? s)) + (tree-go-to s 0 :end)))))) (tm-menu (focus-document-extra-menu t) (:require (previous-section)) @@ -1098,11 +1095,11 @@ (tm-menu (focus-document-extra-icons t) (:require (previous-section)) - (let ((all-secs (all-sections))) - (let ((number-cache (make-ahash-table))) - (mini #t - (=> (eval (get-verbatim-section-title (previous-section) all-secs number-cache #f)) - (link focus-section-menu)))))) + (let* ((all-secs (all-sections)) + (_ (ensure-focus-section-cache all-secs))) + (mini #t + (=> (eval (get-verbatim-section-title (previous-section) #f)) + (link focus-section-menu))))) (tm-menu (focus-extra-menu t) (:require (section-context? t)) @@ -1112,12 +1109,12 @@ (tm-menu (focus-extra-icons t) (:require (section-context? t)) - (let ((all-secs (all-sections))) - (let ((number-cache (make-ahash-table))) - (mini #t - // - (=> (eval (get-verbatim-section-title t all-secs number-cache #f)) - (link focus-section-menu)))))) + (let* ((all-secs (all-sections)) + (_ (ensure-focus-section-cache all-secs))) + (mini #t + // + (=> (eval (get-verbatim-section-title t #f)) + (link focus-section-menu))))) (tm-define (child-proposals t i) (:require (and (tree-in? t '(bibliography bibliography*)) (<= i 1))) From bfabd5015f1ed1865caecc82c4ca61c602269d7b Mon Sep 17 00:00:00 2001 From: Yuki Date: Wed, 25 Mar 2026 18:40:00 +0800 Subject: [PATCH 6/7] devel --- devel/222_63.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 devel/222_63.md diff --git a/devel/222_63.md b/devel/222_63.md new file mode 100644 index 0000000000..f23daf3ed3 --- /dev/null +++ b/devel/222_63.md @@ -0,0 +1,22 @@ +# [222_63] Outline 章节菜单编号与性能优化 + +## 2026/03/25 Outline 章节菜单优化 +### What +- 在 `TeXmacs/progs/text/text-menu.scm` 中补全章节菜单的编号展示逻辑,支持按层级显示完整编号(如 `1.2.3`)并保留缩进。 +- 修复 appendix 字母编号边界:仅 `1..26` 映射 `A..Z`,超界时回退数字显示,避免异常字符。 +- 修复运行期问题: + - 修复 `focus-section-menu` 处括号不匹配导致的 `read-error`。 + - 修复 `ensure-focus-section-cache` 被当作菜单项导致的 `invalid menu item`。 + +性能优化(相对本 PR 之前) +- 将章节编号从“按节点递归回溯父节点”的方式,改为“一次遍历预计算”: + - 线性扫描 `all-sections`,维护最近父级与分组计数器; + - 预先生成每个 section 的完整编号缓存。 +- 增加菜单展示缓存并按结构签名失效: + - 缓存编号与标题映射(按 `tree->path`); + - 使用 section 的 `(label . path)` 作为结构签名; + - 结构未变化时复用缓存,变化时自动重建。 + +### Result +- 大文档下章节菜单打开与切换的卡顿明显减轻。 +- 在保持现有显示行为的前提下,降低重复计算开销,提升菜单响应性。 From e9e09a114d78e6d2978ba8f9613c25c764eac12b Mon Sep 17 00:00:00 2001 From: Yuki Date: Wed, 25 Mar 2026 19:18:17 +0800 Subject: [PATCH 7/7] wip --- TeXmacs/progs/text/text-menu.scm | 187 +++++++++++++++++++++++-------- TeXmacs/tests/tmu/222_63.tmu | 60 ++++++++++ devel/222_63.md | 14 ++- 3 files changed, 210 insertions(+), 51 deletions(-) create mode 100644 TeXmacs/tests/tmu/222_63.tmu diff --git a/TeXmacs/progs/text/text-menu.scm b/TeXmacs/progs/text/text-menu.scm index b398d2a413..f93b77f19f 100644 --- a/TeXmacs/progs/text/text-menu.scm +++ b/TeXmacs/progs/text/text-menu.scm @@ -953,53 +953,152 @@ (define focus-section-cache-signature #f) (define focus-section-cache-numbers #f) -(define focus-section-cache-titles #f) +(define focus-section-cache-latest-by-type #f) +(define focus-section-cache-latest-chapter-or-appendix #f) +(define focus-section-cache-counters #f) +(define focus-section-cache-checkpoints #f) +(define section-cache-checkpoint-step 64) + +(define (clone-ahash-table t) + (list->ahash-table (ahash-table->list t))) + +(define (section-cache-snapshot index) + (list index + (clone-ahash-table focus-section-cache-latest-by-type) + focus-section-cache-latest-chapter-or-appendix + (clone-ahash-table focus-section-cache-counters))) + +(define (section-cache-snapshot-index cp) (car cp)) +(define (section-cache-snapshot-latest-by-type cp) (cadr cp)) +(define (section-cache-snapshot-latest-chapter-or-appendix cp) (caddr cp)) +(define (section-cache-snapshot-counters cp) (cadddr cp)) + +(define (section-signature-prefix-equal? old-signature new-signature) + (cond ((null? old-signature) #t) + ((null? new-signature) #f) + ((equal? (car old-signature) (car new-signature)) + (section-signature-prefix-equal? (cdr old-signature) (cdr new-signature))) + (else #f))) + +(define (section-signature-first-diff-index old-signature new-signature) + (define (iter old new i) + (cond ((and (null? old) (null? new)) #f) + ((or (not (list? old)) (not (list? new))) i) + ((or (null? old) (null? new)) i) + ((equal? (car old) (car new)) (iter (cdr old) (cdr new) (+ i 1))) + (else i))) + (iter old-signature new-signature 0)) + +(define (section-cache-last-checkpoint-at-or-before checkpoints index) + (define (iter cps best) + (cond ((null? cps) best) + ((<= (section-cache-snapshot-index (car cps)) index) + (iter (cdr cps) (car cps))) + (else best))) + (iter checkpoints #f)) + +(define (section-cache-checkpoints-prefix checkpoints index) + (cond ((null? checkpoints) '()) + ((<= (section-cache-snapshot-index (car checkpoints)) index) + (cons (car checkpoints) + (section-cache-checkpoints-prefix (cdr checkpoints) index))) + (else '()))) + +(define (section-cache-recompute-from-index sections old-signature new-signature start-index) + (let* ((checkpoints (or focus-section-cache-checkpoints '())) + (cp (or (section-cache-last-checkpoint-at-or-before checkpoints start-index) + (section-cache-snapshot 0))) + (cp-index (section-cache-snapshot-index cp)) + (cp-prefix (section-cache-checkpoints-prefix checkpoints cp-index))) + (set! focus-section-cache-latest-by-type + (clone-ahash-table (section-cache-snapshot-latest-by-type cp))) + (set! focus-section-cache-latest-chapter-or-appendix + (section-cache-snapshot-latest-chapter-or-appendix cp)) + (set! focus-section-cache-counters + (clone-ahash-table (section-cache-snapshot-counters cp))) + (for (entry (list-tail old-signature cp-index)) + (ahash-remove! focus-section-cache-numbers (cdr entry))) + (let loop ((i cp-index) + (secs (list-tail sections cp-index)) + (new-cps cp-prefix)) + (if (null? secs) + (begin + (set! focus-section-cache-checkpoints new-cps) + (set! focus-section-cache-signature new-signature)) + (begin + (section-cache-process-one! (car secs)) + (let ((next-i (+ i 1))) + (if (== (modulo next-i section-cache-checkpoint-step) 0) + (loop next-i (cdr secs) + (append new-cps (list (section-cache-snapshot next-i)))) + (loop next-i (cdr secs) new-cps)))))))) + +(define (section-cache-process-one! s) + (let* ((path (tree->path s)) + (label (tree-label s)) + (base (section-base-type label)) + (parent-type (section-parent-type label)) + (parent-path + (cond ((eq? parent-type 'chapter-or-appendix) focus-section-cache-latest-chapter-or-appendix) + (parent-type (ahash-ref focus-section-cache-latest-by-type parent-type)) + (else #f))) + (counter-key (section-counter-key base parent-path)) + (current-count (or (ahash-ref focus-section-cache-counters counter-key) 0)) + (num (if (section-numbered? label) (+ current-count 1) current-count)) + (display-num (if (section-numbered? label) + (if (eq? base 'appendix) + (number->letter num) + (number->string num)) + "")) + (parent-num (if parent-path (or (ahash-ref focus-section-cache-numbers parent-path) "") "")) + (full-num (if (section-numbered? label) + (if (> (string-length parent-num) 0) + (string-append parent-num "." display-num) + display-num) + ""))) + (ahash-set! focus-section-cache-counters counter-key num) + (ahash-set! focus-section-cache-numbers path full-num) + (ahash-set! focus-section-cache-latest-by-type base path) + (when (or (eq? base 'chapter) (eq? base 'appendix)) + (set! focus-section-cache-latest-chapter-or-appendix path)))) (define (rebuild-focus-section-cache sections) "一次遍历预计算章节编号与标题缓存" - (let ((latest-by-type (make-ahash-table)) - (latest-chapter-or-appendix #f) - (counters (make-ahash-table)) - (number-map (make-ahash-table)) - (title-map (make-ahash-table))) - (for (s sections) - (let* ((path (tree->path s)) - (label (tree-label s)) - (base (section-base-type label)) - (parent-type (section-parent-type label)) - (parent-section - (cond ((eq? parent-type 'chapter-or-appendix) latest-chapter-or-appendix) - (parent-type (ahash-ref latest-by-type parent-type)) - (else #f))) - (parent-path (and parent-section (tree->path parent-section))) - (counter-key (section-counter-key base parent-path)) - (current-count (or (ahash-ref counters counter-key) 0)) - (num (if (section-numbered? label) (+ current-count 1) current-count)) - (display-num (if (section-numbered? label) - (if (eq? base 'appendix) - (number->letter num) - (number->string num)) - "")) - (parent-num (if parent-path (or (ahash-ref number-map parent-path) "") "")) - (full-num (if (section-numbered? label) - (if (> (string-length parent-num) 0) - (string-append parent-num "." display-num) - display-num) - ""))) - (ahash-set! counters counter-key num) - (ahash-set! number-map path full-num) - (ahash-set! title-map path (tm/section-get-title-string s #f)) - (ahash-set! latest-by-type base s) - (when (or (eq? base 'chapter) (eq? base 'appendix)) - (set! latest-chapter-or-appendix s)))) - (set! focus-section-cache-signature (section-structure-signature sections)) - (set! focus-section-cache-numbers number-map) - (set! focus-section-cache-titles title-map))) + (set! focus-section-cache-latest-by-type (make-ahash-table)) + (set! focus-section-cache-latest-chapter-or-appendix #f) + (set! focus-section-cache-counters (make-ahash-table)) + (set! focus-section-cache-numbers (make-ahash-table)) + (let loop ((i 0) (secs sections) (cps (list (section-cache-snapshot 0)))) + (if (null? secs) + (set! focus-section-cache-checkpoints cps) + (begin + (section-cache-process-one! (car secs)) + (let ((next-i (+ i 1))) + (if (== (modulo next-i section-cache-checkpoint-step) 0) + (loop next-i (cdr secs) (append cps (list (section-cache-snapshot next-i)))) + (loop next-i (cdr secs) cps)))))) + (set! focus-section-cache-signature (section-structure-signature sections))) (define (ensure-focus-section-cache sections) - (let ((signature (section-structure-signature sections))) - (if (not (equal? signature focus-section-cache-signature)) - (rebuild-focus-section-cache sections)))) + (let* ((signature (section-structure-signature sections)) + (old focus-section-cache-signature)) + (cond ((equal? signature old) #t) + ;; 增量路径:仅当新签名是旧签名前缀扩展(常见于文档末尾新增 section) + ((and old + (> (length signature) (length old)) + (section-signature-prefix-equal? old signature)) + (for (s (list-tail sections (length old))) + (section-cache-process-one! s)) + (set! focus-section-cache-checkpoints + (append (or focus-section-cache-checkpoints '()) + (list (section-cache-snapshot (length signature))))) + (set! focus-section-cache-signature signature)) + ;; dirty-range 增量:中间插入/删除/修改后,从首差异索引附近检查点重算后段 + ((and old (section-signature-first-diff-index old signature)) + => (lambda (i) + (section-cache-recompute-from-index sections old signature i))) + (else + (rebuild-focus-section-cache sections))))) ;; Section indent prefixes (define section-indent-levels @@ -1018,9 +1117,7 @@ (define (get-verbatim-section-title s indent?) (let* ((path (tree->path s)) - (title (or (and path focus-section-cache-titles - (ahash-ref focus-section-cache-titles path)) - (tm/section-get-title-string s #f))) + (title (tm/section-get-title-string s #f)) (full-number (or (and path focus-section-cache-numbers (ahash-ref focus-section-cache-numbers path)) "")) diff --git a/TeXmacs/tests/tmu/222_63.tmu b/TeXmacs/tests/tmu/222_63.tmu new file mode 100644 index 0000000000..9624e6a60f --- /dev/null +++ b/TeXmacs/tests/tmu/222_63.tmu @@ -0,0 +1,60 @@ +> + +> + +<\body> + <\hide-preamble> + \; + + > + + + + + + + + + + + + + + + + + + + +<\initial> + <\collection> + + + + + +<\references> + <\collection> + > + > + > + > + > + + + +<\auxiliary> + <\collection> + <\associate|toc> + |math-font-series||第 1 章chapter>|.>>>>|> + + |1.1section|.>>>>|>> + + |1.2section|.>>>>|>> + + |1.2.1subsection|.>>>>|>> + + |math-font-series||附录 Aappendix>|.>>>>|> + + + diff --git a/devel/222_63.md b/devel/222_63.md index f23daf3ed3..b18bcd5bd2 100644 --- a/devel/222_63.md +++ b/devel/222_63.md @@ -1,12 +1,14 @@ -# [222_63] Outline 章节菜单编号与性能优化 +# [222_63] 大纲中章节菜单编号与性能优化 -## 2026/03/25 Outline 章节菜单优化 +## 如何测试 +- 测试文档:TeXmacs/tests/tmu/222_63.tmu +- 测试大纲功能,是否生成了编号 +- 打开某个大文件,测试大纲功能是否相比旧版本使用更流畅 + +## 2026/03/25 大纲中章节菜单优化 ### What - 在 `TeXmacs/progs/text/text-menu.scm` 中补全章节菜单的编号展示逻辑,支持按层级显示完整编号(如 `1.2.3`)并保留缩进。 -- 修复 appendix 字母编号边界:仅 `1..26` 映射 `A..Z`,超界时回退数字显示,避免异常字符。 -- 修复运行期问题: - - 修复 `focus-section-menu` 处括号不匹配导致的 `read-error`。 - - 修复 `ensure-focus-section-cache` 被当作菜单项导致的 `invalid menu item`。 +- 增加了增量缓存,优化在大文件下的使用体验。 性能优化(相对本 PR 之前) - 将章节编号从“按节点递归回溯父节点”的方式,改为“一次遍历预计算”: