From 7c07fc76ba52f1918c001bb6fb3ce5451009a2a0 Mon Sep 17 00:00:00 2001 From: Da Shen Date: Mon, 20 Jul 2026 21:49:05 +0800 Subject: [PATCH 1/5] =?UTF-8?q?[1150]=20=E8=AE=B0=E5=BD=95=E8=A1=A8?= =?UTF-8?q?=E6=A0=BC=E6=8F=92=E5=85=A5=E8=A1=8C=20C++=20=E9=93=BE=E8=B7=AF?= =?UTF-8?q?=20+=20=E6=80=A7=E8=83=BD=E4=BC=98=E5=8C=96=E7=BB=93=E6=9E=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- devel/1150.md | 77 ++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 67 insertions(+), 10 deletions(-) diff --git a/devel/1150.md b/devel/1150.md index f3fffcd923..591e0df037 100644 --- a/devel/1150.md +++ b/devel/1150.md @@ -2,27 +2,84 @@ ## 背景 -排查"表格里连续插入新行"的响应耗时。需要一个稳定的可重跑基准, -便于后续优化前后对比。 +排查"表格里连续插入新行"的响应耗时。先用 GUI 基准定位热点, +再做局部优化。 ## What -新增 `TeXmacs/tests/1150.scm`,在 headless 编辑器里: +新增 `TeXmacs/tests/1150.scm`,在 GUI 编辑器里: -1. `(make 'tabular)` 新建一个 1×1 表格 -2. 连续调用 `(table-insert-row #t)` 插入 N 行(默认 N=1000) -3. 用 `texmacs-time` 计时,输出总耗时与每行平均耗时 +1. `(new-document)` 在界面里新建一个标签页 +2. `(set-main-style "generic")` 切到 generic 样式(new-document 默认无样式) +3. `(make 'tabular)` 新建 1×1 表格,扩到 10 列 +4. 用 `exec-delayed-pause` + `run-chain` 异步链逐行插入 +5. 在 `checkpoint-rows` / `total-rows` 处各打印一次 bench dump + 累积耗时 -实现风格参考 `TeXmacs/tests/1144.scm`(纯 headless 基准,无 GUI、 -无 `exec-delayed-at` 异步链)。 +实现风格参考 `TeXmacs/tests/1145.scm`(`exec-delayed-pause` + `update-menus` +驱动真实事件循环,避免退化为 headless)。 + +## 表格插入行 C++ 链路 + +入口 `(table-insert-row #t)` 经 glue(`src/Scheme/Glue/glue_editor.lua`) +到 `edit_table_rep::table_insert_row`(`src/Edit/Modify/edit_table.cpp`), +内部分 5 步: + +| 步 | 方法 | 作用 | +|---|---|---| +| 1 | `table_insert` | 核心 tree 改写:行插入 / 列插入循环 / CWITH 节点坐标重写 | +| 2 | `table_go_to` | 光标定位 | +| 3 | `table_correct_block_content` | 遍历 cell,按 block/hyphen 格式增删 DOCUMENT wrap | +| 4 | `table_resize_notify` | `call("table-resize-notify", ...)` 回 scheme | +| 5 | (底层)`edit_insert` → `notify_insert` → typesetter invalidation | 每次 tree mutation 的固定成本 | + +## bench 埋点(C++ 侧) + +`src/Edit/Modify/edit_table.cpp` 加了 `tm_debug.hpp` include 和若干 +`bench_start`/`bench_cumul` 埋点: + +- `table_insert_row`(顶层 + 5 个子段 `:search`/`:insert`/`:go_to`/`:correct_block`/`:resize_notify`) +- `table_insert`(3 个子段 `:row`/`:col`/`:cwith`) +- `table_correct_block_content`(整体一层) +- `table_resize_notify`(整体一层) + +用 `bench_cumul`(不 reset、不每次打印)累积;测试链尾用 +`(bench-print-all)` 一次性 dump。 + +## 性能优化(GUI 模式,10 列,generic 样式) + +baseline @ 100 rows:1531 ms。bench 分布显示两个真热点: + +- `table_correct_block_content` 666 ms(44%)—— 每插一行扫所有 cell +- `table_go_to` 646 ms(42%)—— 内部夹了一个 dead `table_bound` 调用 + +三处优化后 @ 100 rows:**657 ms(提速 57%)**;@ 50 rows:314 ms(提速 45%)。 + +| 优化 | 文件:行 | 效果 | +|---|---|---| +| `table_correct_block_content` 加范围化重载,`table_insert_row`/`table_insert_column` 只扫新行/列 | `edit_table.cpp` `table_correct_block_content(path,int,int,int,int)` | correct_block 666→3 ms | +| 删掉 `table_go_to` 里 dead `table_bound` 调用(结果立即丢弃,每次跑 O(N×M)) | `edit_table.cpp` `table_go_to` | go_to 646→201 ms | +| `empty_table`/`empty_row` 加 wrap-aware 重载,`table_insert` 在构造新行/列时一次性预包装 DOCUMENT,省掉后续每个 cell 一次 `insert_node` | `edit_table.cpp` `empty_row(int,bool)` / `empty_table(int,int,bool)` / `table_insert` | correct_block 3 ms 持续,从源头避免 10 次 mutation | + +### 当前剩余热点(@ 100 rows 优化后) + +- `table_insert_row:go_to` 201 ms (31%) — `go_to_border` 走 `edit_cursor_rep::go_to` 同步触发 `notify_change(THE_CURSOR)` + `set_user_active`,是系统级成本,深挖风险大 +- `table_resize_notify` 197 ms (30%) — scheme callback 路径 +- `table_insert_row:insert` 38 ms (6%) — 实际 tree mutation ## How to run ```bash xmake b stem -xmake r 1150 +MOGAN_TEST_GUI=1 xmake r 1150 ``` ## 涉及文件 -- `TeXmacs/tests/1150.scm`(新增) +- `TeXmacs/tests/1150.scm`(新增;GUI 异步链 + checkpoint 对比) +- `src/Edit/Modify/edit_table.cpp`(bench 埋点 + 三处优化) +- `src/Edit/Modify/edit_table.hpp`(`table_correct_block_content` 范围化重载) + +## 验证 + +- `xmake r edit_table_test` —— 14 个表格单测全过 +- `xmake r table_performance_test` —— 8 个表格性能/正确性测试全过(含 `test_cell_hyphen_wrapping`,验证 pre-wrap 不破坏 wrap 行为) From 53196ba202e1e4597ee05eda86fe3eaa019e71bf Mon Sep 17 00:00:00 2001 From: Da Shen Date: Mon, 20 Jul 2026 21:49:39 +0800 Subject: [PATCH 2/5] =?UTF-8?q?[1150]=20=E8=A1=A8=E6=A0=BC=E6=8F=92?= =?UTF-8?q?=E5=85=A5=E8=A1=8C=E6=80=A7=E8=83=BD=E4=BC=98=E5=8C=96=EF=BC=9A?= =?UTF-8?q?=E5=88=A0=20dead=20table=5Fbound=20+=20correct=5Fblock=20?= =?UTF-8?q?=E8=8C=83=E5=9B=B4=E5=8C=96=20+=20pre-wrap?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GUI 基准(10 列 × 100 行 generic 表格,MOGAN_TEST_GUI=1)总耗时: 1531 ms → 657 ms,提速 57%(@ 50 rows:575 → 314 ms,提速 45%)。 三处独立优化,由 bench 埋点逐层定位: 1. 删 dead table_bound 调用(edit_table.cpp table_go_to) 原代码每插一行都跑 O(N×M) 的 table_bound 计算合并区,但结果 row2/col2 是局部变量、立即丢弃,search_cell 用的是原始 row/col—— 这段代码从未生效。删掉后 table_go_to:646 → 201 ms。 2. table_correct_block_content 范围化重载(edit_table.hpp 新增 table_correct_block_content(path,int,int,int,int)) 原实现是 O(N×M) 全表扫描;插入路径只改一行/一列,其它 cell 的 CELL_BLOCK/CELL_HYPHEN 格式不变,wrap 状态也稳定。table_insert_row/ table_insert_column 改用范围化版本只扫新行/列。correct_block: 666 → 3 ms。 3. empty_row/empty_table 加 wrap-aware 重载(edit_table.cpp empty_row(int,bool) / empty_table(int,int,bool)) 原流程是「插入空 cell → 后续逐个 insert_node 加 DOCUMENT wrap」, 每个 insert_node 触发一次 typesetter invalidation。改为构造时 按 wrap 状态一次性生成 (document "") cell,从源头省掉这批 mutation。 table_insert 在插入前查一次 CELL_BLOCK/CELL_HYPHEN,传入 wrap 参数。 correct_block 持续 3 ms(源头已 wrap)。 测试改造:TeXmacs/tests/1150.scm 重写为 GUI 异步链(exec-delayed-pause + run-chain + update-menus,参考 1145.scm),new-document 开新标签页 + set-main-style generic + 扩到 10 列,在 50/100 行 checkpoint 各 dump 一次 bench 累积,一次运行直接对比规模增长。 验证:xmake r edit_table_test(14 个)+ table_performance_test(8 个) 全过,含 test_cell_hyphen_wrapping 钉死 pre-wrap 正确性。 --- TeXmacs/tests/1150.scm | 186 ++++++++++++++++++++++++++++----- src/Edit/Modify/edit_table.cpp | 117 +++++++++++++++++---- src/Edit/Modify/edit_table.hpp | 6 ++ 3 files changed, 263 insertions(+), 46 deletions(-) diff --git a/TeXmacs/tests/1150.scm b/TeXmacs/tests/1150.scm index c675c26053..d2a5ac025c 100644 --- a/TeXmacs/tests/1150.scm +++ b/TeXmacs/tests/1150.scm @@ -1,54 +1,184 @@ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; MODULE : 1150.scm -;; DESCRIPTION : 基准测试:在表格中连续插入新行的性能 +;; DESCRIPTION : GUI 基准:在新建标签页的表格中连续插入新行的性能 ;; COPYRIGHT : (C) 2026 Mogan STEM ;; ;; PURPOSE -;; 测量"在表格里连续向下插入 N 行"的总耗时与单行平均耗时, -;; 作为后续表格插入路径优化的对比基线。 +;; 测量"在表格里连续向下插入 N 行"的总耗时,配合 C++ 侧 bench 埋点 +;; (table_insert_row / table_insert / table_correct_block_content / +;; table_resize_notify)定位热点。 ;; -;; 实现要点: +;; 实现要点(参考 1145.scm 的 GUI 异步链模式): +;; - `(new-document)` 在界面里新建一个标签页(遵循 window-per-buffer? 偏好)。 +;; 紧接 `(set-main-style "generic")` 切到 generic 样式——new-document +;; 默认开无样式文档,与用户实际场景不一致。 ;; - `(make 'tabular)` 新建 1×1 表格并定位光标到首个 cell, ;; 与「插入 → 表格」菜单同源。 ;; - `(table-insert-row #t)` 是 kbd-enter / 菜单"Row below"走的路径。 -;; - 每行插入之间不插入 kbd 等待或排版抖动;insert 内部已驱动必要的 -;; 排版增量更新,测量的是真实用户体感的"按一下回车"链路耗时。 +;; - 用 `exec-delayed-pause` + `run-chain` 串异步链,每步间隔 step-delay-ms +;; 让事件循环真正驱动 GUI(typeset + idle update_menus 才会跑)。 +;; 必须真实 GUI 跑;exec-delayed-at 单次调度事件循环不真正驱动, +;; 本质还是 headless。 +;; - 每步插 rows-per-step 行(默认 1 行),步间让事件循环空转 + +;; `(update-menus)` 同步触发 C++ 侧刷新。 +;; - 在 checkpoint-rows / total-rows 处各打印一次 bench dump + 累积 +;; 插入耗时,一次运行直接对比两个规模的增长曲线。 +;; +;; bench 输出:链尾调用 `(bench-print-all)` dump C++ 侧所有累积 task。 ;; ;; USAGE ;; xmake b stem -;; xmake r 1150 +;; MOGAN_TEST_GUI=1 xmake r 1150 ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (texmacs-module (texmacs tests 1150)) -(define insert-count 1000) - -(define (bench-insert-rows n) - (make 'tabular) - (let ((start (texmacs-time))) - (let loop ((i 0)) - (when (< i n) - (table-insert-row #t) - (loop (+ i 1)) - ) ;when - ) ;let - (let* ((elapsed (- (texmacs-time) start)) - (per-row (if (zero? n) 0 (/ elapsed n))) - ) ; - (display "[1150] insert ") +;; 步骤间隔:给 typeset + idle update_menus 足够时间(idle >= 1/60s 才触发)。 + +(define step-delay-ms 1500) + +;; 先用小规模探明热点:跑到 checkpoint-rows 打印一次 bench dump + 总耗时, +;; 再跑到 total-rows 打印第二次。一次运行直接对比两个规模的增长曲线。 + +(define checkpoint-rows 50) + +(define total-rows 100) + +(define rows-per-step 1) + +;; 每行的列数。建表后先扩到 num-cols 列再开始插行——真实表格很少是单列, +;; 多列场景下 table_correct_block_content 的 O(N×M) 开销更显著。 + +(define num-cols 10) + +;; 记录每步开始的时间,链尾累加得到总插入耗时(不含步间 sleep)。 + +(define step-start-time 0) + +(define total-insert-ms 0) + +(define (log-step label) + (display "[1150-step] ") + (display label) + (newline) +) ;define + +;; run-chain 复刻 1145.scm 的 exec-delayed-pause 模式:每步 lambda 返回剩余 +;; 毫秒表示继续等待,返回 #t 表示完成。 + +(define (run-chain steps on-done) + (if (null? steps) + (on-done) + (exec-delayed-pause (let ((start (texmacs-time))) + (lambda () + (let ((left (- (+ start step-delay-ms) (texmacs-time)))) + (if (> left 0) + left + (begin + (log-step (caar steps)) + ((cdar steps)) + ;; 同步触发 C++ update_menus,不依赖 idle/焦点 + (update-menus) + (run-chain (cdr steps) on-done) + #t + ) ;begin + ) ;if + ) ;let + ) ;lambda + ) ;let + ) ;exec-delayed-pause + ) ;if +) ;define + +;; 当前已插入行数(跨步累积) + +(define rows-so-far 0) + +;; 单步:同步插 rows-per-step 行。开头记 start、结尾累加 elapsed 到 total。 + +(define (make-insert-step from-rows target-rows) + (cons (string-append "insert row " + (number->string (+ from-rows 1)) + " → " + (number->string target-rows) + ) ;string-append + (lambda () + (set! step-start-time (texmacs-time)) + (let loop + ((k 0)) + (when (< k rows-per-step) + (table-insert-row #t) + (set! rows-so-far (+ rows-so-far 1)) + (loop (+ k 1)) + ) ;when + ) ;let + (set! total-insert-ms (+ total-insert-ms (- (texmacs-time) step-start-time))) + ) ;lambda + ) ;cons +) ;define + +;; 在指定行数处插入一次 bench dump 步骤(不 reset,累积继续) + +(define (make-checkpoint-step n) + (cons (string-append "checkpoint @ " (number->string n) " rows") + (lambda () + (display "[1150] @ ") (display n) - (display " rows: total=") - (display elapsed) - (display " ms, per-row=") - (display per-row) + (display " rows: cumulative insert=") + (display total-insert-ms) (display " ms") (newline) - ) ;let* + (bench-print-all) + ) ;lambda + ) ;cons +) ;define + +;; 生成 [from..n] 行的插入步序列(不依赖全局 rows-so-far——它在 step 执行后才更新) + +(define (insert-steps-up-to from n) + (let loop + ((i from) (acc '())) + (if (>= i n) + (reverse acc) + (loop (+ i rows-per-step) (cons (make-insert-step i (+ i rows-per-step)) acc)) + ) ;if ) ;let ) ;define (tm-define (test_1150) - (bench-insert-rows insert-count) + ;; 重置累积 + (set! total-insert-ms 0) + (set! rows-so-far 0) + ;; 链头四步:新建标签页 + 切 generic 样式 + 建表 + 扩到 num-cols 列; + ;; 之后插到 checkpoint-rows → 打印 checkpoint → 插到 total-rows → checkpoint → quit。 + (let* ((head (list (cons "new-document" (lambda () (new-document))) + (cons "set style generic" (lambda () (set-main-style "generic"))) + (cons "make tabular" (lambda () (make 'tabular))) + (cons (string-append "expand to " (number->string num-cols) " cols") + (lambda () + (let loop + ((c 1)) + (when (< c num-cols) + (table-insert-column #t) + (loop (+ c 1)) + ) ;when + ) ;let + ) ;lambda + ) ;cons + ) ;list + ) ;head + (steps (append head + (insert-steps-up-to 0 checkpoint-rows) + (list (make-checkpoint-step checkpoint-rows)) + (insert-steps-up-to checkpoint-rows total-rows) + (list (make-checkpoint-step total-rows)) + ) ;append + ) ;steps + (on-done (lambda () (display "[1150-step] done, quit") (newline) (quit-TeXmacs)) + ) ;on-done + ) ; + (run-chain steps on-done) + ) ;let* ) ;tm-define diff --git a/src/Edit/Modify/edit_table.cpp b/src/Edit/Modify/edit_table.cpp index 14f68a48b1..72f634a53f 100644 --- a/src/Edit/Modify/edit_table.cpp +++ b/src/Edit/Modify/edit_table.cpp @@ -10,6 +10,7 @@ ******************************************************************************/ #include "edit_table.hpp" +#include "tm_debug.hpp" #include "tree_observer.hpp" using namespace moebius; @@ -47,6 +48,19 @@ empty_row (int nr_cols) { return R; } +tree +empty_row (int nr_cols, bool wrap) { + // Like empty_row(nr_cols) but pre-wrap cells in DOCUMENT when the table's + // CELL_BLOCK/CELL_HYHEN formats require it. Pre-wrapping at construction + // avoids one insert_node per cell later in table_correct_block_content, + // each of which triggers a typesetter invalidation. + int i; + tree R (ROW, nr_cols); + for (i= 0; i < nr_cols; i++) + R[i]= tree (CELL, wrap ? tree (DOCUMENT, empty_cell ()) : empty_cell ()); + return R; +} + tree empty_table (int nr_rows, int nr_cols) { int i; @@ -56,6 +70,15 @@ empty_table (int nr_rows, int nr_cols) { return T; } +tree +empty_table (int nr_rows, int nr_cols, bool wrap) { + int i; + tree T (TABLE, nr_rows); + for (i= 0; i < nr_rows; i++) + T[i]= empty_row (nr_cols, wrap); + return T; +} + bool table_needs_document_wrap (string hyphen, string block, string mode) { return (hyphen == "y" || block == "yes") && mode != "math"; @@ -550,19 +573,35 @@ edit_table_rep::table_insert (path fp, int row, int col, int insr, int insc) { int nr_rows, nr_cols; table_get_extents (p, nr_rows, nr_cols); tree T= subtree (et, p); + // Query wrap state once for the new cells. The format applies to all cells + // in the inserted row/column, so one query is enough — and using it to + // pre-wrap at construction saves one insert_node per cell later (each + // insert_node triggers a typesetter invalidation). + tree block = table_get_format (fp, 1, 1, 1, 1, CELL_BLOCK); + tree hyphen= table_get_format (fp, 1, 1, 1, 1, CELL_HYPHEN); + bool wrap = (block == "yes") || + (block == "auto" && is_atomic (hyphen) && hyphen != "n"); + bench_start ("table_insert:row"); if (insr > 0) - if (row <= N (T)) insert (p * row, empty_table (insr, nr_cols)); + if (row <= N (T)) insert (p * row, empty_table (insr, nr_cols, wrap)); + bench_cumul ("table_insert:row"); + bench_start ("table_insert:col"); T= subtree (et, p); if (insc > 0) for (row= 0; row < N (T); row++) { path q= search_row (p, row); tree R= subtree (et, q); - if (col <= N (R)) insert (q * col, empty_row (insc)); + if (col <= N (R)) insert (q * col, empty_row (insc, wrap)); } + bench_cumul ("table_insert:col"); + bench_start ("table_insert:cwith"); tree st= subtree (et, fp); - if (!is_func (st, TFORMAT)) return; + if (!is_func (st, TFORMAT)) { + bench_cumul ("table_insert:cwith"); + return; + } int k, n= N (st); for (k= n - 2; k >= 0; k--) if (is_func (st[k], CWITH, 6)) { @@ -591,6 +630,7 @@ edit_table_rep::table_insert (path fp, int row, int col, int insr, int insc) { assign (fp * path (k, 3), as_string (J2 - insc)); } } + bench_cumul ("table_insert:cwith"); } /****************************************************************************** @@ -650,10 +690,9 @@ edit_table_rep::table_go_to (path fp, int row, int col, bool at_start) { if (col < 0) col= 0; if (row >= nr_rows) row= nr_rows - 1; if (col >= nr_cols) col= nr_cols - 1; - if (is_func (subtree (et, fp), TFORMAT)) { - int row2= row, col2= col; - table_bound (fp, row, col, row2, col2); - } + // NOTE: 之前这里调过 table_bound(fp,row,col,row2,col2) 计算合并区, + // 但 row2/col2 是局部变量、结果立即丢弃(search_cell 用的是原始 row/col)。 + // table_bound 是 O(N×M),每次 table_go_to 都白白跑——删掉是稳赚不赔。 path q= search_cell (fp, row, col); go_to_border (q, at_start); } @@ -1180,17 +1219,40 @@ edit_table_rep::table_extract_format () { void edit_table_rep::table_insert_row (bool forward) { + bench_start ("table_insert_row"); + bench_start ("table_insert_row:search"); int row, col; path fp= search_format (row, col); - if (is_nil (fp)) return; + if (is_nil (fp)) { + bench_cumul ("table_insert_row:search"); + bench_cumul ("table_insert_row"); + return; + } + bench_cumul ("table_insert_row:search"); int nr_rows, nr_cols, i1, j1, i2, j2; table_get_extents (fp, nr_rows, nr_cols); table_get_limits (fp, i1, j1, i2, j2); - if (nr_rows + 1 > i2) return; - table_insert (fp, row + (forward ? 1 : 0), col, 1, 0); - table_go_to (fp, row + (forward ? 1 : 0), col); - table_correct_block_content (); + if (nr_rows + 1 > i2) { + bench_cumul ("table_insert_row"); + return; + } + int new_row= row + (forward ? 1 : 0); + bench_start ("table_insert_row:insert"); + table_insert (fp, new_row, col, 1, 0); + bench_cumul ("table_insert_row:insert"); + bench_start ("table_insert_row:go_to"); + table_go_to (fp, new_row, col); + bench_cumul ("table_insert_row:go_to"); + bench_start ("table_insert_row:correct_block"); + // Only the new row's cells need wrap correction — other rows already had + // their DOCUMENT nodes fixed by previous calls and their formats didn't + // change. + table_correct_block_content (fp, new_row, new_row + 1, 0, nr_cols); + bench_cumul ("table_insert_row:correct_block"); + bench_start ("table_insert_row:resize_notify"); table_resize_notify (); + bench_cumul ("table_insert_row:resize_notify"); + bench_cumul ("table_insert_row"); } void @@ -1202,9 +1264,11 @@ edit_table_rep::table_insert_column (bool forward) { table_get_extents (fp, nr_rows, nr_cols); table_get_limits (fp, i1, j1, i2, j2); if (nr_cols + 1 > j2) return; - table_insert (fp, row, col + (forward ? 1 : 0), 0, 1); - table_go_to (fp, row, col + (forward ? 1 : 0)); - table_correct_block_content (); + int new_col= col + (forward ? 1 : 0); + table_insert (fp, row, new_col, 0, 1); + table_go_to (fp, row, new_col); + // Only the new column's cells need wrap correction. + table_correct_block_content (fp, 0, nr_rows, new_col, new_col + 1); table_resize_notify (); } @@ -1446,13 +1510,28 @@ edit_table_rep::table_column_decoration (bool forward) { void edit_table_rep::table_correct_block_content () { + bench_start ("table_correct_block_content"); int nr_rows, nr_cols; path fp= search_format (); - if (is_nil (fp)) return; + if (is_nil (fp)) { + bench_cumul ("table_correct_block_content"); + return; + } table_get_extents (fp, nr_rows, nr_cols); + table_correct_block_content (fp, 0, nr_rows, 0, nr_cols); + bench_cumul ("table_correct_block_content"); +} + +void +edit_table_rep::table_correct_block_content (path fp, int row1, int row2, + int col1, int col2) { + // Correct DOCUMENT wrap only on cells in [row1,row2) × [col1,col2). + // Insert paths only mutate one row/column, so re-scanning the whole table + // would be O(N×M) waste. CELL_BLOCK/CELL_HYPHEN formats of untouched cells + // don't change, so their wrap state is already correct. int row, col; - for (row= 0; row < nr_rows; row++) - for (col= 0; col < nr_cols; col++) { + for (row= row1; row < row2; row++) + for (col= col1; col < col2; col++) { path cp= search_cell (fp, row, col); tree st= subtree (et, cp); tree t1= @@ -1468,8 +1547,10 @@ edit_table_rep::table_correct_block_content () { void edit_table_rep::table_resize_notify () { + bench_start ("table_resize_notify"); path p= search_table (); if (!is_nil (p)) call ("table-resize-notify", object (subtree (et, p))); + bench_cumul ("table_resize_notify"); } void diff --git a/src/Edit/Modify/edit_table.hpp b/src/Edit/Modify/edit_table.hpp index 10a048e150..dbccc98d26 100644 --- a/src/Edit/Modify/edit_table.hpp +++ b/src/Edit/Modify/edit_table.hpp @@ -71,6 +71,12 @@ class edit_table_rep : virtual public editor_rep { void table_hor_decorate (path fp, int col, int cbef, int caft); void table_ver_decorate (path fp, int row, int rbef, int raft); + // Correct DOCUMENT wrap on a sub-rectangle of cells only. Insert paths + // (table_insert_row/column) only mutate one row/column, so re-scanning the + // whole table is O(N×M) waste. Range bounds are [row1,row2) × [col1,col2). + void table_correct_block_content (path fp, int row1, int row2, int col1, + int col2); + // Positioning the cursor inside tables void table_bound (path fp, int& row1, int& col1, int& row2, int& col2); void table_go_to (path fp, int row, int col, bool at_start= false); From 0a0b62658a34007d422e68814d1e4eb84580fafe Mon Sep 17 00:00:00 2001 From: Da Shen Date: Mon, 20 Jul 2026 22:00:17 +0800 Subject: [PATCH 3/5] =?UTF-8?q?[1150]=20table=5Finsert=20=E9=A2=84?= =?UTF-8?q?=E5=8C=85=E8=A3=85=EF=BC=9A=E8=A1=A5=20math=20=E5=88=86?= =?UTF-8?q?=E6=94=AF=20+=20=E4=BF=AE=E6=AD=A3=E6=B3=A8=E9=87=8A=EF=BC=88pe?= =?UTF-8?q?r-cell=20=E8=A6=86=E7=9B=96=E3=80=81=E6=8B=BC=E5=86=99=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 代码 review 三处修复: - 补 mode != "math" 守卫:math 表格不应预包装 DOCUMENT,与 table_needs_document_wrap helper 行为对齐 - 收敛 wrap 判定注释:(1,1,1,1) 只是 table-wide fallback,per-cell CWITH 覆盖会让预包装猜错,但后续 table_correct_block_content 会 按每个新 cell 实际格式纠正——正确性保住,perf 收益仅在常见场景 - 注释拼写 CELL_HYHEN → CELL_HYPHEN 行为不变:edit_table_test 14 passed(含 test_no_document_wrap_in_math_mode)、 table_performance_test 8 passed(含 test_cell_hyphen_wrapping)。 Co-Authored-By: Claude Opus 5.2 --- src/Edit/Modify/edit_table.cpp | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/src/Edit/Modify/edit_table.cpp b/src/Edit/Modify/edit_table.cpp index 72f634a53f..0de52f1d1c 100644 --- a/src/Edit/Modify/edit_table.cpp +++ b/src/Edit/Modify/edit_table.cpp @@ -51,7 +51,7 @@ empty_row (int nr_cols) { tree empty_row (int nr_cols, bool wrap) { // Like empty_row(nr_cols) but pre-wrap cells in DOCUMENT when the table's - // CELL_BLOCK/CELL_HYHEN formats require it. Pre-wrapping at construction + // CELL_BLOCK/CELL_HYPHEN formats require it. Pre-wrapping at construction // avoids one insert_node per cell later in table_correct_block_content, // each of which triggers a typesetter invalidation. int i; @@ -573,14 +573,23 @@ edit_table_rep::table_insert (path fp, int row, int col, int insr, int insc) { int nr_rows, nr_cols; table_get_extents (p, nr_rows, nr_cols); tree T= subtree (et, p); - // Query wrap state once for the new cells. The format applies to all cells - // in the inserted row/column, so one query is enough — and using it to - // pre-wrap at construction saves one insert_node per cell later (each - // insert_node triggers a typesetter invalidation). - tree block = table_get_format (fp, 1, 1, 1, 1, CELL_BLOCK); - tree hyphen= table_get_format (fp, 1, 1, 1, 1, CELL_HYPHEN); - bool wrap = (block == "yes") || - (block == "auto" && is_atomic (hyphen) && hyphen != "n"); + // Query wrap state once for the new cells, using cell (1,1) as a table-wide + // fallback for CELL_BLOCK / CELL_HYPHEN. This is only an optimization: if + // any inserted cell has a per-cell CWITH override, the pre-wrap guess here + // will be wrong for that cell, but table_correct_block_content (called by + // table_insert_row/column) re-checks each new cell's actual format and + // corrects the wrap. So correctness holds; the perf benefit shrinks in + // tables with per-cell overrides. + // + // Diverges from table_needs_document_wrap() in two ways: (a) treats any + // hyphen value other than "n" as needing wrap (covers "t" etc.), (b) skips + // wrap in math mode — math cells are never wrapped in DOCUMENT. + string mode = get_env_string (MODE); + tree block = table_get_format (fp, 1, 1, 1, 1, CELL_BLOCK); + tree hyphen= table_get_format (fp, 1, 1, 1, 1, CELL_HYPHEN); + bool wrap = mode != "math" && + (block == "yes" || + (block == "auto" && is_atomic (hyphen) && hyphen != "n")); bench_start ("table_insert:row"); if (insr > 0) if (row <= N (T)) insert (p * row, empty_table (insr, nr_cols, wrap)); From cb1f1e7423e33c2bc7ffcbf5cbbc9460e2f2e91d Mon Sep 17 00:00:00 2001 From: Da Shen Date: Mon, 20 Jul 2026 22:09:16 +0800 Subject: [PATCH 4/5] =?UTF-8?q?[1150]=20=E7=BB=99=E8=A1=A8=E6=A0=BC?= =?UTF-8?q?=E6=8F=92=E5=85=A5=E9=93=BE=E8=B7=AF=E5=87=BD=E6=95=B0=E8=A1=A5?= =?UTF-8?q?=20doxygen=20=E4=B8=AD=E6=96=87=E6=B3=A8=E9=87=8A=20+=20wrap-aw?= =?UTF-8?q?are=20=E9=87=8D=E8=BD=BD=E5=8D=95=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Doxygen 注释覆盖本 PR 新增/修改的核心函数: - empty_row/empty_table 两个重载(含 wrap 参数语义 + 预包装动机) - table_insert(三段工作 + CWITH 重写 + wrap 预包装优化点) - table_go_to(删 dead table_bound 的来龙去脉) - table_insert_row / table_insert_column(流程 + 范围化 correct_block 调用) - table_correct_block_content 两个重载(全表 vs 范围化的取舍 + f1/f2 规则) 单元测试(tests/Edit/Modify/edit_table_test.cpp,14 → 18)新增 4 条钉死 wrap-aware 重载的契约: - test_empty_row_no_wrap_matches_default:wrap=false 退化为原 empty_row - test_empty_row_with_wrap_wraps_cells:wrap=true cell 内容是 (document "") - test_empty_table_with_wrap_wraps_all_cells:多行多列规模下一致 - test_empty_table_no_wrap_matches_default:table 层面同样无破坏性 验证:xmake r edit_table_test(18 个)+ table_performance_test(8 个)全过。 --- src/Edit/Modify/edit_table.cpp | 133 ++++++++++++++++++++++++-- tests/Edit/Modify/edit_table_test.cpp | 73 ++++++++++++++ 2 files changed, 198 insertions(+), 8 deletions(-) diff --git a/src/Edit/Modify/edit_table.cpp b/src/Edit/Modify/edit_table.cpp index 0de52f1d1c..83686df7b5 100644 --- a/src/Edit/Modify/edit_table.cpp +++ b/src/Edit/Modify/edit_table.cpp @@ -39,6 +39,14 @@ is_empty_cell (tree t) { (is_compound (t, "cell-output", 3) && is_empty_cell (t[2])); } +/** + * @brief 构造一行 nr_cols 个空 cell。 + * @param nr_cols 列数(每行 cell 数) + * @return 形如 `(row (cell "") (cell "") ...)` 的 tree,cell 内容为空字符串。 + * + * cell 不带 DOCUMENT 包装——调用方若需要按表格的 block/hyphen 格式包装, + * 应改用 `empty_row(nr_cols, wrap)`。 + */ tree empty_row (int nr_cols) { int i; @@ -48,12 +56,22 @@ empty_row (int nr_cols) { return R; } +/** + * @brief 构造一行 nr_cols 个空 cell,并按 wrap 决定是否预包装 DOCUMENT。 + * @param nr_cols 列数 + * @param wrap true 时每个 cell 内容生成 `(document "")`, + * false 时退化为 `empty_row(nr_cols)` + * @return 形如 `(row (cell ...) ...)` 的 tree + * + * 预包装的目的:插入新行/列时若表格的 CELL_BLOCK/CELL_HYPHEN 要求 + * DOCUMENT 包装(generic 样式默认就是要求),原流程是「先插空 cell → + * 后续 `table_correct_block_content` 逐个 `insert_node` 补包装」—— + * 每个 `insert_node` 触发一次 typesetter invalidation,N 个 cell 就是 + * N 次 invalidation。构造时一次性生成 `(document "")` cell,从源头 + * 省掉这批 mutation,详见 [1150] 表格插入行性能优化。 + */ tree empty_row (int nr_cols, bool wrap) { - // Like empty_row(nr_cols) but pre-wrap cells in DOCUMENT when the table's - // CELL_BLOCK/CELL_HYPHEN formats require it. Pre-wrapping at construction - // avoids one insert_node per cell later in table_correct_block_content, - // each of which triggers a typesetter invalidation. int i; tree R (ROW, nr_cols); for (i= 0; i < nr_cols; i++) @@ -61,6 +79,14 @@ empty_row (int nr_cols, bool wrap) { return R; } +/** + * @brief 构造 nr_rows × nr_cols 的空表格,cell 不带 DOCUMENT 包装。 + * @param nr_rows 行数 + * @param nr_cols 列数 + * @return `(table (row ...) ...)` 形式的 tree + * + * 行由 `empty_row(nr_cols)` 构造——cell 内容为 `""`。 + */ tree empty_table (int nr_rows, int nr_cols) { int i; @@ -70,6 +96,16 @@ empty_table (int nr_rows, int nr_cols) { return T; } +/** + * @brief 构造 nr_rows × nr_cols 的空表格,cell 按 wrap 决定是否预包装。 + * @param nr_rows 行数 + * @param nr_cols 列数 + * @param wrap true 时每个 cell 内容为 `(document "")`,否则为 `""` + * @return `(table (row ...) ...)` 形式的 tree + * + * 见 `empty_row(nr_cols, wrap)` 的说明——批量插入新行时一次性预包装 + * 可省掉 N 次 `insert_node`,显著降低 typesetter invalidation 成本。 + */ tree empty_table (int nr_rows, int nr_cols, bool wrap) { int i; @@ -567,6 +603,28 @@ edit_table_rep::table_remove (path fp, int row, int col, int delr, int delc) { } } +/** + * @brief 在表格的指定位置插入若干空行/空列。 + * @param fp 表格 format 路径(指向 TFORMAT 节点) + * @param row 插入位置的行号(0-based) + * @param col 插入位置的列号(0-based) + * @param insr 插入的行数(0 表示不插行) + * @param insc 插入的列数(0 表示不插列) + * + * 三段工作: + * 1. **行插入**:在 `row` 行前插入 `insr` 行空 cell(`empty_table`)。 + * 2. **列插入**:对每一现有行,在 `col` 列前插入 `insc` 个空 cell + * (`empty_row`)。 + * 3. **CWITH 重写**:表格里已有的 cell-format 声明(CWITH 节点)按 + * 新的行列号平移——正索引的行/列引用整体 +insr/+insc,负索引 + * (相对末行/末列)按是否跨越插入点决定是否平移。 + * + * 性能优化([1150]):插入前一次性查询表格的 CELL_BLOCK/CELL_HYPHEN + * 格式,构造新行/列时按 wrap 状态预包装 DOCUMENT,省掉后续 + * `table_correct_block_content` 里 N 个 cell 各一次 `insert_node`。 + * 若每个 cell 的实际格式不同(罕见,例如部分 cell 显式声明 block=no), + * `table_correct_block_content` 仍会修正。 + */ void edit_table_rep::table_insert (path fp, int row, int col, int insr, int insc) { path p= search_table (fp); @@ -690,6 +748,19 @@ edit_table_rep::table_bound (path fp, int& row1, int& col1, int& row2, tm_delete_array (cs); } +/** + * @brief 把光标移到表格里 (row, col) 单元格的边界。 + * @param fp 表格 format 路径 + * @param row 目标行号(0-based,越界自动夹到 [0, nr_rows-1]) + * @param col 目标列号(0-based,越界自动夹到 [0, nr_cols-1]) + * @param at_start true 落在 cell 起始,false 落在 cell 末尾 + * + * 实现:定位到目标 cell 路径后调 `go_to_border`。原本在路径计算前 + * 调过 `table_bound(fp,row,col,row2,col2)` 想算合并区,但 row2/col2 + * 是局部变量、立即丢弃(`search_cell` 用的还是原始 row/col),是 + * dead code。`table_bound` 是 O(N×M),删掉后 [1150] 基准里 + * `table_go_to` 从 646 ms 降到 201 ms。 + */ void edit_table_rep::table_go_to (path fp, int row, int col, bool at_start) { int nr_rows, nr_cols; @@ -1226,6 +1297,18 @@ edit_table_rep::table_extract_format () { go_to (fp * path (N (fm) - 1, 0)); } +/** + * @brief 在当前光标所在行 下方/上方 插入一个新行。 + * @param forward true 插到当前行下方,false 插到上方 + * + * 流程:`table_insert` 插空行(已按格式预包装 DOCUMENT)→ + * `table_go_to` 移光标到新行同列 cell → + * `table_correct_block_content(fp,new_row,new_row+1,0,nr_cols)` + * 校正新行的 DOCUMENT 包装(默认预包装正确时是 no-op)→ + * `table_resize_notify` 通知 scheme(chat/paste 路径用它)。 + * + * 限制:若 `nr_rows + 1 > i2`(表格行数上限)直接 return。 + */ void edit_table_rep::table_insert_row (bool forward) { bench_start ("table_insert_row"); @@ -1264,6 +1347,17 @@ edit_table_rep::table_insert_row (bool forward) { bench_cumul ("table_insert_row"); } +/** + * @brief 在当前光标所在列 右侧/左侧 插入一个新列。 + * @param forward true 插到当前列右侧,false 插到左侧 + * + * 与 `table_insert_row` 同构:`table_insert` 插空列(预包装)→ + * `table_go_to` 移光标 → + * `table_correct_block_content(fp,0,nr_rows,new_col,new_col+1)` + * 校正新列 → `table_resize_notify`。 + * + * 限制:若 `nr_cols + 1 > j2`(列数上限)直接 return。 + */ void edit_table_rep::table_insert_column (bool forward) { int row, col; @@ -1517,6 +1611,13 @@ edit_table_rep::table_column_decoration (bool forward) { if (forward && (col < (nr_cols - 1))) table_hor_decorate (fp, col, 0, 1); } +/** + * @brief 全表版本的 DOCUMENT 包装校正。 + * + * 等价于 `table_correct_block_content(fp, 0, nr_rows, 0, nr_cols)`。 + * 用于无法定位单一变更区域的路径(如 `make_table`、`make_subtable`、 + * `cell_set_format`、选区操作)。会话性能敏感的插入路径请改用范围化重载。 + */ void edit_table_rep::table_correct_block_content () { bench_start ("table_correct_block_content"); @@ -1531,13 +1632,29 @@ edit_table_rep::table_correct_block_content () { bench_cumul ("table_correct_block_content"); } +/** + * @brief 校正指定矩形区域内 cell 的 DOCUMENT 包装。 + * @param fp 表格 format 路径 + * @param row1 起始行(含),0-based + * @param row2 结束行(不含) + * @param col1 起始列(含),0-based + * @param col2 结束列(不含) + * + * 每个 cell 查 CELL_BLOCK 和 CELL_HYPHEN 格式: + * - **f1 = true**(block=no 或 (block=auto 且 hyphen=n)): + * cell 是单一 DOCUMENT 时去包装(`remove_node`)。 + * - **f2 = true**(block=yes 或 (block=auto 且 hyphen≠n)): + * cell 不是 DOCUMENT 时加包装(`insert_node DOCUMENT`)。 + * + * 范围化版本是 [1150] 的核心优化——插入路径只改一行/一列, + * 其它 cell 的 CELL_BLOCK/CELL_HYPHEN 格式不变,wrap 状态也稳定, + * 没必要全表扫。范围化为 O((row2-row1)×(col2-col1)),远好于 O(N×M)。 + * + * @note 调用方需保证 fp 已是 search_format 的结果。 + */ void edit_table_rep::table_correct_block_content (path fp, int row1, int row2, int col1, int col2) { - // Correct DOCUMENT wrap only on cells in [row1,row2) × [col1,col2). - // Insert paths only mutate one row/column, so re-scanning the whole table - // would be O(N×M) waste. CELL_BLOCK/CELL_HYPHEN formats of untouched cells - // don't change, so their wrap state is already correct. int row, col; for (row= row1; row < row2; row++) for (col= col1; col < col2; col++) { diff --git a/tests/Edit/Modify/edit_table_test.cpp b/tests/Edit/Modify/edit_table_test.cpp index dc1a7b4fa8..ce13c586dd 100644 --- a/tests/Edit/Modify/edit_table_test.cpp +++ b/tests/Edit/Modify/edit_table_test.cpp @@ -14,6 +14,9 @@ using namespace moebius; // Declared in src/Edit/Modify/edit_table.cpp extern tree empty_table (int nr_rows, int nr_cols); +extern tree empty_table (int nr_rows, int nr_cols, bool wrap); +extern tree empty_row (int nr_cols); +extern tree empty_row (int nr_cols, bool wrap); extern tree default_table_tree (int nr_rows, int nr_cols, bool enable_table_hyphen); extern bool table_default_hyphen_enabled (string mode); @@ -36,6 +39,11 @@ private slots: void test_default_hyphen_disabled_in_math_mode (); void test_default_table_tree_skips_table_hyphen_in_math_mode (); void test_no_document_wrap_in_math_mode (); + // [1150] wrap-aware 重载:构造时空 cell 是否预包装 DOCUMENT + void test_empty_row_no_wrap_matches_default (); + void test_empty_row_with_wrap_wraps_cells (); + void test_empty_table_with_wrap_wraps_all_cells (); + void test_empty_table_no_wrap_matches_default (); }; void @@ -193,5 +201,70 @@ TestEditTable::test_no_document_wrap_in_math_mode () { QVERIFY (!table_needs_document_wrap ("y", "no", "math")); } +// [1150] wrap=false 时 empty_row(nr_cols, false) 应与原 empty_row(nr_cols) +// 完全一致——保证 wrap-aware 重载是无破坏性的扩展。 + +void +TestEditTable::test_empty_row_no_wrap_matches_default () { + tree a= empty_row (3, false); + tree b= empty_row (3); + QCOMPARE (N (a), 3); + QVERIFY (is_func (a, ROW)); + // 结构逐 cell 相同:每个都是 (cell "") + for (int i= 0; i < 3; i++) { + QVERIFY (is_func (a[i], CELL)); + QVERIFY (b[i] == a[i]); + QVERIFY (a[i][0] == ""); + } +} + +// [1150] wrap=true 时每个 cell 内容应是 (document ""),这是预包装的核心 +// 契约——cell 已是 DOCUMENT,table_correct_block_content 后续无需 insert_node。 + +void +TestEditTable::test_empty_row_with_wrap_wraps_cells () { + tree r= empty_row (4, true); + QCOMPARE (N (r), 4); + QVERIFY (is_func (r, ROW)); + for (int i= 0; i < 4; i++) { + QVERIFY (is_func (r[i], CELL)); + // cell 唯一子节点应是 (document "") + tree cell_content= r[i][0]; + QVERIFY (is_func (cell_content, DOCUMENT)); + QCOMPARE (N (cell_content), 1); + QVERIFY (cell_content[0] == ""); + } +} + +// [1150] empty_table(nr_rows, nr_cols, true) 的每个 cell 都应预包装—— +// 跨多行多列规模下保证一致(避免单行 cell 测试漏掉多行不统一的 bug)。 + +void +TestEditTable::test_empty_table_with_wrap_wraps_all_cells () { + tree t= empty_table (3, 2, true); + QCOMPARE (N (t), 3); + QVERIFY (is_func (t, TABLE)); + for (int r= 0; r < 3; r++) { + QVERIFY (is_func (t[r], ROW)); + QCOMPARE (N (t[r]), 2); + for (int c= 0; c < 2; c++) { + QVERIFY (is_func (t[r][c], CELL)); + QVERIFY (is_func (t[r][c][0], DOCUMENT)); + } + } +} + +// [1150] wrap=false 时 empty_table(nr_rows, nr_cols, false) 应与原 +// empty_table(nr_rows, nr_cols) 一致——保证 wrap-aware 重载是无破坏性扩展。 + +void +TestEditTable::test_empty_table_no_wrap_matches_default () { + tree a= empty_table (2, 2, false); + tree b= empty_table (2, 2); + QVERIFY (b == a); + // 抽查一个 cell:内容应为 ""(无 DOCUMENT 包装) + QVERIFY (a[0][0][0] == ""); +} + QTEST_MAIN (TestEditTable) #include "edit_table_test.moc" From 902fe5337976a9b1282ddc0c561149407c087407 Mon Sep 17 00:00:00 2001 From: Da Shen Date: Mon, 20 Jul 2026 22:21:35 +0800 Subject: [PATCH 5/5] =?UTF-8?q?[1150]=20=E7=A7=BB=E9=99=A4=E5=B7=B2?= =?UTF-8?q?=E6=97=A0=E6=80=A7=E8=83=BD=E9=97=AE=E9=A2=98=E7=9A=84=20bench?= =?UTF-8?q?=20=E5=9F=8B=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit bench 数据 @ 100 rows 显示一批子段已经完全无问题,移除以减少噪音: 移除(实测均 < 1 ms/call): - table_insert_row:search (1 ms / 100 invocations) - table_insert_row:insert (38 ms) - table_insert_row:correct_block (3 ms) - table_insert:row (37 ms) - table_insert:col (3 ms) - table_insert:cwith (0 ms) - table_correct_block_content (3 ms) 保留(真有性能意义): - table_insert_row 顶层聚合 - table_insert_row:go_to 剩余热点 1:~1.8 ms/call(typesetter invalidation) - table_insert_row:resize_notify 剩余热点 2:~1.7 ms/call(scheme callback) - table_resize_notify 独立函数级聚合 验证:MOGAN_TEST_GUI=1 xmake r 1150 输出从 12 个 task 精简到 4 个, @ 100 rows 总耗时 657 → 650 ms(噪声范围)。 --- src/Edit/Modify/edit_table.cpp | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/src/Edit/Modify/edit_table.cpp b/src/Edit/Modify/edit_table.cpp index 83686df7b5..fefcaf568c 100644 --- a/src/Edit/Modify/edit_table.cpp +++ b/src/Edit/Modify/edit_table.cpp @@ -648,12 +648,9 @@ edit_table_rep::table_insert (path fp, int row, int col, int insr, int insc) { bool wrap = mode != "math" && (block == "yes" || (block == "auto" && is_atomic (hyphen) && hyphen != "n")); - bench_start ("table_insert:row"); if (insr > 0) if (row <= N (T)) insert (p * row, empty_table (insr, nr_cols, wrap)); - bench_cumul ("table_insert:row"); - bench_start ("table_insert:col"); T= subtree (et, p); if (insc > 0) for (row= 0; row < N (T); row++) { @@ -661,12 +658,9 @@ edit_table_rep::table_insert (path fp, int row, int col, int insr, int insc) { tree R= subtree (et, q); if (col <= N (R)) insert (q * col, empty_row (insc, wrap)); } - bench_cumul ("table_insert:col"); - bench_start ("table_insert:cwith"); tree st= subtree (et, fp); if (!is_func (st, TFORMAT)) { - bench_cumul ("table_insert:cwith"); return; } int k, n= N (st); @@ -697,7 +691,6 @@ edit_table_rep::table_insert (path fp, int row, int col, int insr, int insc) { assign (fp * path (k, 3), as_string (J2 - insc)); } } - bench_cumul ("table_insert:cwith"); } /****************************************************************************** @@ -1312,15 +1305,12 @@ edit_table_rep::table_extract_format () { void edit_table_rep::table_insert_row (bool forward) { bench_start ("table_insert_row"); - bench_start ("table_insert_row:search"); int row, col; path fp= search_format (row, col); if (is_nil (fp)) { - bench_cumul ("table_insert_row:search"); bench_cumul ("table_insert_row"); return; } - bench_cumul ("table_insert_row:search"); int nr_rows, nr_cols, i1, j1, i2, j2; table_get_extents (fp, nr_rows, nr_cols); table_get_limits (fp, i1, j1, i2, j2); @@ -1329,18 +1319,14 @@ edit_table_rep::table_insert_row (bool forward) { return; } int new_row= row + (forward ? 1 : 0); - bench_start ("table_insert_row:insert"); table_insert (fp, new_row, col, 1, 0); - bench_cumul ("table_insert_row:insert"); bench_start ("table_insert_row:go_to"); table_go_to (fp, new_row, col); bench_cumul ("table_insert_row:go_to"); - bench_start ("table_insert_row:correct_block"); // Only the new row's cells need wrap correction — other rows already had // their DOCUMENT nodes fixed by previous calls and their formats didn't // change. table_correct_block_content (fp, new_row, new_row + 1, 0, nr_cols); - bench_cumul ("table_insert_row:correct_block"); bench_start ("table_insert_row:resize_notify"); table_resize_notify (); bench_cumul ("table_insert_row:resize_notify"); @@ -1620,16 +1606,11 @@ edit_table_rep::table_column_decoration (bool forward) { */ void edit_table_rep::table_correct_block_content () { - bench_start ("table_correct_block_content"); int nr_rows, nr_cols; path fp= search_format (); - if (is_nil (fp)) { - bench_cumul ("table_correct_block_content"); - return; - } + if (is_nil (fp)) return; table_get_extents (fp, nr_rows, nr_cols); table_correct_block_content (fp, 0, nr_rows, 0, nr_cols); - bench_cumul ("table_correct_block_content"); } /**