diff --git a/pi-coding-agent-render.el b/pi-coding-agent-render.el index 59771b0..edac816 100644 --- a/pi-coding-agent-render.el +++ b/pi-coding-agent-render.el @@ -5251,8 +5251,10 @@ Uses the current buffer's completed-thinking display mode." (defun pi-coding-agent--history-postprocess-start () "Return the first position that needs eager history post-processing. Large resumed histories should render the visible tail promptly. Older content -can rely on normal jit-lock when visited, while display-only table decoration is -kept to the same hot-tail suffix used by resize refreshes." +relies on jit-lock when visited: tree-sitter fontification and the registered +`pi-coding-agent--jit-decorate-tables' pass both run as each region scrolls into +view, so eager decoration is limited to the same hot-tail suffix used by resize +refreshes." (if (markerp pi-coding-agent--hot-tail-start) (marker-position pi-coding-agent--hot-tail-start) (point-min))) @@ -5274,8 +5276,10 @@ contain `|' but cannot be pipe tables.") "Run consolidated display post-processing after history replay. History replay inserts many small user/assistant chunks. Running fontification and table decoration after each chunk is expensive in large sessions, so replay -defers that work. Fontification is left to jit-lock on redisplay; the only -synchronous pass decorates candidate tables in the recent hot tail." +defers that work. Fontification and table decoration are both left to jit-lock +on redisplay (see `pi-coding-agent--jit-decorate-tables'); the only synchronous +pass here decorates candidate tables in the recent hot tail so the visible tail +is a grid immediately, without waiting for a redisplay." (let ((start (pi-coding-agent--history-postprocess-start)) (end (point-max))) (when (pi-coding-agent--history-table-candidate-p start end) diff --git a/pi-coding-agent-table.el b/pi-coding-agent-table.el index 56207a4..b4723b3 100644 --- a/pi-coding-agent-table.el +++ b/pi-coding-agent-table.el @@ -35,6 +35,7 @@ ;; - `pi-coding-agent--decorate-tables-in-region' — stable path ;; - `pi-coding-agent--maybe-decorate-streaming-table' — streaming path ;; - `pi-coding-agent--maybe-refresh-hot-tail-tables' — resize path +;; - `pi-coding-agent--jit-decorate-tables' — lazy scroll-into-view path ;; ;; Depends on `pi-coding-agent-ui' for visible-text extraction and ;; scroll preservation, and on `markdown-table-wrap' for the wrapping engine. @@ -630,6 +631,31 @@ Idempotent: existing table overlays in the region are removed first." (when (pi-coding-agent--table-has-data-row-p (car region) (cdr region)) (pi-coding-agent--decorate-table (car region) (cdr region) width))))) +;;;; Lazy Scroll-Into-View Decoration (jit-lock) + +;; History replay only decorates the hot tail eagerly (see +;; `pi-coding-agent--postprocess-history-buffer'); older tables are left as raw +;; markdown so large resumed sessions load promptly. Tree-sitter fontification +;; already renders lazily as jit-lock brings each region into view, but table +;; decoration is a display-overlay pass that jit-lock does not know about, so +;; older tables would stay raw until a resize or manual toggle. Registering a +;; jit-lock function closes that gap: as you scroll an older table into view, +;; it is decorated on the same redisplay pass that fontifies it -- no load-time +;; cost, because jit-lock only ever runs on regions about to be displayed. + +(defun pi-coding-agent--jit-decorate-tables (beg end) + "Decorate pipe tables overlapping BEG..END, expanded to whole tables. +Registered with `jit-lock-register' so tables render when scrolled into +view. jit-lock hands out arbitrary chunk boundaries that may bisect a +table, so the region is grown to the full extent of every overlapping +table before decorating; this keeps the idempotent overlay-removal in +`pi-coding-agent--decorate-tables-in-region' aligned to whole tables and +never leaves a table decorated from a partial range." + (when-let* ((regions (pi-coding-agent--treesit-table-regions beg end))) + (let ((full-beg (apply #'min beg (mapcar #'car regions))) + (full-end (apply #'max end (mapcar #'cdr regions)))) + (pi-coding-agent--decorate-tables-in-region full-beg full-end)))) + ;;;; Cleanup (defun pi-coding-agent--cleanup-visible-string-buffer () diff --git a/pi-coding-agent-ui.el b/pi-coding-agent-ui.el index 8e438e2..1281ff6 100644 --- a/pi-coding-agent-ui.el +++ b/pi-coding-agent-ui.el @@ -60,6 +60,7 @@ (declare-function pi-coding-agent--cleanup-on-kill "pi-coding-agent-render") (declare-function pi-coding-agent--restore-tool-properties "pi-coding-agent-render") (declare-function pi-coding-agent--maybe-refresh-hot-tail-tables "pi-coding-agent-table") +(declare-function pi-coding-agent--jit-decorate-tables "pi-coding-agent-table") ;; pi-coding-agent-input.el (input buffer commands) (declare-function pi-coding-agent-quit "pi-coding-agent-input") @@ -857,6 +858,12 @@ This is a read-only buffer showing the conversation history." ;; Run after font-lock to undo markdown damage in tool overlays. (jit-lock-register #'pi-coding-agent--restore-tool-properties) + ;; Decorate pipe tables lazily as they scroll into view. History replay + ;; eagerly decorates only the hot tail; this jit-lock pass renders older + ;; tables on the same redisplay that fontifies them, so resumed sessions + ;; stay fast to load yet every table becomes a grid once seen. + (jit-lock-register #'pi-coding-agent--jit-decorate-tables) + ;; Compute theme-derived faces used by chat overlays. (pi-coding-agent--update-theme-derived-faces) diff --git a/test/pi-coding-agent-table-test.el b/test/pi-coding-agent-table-test.el index 42e2d36..6fe71fc 100644 --- a/test/pi-coding-agent-table-test.el +++ b/test/pi-coding-agent-table-test.el @@ -885,6 +885,60 @@ what the parser recognizes as a `pipe_table'." (pi-coding-agent--display-message-delta "| Auth | Done |\n") (should (>= (pi-coding-agent-test--table-overlay-count) 1)))) +(ert-deftest pi-coding-agent-test-jit-decorate-tables-decorates-region () + "jit-decorate-tables renders a table overlapping the requested region." + (with-temp-buffer + (pi-coding-agent-chat-mode) + (let ((inhibit-read-only t)) + (insert pi-coding-agent-test--wide-table)) + (font-lock-ensure) + (pi-coding-agent--jit-decorate-tables (point-min) (point-max)) + (should (>= (pi-coding-agent-test--table-overlay-count) 1)))) + +(ert-deftest pi-coding-agent-test-jit-decorate-tables-expands-partial-region () + "A jit-lock chunk that bisects a table still decorates the whole table. +jit-lock hands out arbitrary boundaries; the region is grown to the full +tree-sitter table extent so overlays cover every row, not a partial range." + (with-temp-buffer + (pi-coding-agent-chat-mode) + (let ((table-beg nil) + (inhibit-read-only t)) + (insert "Some intro prose before the table.\n\n") + (setq table-beg (point)) + (insert pi-coding-agent-test--wide-table) + (font-lock-ensure) + ;; Request a range that begins in the middle of the table and ends + ;; before its last row -- the worst case for a redisplay chunk. + (pi-coding-agent--jit-decorate-tables (+ table-beg 20) (- (point-max) 20)) + ;; The whole four-line table (header, separator, two data rows) gets + ;; one overlay per raw line, so all rows are covered, not just those + ;; inside the narrow request. + (should (= (pi-coding-agent-test--table-overlay-count) 4))))) + +(ert-deftest pi-coding-agent-test-jit-decorate-tables-noop-without-table () + "jit-decorate-tables creates no overlays when the region has no table." + (with-temp-buffer + (pi-coding-agent-chat-mode) + (let ((inhibit-read-only t)) + (insert "Just prose here, no pipe table at all.\n\nMore prose.\n")) + (font-lock-ensure) + (pi-coding-agent--jit-decorate-tables (point-min) (point-max)) + (should (= (pi-coding-agent-test--table-overlay-count) 0)))) + +(ert-deftest pi-coding-agent-test-jit-decorate-tables-respects-raw-toggle () + "jit-decorate-tables leaves user-toggled raw tables undecorated. +The `pi-coding-agent--skip-raw-tables' advice guards every decoration +path, including this lazy one, so scrolling a raw table into view does +not clobber the user's choice." + (with-temp-buffer + (pi-coding-agent-chat-mode) + (let ((inhibit-read-only t)) + (insert pi-coding-agent-test--wide-table)) + (font-lock-ensure) + (pi-coding-agent--mark-table-raw (point-min) (point-max)) + (pi-coding-agent--jit-decorate-tables (point-min) (point-max)) + (should (= (pi-coding-agent-test--table-overlay-count) 0)))) + (ert-deftest pi-coding-agent-test-chat-buffer-hidden-p-sees-visible-window-on-other-frame () "A chat buffer visible on another frame is not hidden." (with-temp-buffer