refactor(ebox): route retained updates through TP
Make TP the sole owner of live-buffer text-property publication, mount spans, scoped diff execution, and transaction rollback. Ebox now computes layout owners and retained surface plans, publishes handle/viewport/theme/scroll changes through TP, and keeps its mirrored runtime state transactionally consistent. Remove the former Ebox marker/index/patch executor instead of preserving a second mutation path.\n\nVerification:\n- make ci EMACS=/Applications/Emacs.app/Contents/MacOS/Emacs\n- make package-lint-install EMACS=/Applications/Emacs.app/Contents/MacOS/Emacs\n- strict byte compilation passed for 16 files\n- Ebox production has no tp-- private calls or marker writers\n- TP production has no Ebox dependency
This commit is contained in:
parent
cdf841232a
commit
654c824630
@ -25,7 +25,7 @@ This is the maintainer entry point for the standalone Ebox repository. It descri
|
|||||||
| `ebox-flex.el` | Flex normalization, lines, free-space distribution, and flex rendering. |
|
| `ebox-flex.el` | Flex normalization, lines, free-space distribution, and flex rendering. |
|
||||||
| `ebox-grid.el` | Tracks, implicit tracks, fractions, minmax/repeat, gap, placement, span, and alignment. |
|
| `ebox-grid.el` | Tracks, implicit tracks, fractions, minmax/repeat, gap, placement, span, and alignment. |
|
||||||
| `ebox-surface.el` | Candidate identity, projection to TP surface plans, retained mount/update, and the rollback-capable Ebox runtime-state participant. |
|
| `ebox-surface.el` | Candidate identity, projection to TP surface plans, retained mount/update, and the rollback-capable Ebox runtime-state participant. |
|
||||||
| `ebox-buffer-backend.el` | Text properties, display spaces/borders, markers, extents, and specialized local patch primitives. |
|
| `ebox-buffer-backend.el` | Propertized render-string construction, display spaces/borders, and existing-slot shaping. |
|
||||||
| `ebox-incremental.el` | Runtime state, snapshots, dirty planning, owner escalation, pure commit preparation, and reports. |
|
| `ebox-incremental.el` | Runtime state, snapshots, dirty planning, owner escalation, pure commit preparation, and reports. |
|
||||||
| `ebox-dsl.el` | Data-oriented `.ebox` forms and lowering to public nodes. |
|
| `ebox-dsl.el` | Data-oriented `.ebox` forms and lowering to public nodes. |
|
||||||
| `ebox-selector.el` | CSS-like queries over trees and runtime handles. |
|
| `ebox-selector.el` | CSS-like queries over trees and runtime handles. |
|
||||||
@ -61,10 +61,10 @@ Caller-owned Source Tree
|
|||||||
| Measurement | `ebox-measure.el` | Application state or dirty policy. |
|
| Measurement | `ebox-measure.el` | Application state or dirty policy. |
|
||||||
| Formatting Context | `ebox-layout.el`, `ebox-flex.el`, `ebox-grid.el` | Buffer edits. |
|
| Formatting Context | `ebox-layout.el`, `ebox-flex.el`, `ebox-grid.el` | Buffer edits. |
|
||||||
| Fragment/Snapshot | `ebox-fragment.el`, `ebox-incremental.el` | Source parsing or identity allocation. |
|
| Fragment/Snapshot | `ebox-fragment.el`, `ebox-incremental.el` | Source parsing or identity allocation. |
|
||||||
| Surface Projection and Full-Root Publication | `ebox-surface.el` plus public TP surface APIs | Ebox layout decisions or generic diff execution. |
|
| Surface Projection and Runtime Publication | `ebox-surface.el` plus public TP surface APIs | Ebox layout decisions or generic diff execution. |
|
||||||
| Dirty/Patch Semantics | `ebox-incremental.el` | Raw measurement or TP buffer writes. |
|
| Dirty/Patch Semantics | `ebox-incremental.el` | Raw measurement or TP buffer writes. |
|
||||||
| Generic Surface Diff/Commit | TP | Ebox geometry, dirty policy, or application state. |
|
| Generic Surface Diff/Commit | TP | Ebox geometry, dirty policy, or application state. |
|
||||||
| Specialized Local Backend | `ebox-buffer-backend.el` | Full-root mount/commit ownership, style semantics, or application state. |
|
| Render-String Backend | `ebox-buffer-backend.el` | Live buffer writes, retained markers, style semantics, or application state. |
|
||||||
|
|
||||||
## Invariants
|
## Invariants
|
||||||
|
|
||||||
@ -79,8 +79,8 @@ Caller-owned Source Tree
|
|||||||
- Buffer coordinates belong to the generation that produced them and must be refreshed after mutation.
|
- Buffer coordinates belong to the generation that produced them and must be refreshed after mutation.
|
||||||
- Grid uses the normal measurement and rendering pipeline. Native reflow may reject an ineligible tree and must fall back to Elisp without changing correctness.
|
- Grid uses the normal measurement and rendering pipeline. Native reflow may reject an ineligible tree and must fall back to Elisp without changing correctness.
|
||||||
- Loading Ebox never builds or installs the optional Rust module.
|
- Loading Ebox never builds or installs the optional Rust module.
|
||||||
- Phase 8 is active: `ebox-render-to-buffer` and declarative `ebox-commit` use TP as their only live publication boundary, while Ebox runtime state joins the same rollback-capable transaction. Existing specialized numeric region, selector, viewport, and scroll update publishers remain until the local-update cutover; they must not become an alternate full-root path.
|
- Phase 9 is active: first mount, declarative commit, handle/selector update, viewport/theme update, batch flush, and scroll update all publish through TP surfaces. Ebox computes dirty/layout ownership and joins its opaque runtime state to the same rollback-capable transaction; it has no second live buffer executor.
|
||||||
- Successful full-root reports preserve the Ebox semantic strategy and planned publication scope, then add `:publication-scope tp-surface`, TP operation counts, surface revision, and retained-object reconciliation counts.
|
- Successful reports preserve the Ebox semantic strategy and planned publication scope, then add `:publication-scope tp-surface`, TP physical operation counts, surface revision, scoped/full-root facts, and retained-object reconciliation counts.
|
||||||
|
|
||||||
## Grid contract
|
## Grid contract
|
||||||
|
|
||||||
|
|||||||
@ -25,7 +25,7 @@
|
|||||||
| `ebox-flex.el` | flex 归一化、line、剩余空间分配和 flex 渲染。 |
|
| `ebox-flex.el` | flex 归一化、line、剩余空间分配和 flex 渲染。 |
|
||||||
| `ebox-grid.el` | 轨道、隐式轨道、分数、minmax/repeat、gap、placement、span 和对齐。 |
|
| `ebox-grid.el` | 轨道、隐式轨道、分数、minmax/repeat、gap、placement、span 和对齐。 |
|
||||||
| `ebox-surface.el` | 候选 identity、向 TP surface plan 的投影、retained mount/update,以及支持 rollback 的 Ebox runtime-state participant。 |
|
| `ebox-surface.el` | 候选 identity、向 TP surface plan 的投影、retained mount/update,以及支持 rollback 的 Ebox runtime-state participant。 |
|
||||||
| `ebox-buffer-backend.el` | text property、display space/border、marker、extent 与专用局部 patch primitive。 |
|
| `ebox-buffer-backend.el` | 带文本属性的渲染字符串构造、display space/border 与既有 slot 整形。 |
|
||||||
| `ebox-incremental.el` | runtime、snapshot、dirty 规划、owner 提升、纯 commit 准备和报告。 |
|
| `ebox-incremental.el` | runtime、snapshot、dirty 规划、owner 提升、纯 commit 准备和报告。 |
|
||||||
| `ebox-dsl.el` | 数据型 `.ebox` form,以及向公共节点的 lowering。 |
|
| `ebox-dsl.el` | 数据型 `.ebox` form,以及向公共节点的 lowering。 |
|
||||||
| `ebox-selector.el` | 对树和 runtime handle 的 CSS-like 查询。 |
|
| `ebox-selector.el` | 对树和 runtime handle 的 CSS-like 查询。 |
|
||||||
@ -61,10 +61,10 @@ active 合同还覆盖 `Makefile`、`.github/workflows/ci.yml`、`tests/ebox-cor
|
|||||||
| Measurement | `ebox-measure.el` | 应用状态或 dirty 策略。 |
|
| Measurement | `ebox-measure.el` | 应用状态或 dirty 策略。 |
|
||||||
| Formatting Context | `ebox-layout.el`、`ebox-flex.el`、`ebox-grid.el` | Buffer 编辑。 |
|
| Formatting Context | `ebox-layout.el`、`ebox-flex.el`、`ebox-grid.el` | Buffer 编辑。 |
|
||||||
| Fragment/Snapshot | `ebox-fragment.el`、`ebox-incremental.el` | Source parsing 或 identity 分配。 |
|
| Fragment/Snapshot | `ebox-fragment.el`、`ebox-incremental.el` | Source parsing 或 identity 分配。 |
|
||||||
| Surface 投影与整根发布 | `ebox-surface.el` 加公共 TP surface API | Ebox 布局决策或通用 diff 执行。 |
|
| Surface 投影与 Runtime 发布 | `ebox-surface.el` 加公共 TP surface API | Ebox 布局决策或通用 diff 执行。 |
|
||||||
| Dirty/Patch 语义 | `ebox-incremental.el` | 原始测量或 TP buffer 写入。 |
|
| Dirty/Patch 语义 | `ebox-incremental.el` | 原始测量或 TP buffer 写入。 |
|
||||||
| 通用 Surface Diff/Commit | TP | Ebox 几何、dirty 策略或应用状态。 |
|
| 通用 Surface Diff/Commit | TP | Ebox 几何、dirty 策略或应用状态。 |
|
||||||
| 专用局部 Backend | `ebox-buffer-backend.el` | 整根 mount/commit 所有权、样式语义或应用状态。 |
|
| 渲染字符串 Backend | `ebox-buffer-backend.el` | Live buffer 写入、retained marker、样式语义或应用状态。 |
|
||||||
|
|
||||||
## 不变量
|
## 不变量
|
||||||
|
|
||||||
@ -79,8 +79,8 @@ active 合同还覆盖 `Makefile`、`.github/workflows/ci.yml`、`tests/ebox-cor
|
|||||||
- Buffer 坐标属于生成它的 generation,变更后必须重新获取。
|
- Buffer 坐标属于生成它的 generation,变更后必须重新获取。
|
||||||
- Grid 使用普通测量与渲染流水线;native reflow 可以拒绝不适合的树并回退到 Elisp,正确性不变。
|
- Grid 使用普通测量与渲染流水线;native reflow 可以拒绝不适合的树并回退到 Elisp,正确性不变。
|
||||||
- 加载 Ebox 不会构建或安装可选 Rust 模块。
|
- 加载 Ebox 不会构建或安装可选 Rust 模块。
|
||||||
- 当前已进入 Phase 8:`ebox-render-to-buffer` 与声明式 `ebox-commit` 只通过 TP 这一条 live 发布边界,Ebox runtime state 在同一个可 rollback transaction 中参与提交。现有的数字 region、selector、viewport 与 scroll 专用局部 publisher 暂时保留到局部更新切换阶段,但不得成为第二条整根发布路径。
|
- 当前已进入 Phase 9:首次 mount、声明式 commit、handle/selector 更新、viewport/theme 更新、batch flush 与 scroll 更新全部通过 TP surface 发布。Ebox 只计算 dirty/layout owner,并让不透明 runtime state 参与同一个可 rollback transaction;不存在第二个 live buffer executor。
|
||||||
- 成功的整根报告会保留 Ebox 语义策略与 planned publication scope,再加入 `:publication-scope tp-surface`、TP operation 数、surface revision 和 retained-object reconciliation 统计。
|
- 成功报告会保留 Ebox 语义 strategy 与 planned publication scope,再加入 `:publication-scope tp-surface`、TP 物理 operation 数、surface revision、scoped/full-root 事实和 retained-object reconciliation 统计。
|
||||||
|
|
||||||
## Grid 合同
|
## Grid 合同
|
||||||
|
|
||||||
|
|||||||
@ -9,8 +9,8 @@ This document defines the low-level publication contract. It is independent of E
|
|||||||
3. `ebox-style.el`, `ebox-measure.el`, and the layout modules own normalization, measurement, and geometry.
|
3. `ebox-style.el`, `ebox-measure.el`, and the layout modules own normalization, measurement, and geometry.
|
||||||
4. `ebox-incremental.el` owns dirty classification, patch planning, pure declarative commit preparation, and Ebox report semantics.
|
4. `ebox-incremental.el` owns dirty classification, patch planning, pure declarative commit preparation, and Ebox report semantics.
|
||||||
5. `ebox-surface.el` projects retained candidates and joins Ebox runtime-state publication to the TP transaction.
|
5. `ebox-surface.el` projects retained candidates and joins Ebox runtime-state publication to the TP transaction.
|
||||||
6. TP owns generic retained-surface reconciliation, buffer diff execution, revision changes, and rollback for initial mount and declarative full-root commit.
|
6. TP owns generic retained-surface reconciliation, mount/index state, all live buffer diff execution, revision changes, and rollback for initial mount, declarative commit, handle update, viewport/theme update, and scroll update.
|
||||||
7. `ebox-buffer-backend.el` owns Ebox marker/index primitives and the specialized local numeric region, viewport, and scroll publishers that have not yet crossed the local-update cutover.
|
7. `ebox-buffer-backend.el` only builds and reshapes propertized render strings. It does not own live markers or a buffer mutation executor.
|
||||||
|
|
||||||
No layer may infer application state from visible buffer text. No public caller may mutate a published tree in place.
|
No layer may infer application state from visible buffer text. No public caller may mutate a published tree in place.
|
||||||
|
|
||||||
@ -26,14 +26,14 @@ fresh root
|
|||||||
-> produce TP surface plan plus Ebox report base
|
-> produce TP surface plan plus Ebox report base
|
||||||
-> TP prepares and atomically publishes buffer changes
|
-> TP prepares and atomically publishes buffer changes
|
||||||
-> Ebox transaction participant publishes matching runtime indexes/report
|
-> Ebox transaction participant publishes matching runtime indexes/report
|
||||||
-> retire superseded markers and timers after success
|
-> retire superseded Ebox timers and warm the committed scroll state after success
|
||||||
```
|
```
|
||||||
|
|
||||||
The candidate is either fully published or discarded. A failed render, TP write, Ebox participant, or publication callback preserves the previous buffer text, properties, TP revision, Ebox runtime identity, scroll state, and last successful report. If the target buffer is killed during publication, teardown is authoritative and rollback never resurrects it.
|
The candidate is either fully published or discarded. A failed render, TP write, Ebox participant, or publication callback preserves the previous buffer text, properties, TP revision, Ebox runtime identity, scroll state, and last successful report. If the target buffer is killed during publication, teardown is authoritative and rollback never resurrects it.
|
||||||
|
|
||||||
## Patch order
|
## Patch order
|
||||||
|
|
||||||
The planner prefers `paint-patch`, then `span-patch`, then `owner-rerender`, and finally `root-rerender` when geometry or identity makes a smaller operation unsafe. A patch must not silently widen its semantic scope. Declarative full-root reports preserve that Ebox strategy and planned scope while separately recording TP's actual surface operations and revision.
|
The planner prefers `paint-patch`, then `span-patch`, then `owner-rerender`, and finally `root-rerender` when geometry or identity makes a smaller operation unsafe. These names describe Ebox semantic owner scope; TP alone computes and executes the physical text/property diff. A patch must not silently widen its semantic scope. Reports preserve the Ebox strategy and planned scope while separately recording TP's actual surface operations and revision.
|
||||||
|
|
||||||
## Identity and coordinates
|
## Identity and coordinates
|
||||||
|
|
||||||
|
|||||||
@ -9,8 +9,8 @@
|
|||||||
3. `ebox-style.el`、`ebox-measure.el` 与布局模块拥有归一化、测量和几何。
|
3. `ebox-style.el`、`ebox-measure.el` 与布局模块拥有归一化、测量和几何。
|
||||||
4. `ebox-incremental.el` 拥有 dirty 分类、patch 规划、纯声明式 commit 准备和 Ebox 报告语义。
|
4. `ebox-incremental.el` 拥有 dirty 分类、patch 规划、纯声明式 commit 准备和 Ebox 报告语义。
|
||||||
5. `ebox-surface.el` 投影 retained candidate,并让 Ebox runtime-state 发布加入 TP transaction。
|
5. `ebox-surface.el` 投影 retained candidate,并让 Ebox runtime-state 发布加入 TP transaction。
|
||||||
6. TP 拥有首次 mount 与声明式整根 commit 的通用 retained-surface reconciliation、buffer diff 执行、revision 变更和 rollback。
|
6. TP 拥有首次 mount、声明式 commit、handle 更新、viewport/theme 更新和 scroll 更新的通用 retained-surface reconciliation、mount/index 状态、全部 live buffer diff 执行、revision 变更和 rollback。
|
||||||
7. `ebox-buffer-backend.el` 拥有 Ebox marker/index primitive,以及尚未完成局部更新切换的数字 region、viewport 与 scroll 专用 publisher。
|
7. `ebox-buffer-backend.el` 只构造和整形带文本属性的渲染字符串,不拥有 live marker 或 buffer mutation executor。
|
||||||
|
|
||||||
任何层都不能从可见 buffer 文本推断应用状态;公共调用者不能原地修改已经发布的树。
|
任何层都不能从可见 buffer 文本推断应用状态;公共调用者不能原地修改已经发布的树。
|
||||||
|
|
||||||
@ -26,14 +26,14 @@
|
|||||||
-> 生成 TP surface plan 与 Ebox report base
|
-> 生成 TP surface plan 与 Ebox report base
|
||||||
-> TP 准备并原子发布 buffer 变更
|
-> TP 准备并原子发布 buffer 变更
|
||||||
-> Ebox transaction participant 发布匹配的 runtime index/report
|
-> Ebox transaction participant 发布匹配的 runtime index/report
|
||||||
-> 成功后退役旧 marker 与 timer
|
-> 成功后退役 Ebox timer,并预热已提交的 scroll state
|
||||||
```
|
```
|
||||||
|
|
||||||
候选要么完整发布,要么丢弃。render、TP 写入、Ebox participant 或发布回调失败时,必须保留之前的 buffer 文本、属性、TP revision、Ebox runtime identity、滚动状态和最近一次成功报告。如果目标 buffer 在发布期间被 kill,teardown 是最终状态,rollback 绝不能把它复活。
|
候选要么完整发布,要么丢弃。render、TP 写入、Ebox participant 或发布回调失败时,必须保留之前的 buffer 文本、属性、TP revision、Ebox runtime identity、滚动状态和最近一次成功报告。如果目标 buffer 在发布期间被 kill,teardown 是最终状态,rollback 绝不能把它复活。
|
||||||
|
|
||||||
## Patch 顺序
|
## Patch 顺序
|
||||||
|
|
||||||
规划器优先使用 `paint-patch`,再使用 `span-patch`、`owner-rerender`,最后在几何或 identity 使小操作不安全时使用 `root-rerender`。Patch 不得静默扩大语义范围。声明式整根报告保留该 Ebox strategy 与 planned scope,同时单独记录 TP 的实际 surface operation 和 revision。
|
规划器优先使用 `paint-patch`,再使用 `span-patch`、`owner-rerender`,最后在几何或 identity 使小操作不安全时使用 `root-rerender`。这些名称描述的是 Ebox 的语义 owner 范围;只有 TP 负责计算并执行物理文本/属性 diff。Patch 不得静默扩大语义范围。报告保留 Ebox strategy 与 planned scope,同时单独记录 TP 的实际 surface operation 和 revision。
|
||||||
|
|
||||||
## Identity 与坐标
|
## Identity 与坐标
|
||||||
|
|
||||||
|
|||||||
@ -108,7 +108,7 @@ Selectors query the rendered tree and return public match records. They do not o
|
|||||||
(ebox-region-update handle :content "Ready" :color "#166534"))
|
(ebox-region-update handle :content "Ready" :color "#166534"))
|
||||||
```
|
```
|
||||||
|
|
||||||
The same logical id in two buffers resolves to two different handles, so updating one surface cannot accidentally mutate the other. A handle becomes stale when its retained object is removed or its buffer is killed. Prefer `ebox-region-resolve` handles for new multi-buffer code; numeric region ids remain an internal/legacy update form during the local-update cutover.
|
The same logical id in two buffers resolves to two different handles, so updating one surface cannot accidentally mutate the other. A handle becomes stale when its retained object is removed or its buffer is killed. `ebox-region-update` accepts only a live handle; numeric region ids are internal render metadata and are not an update API.
|
||||||
|
|
||||||
## 8. Standalone `.ebox` files
|
## 8. Standalone `.ebox` files
|
||||||
|
|
||||||
|
|||||||
@ -108,7 +108,7 @@ Selector 查询已渲染的树并返回公共匹配记录,但不拥有应用
|
|||||||
(ebox-region-update handle :content "Ready" :color "#166534"))
|
(ebox-region-update handle :content "Ready" :color "#166534"))
|
||||||
```
|
```
|
||||||
|
|
||||||
两个 buffer 中相同的逻辑 id 会解析为两个不同 handle,因此更新一个 surface 不会误改另一个。retained object 被删除或 buffer 被 kill 后,原 handle 会变 stale。新的多 buffer 代码应优先使用 `ebox-region-resolve` handle;在局部更新执行器切换完成前,数字 region id 仍作为内部/旧式更新形式保留。
|
两个 buffer 中相同的逻辑 id 会解析为两个不同 handle,因此更新一个 surface 不会误改另一个。retained object 被删除或 buffer 被 kill 后,原 handle 会变 stale。`ebox-region-update` 只接受 live handle;数字 region id 只是内部渲染元数据,不是更新 API。
|
||||||
|
|
||||||
## 8. 独立 `.ebox` 文件
|
## 8. 独立 `.ebox` 文件
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
3669
ebox-incremental.el
3669
ebox-incremental.el
File diff suppressed because it is too large
Load Diff
@ -64,8 +64,6 @@
|
|||||||
(declare-function ebox-string-lines "ebox" (string))
|
(declare-function ebox-string-lines "ebox" (string))
|
||||||
(declare-function ebox-string-pixel-width "ebox-measure" (string))
|
(declare-function ebox-string-pixel-width "ebox-measure" (string))
|
||||||
(declare-function ebox-display-signature "ebox-measure" ())
|
(declare-function ebox-display-signature "ebox-measure" ())
|
||||||
(declare-function ebox--register-rendered-root-metadata
|
|
||||||
"ebox" (rendered prepared))
|
|
||||||
|
|
||||||
(defvar ebox--flex-content-min-width-table)
|
(defvar ebox--flex-content-min-width-table)
|
||||||
|
|
||||||
@ -180,9 +178,6 @@ module while loading the package."
|
|||||||
(defvar ebox-native-reflow--build-process nil
|
(defvar ebox-native-reflow--build-process nil
|
||||||
"Live asynchronous process started by `ebox-native-build', or nil.")
|
"Live asynchronous process started by `ebox-native-build', or nil.")
|
||||||
|
|
||||||
(defvar ebox-native-reflow--last-owner-proof-error nil
|
|
||||||
"Most recent rejected native owner proof error.")
|
|
||||||
|
|
||||||
(defconst ebox-native-reflow--build-buffer-name "*Ebox Native Build*"
|
(defconst ebox-native-reflow--build-buffer-name "*Ebox Native Build*"
|
||||||
"Compilation buffer used by `ebox-native-build'.")
|
"Compilation buffer used by `ebox-native-build'.")
|
||||||
|
|
||||||
@ -2841,8 +2836,7 @@ typed line-width and property invariants instead of repeating them in Emacs."
|
|||||||
decoded)
|
decoded)
|
||||||
(let ((header (aref decoded 0))
|
(let ((header (aref decoded 0))
|
||||||
(rendered (aref decoded 1))
|
(rendered (aref decoded 1))
|
||||||
(line-widths (aref decoded 2))
|
(line-widths (aref decoded 2)))
|
||||||
(metadata (aref decoded 3)))
|
|
||||||
(unless trusted-native-encoder-p
|
(unless trusted-native-encoder-p
|
||||||
(ebox-native-reflow--validate-literal-properties
|
(ebox-native-reflow--validate-literal-properties
|
||||||
rendered styles (plist-get header :complete)
|
rendered styles (plist-get header :complete)
|
||||||
@ -2855,9 +2849,6 @@ typed line-width and property invariants instead of repeating them in Emacs."
|
|||||||
unless (= (ebox-string-pixel-width line)
|
unless (= (ebox-string-pixel-width line)
|
||||||
(aref line-widths index))
|
(aref line-widths index))
|
||||||
do (error "Native reflow tape line width mismatch"))))
|
do (error "Native reflow tape line width mismatch"))))
|
||||||
(when (and trusted-native-encoder-p metadata
|
|
||||||
(fboundp 'ebox--register-rendered-root-metadata))
|
|
||||||
(ebox--register-rendered-root-metadata rendered metadata))
|
|
||||||
rendered))))
|
rendered))))
|
||||||
|
|
||||||
(defun ebox-native-reflow-take-layout
|
(defun ebox-native-reflow-take-layout
|
||||||
@ -2918,57 +2909,6 @@ template ids are expanded before returning."
|
|||||||
payload (plist-get package :styles) expected t
|
payload (plist-get package :styles) expected t
|
||||||
(plist-get package :property-templates))))))
|
(plist-get package :property-templates))))))
|
||||||
|
|
||||||
(defun ebox-native-reflow-render-owner-proof
|
|
||||||
(node snapshot _dirty context)
|
|
||||||
"Return a native proof render for NODE and CONTEXT, or nil when unsupported."
|
|
||||||
(when (and (not (plist-get context :root-owner-p))
|
|
||||||
(ebox-native-reflow--native-node-supported-p node)
|
|
||||||
(ebox-native-reflow-layout-ready-p)
|
|
||||||
(>= (length (plist-get snapshot :buffer-spans)) 2))
|
|
||||||
(let* ((details-function (plist-get context :details-function))
|
|
||||||
(details
|
|
||||||
(if (functionp details-function)
|
|
||||||
(funcall details-function)
|
|
||||||
context))
|
|
||||||
(stored-viewport-height (plist-get details :viewport-height))
|
|
||||||
(viewport-height
|
|
||||||
(cond
|
|
||||||
((and (numberp stored-viewport-height)
|
|
||||||
(> stored-viewport-height 0))
|
|
||||||
(truncate stored-viewport-height))
|
|
||||||
((not (plist-get details :viewport-height-dependent-p))
|
|
||||||
(max 1 (length (plist-get snapshot :buffer-spans))))))
|
|
||||||
(node-type (and (listp node) (plist-get node :ebox-type)))
|
|
||||||
(exact-slot-width (plist-get details :parent-content-width))
|
|
||||||
(viewport-width-known-p
|
|
||||||
(and (numberp exact-slot-width) (> exact-slot-width 0)))
|
|
||||||
(slot-width
|
|
||||||
(or (and viewport-width-known-p
|
|
||||||
(truncate exact-slot-width))
|
|
||||||
(plist-get details :slot-width-placeholder))))
|
|
||||||
(when (and (plist-get details :side-effects-safe-p)
|
|
||||||
(integerp viewport-height) (> viewport-height 0)
|
|
||||||
(integerp slot-width) (> slot-width 0))
|
|
||||||
(condition-case err
|
|
||||||
(let ((frame
|
|
||||||
(list :key 1
|
|
||||||
:viewport-width slot-width
|
|
||||||
:viewport-width-known viewport-width-known-p
|
|
||||||
:viewport-height viewport-height
|
|
||||||
:runtime-revision
|
|
||||||
(or (plist-get details :runtime-revision) 0)
|
|
||||||
:context-hash 0
|
|
||||||
:complete t
|
|
||||||
:root-metadata nil)))
|
|
||||||
(when (and viewport-width-known-p (eq node-type 'box))
|
|
||||||
(setq frame (plist-put frame :root-width slot-width)))
|
|
||||||
(setq ebox-native-reflow--last-owner-proof-error nil)
|
|
||||||
(ebox-native-reflow-render-proof-sync node frame))
|
|
||||||
(error
|
|
||||||
(setq ebox-native-reflow--last-owner-proof-error
|
|
||||||
(error-message-string err))
|
|
||||||
(signal (car err) (cdr err))))))))
|
|
||||||
|
|
||||||
(defun ebox-native-reflow-submit (session generation frames)
|
(defun ebox-native-reflow-submit (session generation frames)
|
||||||
"Submit FRAMES for SESSION GENERATION without waiting for results."
|
"Submit FRAMES for SESSION GENERATION without waiting for results."
|
||||||
(unless (and (integerp generation) (>= generation 0))
|
(unless (and (integerp generation) (>= generation 0))
|
||||||
@ -3549,9 +3489,6 @@ When FULL-P is non-nil, query the complete restoration job."
|
|||||||
(signal (car err) (cdr err))))))
|
(signal (car err) (cdr err))))))
|
||||||
(unless (or (stringp result) (plist-get result :native-patch))
|
(unless (or (stringp result) (plist-get result :native-patch))
|
||||||
(error "Native reflow result disappeared before materialization"))
|
(error "Native reflow result disappeared before materialization"))
|
||||||
(when (and (stringp result)
|
|
||||||
(fboundp 'ebox--rendered-root-metadata))
|
|
||||||
(ebox--rendered-root-metadata result t))
|
|
||||||
result))
|
result))
|
||||||
|
|
||||||
(defun ebox-native-reflow--materialize-next-ready-frame (preparation)
|
(defun ebox-native-reflow--materialize-next-ready-frame (preparation)
|
||||||
|
|||||||
@ -24,7 +24,6 @@
|
|||||||
(declare-function ebox-incremental-begin-batch "ebox-incremental" (buffer))
|
(declare-function ebox-incremental-begin-batch "ebox-incremental" (buffer))
|
||||||
(declare-function ebox-incremental-flush "ebox-incremental" (buffer))
|
(declare-function ebox-incremental-flush "ebox-incremental" (buffer))
|
||||||
(declare-function ebox-region-update "ebox" (region-id &rest props))
|
(declare-function ebox-region-update "ebox" (region-id &rest props))
|
||||||
(defvar ebox--region-update-buffer-hint)
|
|
||||||
|
|
||||||
(cl-defstruct
|
(cl-defstruct
|
||||||
(ebox-region-handle
|
(ebox-region-handle
|
||||||
@ -549,8 +548,7 @@ tree-query handle fields."
|
|||||||
|
|
||||||
(defun ebox-selector--update-region (handle props)
|
(defun ebox-selector--update-region (handle props)
|
||||||
"Apply PROPS to HANDLE's region through `ebox-region-update'."
|
"Apply PROPS to HANDLE's region through `ebox-region-update'."
|
||||||
(let ((ebox--region-update-buffer-hint (plist-get handle :buffer)))
|
(apply #'ebox-region-update (plist-get handle :region-handle) props))
|
||||||
(apply #'ebox-region-update (plist-get handle :region-id) props)))
|
|
||||||
|
|
||||||
;;;###autoload
|
;;;###autoload
|
||||||
(defun ebox-selector-update-buffer (buffer selector &rest props)
|
(defun ebox-selector-update-buffer (buffer selector &rest props)
|
||||||
|
|||||||
759
ebox-surface.el
759
ebox-surface.el
@ -14,6 +14,7 @@
|
|||||||
(require 'ebox-tree)
|
(require 'ebox-tree)
|
||||||
(require 'ebox-layout)
|
(require 'ebox-layout)
|
||||||
(require 'ebox-incremental)
|
(require 'ebox-incremental)
|
||||||
|
(require 'tp-reactive)
|
||||||
(require 'tp-surface)
|
(require 'tp-surface)
|
||||||
|
|
||||||
(defvar ebox-region-types)
|
(defvar ebox-region-types)
|
||||||
@ -21,8 +22,6 @@
|
|||||||
(defvar ebox--scroll-global-state)
|
(defvar ebox--scroll-global-state)
|
||||||
(defvar ebox--scroll-idle-prefetch-timers)
|
(defvar ebox--scroll-idle-prefetch-timers)
|
||||||
(defvar ebox--smooth-scroll-state-table)
|
(defvar ebox--smooth-scroll-state-table)
|
||||||
(defvar ebox--box-extents)
|
|
||||||
(defvar ebox--box-extent-template)
|
|
||||||
(defvar ebox--buffer-render-state-table)
|
(defvar ebox--buffer-render-state-table)
|
||||||
(defvar ebox--viewport-dependent-node-ids-cache)
|
(defvar ebox--viewport-dependent-node-ids-cache)
|
||||||
(defvar ebox--viewport-dependent-subtree-cache)
|
(defvar ebox--viewport-dependent-subtree-cache)
|
||||||
@ -31,39 +30,25 @@
|
|||||||
(defvar ebox--render-cache-table)
|
(defvar ebox--render-cache-table)
|
||||||
(defvar ebox--render-cache-signature-cache)
|
(defvar ebox--render-cache-signature-cache)
|
||||||
(defvar ebox--flex-content-min-width-table)
|
(defvar ebox--flex-content-min-width-table)
|
||||||
|
(defvar ebox--scroll-window-initial-lookahead-lines-override)
|
||||||
|
(defvar ebox-viewport-width)
|
||||||
|
(defvar ebox-viewport-height)
|
||||||
|
|
||||||
(declare-function ebox--render-layout "ebox-layout" (node))
|
(declare-function ebox--render-layout "ebox-layout" (node))
|
||||||
(declare-function ebox-buffer--build-region-role-span-template
|
|
||||||
"ebox-buffer-backend" (string))
|
|
||||||
(declare-function ebox-buffer-materialize-region-role-spans
|
|
||||||
"ebox-buffer-backend" (buffer))
|
|
||||||
(declare-function ebox--build-box-extent-template "ebox" (rendered))
|
|
||||||
(declare-function ebox--build-scroll-content-span-template "ebox" (string))
|
|
||||||
(declare-function ebox--set-box-extents "ebox" (region-id start end))
|
|
||||||
(declare-function ebox--clear-buffer-extents
|
|
||||||
"ebox" (&optional buffer preserve-template))
|
|
||||||
(declare-function ebox--clear-box-extents-for-region-ids
|
|
||||||
"ebox" (region-ids))
|
|
||||||
(declare-function ebox--install-box-extent-template
|
|
||||||
"ebox" (buffer template start))
|
|
||||||
(declare-function ebox--install-buffer-scroll-content-span-template
|
|
||||||
"ebox" (buffer template start &optional defer-window-region-set))
|
|
||||||
(declare-function ebox-incremental--hash-snapshot
|
(declare-function ebox-incremental--hash-snapshot
|
||||||
"ebox-incremental" (table keys))
|
"ebox-incremental" (table keys))
|
||||||
(declare-function ebox-incremental--restore-hash-snapshot
|
(declare-function ebox-incremental--restore-hash-snapshot
|
||||||
"ebox-incremental" (table entries))
|
"ebox-incremental" (table entries))
|
||||||
(declare-function ebox-incremental--replace-hash-entries
|
(declare-function ebox-incremental--replace-hash-entries
|
||||||
"ebox-incremental" (target keys source))
|
"ebox-incremental" (target keys source))
|
||||||
(declare-function ebox-incremental--detach-scroll-state-table-markers
|
|
||||||
"ebox-incremental" (table))
|
|
||||||
(declare-function ebox--runtime-region-id-conflict
|
(declare-function ebox--runtime-region-id-conflict
|
||||||
"ebox-incremental" (region-id-set target-buffer))
|
"ebox-incremental" (region-id-set target-buffer))
|
||||||
(declare-function ebox--viewport-dependent-node-id-axes
|
(declare-function ebox--viewport-dependent-node-id-axes
|
||||||
"ebox-incremental" (node))
|
"ebox-incremental" (node))
|
||||||
(declare-function ebox-incremental--detach-region-role-span-table
|
(declare-function ebox--update-report
|
||||||
"ebox-incremental" (table))
|
"ebox-incremental" (region-id strategy &rest props))
|
||||||
(declare-function ebox-incremental--finalize-declarative-scroll-publication
|
(declare-function ebox-incremental--finalize-declarative-scroll-publication
|
||||||
"ebox-incremental" (scroll-keys))
|
"ebox-incremental" (scroll-keys &optional prefetch-delay))
|
||||||
(declare-function ebox--scroll-schedule-idle-prefetch
|
(declare-function ebox--scroll-schedule-idle-prefetch
|
||||||
"ebox" (region-id &optional delay))
|
"ebox" (region-id &optional delay))
|
||||||
(declare-function ebox--scroll-clear-state "ebox" (region-id))
|
(declare-function ebox--scroll-clear-state "ebox" (region-id))
|
||||||
@ -72,6 +57,113 @@
|
|||||||
(defvar-local ebox-surface--buffer-surface nil
|
(defvar-local ebox-surface--buffer-surface nil
|
||||||
"Live TP content surface mounted for the current Ebox buffer.")
|
"Live TP content surface mounted for the current Ebox buffer.")
|
||||||
|
|
||||||
|
(cl-defstruct (ebox-surface--signals
|
||||||
|
(:constructor ebox-surface--make-signals))
|
||||||
|
"TP signals carrying one mounted Ebox surface's host context."
|
||||||
|
buffer viewport-width viewport-height display scroll)
|
||||||
|
|
||||||
|
(defvar-local ebox-surface--context-signals nil
|
||||||
|
"Buffer-scoped TP signals consumed by the mounted Ebox producer.")
|
||||||
|
|
||||||
|
(defun ebox-surface--signals-live-p (signals)
|
||||||
|
"Return non-nil when every signal in SIGNALS is live."
|
||||||
|
(and (ebox-surface--signals-p signals)
|
||||||
|
(buffer-live-p (ebox-surface--signals-buffer signals))
|
||||||
|
(cl-every
|
||||||
|
#'tp-signal-live-p
|
||||||
|
(list (ebox-surface--signals-viewport-width signals)
|
||||||
|
(ebox-surface--signals-viewport-height signals)
|
||||||
|
(ebox-surface--signals-display signals)
|
||||||
|
(ebox-surface--signals-scroll signals)))))
|
||||||
|
|
||||||
|
(defun ebox-surface--dispose-signals (signals)
|
||||||
|
"Dispose live TP SIGNALS owned by an abandoned Ebox mount."
|
||||||
|
(when (ebox-surface--signals-p signals)
|
||||||
|
(dolist (signal
|
||||||
|
(list (ebox-surface--signals-viewport-width signals)
|
||||||
|
(ebox-surface--signals-viewport-height signals)
|
||||||
|
(ebox-surface--signals-display signals)
|
||||||
|
(ebox-surface--signals-scroll signals)))
|
||||||
|
(when (tp-signal-live-p signal)
|
||||||
|
(tp-signal-dispose signal)))
|
||||||
|
(when-let ((buffer (ebox-surface--signals-buffer signals)))
|
||||||
|
(when (buffer-live-p buffer)
|
||||||
|
(with-current-buffer buffer
|
||||||
|
(when (eq ebox-surface--context-signals signals)
|
||||||
|
(setq-local ebox-surface--context-signals nil)))))))
|
||||||
|
|
||||||
|
(defun ebox-surface--scroll-offsets (table)
|
||||||
|
"Return immutable sorted scroll offsets copied from TABLE."
|
||||||
|
(let (offsets)
|
||||||
|
(when (hash-table-p table)
|
||||||
|
(maphash
|
||||||
|
(lambda (region-id state)
|
||||||
|
(push (cons region-id (or (plist-get state :scroll-offset) 0))
|
||||||
|
offsets))
|
||||||
|
table))
|
||||||
|
(sort offsets (lambda (left right) (< (car left) (car right))))))
|
||||||
|
|
||||||
|
(defun ebox-surface--context-values
|
||||||
|
(buffer old-state state-overrides)
|
||||||
|
"Return BUFFER context values after OLD-STATE and STATE-OVERRIDES."
|
||||||
|
(cl-labels
|
||||||
|
((value (key fallback)
|
||||||
|
(if (plist-member state-overrides key)
|
||||||
|
(plist-get state-overrides key)
|
||||||
|
(if (plist-member old-state key)
|
||||||
|
(plist-get old-state key)
|
||||||
|
fallback))))
|
||||||
|
(let ((scroll-table
|
||||||
|
(value :scroll-state-table
|
||||||
|
(plist-get old-state :scroll-state-table))))
|
||||||
|
(list :viewport-width
|
||||||
|
(value :viewport-width ebox-viewport-width)
|
||||||
|
:viewport-height
|
||||||
|
(value :viewport-height ebox-viewport-height)
|
||||||
|
:display-signature
|
||||||
|
(value :display-signature
|
||||||
|
(with-current-buffer buffer
|
||||||
|
(ebox--current-display-signature)))
|
||||||
|
:scroll-offsets
|
||||||
|
(ebox-surface--scroll-offsets scroll-table)))))
|
||||||
|
|
||||||
|
(defun ebox-surface--ensure-signals (buffer values)
|
||||||
|
"Return BUFFER's context signals for VALUES and whether they were created."
|
||||||
|
(let ((signals
|
||||||
|
(with-current-buffer buffer ebox-surface--context-signals)))
|
||||||
|
(if (ebox-surface--signals-live-p signals)
|
||||||
|
(cons signals nil)
|
||||||
|
(when signals (ebox-surface--dispose-signals signals))
|
||||||
|
(setq signals
|
||||||
|
(ebox-surface--make-signals
|
||||||
|
:buffer buffer
|
||||||
|
:viewport-width
|
||||||
|
(tp-signal-create (plist-get values :viewport-width)
|
||||||
|
:scope buffer)
|
||||||
|
:viewport-height
|
||||||
|
(tp-signal-create (plist-get values :viewport-height)
|
||||||
|
:scope buffer)
|
||||||
|
:display
|
||||||
|
(tp-signal-create (plist-get values :display-signature)
|
||||||
|
:scope buffer)
|
||||||
|
:scroll
|
||||||
|
(tp-signal-create (plist-get values :scroll-offsets)
|
||||||
|
:scope buffer)))
|
||||||
|
(with-current-buffer buffer
|
||||||
|
(setq-local ebox-surface--context-signals signals))
|
||||||
|
(cons signals t))))
|
||||||
|
|
||||||
|
(defun ebox-surface--stage-signal-values (signals values)
|
||||||
|
"Stage VALUES into TP context SIGNALS in the active transaction."
|
||||||
|
(tp-signal-set (ebox-surface--signals-viewport-width signals)
|
||||||
|
(plist-get values :viewport-width))
|
||||||
|
(tp-signal-set (ebox-surface--signals-viewport-height signals)
|
||||||
|
(plist-get values :viewport-height))
|
||||||
|
(tp-signal-set (ebox-surface--signals-display signals)
|
||||||
|
(plist-get values :display-signature))
|
||||||
|
(tp-signal-set (ebox-surface--signals-scroll signals)
|
||||||
|
(plist-get values :scroll-offsets)))
|
||||||
|
|
||||||
(defun ebox-surface--live-buffer-surface (buffer)
|
(defun ebox-surface--live-buffer-surface (buffer)
|
||||||
"Return BUFFER's live Ebox TP surface, or nil."
|
"Return BUFFER's live Ebox TP surface, or nil."
|
||||||
(when (buffer-live-p buffer)
|
(when (buffer-live-p buffer)
|
||||||
@ -79,33 +171,49 @@
|
|||||||
(with-current-buffer buffer ebox-surface--buffer-surface)))
|
(with-current-buffer buffer ebox-surface--buffer-surface)))
|
||||||
(and (tp-surface-live-p surface) surface))))
|
(and (tp-surface-live-p surface) surface))))
|
||||||
|
|
||||||
(defun ebox-surface--buffer-extent-snapshot (buffer)
|
(defun ebox-surface-buffer-mounted-p (buffer)
|
||||||
"Return numeric live box extents owned by BUFFER."
|
"Return non-nil when BUFFER has a live Ebox TP surface."
|
||||||
(let (snapshot)
|
(not (null (ebox-surface--live-buffer-surface buffer))))
|
||||||
(maphash
|
|
||||||
(lambda (region-id extents)
|
|
||||||
(when (eq (marker-buffer (car extents)) buffer)
|
|
||||||
(push (list region-id
|
|
||||||
(marker-position (car extents))
|
|
||||||
(marker-position (cdr extents)))
|
|
||||||
snapshot)))
|
|
||||||
ebox--box-extents)
|
|
||||||
snapshot))
|
|
||||||
|
|
||||||
(defun ebox-surface--restore-buffer-extents (buffer snapshot template)
|
(defun ebox-surface-region-mounts (buffer region-id &optional roles)
|
||||||
"Restore BUFFER box extents from SNAPSHOT and numeric TEMPLATE."
|
"Return REGION-ID's TP-owned output mounts in BUFFER.
|
||||||
(ebox--clear-buffer-extents buffer)
|
When ROLES is non-nil, keep only direct mounts owning a listed Ebox role.
|
||||||
(when (buffer-live-p buffer)
|
Without ROLES, include descendant output attached to the region's object.
|
||||||
(with-current-buffer buffer
|
The returned ranges are numeric snapshots; TP retains marker ownership."
|
||||||
(setq-local ebox--box-extent-template template)
|
(when-let* ((surface (ebox-surface--live-buffer-surface buffer))
|
||||||
(dolist (entry snapshot)
|
(state (tp-surface-client-state surface))
|
||||||
(ebox--set-box-extents (nth 0 entry) (nth 1 entry) (nth 2 entry))))))
|
(table (plist-get state :region-surface-object-table))
|
||||||
|
(object (gethash region-id table)))
|
||||||
|
(cl-remove-if-not
|
||||||
|
(lambda (mount)
|
||||||
|
(let ((tags (plist-get mount :tags)))
|
||||||
|
(if roles
|
||||||
|
(and (equal (plist-get tags :ebox/region-id) region-id)
|
||||||
|
(cl-intersection roles (plist-get tags :ebox/roles)))
|
||||||
|
(or (equal (plist-get tags :ebox/region-id) region-id)
|
||||||
|
(plist-get tags :ebox/descendant-output)))))
|
||||||
|
(tp-object-mounts object))))
|
||||||
|
|
||||||
|
(defun ebox-surface-region-bounds (buffer region-id &optional roles)
|
||||||
|
"Return numeric bounds for REGION-ID's TP mounts in BUFFER.
|
||||||
|
ROLES has the same filtering meaning as in `ebox-surface-region-mounts'."
|
||||||
|
(when-let ((mounts (ebox-surface-region-mounts buffer region-id roles)))
|
||||||
|
(cons (apply #'min (mapcar (lambda (mount) (plist-get mount :start)) mounts))
|
||||||
|
(apply #'max (mapcar (lambda (mount) (plist-get mount :end)) mounts)))))
|
||||||
|
|
||||||
(defun ebox-surface--runtime-keys (state key)
|
(defun ebox-surface--runtime-keys (state key)
|
||||||
"Return hash keys stored under KEY in runtime STATE."
|
"Return hash keys stored under KEY in runtime STATE."
|
||||||
(when-let ((table (plist-get state key)))
|
(when-let ((table (plist-get state key)))
|
||||||
(ebox-surface--hash-keys table)))
|
(ebox-surface--hash-keys table)))
|
||||||
|
|
||||||
|
(defun ebox-surface--bind-scroll-states-to-buffer (state buffer)
|
||||||
|
"Bind every semantic scroll state in STATE to its owning BUFFER."
|
||||||
|
(when-let ((table (plist-get state :scroll-state-table)))
|
||||||
|
(maphash
|
||||||
|
(lambda (region-id scroll-state)
|
||||||
|
(puthash region-id (plist-put scroll-state :buffer buffer) table))
|
||||||
|
table)))
|
||||||
|
|
||||||
(defun ebox-surface--commit-report (surface state report-base)
|
(defun ebox-surface--commit-report (surface state report-base)
|
||||||
"Return Ebox's compact report for SURFACE, STATE, and REPORT-BASE."
|
"Return Ebox's compact report for SURFACE, STATE, and REPORT-BASE."
|
||||||
(let* ((tp-report (tp-surface-report surface))
|
(let* ((tp-report (tp-surface-report surface))
|
||||||
@ -114,6 +222,8 @@
|
|||||||
(or (plist-get tp-report :property-operations) 0))
|
(or (plist-get tp-report :property-operations) 0))
|
||||||
(patch-count (+ text-operations property-operations))
|
(patch-count (+ text-operations property-operations))
|
||||||
(report (copy-sequence report-base))
|
(report (copy-sequence report-base))
|
||||||
|
(scoped-p (and (not (plist-get tp-report :full-root))
|
||||||
|
(> (or (plist-get tp-report :scope-count) 0) 0)))
|
||||||
(planned-publication-scope (plist-get report :publication-scope)))
|
(planned-publication-scope (plist-get report :publication-scope)))
|
||||||
(dolist (entry
|
(dolist (entry
|
||||||
`((:runtime-published . t)
|
`((:runtime-published . t)
|
||||||
@ -121,7 +231,12 @@
|
|||||||
(:surface-revision . ,(tp-surface-revision surface))
|
(:surface-revision . ,(tp-surface-revision surface))
|
||||||
(:publication-scope . tp-surface)
|
(:publication-scope . tp-surface)
|
||||||
(:planned-publication-scope . ,planned-publication-scope)
|
(:planned-publication-scope . ,planned-publication-scope)
|
||||||
(:tp-render-scope . surface-plan)
|
(:tp-render-scope . ,(if scoped-p 'objects 'surface-plan))
|
||||||
|
(:tp-full-root . ,(plist-get tp-report :full-root))
|
||||||
|
(:tp-scope-count . ,(plist-get tp-report :scope-count))
|
||||||
|
(:tp-scope-range-count
|
||||||
|
. ,(plist-get tp-report :scope-range-count))
|
||||||
|
(:tp-scope-fallback . ,(plist-get tp-report :scope-fallback))
|
||||||
(:tp-operation-count . ,patch-count)
|
(:tp-operation-count . ,patch-count)
|
||||||
(:tp-transaction-id . ,(plist-get tp-report :transaction-id))
|
(:tp-transaction-id . ,(plist-get tp-report :transaction-id))
|
||||||
(:tp-text-operations . ,text-operations)
|
(:tp-text-operations . ,text-operations)
|
||||||
@ -159,8 +274,8 @@
|
|||||||
"Register BUFFER runtime publication for SURFACE after OLD-STATE.
|
"Register BUFFER runtime publication for SURFACE after OLD-STATE.
|
||||||
REPORT-BASE requests an Ebox commit report. AFTER-PUBLICATION, when non-nil,
|
REPORT-BASE requests an Ebox commit report. AFTER-PUBLICATION, when non-nil,
|
||||||
runs after TP and Ebox point at the same candidate generation."
|
runs after TP and Ebox point at the same candidate generation."
|
||||||
(let (new-state region-snapshot scroll-snapshot extent-snapshot
|
(let (new-state region-snapshot scroll-snapshot
|
||||||
old-template old-surface old-mirror region-keys scroll-keys)
|
old-surface old-mirror region-keys scroll-keys)
|
||||||
(tp-transaction-participate
|
(tp-transaction-participate
|
||||||
(list 'ebox/runtime buffer)
|
(list 'ebox/runtime buffer)
|
||||||
(lambda ()
|
(lambda ()
|
||||||
@ -168,8 +283,6 @@ runs after TP and Ebox point at the same candidate generation."
|
|||||||
old-surface
|
old-surface
|
||||||
(with-current-buffer buffer ebox-surface--buffer-surface)
|
(with-current-buffer buffer ebox-surface--buffer-surface)
|
||||||
old-mirror (gethash buffer ebox--buffer-render-state-table)
|
old-mirror (gethash buffer ebox--buffer-render-state-table)
|
||||||
old-template (with-current-buffer buffer ebox--box-extent-template)
|
|
||||||
extent-snapshot (ebox-surface--buffer-extent-snapshot buffer)
|
|
||||||
region-keys
|
region-keys
|
||||||
(delete-dups
|
(delete-dups
|
||||||
(append (ebox-surface--runtime-keys old-state :region-id-set)
|
(append (ebox-surface--runtime-keys old-state :region-id-set)
|
||||||
@ -188,6 +301,7 @@ runs after TP and Ebox point at the same candidate generation."
|
|||||||
(plist-put new-state :surface surface)
|
(plist-put new-state :surface surface)
|
||||||
(plist-put new-state :runtime-revision
|
(plist-put new-state :runtime-revision
|
||||||
(1- (tp-surface-revision surface)))
|
(1- (tp-surface-revision surface)))
|
||||||
|
(ebox-surface--bind-scroll-states-to-buffer new-state buffer)
|
||||||
(when report-base
|
(when report-base
|
||||||
(plist-put new-state :last-update-report
|
(plist-put new-state :last-update-report
|
||||||
(ebox-surface--commit-report
|
(ebox-surface--commit-report
|
||||||
@ -203,21 +317,12 @@ runs after TP and Ebox point at the same candidate generation."
|
|||||||
(ebox-incremental--replace-hash-entries
|
(ebox-incremental--replace-hash-entries
|
||||||
ebox--scroll-global-state scroll-keys
|
ebox--scroll-global-state scroll-keys
|
||||||
(plist-get new-state :scroll-state-table))
|
(plist-get new-state :scroll-state-table))
|
||||||
(ebox--clear-buffer-extents buffer)
|
|
||||||
(when new-state
|
|
||||||
(ebox--install-box-extent-template
|
|
||||||
buffer (plist-get new-state :box-extent-template) 1)
|
|
||||||
(ebox--install-buffer-scroll-content-span-template
|
|
||||||
buffer (plist-get new-state :scroll-content-span-template) 1)
|
|
||||||
(ebox-buffer-materialize-region-role-spans buffer))
|
|
||||||
(when after-publication
|
(when after-publication
|
||||||
(funcall after-publication
|
(funcall after-publication
|
||||||
(plist-get new-state :last-update-report)))
|
(plist-get new-state :last-update-report)))
|
||||||
(unless (buffer-live-p buffer)
|
(unless (buffer-live-p buffer)
|
||||||
(error "Ebox declarative target died during publication")))
|
(error "Ebox declarative target died during publication")))
|
||||||
(lambda ()
|
(lambda ()
|
||||||
(when-let ((table (plist-get new-state :scroll-state-table)))
|
|
||||||
(ebox-incremental--detach-scroll-state-table-markers table))
|
|
||||||
(if (buffer-live-p buffer)
|
(if (buffer-live-p buffer)
|
||||||
(progn
|
(progn
|
||||||
(when region-snapshot
|
(when region-snapshot
|
||||||
@ -228,8 +333,6 @@ runs after TP and Ebox point at the same candidate generation."
|
|||||||
ebox--scroll-global-state scroll-snapshot))
|
ebox--scroll-global-state scroll-snapshot))
|
||||||
(with-current-buffer buffer
|
(with-current-buffer buffer
|
||||||
(setq-local ebox-surface--buffer-surface old-surface))
|
(setq-local ebox-surface--buffer-surface old-surface))
|
||||||
(ebox-surface--restore-buffer-extents
|
|
||||||
buffer extent-snapshot old-template)
|
|
||||||
(if old-mirror
|
(if old-mirror
|
||||||
(puthash buffer old-mirror ebox--buffer-render-state-table)
|
(puthash buffer old-mirror ebox--buffer-render-state-table)
|
||||||
(remhash buffer ebox--buffer-render-state-table)))
|
(remhash buffer ebox--buffer-render-state-table)))
|
||||||
@ -238,20 +341,7 @@ runs after TP and Ebox point at the same candidate generation."
|
|||||||
(remhash region-id ebox--region-box-table))
|
(remhash region-id ebox--region-box-table))
|
||||||
(dolist (region-id scroll-keys)
|
(dolist (region-id scroll-keys)
|
||||||
(ebox--scroll-clear-state region-id)
|
(ebox--scroll-clear-state region-id)
|
||||||
(ebox--smooth-scroll-stop region-id))
|
(ebox--smooth-scroll-stop region-id)))))))
|
||||||
(ebox--clear-box-extents-for-region-ids region-keys))))))
|
|
||||||
|
|
||||||
(defun ebox-surface--retire-state (old-state new-state)
|
|
||||||
"Retire marker and timer resources in OLD-STATE after NEW-STATE commits."
|
|
||||||
(when old-state
|
|
||||||
(let ((old-role-table (plist-get old-state :region-role-span-table))
|
|
||||||
(new-role-table (plist-get new-state :region-role-span-table)))
|
|
||||||
(unless (eq old-role-table new-role-table)
|
|
||||||
(ebox-incremental--detach-region-role-span-table old-role-table)))
|
|
||||||
(let ((old-scroll-table (plist-get old-state :scroll-state-table))
|
|
||||||
(new-scroll-table (plist-get new-state :scroll-state-table)))
|
|
||||||
(unless (eq old-scroll-table new-scroll-table)
|
|
||||||
(ebox-incremental--detach-scroll-state-table-markers old-scroll-table)))))
|
|
||||||
|
|
||||||
(defun ebox-surface-mount-buffer
|
(defun ebox-surface-mount-buffer
|
||||||
(buffer source &optional report-base after-publication
|
(buffer source &optional report-base after-publication
|
||||||
@ -262,6 +352,12 @@ the rollback-capable transaction. PRESERVE-IDENTITIES-P retains identities
|
|||||||
already present in a logical candidate. STATE-OVERRIDES augments its runtime."
|
already present in a logical candidate. STATE-OVERRIDES augments its runtime."
|
||||||
(let* ((surface (ebox-surface--live-buffer-surface buffer))
|
(let* ((surface (ebox-surface--live-buffer-surface buffer))
|
||||||
(old-state (and surface (tp-surface-client-state surface)))
|
(old-state (and surface (tp-surface-client-state surface)))
|
||||||
|
(context-values
|
||||||
|
(ebox-surface--context-values buffer old-state state-overrides))
|
||||||
|
(signals-result
|
||||||
|
(ebox-surface--ensure-signals buffer context-values))
|
||||||
|
(signals (car signals-result))
|
||||||
|
(signals-created-p (cdr signals-result))
|
||||||
(preserve-identities-p
|
(preserve-identities-p
|
||||||
(or preserve-identities-p
|
(or preserve-identities-p
|
||||||
(and (null old-state)
|
(and (null old-state)
|
||||||
@ -269,8 +365,12 @@ already present in a logical candidate. STATE-OVERRIDES augments its runtime."
|
|||||||
source buffer))))
|
source buffer))))
|
||||||
(producer
|
(producer
|
||||||
(ebox-surface-producer
|
(ebox-surface-producer
|
||||||
source old-state preserve-identities-p state-overrides)))
|
source old-state preserve-identities-p state-overrides signals))
|
||||||
|
success)
|
||||||
|
(unwind-protect
|
||||||
|
(progn
|
||||||
(tp-with-transaction
|
(tp-with-transaction
|
||||||
|
(ebox-surface--stage-signal-values signals context-values)
|
||||||
(if surface
|
(if surface
|
||||||
(tp-surface-update surface producer)
|
(tp-surface-update surface producer)
|
||||||
(setq surface
|
(setq surface
|
||||||
@ -279,14 +379,226 @@ already present in a logical candidate. STATE-OVERRIDES augments its runtime."
|
|||||||
'(:capability content :inhibit-read-only t))))
|
'(:capability content :inhibit-read-only t))))
|
||||||
(ebox-surface--publish-runtime-state
|
(ebox-surface--publish-runtime-state
|
||||||
buffer surface old-state report-base after-publication))
|
buffer surface old-state report-base after-publication))
|
||||||
|
(setq success t)
|
||||||
(let* ((new-state (tp-surface-client-state surface))
|
(let* ((new-state (tp-surface-client-state surface))
|
||||||
(scroll-keys
|
(scroll-keys
|
||||||
(delete-dups
|
(delete-dups
|
||||||
(append (copy-sequence (plist-get old-state :scroll-region-ids))
|
(append
|
||||||
(copy-sequence (plist-get new-state :scroll-region-ids))))))
|
(copy-sequence (plist-get old-state :scroll-region-ids))
|
||||||
(ebox-surface--retire-state old-state new-state)
|
(copy-sequence
|
||||||
(ebox-incremental--finalize-declarative-scroll-publication scroll-keys))
|
(plist-get new-state :scroll-region-ids))))))
|
||||||
surface))
|
(ebox-incremental--finalize-declarative-scroll-publication
|
||||||
|
scroll-keys))
|
||||||
|
surface)
|
||||||
|
(when (and signals-created-p (not success))
|
||||||
|
(ebox-surface--dispose-signals signals)))))
|
||||||
|
|
||||||
|
(defun ebox-surface--viewport-report (state width height axes strategy)
|
||||||
|
"Return a viewport report for STATE, WIDTH, HEIGHT, AXES, and STRATEGY."
|
||||||
|
(let ((root (plist-get state :root-node)))
|
||||||
|
(ebox--update-report
|
||||||
|
nil strategy
|
||||||
|
:constraint-source 'viewport
|
||||||
|
:constraint-owner-id 'viewport
|
||||||
|
:constraint-owner-type 'viewport
|
||||||
|
:constraint-root-node-id (plist-get root :node-id)
|
||||||
|
:viewport-axes axes
|
||||||
|
:target-viewport-width width
|
||||||
|
:target-viewport-height height
|
||||||
|
:dirty-kinds (unless (eq axes 'none) '(geometry))
|
||||||
|
:dirty-count (if (eq axes 'none) 0 1)
|
||||||
|
:patch-count (if (eq axes 'none) 0 1)
|
||||||
|
:patch-ops (unless (eq axes 'none) '(root-rerender))
|
||||||
|
:owner-ids (unless (eq axes 'none)
|
||||||
|
(list (plist-get root :node-id)))
|
||||||
|
:root-rerender (not (eq axes 'none)))))
|
||||||
|
|
||||||
|
(defun ebox-surface--publish-context-no-op
|
||||||
|
(buffer surface state values state-overrides report)
|
||||||
|
"Commit VALUES and no-op STATE-OVERRIDES into STATE for BUFFER and SURFACE."
|
||||||
|
(let ((signals
|
||||||
|
(with-current-buffer buffer ebox-surface--context-signals))
|
||||||
|
(old-width (plist-get state :viewport-width))
|
||||||
|
(old-height (plist-get state :viewport-height))
|
||||||
|
(old-display (plist-get state :display-signature))
|
||||||
|
(old-report (plist-get state :last-update-report)))
|
||||||
|
(unless (ebox-surface--signals-live-p signals)
|
||||||
|
(error "Ebox mounted surface has no live context signals"))
|
||||||
|
(tp-with-transaction
|
||||||
|
(ebox-surface--stage-signal-values signals values)
|
||||||
|
(tp-transaction-participate
|
||||||
|
(list 'ebox/context-no-op buffer)
|
||||||
|
(lambda ()
|
||||||
|
(unless (eq state (tp-surface-client-state surface))
|
||||||
|
(error "Ebox no-op context unexpectedly published a surface"))
|
||||||
|
(plist-put state :viewport-width
|
||||||
|
(plist-get state-overrides :viewport-width))
|
||||||
|
(plist-put state :viewport-height
|
||||||
|
(plist-get state-overrides :viewport-height))
|
||||||
|
(plist-put state :display-signature
|
||||||
|
(plist-get state-overrides :display-signature))
|
||||||
|
(plist-put state :last-update-report report))
|
||||||
|
(lambda ()
|
||||||
|
(plist-put state :viewport-width old-width)
|
||||||
|
(plist-put state :viewport-height old-height)
|
||||||
|
(plist-put state :display-signature old-display)
|
||||||
|
(plist-put state :last-update-report old-report))))
|
||||||
|
report))
|
||||||
|
|
||||||
|
(defun ebox-surface-update-buffer-viewport (buffer width &optional height)
|
||||||
|
"Publish BUFFER for viewport WIDTH and optional HEIGHT through TP."
|
||||||
|
(let* ((surface (ebox-surface--live-buffer-surface buffer))
|
||||||
|
(state (and surface (tp-surface-client-state surface))))
|
||||||
|
(unless state
|
||||||
|
(error "Ebox viewport update requires a mounted TP surface"))
|
||||||
|
(let* ((old-width (plist-get state :viewport-width))
|
||||||
|
(old-height (plist-get state :viewport-height))
|
||||||
|
(target-height (or height old-height))
|
||||||
|
(display-signature
|
||||||
|
(with-current-buffer buffer
|
||||||
|
(ebox--current-display-signature)))
|
||||||
|
(display-changed
|
||||||
|
(not (equal display-signature
|
||||||
|
(plist-get state :display-signature))))
|
||||||
|
(width-changed (not (equal width old-width)))
|
||||||
|
(height-changed (not (equal target-height old-height)))
|
||||||
|
(axes (cond ((and width-changed height-changed) 'both)
|
||||||
|
(width-changed 'width)
|
||||||
|
(height-changed 'height)
|
||||||
|
(t 'none))))
|
||||||
|
(if (and (eq axes 'none) (not display-changed))
|
||||||
|
(let ((report
|
||||||
|
(ebox-surface--viewport-report
|
||||||
|
state width target-height axes 'no-op)))
|
||||||
|
(plist-put state :last-update-report report)
|
||||||
|
report)
|
||||||
|
(ebox-incremental--notify-before-runtime-mutation buffer 'viewport)
|
||||||
|
(unless (eq state (tp-surface-client-state surface))
|
||||||
|
(error "Ebox runtime changed during viewport update notification"))
|
||||||
|
(let ((commit-input
|
||||||
|
(ebox-incremental-prepare-viewport-commit
|
||||||
|
buffer width target-height axes display-signature)))
|
||||||
|
(if (plist-get commit-input :no-op)
|
||||||
|
(let* ((state-overrides
|
||||||
|
(plist-get commit-input :state-overrides))
|
||||||
|
(values
|
||||||
|
(ebox-surface--context-values
|
||||||
|
buffer state state-overrides)))
|
||||||
|
(ebox-surface--publish-context-no-op
|
||||||
|
buffer surface state values state-overrides
|
||||||
|
(plist-get commit-input :report-base)))
|
||||||
|
(ebox-surface-update-buffer-scoped
|
||||||
|
buffer
|
||||||
|
(plist-get commit-input :root)
|
||||||
|
(plist-get commit-input :scope-node-ids)
|
||||||
|
(plist-get commit-input :report-base)
|
||||||
|
(plist-get commit-input :state-overrides)
|
||||||
|
nil nil 0)
|
||||||
|
(plist-get (tp-surface-client-state surface)
|
||||||
|
:last-update-report)))))))
|
||||||
|
|
||||||
|
(defun ebox-surface--projection-start (context root)
|
||||||
|
"Create retained TP identities for ROOT in CONTEXT."
|
||||||
|
(let* ((surface-root
|
||||||
|
(tp-object-ensure context nil ebox-surface--root-key 'ebox/surface))
|
||||||
|
(node-root
|
||||||
|
(tp-object-ensure context surface-root
|
||||||
|
ebox-surface--nodes-key 'ebox/nodes))
|
||||||
|
(objects-by-node (make-hash-table :test 'eq)))
|
||||||
|
(tp-object-retain context node-root)
|
||||||
|
(ebox-surface--ensure-node-tree context node-root root objects-by-node)
|
||||||
|
(cons surface-root objects-by-node)))
|
||||||
|
|
||||||
|
(defun ebox-surface--projection-result
|
||||||
|
(context projection state rendered)
|
||||||
|
"Complete CONTEXT PROJECTION for STATE from RENDERED output."
|
||||||
|
(let* ((surface-root (car projection))
|
||||||
|
(objects-by-node (cdr projection))
|
||||||
|
(node-objects (ebox-surface--node-object-table objects-by-node))
|
||||||
|
(region-objects
|
||||||
|
(ebox-surface--region-object-table state node-objects)))
|
||||||
|
(plist-put state :surface-node-object-table node-objects)
|
||||||
|
(plist-put state :region-surface-object-table region-objects)
|
||||||
|
(plist-put state :surface-object-region-table
|
||||||
|
(ebox-surface--object-region-table region-objects))
|
||||||
|
(plist-put state :logical-id-region-table
|
||||||
|
(ebox-surface--logical-id-region-table state region-objects))
|
||||||
|
(tp-surface-result-create
|
||||||
|
(ebox-surface--surface-plan
|
||||||
|
context surface-root rendered state node-objects region-objects)
|
||||||
|
state)))
|
||||||
|
|
||||||
|
(defun ebox-surface--mounted-object-for-node (state node-id)
|
||||||
|
"Return NODE-ID's nearest live retained object with mounts in STATE."
|
||||||
|
(let ((objects (plist-get state :surface-node-object-table))
|
||||||
|
(parents (plist-get state :parent-table))
|
||||||
|
object)
|
||||||
|
(while (and node-id
|
||||||
|
(progn
|
||||||
|
(setq object (and objects (gethash node-id objects)))
|
||||||
|
(or (not object) (null (tp-object-mounts object)))))
|
||||||
|
(setq node-id (and parents (gethash node-id parents))))
|
||||||
|
(unless (and object (tp-object-live-p object)
|
||||||
|
(tp-object-mounts object))
|
||||||
|
(error "Ebox scoped owner has no mounted TP object"))
|
||||||
|
object))
|
||||||
|
|
||||||
|
(defun ebox-surface--objects-for-node-ids (state node-ids)
|
||||||
|
"Return mounted retained objects in STATE for Ebox NODE-IDS."
|
||||||
|
(delete-dups
|
||||||
|
(mapcar (lambda (node-id)
|
||||||
|
(ebox-surface--mounted-object-for-node state node-id))
|
||||||
|
node-ids)))
|
||||||
|
|
||||||
|
(defun ebox-surface-update-buffer-scoped
|
||||||
|
(buffer source scope-node-ids report-base state-overrides
|
||||||
|
&optional after-publication on-mismatch scroll-prefetch-delay)
|
||||||
|
"Publish BUFFER SOURCE within SCOPE-NODE-IDS through TP.
|
||||||
|
STATE-OVERRIDES augments the isolated candidate runtime. REPORT-BASE and
|
||||||
|
AFTER-PUBLICATION have the same meaning as in `ebox-surface-mount-buffer'.
|
||||||
|
ON-MISMATCH is forwarded to TP's scoped publication policy.
|
||||||
|
SCROLL-PREFETCH-DELAY controls post-publication lazy scroll warming."
|
||||||
|
(let* ((surface (ebox-surface--live-buffer-surface buffer))
|
||||||
|
(old-state (and surface (tp-surface-client-state surface))))
|
||||||
|
(unless surface
|
||||||
|
(error "Ebox scoped update requires a mounted TP surface"))
|
||||||
|
(let ((objects
|
||||||
|
(ebox-surface--objects-for-node-ids old-state scope-node-ids)))
|
||||||
|
(unless objects
|
||||||
|
(error "Ebox scoped update requires at least one retained owner"))
|
||||||
|
(let* ((context-values
|
||||||
|
(ebox-surface--context-values buffer old-state state-overrides))
|
||||||
|
(signals-result
|
||||||
|
(ebox-surface--ensure-signals buffer context-values))
|
||||||
|
(signals (car signals-result))
|
||||||
|
(signals-created-p (cdr signals-result))
|
||||||
|
(producer
|
||||||
|
(ebox-surface-producer
|
||||||
|
source old-state t state-overrides signals))
|
||||||
|
success)
|
||||||
|
(unwind-protect
|
||||||
|
(progn
|
||||||
|
(tp-with-transaction
|
||||||
|
(ebox-surface--stage-signal-values signals context-values)
|
||||||
|
(tp-surface-update-scoped
|
||||||
|
surface objects producer
|
||||||
|
(and on-mismatch (list :on-mismatch on-mismatch)))
|
||||||
|
(ebox-surface--publish-runtime-state
|
||||||
|
buffer surface old-state report-base after-publication))
|
||||||
|
(setq success t)
|
||||||
|
(let* ((new-state (tp-surface-client-state surface))
|
||||||
|
(scroll-keys
|
||||||
|
(delete-dups
|
||||||
|
(append
|
||||||
|
(copy-sequence
|
||||||
|
(plist-get old-state :scroll-region-ids))
|
||||||
|
(copy-sequence
|
||||||
|
(plist-get new-state :scroll-region-ids))))))
|
||||||
|
(ebox-incremental--finalize-declarative-scroll-publication
|
||||||
|
scroll-keys scroll-prefetch-delay))
|
||||||
|
surface)
|
||||||
|
(when (and signals-created-p (not success))
|
||||||
|
(ebox-surface--dispose-signals signals)))))))
|
||||||
|
|
||||||
(defconst ebox-surface--root-key 'ebox/surface
|
(defconst ebox-surface--root-key 'ebox/surface
|
||||||
"Stable TP key for an Ebox surface projection root.")
|
"Stable TP key for an Ebox surface projection root.")
|
||||||
@ -432,12 +744,82 @@ identities while always discarding TP handles and render-cache attachments."
|
|||||||
(maphash (lambda (key _value) (push key keys)) table)
|
(maphash (lambda (key _value) (push key keys)) table)
|
||||||
keys))
|
keys))
|
||||||
|
|
||||||
|
(defun ebox-surface--retained-scroll-lookahead (state)
|
||||||
|
"Return lookahead needed to retain STATE's prepared lazy prefixes."
|
||||||
|
(when-let ((table (plist-get state :scroll-state-table)))
|
||||||
|
(let ((maximum 0))
|
||||||
|
(maphash
|
||||||
|
(lambda (_region-id scroll-state)
|
||||||
|
(let ((retained (length (plist-get scroll-state :content-lines)))
|
||||||
|
(offset (or (plist-get scroll-state :scroll-offset) 0))
|
||||||
|
(height (or (plist-get scroll-state :content-height) 0)))
|
||||||
|
(setq maximum
|
||||||
|
(max maximum (- retained offset height 1)))))
|
||||||
|
table)
|
||||||
|
(max 0 maximum))))
|
||||||
|
|
||||||
|
(defun ebox-surface--scroll-metadata-snapshot (table producer-region-ids)
|
||||||
|
"Return reusable lazy-scroll metadata copied from TABLE.
|
||||||
|
PRODUCER-REGION-IDS names scroll-only updates whose producer closures remain
|
||||||
|
valid because their declarative layout and viewport did not change."
|
||||||
|
(let ((snapshot (make-hash-table :test 'equal)))
|
||||||
|
(maphash
|
||||||
|
(lambda (region-id state)
|
||||||
|
(let ((preserve-producer-p
|
||||||
|
(member region-id producer-region-ids))
|
||||||
|
(producer-keys
|
||||||
|
'(:content-lines :rendered-content-lines
|
||||||
|
:content-lines-complete-p :render-content-prefix
|
||||||
|
:materialize-content-lines :region-line-bounds-index
|
||||||
|
:region-line-span-index :region-line-span-index-deferred
|
||||||
|
:rendered-region-line-span-index
|
||||||
|
:rendered-region-line-span-index-deferred
|
||||||
|
:region-line-span-hints :cache-miss-prefetch-target-lines
|
||||||
|
:native-reflow-target-prefix-p
|
||||||
|
:native-reflow-prefix-reset-p :native-reflow-visible-offset
|
||||||
|
:native-reflow-visible-lines))
|
||||||
|
metadata)
|
||||||
|
(dolist (key (append
|
||||||
|
'(:content-region-id-set :lazy-scroll-prefix-dirty
|
||||||
|
:lazy-scroll-window-refresh-required)
|
||||||
|
(and preserve-producer-p producer-keys)))
|
||||||
|
(when (if (memq key producer-keys)
|
||||||
|
(plist-member state key)
|
||||||
|
(plist-get state key))
|
||||||
|
(setq metadata
|
||||||
|
(plist-put metadata key (plist-get state key)))))
|
||||||
|
(when metadata
|
||||||
|
(puthash region-id metadata snapshot))))
|
||||||
|
table)
|
||||||
|
snapshot))
|
||||||
|
|
||||||
|
(defun ebox-surface--restore-scroll-metadata (table snapshot)
|
||||||
|
"Restore reusable lazy-scroll SNAPSHOT fields into TABLE."
|
||||||
|
(maphash
|
||||||
|
(lambda (region-id metadata)
|
||||||
|
(when-let ((state (gethash region-id table)))
|
||||||
|
(while metadata
|
||||||
|
(setq state
|
||||||
|
(plist-put state (pop metadata) (pop metadata))))
|
||||||
|
(puthash region-id state table)))
|
||||||
|
snapshot))
|
||||||
|
|
||||||
(defun ebox-surface--render-candidate (state)
|
(defun ebox-surface--render-candidate (state)
|
||||||
"Render candidate STATE in isolated Ebox side tables."
|
"Render candidate STATE in isolated Ebox side tables."
|
||||||
(let ((scroll-table (make-hash-table :test 'equal))
|
(let ((scroll-table
|
||||||
|
(or (plist-get state :scroll-state-table)
|
||||||
|
(make-hash-table :test 'equal)))
|
||||||
|
(scroll-metadata
|
||||||
|
(ebox-surface--scroll-metadata-snapshot
|
||||||
|
(or (plist-get state :scroll-state-table)
|
||||||
|
(make-hash-table :test 'equal))
|
||||||
|
(plist-get state :preserve-scroll-producer-region-ids)))
|
||||||
(timer-table (make-hash-table :test 'equal))
|
(timer-table (make-hash-table :test 'equal))
|
||||||
(smooth-table (make-hash-table :test 'equal)))
|
(smooth-table (make-hash-table :test 'equal)))
|
||||||
(let ((ebox--region-box-table (plist-get state :region-box-table))
|
(cl-remf state :preserve-scroll-producer-region-ids)
|
||||||
|
(let ((ebox-viewport-width (plist-get state :viewport-width))
|
||||||
|
(ebox-viewport-height (plist-get state :viewport-height))
|
||||||
|
(ebox--region-box-table (plist-get state :region-box-table))
|
||||||
(ebox--scroll-global-state scroll-table)
|
(ebox--scroll-global-state scroll-table)
|
||||||
(ebox--scroll-idle-prefetch-timers timer-table)
|
(ebox--scroll-idle-prefetch-timers timer-table)
|
||||||
(ebox--smooth-scroll-state-table smooth-table)
|
(ebox--smooth-scroll-state-table smooth-table)
|
||||||
@ -446,6 +828,8 @@ identities while always discarding TP handles and render-cache attachments."
|
|||||||
(ebox--render-cache-table (plist-get state :render-cache))
|
(ebox--render-cache-table (plist-get state :render-cache))
|
||||||
(ebox--render-cache-signature-cache
|
(ebox--render-cache-signature-cache
|
||||||
(plist-get state :render-signature-cache))
|
(plist-get state :render-signature-cache))
|
||||||
|
(ebox--scroll-window-initial-lookahead-lines-override
|
||||||
|
(ebox-surface--retained-scroll-lookahead state))
|
||||||
(ebox--viewport-dependent-node-ids-cache
|
(ebox--viewport-dependent-node-ids-cache
|
||||||
(make-hash-table :test 'eq))
|
(make-hash-table :test 'eq))
|
||||||
(ebox--viewport-dependent-subtree-cache
|
(ebox--viewport-dependent-subtree-cache
|
||||||
@ -458,28 +842,16 @@ identities while always discarding TP handles and render-cache attachments."
|
|||||||
(lambda (&rest _) nil)))
|
(lambda (&rest _) nil)))
|
||||||
(prog1 (let ((ebox--surface-materialization-active t))
|
(prog1 (let ((ebox--surface-materialization-active t))
|
||||||
(ebox--render-layout (plist-get state :root-node)))
|
(ebox--render-layout (plist-get state :root-node)))
|
||||||
|
(ebox-surface--restore-scroll-metadata
|
||||||
|
scroll-table scroll-metadata)
|
||||||
(plist-put state :scroll-state-table scroll-table)
|
(plist-put state :scroll-state-table scroll-table)
|
||||||
(plist-put state :scroll-region-ids
|
(plist-put state :scroll-region-ids
|
||||||
(ebox-surface--hash-keys scroll-table)))))))
|
(ebox-surface--hash-keys scroll-table)))))))
|
||||||
|
|
||||||
(defun ebox-surface--finish-runtime-state (state)
|
(defun ebox-surface--finish-runtime-state (state)
|
||||||
"Install post-layout runtime indexes into candidate STATE."
|
"Install post-layout runtime indexes into candidate STATE."
|
||||||
(setq state
|
|
||||||
(ebox--render-state-install-index
|
(ebox--render-state-install-index
|
||||||
state (ebox--runtime-index (plist-get state :root-node) t)))
|
state (ebox--runtime-index (plist-get state :root-node) t)))
|
||||||
(let* ((root (plist-get state :root-node))
|
|
||||||
(ebox--viewport-dependent-node-ids-cache
|
|
||||||
(make-hash-table :test 'eq))
|
|
||||||
(ebox--viewport-height-dependent-subtree-cache
|
|
||||||
(plist-get state :viewport-height-dependent-subtree-cache))
|
|
||||||
(axes (ebox--viewport-dependent-node-id-axes root)))
|
|
||||||
(plist-put state :viewport-dependent-node-id-axes axes)
|
|
||||||
(plist-put state :viewport-dependent-node-ids
|
|
||||||
(and axes
|
|
||||||
(delete-dups
|
|
||||||
(copy-sequence (append (car axes) (cdr axes))))))
|
|
||||||
(plist-put state :viewport-dependent-node-ids-ready t))
|
|
||||||
state)
|
|
||||||
|
|
||||||
(defun ebox-surface--role-ids-at (rendered position)
|
(defun ebox-surface--role-ids-at (rendered position)
|
||||||
"Return namespaced Ebox role/id pairs at POSITION in RENDERED."
|
"Return namespaced Ebox role/id pairs at POSITION in RENDERED."
|
||||||
@ -500,56 +872,102 @@ identities while always discarding TP handles and render-cache attachments."
|
|||||||
(cl-loop for (role . owner) in role-ids
|
(cl-loop for (role . owner) in role-ids
|
||||||
when (equal owner region-id) collect role)))
|
when (equal owner region-id) collect role)))
|
||||||
|
|
||||||
(defun ebox-surface--attach-fragment
|
(defun ebox-surface--attach-fragment-ancestors
|
||||||
(context fragment role-ids region-objects)
|
(context fragment node-id state node-objects attached)
|
||||||
"Attach FRAGMENT to ROLE-IDS owners through REGION-OBJECTS in CONTEXT."
|
"Attach FRAGMENT in CONTEXT to NODE-ID ancestors from STATE.
|
||||||
(dolist (region-id (delete-dups (mapcar #'cdr role-ids)))
|
NODE-OBJECTS resolves retained objects; ATTACHED prevents duplicate mounts."
|
||||||
(let ((object (gethash region-id region-objects)))
|
(let ((parent-table (plist-get state :parent-table))
|
||||||
|
(current (gethash node-id (plist-get state :parent-table))))
|
||||||
|
(while current
|
||||||
|
(let ((object (gethash current node-objects)))
|
||||||
(unless object
|
(unless object
|
||||||
|
(error "Ebox output ancestor has no TP object: %S" current))
|
||||||
|
(unless (gethash object attached)
|
||||||
|
(tp-object-attach-fragment
|
||||||
|
context object fragment '(:ebox/descendant-output t))
|
||||||
|
(puthash object t attached)))
|
||||||
|
(setq current (gethash current parent-table)))))
|
||||||
|
|
||||||
|
(defun ebox-surface--attach-fragment
|
||||||
|
(context fragment role-ids state node-objects region-objects)
|
||||||
|
"Attach FRAGMENT to direct and ancestor Ebox owners in CONTEXT.
|
||||||
|
ROLE-IDS identifies direct rendered regions. STATE and NODE-OBJECTS extend
|
||||||
|
that ownership through the Ebox layout tree so a planned ancestor owner may
|
||||||
|
authorize every descendant output interval without exposing ranges to TP."
|
||||||
|
(let ((attached (make-hash-table :test 'eq))
|
||||||
|
(region-node-table (plist-get state :region-node-table)))
|
||||||
|
(dolist (region-id (delete-dups (mapcar #'cdr role-ids)))
|
||||||
|
(let ((object (gethash region-id region-objects))
|
||||||
|
(node-id (gethash region-id region-node-table)))
|
||||||
|
(unless (and object node-id)
|
||||||
(error "Ebox output references unknown region %S" region-id))
|
(error "Ebox output references unknown region %S" region-id))
|
||||||
|
(unless (gethash object attached)
|
||||||
(tp-object-attach-fragment
|
(tp-object-attach-fragment
|
||||||
context object fragment
|
context object fragment
|
||||||
(ebox-surface--region-role-tags region-id role-ids)))))
|
(ebox-surface--region-role-tags region-id role-ids))
|
||||||
|
(puthash object t attached))
|
||||||
|
(ebox-surface--attach-fragment-ancestors
|
||||||
|
context fragment node-id state node-objects attached)))))
|
||||||
|
|
||||||
(defun ebox-surface--fragment-plan
|
(defun ebox-surface--fragment-plan
|
||||||
(context parent rendered start end index region-objects)
|
(context parent rendered start end index role-ids
|
||||||
"Return one plan below PARENT for RENDERED START..END at INDEX in CONTEXT."
|
state node-objects region-objects)
|
||||||
|
"Return one plan below PARENT for RENDERED START..END at INDEX.
|
||||||
|
ROLE-IDS names the Ebox owners attached through REGION-OBJECTS in CONTEXT."
|
||||||
(let* ((key (cons 'ebox/fragment index))
|
(let* ((key (cons 'ebox/fragment index))
|
||||||
(object (tp-object-ensure context parent key 'ebox/fragment))
|
(object (tp-object-ensure context parent key 'ebox/fragment))
|
||||||
(role-ids (ebox-surface--role-ids-at rendered start))
|
|
||||||
(tags (list :ebox/fragment index :ebox/role-ids role-ids)))
|
(tags (list :ebox/fragment index :ebox/role-ids role-ids)))
|
||||||
(ebox-surface--attach-fragment
|
(ebox-surface--attach-fragment
|
||||||
context object role-ids region-objects)
|
context object role-ids state node-objects region-objects)
|
||||||
(tp-surface-plan-create
|
(tp-surface-plan-create
|
||||||
:key key :kind 'ebox/fragment
|
:key key :kind 'ebox/fragment
|
||||||
:text (substring rendered start end) :tags tags
|
:text (substring rendered start end) :tags tags
|
||||||
:capability 'content)))
|
:capability 'content)))
|
||||||
|
|
||||||
(defun ebox-surface--fragment-plans
|
(defun ebox-surface--fragment-plans
|
||||||
(context parent rendered region-objects)
|
(context parent rendered state node-objects region-objects)
|
||||||
"Return property-interval plans in CONTEXT for RENDERED below PARENT."
|
"Return property-interval plans in CONTEXT for RENDERED below PARENT."
|
||||||
(let ((position 0)
|
(let ((position 0)
|
||||||
(limit (length rendered))
|
(limit (length rendered))
|
||||||
(index 0)
|
intervals previous-role-ids next-role-ids)
|
||||||
plans)
|
|
||||||
(while (< position limit)
|
(while (< position limit)
|
||||||
(let ((next (or (next-property-change position rendered limit) limit)))
|
(let ((next (or (next-property-change position rendered limit) limit)))
|
||||||
(push (ebox-surface--fragment-plan
|
(push (list :start position :end next
|
||||||
context parent rendered position next index region-objects)
|
:role-ids
|
||||||
plans)
|
(ebox-surface--role-ids-at rendered position))
|
||||||
(setq position (max next (1+ position))
|
intervals)
|
||||||
index (1+ index))))
|
(setq position (max next (1+ position)))))
|
||||||
(nreverse plans)))
|
(setq intervals (nreverse intervals))
|
||||||
|
(dolist (interval intervals)
|
||||||
|
(if-let ((roles (plist-get interval :role-ids)))
|
||||||
|
(setq previous-role-ids roles)
|
||||||
|
(plist-put interval :previous-role-ids previous-role-ids)))
|
||||||
|
(dolist (interval (reverse (copy-sequence intervals)))
|
||||||
|
(if-let ((roles (plist-get interval :role-ids)))
|
||||||
|
(setq next-role-ids roles)
|
||||||
|
(plist-put interval :role-ids
|
||||||
|
(delete-dups
|
||||||
|
(append (plist-get interval :previous-role-ids)
|
||||||
|
next-role-ids)))))
|
||||||
|
(cl-loop for interval in intervals
|
||||||
|
for fragment-index from 0
|
||||||
|
collect
|
||||||
|
(ebox-surface--fragment-plan
|
||||||
|
context parent rendered
|
||||||
|
(plist-get interval :start) (plist-get interval :end)
|
||||||
|
fragment-index (plist-get interval :role-ids)
|
||||||
|
state node-objects region-objects))))
|
||||||
|
|
||||||
(defun ebox-surface--surface-plan
|
(defun ebox-surface--surface-plan
|
||||||
(context surface-root rendered region-objects)
|
(context surface-root rendered state node-objects region-objects)
|
||||||
"Return CONTEXT's plan below SURFACE-ROOT for RENDERED and REGION-OBJECTS."
|
"Return CONTEXT's plan below SURFACE-ROOT for RENDERED and STATE.
|
||||||
|
NODE-OBJECTS and REGION-OBJECTS supply retained ownership for its fragments."
|
||||||
(let* ((fragment-root
|
(let* ((fragment-root
|
||||||
(tp-object-ensure context surface-root
|
(tp-object-ensure context surface-root
|
||||||
ebox-surface--fragments-key 'ebox/fragments))
|
ebox-surface--fragments-key 'ebox/fragments))
|
||||||
(fragments
|
(fragments
|
||||||
(ebox-surface--fragment-plans
|
(ebox-surface--fragment-plans
|
||||||
context fragment-root rendered region-objects)))
|
context fragment-root rendered state node-objects region-objects)))
|
||||||
(tp-surface-plan-create
|
(tp-surface-plan-create
|
||||||
:key ebox-surface--root-key :kind 'ebox/surface
|
:key ebox-surface--root-key :kind 'ebox/surface
|
||||||
:children
|
:children
|
||||||
@ -564,10 +982,36 @@ identities while always discarding TP handles and render-cache attachments."
|
|||||||
(setq state (plist-put state (pop overrides) (pop overrides))))
|
(setq state (plist-put state (pop overrides) (pop overrides))))
|
||||||
state)
|
state)
|
||||||
|
|
||||||
|
(defun ebox-surface--apply-scroll-offsets (root offsets)
|
||||||
|
"Apply surface-scoped scroll OFFSETS to matching boxes below ROOT."
|
||||||
|
(cl-labels
|
||||||
|
((visit (node)
|
||||||
|
(when (and (listp node) (not (stringp node)))
|
||||||
|
(when (eq (plist-get node :ebox-type) 'box)
|
||||||
|
(when-let* ((region-id (plist-get node :region-id))
|
||||||
|
(entry (assq region-id offsets)))
|
||||||
|
(ebox-put node :scroll-offset (cdr entry))))
|
||||||
|
(dolist (child (ebox-tree--children-raw node))
|
||||||
|
(visit child)))))
|
||||||
|
(visit root))
|
||||||
|
root)
|
||||||
|
|
||||||
|
(defun ebox-surface--context-axes (root)
|
||||||
|
"Return ROOT's exact viewport dependency axes without shared caches."
|
||||||
|
(let ((ebox--viewport-dependent-node-ids-cache
|
||||||
|
(make-hash-table :test 'eq))
|
||||||
|
(ebox--viewport-dependent-subtree-cache
|
||||||
|
(make-hash-table :test 'eq))
|
||||||
|
(ebox--viewport-height-dependent-subtree-cache
|
||||||
|
(make-hash-table :test 'eq)))
|
||||||
|
(ebox--viewport-dependent-node-id-axes root)))
|
||||||
|
|
||||||
(defun ebox-surface--project
|
(defun ebox-surface--project
|
||||||
(context source previous-state preserve-identities-p state-overrides)
|
(context source previous-state preserve-identities-p state-overrides
|
||||||
|
signals)
|
||||||
"Project SOURCE in CONTEXT using PREVIOUS-STATE and identity policy.
|
"Project SOURCE in CONTEXT using PREVIOUS-STATE and identity policy.
|
||||||
PRESERVE-IDENTITIES-P retains existing Ebox node and region identities."
|
PRESERVE-IDENTITIES-P retains existing Ebox node and region identities.
|
||||||
|
SIGNALS, when non-nil, supplies the mounted surface's host context."
|
||||||
(if (stringp source)
|
(if (stringp source)
|
||||||
(let ((object
|
(let ((object
|
||||||
(tp-object-ensure context nil ebox-surface--root-key
|
(tp-object-ensure context nil ebox-surface--root-key
|
||||||
@ -579,52 +1023,63 @@ PRESERVE-IDENTITIES-P retains existing Ebox node and region identities."
|
|||||||
:text source :capability 'content)))
|
:text source :capability 'content)))
|
||||||
(let* ((root (ebox-surface--candidate-root
|
(let* ((root (ebox-surface--candidate-root
|
||||||
source previous-state preserve-identities-p))
|
source previous-state preserve-identities-p))
|
||||||
|
(axes (and signals (ebox-surface--context-axes root)))
|
||||||
|
(viewport-width
|
||||||
|
(and signals
|
||||||
|
(funcall
|
||||||
|
(if (car axes) #'tp-signal-read #'tp-signal-peek)
|
||||||
|
(ebox-surface--signals-viewport-width signals))))
|
||||||
|
(viewport-height
|
||||||
|
(and signals
|
||||||
|
(funcall
|
||||||
|
(if (cdr axes) #'tp-signal-read #'tp-signal-peek)
|
||||||
|
(ebox-surface--signals-viewport-height signals))))
|
||||||
|
(display-signature
|
||||||
|
(and signals
|
||||||
|
(tp-signal-read
|
||||||
|
(ebox-surface--signals-display signals))))
|
||||||
|
(scroll-offsets
|
||||||
|
(and signals
|
||||||
|
(tp-signal-peek
|
||||||
|
(ebox-surface--signals-scroll signals))))
|
||||||
(state
|
(state
|
||||||
(ebox-surface--apply-state-overrides
|
(ebox-surface--apply-state-overrides
|
||||||
(ebox--new-buffer-render-state root)
|
(ebox--new-buffer-render-state root)
|
||||||
(copy-sequence state-overrides)))
|
(copy-sequence state-overrides))))
|
||||||
(surface-root
|
(when signals
|
||||||
(tp-object-ensure context nil ebox-surface--root-key 'ebox/surface))
|
(plist-put state :viewport-width viewport-width)
|
||||||
(node-root
|
(plist-put state :viewport-height viewport-height)
|
||||||
(tp-object-ensure context surface-root
|
(plist-put state :display-signature display-signature)
|
||||||
ebox-surface--nodes-key 'ebox/nodes))
|
(plist-put state :viewport-dependent-node-id-axes axes)
|
||||||
(objects-by-node (make-hash-table :test 'eq)))
|
(plist-put state :viewport-dependent-node-ids
|
||||||
(tp-object-retain context node-root)
|
(delete-dups
|
||||||
(ebox-surface--ensure-node-tree context node-root root objects-by-node)
|
(copy-sequence (append (car axes) (cdr axes)))))
|
||||||
|
(plist-put state :viewport-dependent-node-ids-ready t)
|
||||||
|
(ebox-surface--apply-scroll-offsets root scroll-offsets))
|
||||||
|
(let ((projection (ebox-surface--projection-start context root)))
|
||||||
(plist-put state :region-box-table (make-hash-table :test 'equal))
|
(plist-put state :region-box-table (make-hash-table :test 'equal))
|
||||||
(let ((rendered (ebox-surface--render-candidate state)))
|
(let ((rendered (ebox-surface--render-candidate state)))
|
||||||
|
(when (and signals
|
||||||
|
(hash-table-p (plist-get state :scroll-state-table))
|
||||||
|
(> (hash-table-count
|
||||||
|
(plist-get state :scroll-state-table))
|
||||||
|
0))
|
||||||
|
(tp-signal-read (ebox-surface--signals-scroll signals)))
|
||||||
(setq state (ebox-surface--finish-runtime-state state))
|
(setq state (ebox-surface--finish-runtime-state state))
|
||||||
(plist-put state :region-role-span-table
|
(ebox-surface--projection-result
|
||||||
(ebox-buffer--build-region-role-span-template rendered))
|
context projection state rendered))))))
|
||||||
(plist-put state :box-extent-template
|
|
||||||
(ebox--build-box-extent-template rendered))
|
|
||||||
(plist-put state :scroll-content-span-template
|
|
||||||
(let ((ebox--scroll-global-state
|
|
||||||
(plist-get state :scroll-state-table)))
|
|
||||||
(ebox--build-scroll-content-span-template rendered)))
|
|
||||||
(let* ((node-objects (ebox-surface--node-object-table objects-by-node))
|
|
||||||
(region-objects
|
|
||||||
(ebox-surface--region-object-table state node-objects)))
|
|
||||||
(plist-put state :surface-node-object-table node-objects)
|
|
||||||
(plist-put state :region-surface-object-table region-objects)
|
|
||||||
(plist-put state :surface-object-region-table
|
|
||||||
(ebox-surface--object-region-table region-objects))
|
|
||||||
(plist-put state :logical-id-region-table
|
|
||||||
(ebox-surface--logical-id-region-table
|
|
||||||
state region-objects))
|
|
||||||
(tp-surface-result-create
|
|
||||||
(ebox-surface--surface-plan
|
|
||||||
context surface-root rendered region-objects)
|
|
||||||
state))))))
|
|
||||||
|
|
||||||
(defun ebox-surface-producer
|
(defun ebox-surface-producer
|
||||||
(source &optional previous-state preserve-identities-p state-overrides)
|
(source &optional previous-state preserve-identities-p state-overrides
|
||||||
|
signals)
|
||||||
"Return a TP producer for Ebox SOURCE and optional PREVIOUS-STATE.
|
"Return a TP producer for Ebox SOURCE and optional PREVIOUS-STATE.
|
||||||
PRESERVE-IDENTITIES-P retains existing Ebox node and region identities.
|
PRESERVE-IDENTITIES-P retains existing Ebox node and region identities.
|
||||||
STATE-OVERRIDES augments the candidate Ebox runtime state."
|
STATE-OVERRIDES augments the candidate Ebox runtime state. SIGNALS carries
|
||||||
|
the mounted surface context; one-shot string materialization omits it."
|
||||||
(lambda (context)
|
(lambda (context)
|
||||||
(ebox-surface--project
|
(ebox-surface--project
|
||||||
context source previous-state preserve-identities-p state-overrides)))
|
context source previous-state preserve-identities-p state-overrides
|
||||||
|
signals)))
|
||||||
|
|
||||||
(provide 'ebox-surface)
|
(provide 'ebox-surface)
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -14,7 +14,6 @@
|
|||||||
(setq ebox--runtime-node-id-counter 0)
|
(setq ebox--runtime-node-id-counter 0)
|
||||||
(setq ebox--render-region-id nil)
|
(setq ebox--render-region-id nil)
|
||||||
(dolist (table (list ebox--region-box-table
|
(dolist (table (list ebox--region-box-table
|
||||||
ebox--box-extents
|
|
||||||
ebox--buffer-render-state-table
|
ebox--buffer-render-state-table
|
||||||
ebox--scroll-global-state))
|
ebox--scroll-global-state))
|
||||||
(clrhash table)))
|
(clrhash table)))
|
||||||
@ -296,11 +295,14 @@
|
|||||||
other-node))
|
other-node))
|
||||||
(let ((calls nil))
|
(let ((calls nil))
|
||||||
(cl-letf (((symbol-function 'ebox-region-update)
|
(cl-letf (((symbol-function 'ebox-region-update)
|
||||||
(lambda (region-id &rest _props)
|
(lambda (region-handle &rest _props)
|
||||||
(let ((buffer ebox--region-update-buffer-hint))
|
(should (ebox-region-handle-p region-handle))
|
||||||
(push (cons region-id buffer) calls)
|
(let ((buffer
|
||||||
|
(car (ebox-selector--region-target
|
||||||
|
region-handle))))
|
||||||
|
(push (cons region-handle buffer) calls)
|
||||||
(list :buffer buffer
|
(list :buffer buffer
|
||||||
:region-id region-id
|
:region-handle region-handle
|
||||||
:strategy 'stub-direct)))))
|
:strategy 'stub-direct)))))
|
||||||
(ebox-selector-update-buffer
|
(ebox-selector-update-buffer
|
||||||
other-buffer "#other-card" :border-color "#0f766e")
|
other-buffer "#other-card" :border-color "#0f766e")
|
||||||
@ -330,11 +332,15 @@
|
|||||||
node))
|
node))
|
||||||
(let* ((match (car (ebox-selector-query-buffer buffer "#card")))
|
(let* ((match (car (ebox-selector-query-buffer buffer "#card")))
|
||||||
(region-id (plist-get match :region-id))
|
(region-id (plist-get match :region-id))
|
||||||
|
(region-handle (plist-get match :region-handle))
|
||||||
(poisoned-entry (list :poisoned-region region-id)))
|
(poisoned-entry (list :poisoned-region region-id)))
|
||||||
(puthash region-id poisoned-entry ebox--region-box-table)
|
(puthash region-id poisoned-entry ebox--region-box-table)
|
||||||
(cl-letf (((symbol-function 'ebox-region-update)
|
(cl-letf (((symbol-function 'ebox-region-update)
|
||||||
(lambda (called-region-id &rest _props)
|
(lambda (called-handle &rest _props)
|
||||||
(should (eq called-region-id region-id))
|
(should (ebox-region-handle-p called-handle))
|
||||||
|
(should (equal
|
||||||
|
(ebox-selector--region-target called-handle)
|
||||||
|
(ebox-selector--region-target region-handle)))
|
||||||
(should (eq (gethash region-id ebox--region-box-table)
|
(should (eq (gethash region-id ebox--region-box-table)
|
||||||
poisoned-entry))
|
poisoned-entry))
|
||||||
(list :strategy 'stub-direct))))
|
(list :strategy 'stub-direct))))
|
||||||
@ -363,8 +369,9 @@
|
|||||||
:patch-ops '(owner-rerender)
|
:patch-ops '(owner-rerender)
|
||||||
:batched-count 2)))
|
:batched-count 2)))
|
||||||
(cl-letf (((symbol-function 'ebox-region-update)
|
(cl-letf (((symbol-function 'ebox-region-update)
|
||||||
(lambda (region-id &rest _props)
|
(lambda (region-handle &rest _props)
|
||||||
(push (list :region-id region-id
|
(should (ebox-region-handle-p region-handle))
|
||||||
|
(push (list :region-handle region-handle
|
||||||
:strategy 'wrong-direct)
|
:strategy 'wrong-direct)
|
||||||
direct-reports)))
|
direct-reports)))
|
||||||
((symbol-function 'ebox-incremental-flush)
|
((symbol-function 'ebox-incremental-flush)
|
||||||
@ -380,5 +387,33 @@
|
|||||||
(when (and buffer (buffer-live-p buffer))
|
(when (and buffer (buffer-live-p buffer))
|
||||||
(kill-buffer buffer)))))
|
(kill-buffer buffer)))))
|
||||||
|
|
||||||
|
(ert-deftest ebox-selector-multi-match-publishes-one-scoped-generation ()
|
||||||
|
"Multi-match updates should publish once through a scoped TP transaction."
|
||||||
|
(ebox-selector-test--reset-runtime-state)
|
||||||
|
(let* ((node (ebox-build
|
||||||
|
'(column
|
||||||
|
(box :class card :content "One")
|
||||||
|
(box :class card :content "Two"))))
|
||||||
|
buffer)
|
||||||
|
(unwind-protect
|
||||||
|
(progn
|
||||||
|
(setq buffer (ebox-render-to-buffer
|
||||||
|
(generate-new-buffer-name " *ebox-selector-test*")
|
||||||
|
node))
|
||||||
|
(let* ((surface (with-current-buffer
|
||||||
|
buffer ebox-surface--buffer-surface))
|
||||||
|
(revision (tp-surface-revision surface))
|
||||||
|
(result
|
||||||
|
(ebox-selector-update-buffer
|
||||||
|
buffer ".card" :border-color "#ef4444"))
|
||||||
|
(report (car (plist-get result :reports))))
|
||||||
|
(should (= (tp-surface-revision surface) (1+ revision)))
|
||||||
|
(should-not (plist-get (tp-surface-report surface) :full-root))
|
||||||
|
(should (= (plist-get report :flush-count) 1))
|
||||||
|
(should (= (plist-get report :batched-count) 2))
|
||||||
|
(should (equal report (ebox-buffer-update-report buffer)))))
|
||||||
|
(when (and buffer (buffer-live-p buffer))
|
||||||
|
(kill-buffer buffer)))))
|
||||||
|
|
||||||
(provide 'ebox-selector-tests)
|
(provide 'ebox-selector-tests)
|
||||||
;;; ebox-selector-tests.el ends here
|
;;; ebox-selector-tests.el ends here
|
||||||
|
|||||||
@ -17,8 +17,7 @@
|
|||||||
(setq ebox--region-id-counter 0
|
(setq ebox--region-id-counter 0
|
||||||
ebox--runtime-node-id-counter 0)
|
ebox--runtime-node-id-counter 0)
|
||||||
(dolist (table (list ebox--region-box-table
|
(dolist (table (list ebox--region-box-table
|
||||||
ebox--scroll-global-state
|
ebox--scroll-global-state))
|
||||||
ebox--rendered-root-metadata-table))
|
|
||||||
(clrhash table)))
|
(clrhash table)))
|
||||||
|
|
||||||
(defun ebox-surface-test--interactive-content ()
|
(defun ebox-surface-test--interactive-content ()
|
||||||
@ -334,6 +333,179 @@
|
|||||||
(kill-buffer buffer)))
|
(kill-buffer buffer)))
|
||||||
(should-not (tp-surface-live-p surface))))
|
(should-not (tp-surface-live-p surface))))
|
||||||
|
|
||||||
|
(ert-deftest ebox-viewport-rerender-publishes-only-through-tp ()
|
||||||
|
"A mounted viewport update should advance its TP surface."
|
||||||
|
(ebox-surface-test--reset-render-state)
|
||||||
|
(let ((buffer (generate-new-buffer " *ebox-surface-viewport*")))
|
||||||
|
(unwind-protect
|
||||||
|
(let ((ebox-viewport-width 120)
|
||||||
|
(ebox-viewport-height 4)
|
||||||
|
(ebox-runtime-idle-prewarm nil)
|
||||||
|
(ebox-runtime-idle-reflow-cache-prewarm nil))
|
||||||
|
(ebox-render-to-buffer
|
||||||
|
buffer
|
||||||
|
(ebox-create :content "Viewport" :width '(viewport)))
|
||||||
|
(let* ((surface
|
||||||
|
(with-current-buffer buffer ebox-surface--buffer-surface))
|
||||||
|
(signals
|
||||||
|
(with-current-buffer buffer
|
||||||
|
ebox-surface--context-signals))
|
||||||
|
(revision (tp-surface-revision surface)))
|
||||||
|
(should (= (tp-signal-peek
|
||||||
|
(ebox-surface--signals-viewport-width signals))
|
||||||
|
120))
|
||||||
|
(ebox-rerender-buffer-with-context buffer 180 4)
|
||||||
|
(should (= (tp-surface-revision surface) (1+ revision)))
|
||||||
|
(should (= (tp-signal-peek
|
||||||
|
(ebox-surface--signals-viewport-width signals))
|
||||||
|
180))
|
||||||
|
(should (eq (ebox--buffer-render-state buffer)
|
||||||
|
(tp-surface-client-state surface)))
|
||||||
|
(should (= (with-current-buffer buffer
|
||||||
|
(ebox--string-pixel-width
|
||||||
|
(buffer-substring (line-beginning-position)
|
||||||
|
(line-end-position))))
|
||||||
|
180))
|
||||||
|
(let ((report (ebox-buffer-update-report buffer)))
|
||||||
|
(should (eq (plist-get report :constraint-source) 'viewport))
|
||||||
|
(should (plist-get report :runtime-published)))))
|
||||||
|
(when (buffer-live-p buffer)
|
||||||
|
(kill-buffer buffer)))))
|
||||||
|
|
||||||
|
(ert-deftest ebox-surface-context-signals-track-exact-layout-dependencies ()
|
||||||
|
"A mounted producer should subscribe only to context it can consume."
|
||||||
|
(ebox-surface-test--reset-render-state)
|
||||||
|
(let ((responsive (generate-new-buffer " *ebox-responsive-signals*"))
|
||||||
|
(static (generate-new-buffer " *ebox-static-signals*"))
|
||||||
|
responsive-signals static-signals)
|
||||||
|
(unwind-protect
|
||||||
|
(let ((ebox-viewport-width 160)
|
||||||
|
(ebox-viewport-height 2))
|
||||||
|
(ebox-render-to-buffer
|
||||||
|
responsive
|
||||||
|
(ebox-create :content "zero\none\ntwo\nthree"
|
||||||
|
:width '(viewport)
|
||||||
|
:height '(viewport-height)
|
||||||
|
:overflow 'scroll))
|
||||||
|
(setq responsive-signals
|
||||||
|
(with-current-buffer responsive
|
||||||
|
ebox-surface--context-signals))
|
||||||
|
(dolist (signal
|
||||||
|
(list
|
||||||
|
(ebox-surface--signals-viewport-width
|
||||||
|
responsive-signals)
|
||||||
|
(ebox-surface--signals-viewport-height
|
||||||
|
responsive-signals)
|
||||||
|
(ebox-surface--signals-display responsive-signals)
|
||||||
|
(ebox-surface--signals-scroll responsive-signals)))
|
||||||
|
(should (tp-signal-live-p signal))
|
||||||
|
(should (= (tp-signal-subscriber-count signal) 1)))
|
||||||
|
(ebox-render-to-buffer
|
||||||
|
static
|
||||||
|
(ebox-create :content "Static" :width '(100)))
|
||||||
|
(setq static-signals
|
||||||
|
(with-current-buffer static ebox-surface--context-signals))
|
||||||
|
(should (= (tp-signal-subscriber-count
|
||||||
|
(ebox-surface--signals-viewport-width static-signals))
|
||||||
|
0))
|
||||||
|
(should (= (tp-signal-subscriber-count
|
||||||
|
(ebox-surface--signals-viewport-height static-signals))
|
||||||
|
0))
|
||||||
|
(should (= (tp-signal-subscriber-count
|
||||||
|
(ebox-surface--signals-display static-signals))
|
||||||
|
1))
|
||||||
|
(should (= (tp-signal-subscriber-count
|
||||||
|
(ebox-surface--signals-scroll static-signals))
|
||||||
|
0)))
|
||||||
|
(dolist (buffer (list responsive static))
|
||||||
|
(when (buffer-live-p buffer)
|
||||||
|
(kill-buffer buffer))))
|
||||||
|
(dolist (signals (list responsive-signals static-signals))
|
||||||
|
(when signals
|
||||||
|
(dolist (signal
|
||||||
|
(list (ebox-surface--signals-viewport-width signals)
|
||||||
|
(ebox-surface--signals-viewport-height signals)
|
||||||
|
(ebox-surface--signals-display signals)
|
||||||
|
(ebox-surface--signals-scroll signals)))
|
||||||
|
(should-not (tp-signal-live-p signal)))))))
|
||||||
|
|
||||||
|
(ert-deftest ebox-static-viewport-change-updates-context-without-rendering ()
|
||||||
|
"Unused viewport state should change without publishing the surface."
|
||||||
|
(ebox-surface-test--reset-render-state)
|
||||||
|
(let ((buffer (generate-new-buffer " *ebox-static-viewport*")))
|
||||||
|
(unwind-protect
|
||||||
|
(let ((ebox-viewport-width 120)
|
||||||
|
(ebox-viewport-height 4)
|
||||||
|
(ebox-runtime-idle-prewarm nil)
|
||||||
|
(ebox-runtime-idle-reflow-cache-prewarm nil))
|
||||||
|
(ebox-render-to-buffer
|
||||||
|
buffer
|
||||||
|
(ebox-create :content "Static" :width '(100)))
|
||||||
|
(let* ((surface
|
||||||
|
(with-current-buffer buffer ebox-surface--buffer-surface))
|
||||||
|
(signals
|
||||||
|
(with-current-buffer buffer
|
||||||
|
ebox-surface--context-signals))
|
||||||
|
(revision (tp-surface-revision surface))
|
||||||
|
(renders 0)
|
||||||
|
(original
|
||||||
|
(symbol-function 'ebox-surface--render-candidate)))
|
||||||
|
(cl-letf (((symbol-function 'ebox-surface--render-candidate)
|
||||||
|
(lambda (state)
|
||||||
|
(cl-incf renders)
|
||||||
|
(funcall original state))))
|
||||||
|
(ebox-rerender-buffer-with-context buffer 180 4))
|
||||||
|
(should (= renders 0))
|
||||||
|
(should (= (tp-surface-revision surface) revision))
|
||||||
|
(should (= (plist-get (tp-surface-client-state surface)
|
||||||
|
:viewport-width)
|
||||||
|
180))
|
||||||
|
(should (= (tp-signal-peek
|
||||||
|
(ebox-surface--signals-viewport-width signals))
|
||||||
|
180))
|
||||||
|
(should (eq (plist-get (ebox-buffer-update-report buffer)
|
||||||
|
:strategy)
|
||||||
|
'no-op))))
|
||||||
|
(when (buffer-live-p buffer)
|
||||||
|
(kill-buffer buffer)))))
|
||||||
|
|
||||||
|
(ert-deftest ebox-theme-change-rerenders-with-an-unchanged-viewport ()
|
||||||
|
"A changed display signature should invalidate a fixed-width surface."
|
||||||
|
(ebox-surface-test--reset-render-state)
|
||||||
|
(let ((buffer (generate-new-buffer " *ebox-theme-context*")))
|
||||||
|
(unwind-protect
|
||||||
|
(let ((ebox-viewport-width 120)
|
||||||
|
(ebox-viewport-height 4)
|
||||||
|
(ebox-runtime-idle-prewarm nil)
|
||||||
|
(ebox-runtime-idle-reflow-cache-prewarm nil))
|
||||||
|
(ebox-render-to-buffer
|
||||||
|
buffer
|
||||||
|
(ebox-create :content "Theme" :width '(100)))
|
||||||
|
(let* ((surface
|
||||||
|
(with-current-buffer buffer ebox-surface--buffer-surface))
|
||||||
|
(signals
|
||||||
|
(with-current-buffer buffer
|
||||||
|
ebox-surface--context-signals))
|
||||||
|
(revision (tp-surface-revision surface))
|
||||||
|
(next-signature '(ebox-test-theme dark)))
|
||||||
|
(cl-letf (((symbol-function 'ebox--display-signature)
|
||||||
|
(lambda () next-signature)))
|
||||||
|
(ebox-rerender-buffer-with-context buffer 120 4))
|
||||||
|
(should (= (tp-surface-revision surface) (1+ revision)))
|
||||||
|
(should (equal
|
||||||
|
(tp-signal-peek
|
||||||
|
(ebox-surface--signals-display signals))
|
||||||
|
next-signature))
|
||||||
|
(should (equal (plist-get (tp-surface-client-state surface)
|
||||||
|
:display-signature)
|
||||||
|
next-signature))
|
||||||
|
(let ((report (ebox-buffer-update-report buffer)))
|
||||||
|
(should (plist-get report :host-context-changed))
|
||||||
|
(should (memq :display-signature
|
||||||
|
(plist-get report :dirty-keys))))))
|
||||||
|
(when (buffer-live-p buffer)
|
||||||
|
(kill-buffer buffer)))))
|
||||||
|
|
||||||
(ert-deftest ebox-render-to-buffer-reuses-one-source-across-buffers ()
|
(ert-deftest ebox-render-to-buffer-reuses-one-source-across-buffers ()
|
||||||
"The public mount path should never transfer ownership of its source tree."
|
"The public mount path should never transfer ownership of its source tree."
|
||||||
(ebox-surface-test--reset-render-state)
|
(ebox-surface-test--reset-render-state)
|
||||||
@ -364,7 +536,7 @@
|
|||||||
(kill-buffer buffer))))))
|
(kill-buffer buffer))))))
|
||||||
|
|
||||||
(ert-deftest ebox-commit-publishes-through-the-mounted-tp-surface ()
|
(ert-deftest ebox-commit-publishes-through-the-mounted-tp-surface ()
|
||||||
"Declarative commits should bypass every legacy Ebox buffer publisher."
|
"Declarative commits should publish through the mounted TP surface."
|
||||||
(ebox-surface-test--reset-render-state)
|
(ebox-surface-test--reset-render-state)
|
||||||
(let ((buffer (generate-new-buffer " *ebox-surface-commit*")))
|
(let ((buffer (generate-new-buffer " *ebox-surface-commit*")))
|
||||||
(unwind-protect
|
(unwind-protect
|
||||||
@ -376,14 +548,11 @@
|
|||||||
buffer ebox-surface--buffer-surface))
|
buffer ebox-surface--buffer-surface))
|
||||||
(revision (tp-surface-revision surface))
|
(revision (tp-surface-revision surface))
|
||||||
report)
|
report)
|
||||||
(cl-letf (((symbol-function 'ebox-incremental-commit)
|
|
||||||
(lambda (&rest _)
|
|
||||||
(error "Legacy Ebox publisher was called"))))
|
|
||||||
(setq report
|
(setq report
|
||||||
(ebox-commit
|
(ebox-commit
|
||||||
buffer
|
buffer
|
||||||
(ebox-create :key 'root :content "After"
|
(ebox-create :key 'root :content "After"
|
||||||
:width '(100)))))
|
:width '(100))))
|
||||||
(should (= (tp-surface-revision surface) (1+ revision)))
|
(should (= (tp-surface-revision surface) (1+ revision)))
|
||||||
(should (plist-get report :runtime-published))
|
(should (plist-get report :runtime-published))
|
||||||
(should (equal report (ebox-buffer-update-report buffer)))
|
(should (equal report (ebox-buffer-update-report buffer)))
|
||||||
@ -423,6 +592,48 @@
|
|||||||
(when (buffer-live-p buffer)
|
(when (buffer-live-p buffer)
|
||||||
(kill-buffer buffer)))))
|
(kill-buffer buffer)))))
|
||||||
|
|
||||||
|
(ert-deftest ebox-context-signal-rolls-back-with-failed-publication ()
|
||||||
|
"A failed Ebox publication should restore its TP context source."
|
||||||
|
(ebox-surface-test--reset-render-state)
|
||||||
|
(let ((buffer (generate-new-buffer " *ebox-context-rollback*")))
|
||||||
|
(unwind-protect
|
||||||
|
(let ((ebox-viewport-width 120)
|
||||||
|
(ebox-viewport-height 4))
|
||||||
|
(ebox-render-to-buffer
|
||||||
|
buffer
|
||||||
|
(ebox-create :key 'root :content "Stable"
|
||||||
|
:width '(viewport)))
|
||||||
|
(let* ((surface
|
||||||
|
(with-current-buffer buffer ebox-surface--buffer-surface))
|
||||||
|
(signals
|
||||||
|
(with-current-buffer buffer
|
||||||
|
ebox-surface--context-signals))
|
||||||
|
(width-signal
|
||||||
|
(ebox-surface--signals-viewport-width signals))
|
||||||
|
(state (tp-surface-client-state surface))
|
||||||
|
(revision (tp-surface-revision surface))
|
||||||
|
(contents
|
||||||
|
(with-current-buffer buffer
|
||||||
|
(buffer-substring (point-min) (point-max)))))
|
||||||
|
(should-error
|
||||||
|
(ebox-surface-mount-buffer
|
||||||
|
buffer
|
||||||
|
(ebox-create :key 'root :content "Rejected"
|
||||||
|
:width '(viewport))
|
||||||
|
(ebox--update-report nil 'root-rerender)
|
||||||
|
(lambda (_report) (error "Reject context publication"))
|
||||||
|
nil
|
||||||
|
'(:viewport-width 180 :viewport-height 4)))
|
||||||
|
(should (= (tp-signal-peek width-signal) 120))
|
||||||
|
(should (= (tp-surface-revision surface) revision))
|
||||||
|
(should (eq (tp-surface-client-state surface) state))
|
||||||
|
(should (equal-including-properties
|
||||||
|
(with-current-buffer buffer
|
||||||
|
(buffer-substring (point-min) (point-max)))
|
||||||
|
contents))))
|
||||||
|
(when (buffer-live-p buffer)
|
||||||
|
(kill-buffer buffer)))))
|
||||||
|
|
||||||
(ert-deftest ebox-commit-killed-buffer-rollback-does-not-revive-runtime ()
|
(ert-deftest ebox-commit-killed-buffer-rollback-does-not-revive-runtime ()
|
||||||
"A failed commit must not restore runtime state for a killed buffer."
|
"A failed commit must not restore runtime state for a killed buffer."
|
||||||
(ebox-surface-test--reset-render-state)
|
(ebox-surface-test--reset-render-state)
|
||||||
@ -496,6 +707,55 @@
|
|||||||
(when (buffer-live-p buffer)
|
(when (buffer-live-p buffer)
|
||||||
(kill-buffer buffer))))))
|
(kill-buffer buffer))))))
|
||||||
|
|
||||||
|
(ert-deftest ebox-region-handle-update-uses-scoped-tp-publication ()
|
||||||
|
"A handle update should publish a scoped TP plan."
|
||||||
|
(ebox-surface-test--reset-render-state)
|
||||||
|
(let ((buffer (generate-new-buffer " *ebox-scoped-handle*")))
|
||||||
|
(unwind-protect
|
||||||
|
(progn
|
||||||
|
(ebox-render-to-buffer
|
||||||
|
buffer
|
||||||
|
(ebox-column
|
||||||
|
(ebox-create :id "left" :content "Left" :width '(100))
|
||||||
|
(ebox-create :id "right" :content "Right" :width '(100))))
|
||||||
|
(let* ((surface (with-current-buffer
|
||||||
|
buffer ebox-surface--buffer-surface))
|
||||||
|
(revision (tp-surface-revision surface))
|
||||||
|
(handle (ebox-region-resolve buffer "left"))
|
||||||
|
report)
|
||||||
|
(setq report
|
||||||
|
(ebox-region-update handle :content "Changed"))
|
||||||
|
(should (= (tp-surface-revision surface) (1+ revision)))
|
||||||
|
(should-not (plist-get (tp-surface-report surface) :full-root))
|
||||||
|
(should (> (plist-get (tp-surface-report surface) :scope-count) 0))
|
||||||
|
(should (eq (plist-get report :tp-render-scope) 'objects))
|
||||||
|
(should (string-match-p
|
||||||
|
"Changed"
|
||||||
|
(with-current-buffer buffer (buffer-string))))))
|
||||||
|
(when (buffer-live-p buffer)
|
||||||
|
(kill-buffer buffer)))))
|
||||||
|
|
||||||
|
(ert-deftest ebox-region-update-rejects-process-global-region-ids ()
|
||||||
|
"Direct updates should require a surface-scoped region handle."
|
||||||
|
(ebox-surface-test--reset-render-state)
|
||||||
|
(let ((buffer (generate-new-buffer " *ebox-handle-only-update*")))
|
||||||
|
(unwind-protect
|
||||||
|
(progn
|
||||||
|
(ebox-render-to-buffer
|
||||||
|
buffer
|
||||||
|
(ebox-create :id "target" :content "Before" :width '(100)))
|
||||||
|
(let* ((match (car (ebox-selector-query-buffer buffer "#target")))
|
||||||
|
(region-id (plist-get match :region-id)))
|
||||||
|
(should-error (ebox-region-update region-id :content "Wrong")
|
||||||
|
:type 'wrong-type-argument)
|
||||||
|
(ebox-region-update (plist-get match :region-handle)
|
||||||
|
:content "After")
|
||||||
|
(should (string-match-p
|
||||||
|
"After"
|
||||||
|
(with-current-buffer buffer (buffer-string))))))
|
||||||
|
(when (buffer-live-p buffer)
|
||||||
|
(kill-buffer buffer)))))
|
||||||
|
|
||||||
(ert-deftest ebox-selector-query-returns-an-editable-region-handle ()
|
(ert-deftest ebox-selector-query-returns-an-editable-region-handle ()
|
||||||
"A live selector match should carry the same handle accepted by updates."
|
"A live selector match should carry the same handle accepted by updates."
|
||||||
(ebox-surface-test--reset-render-state)
|
(ebox-surface-test--reset-render-state)
|
||||||
@ -535,6 +795,91 @@
|
|||||||
(when (buffer-live-p buffer)
|
(when (buffer-live-p buffer)
|
||||||
(kill-buffer buffer)))))
|
(kill-buffer buffer)))))
|
||||||
|
|
||||||
|
(ert-deftest ebox-scroll-update-publishes-only-through-tp ()
|
||||||
|
"A mounted scroll update should advance one TP surface revision."
|
||||||
|
(ebox-surface-test--reset-render-state)
|
||||||
|
(let ((buffer (generate-new-buffer " *ebox-surface-scroll*")))
|
||||||
|
(unwind-protect
|
||||||
|
(let ((ebox-viewport-width 240)
|
||||||
|
(ebox-viewport-height 2))
|
||||||
|
(ebox-render-to-buffer
|
||||||
|
buffer
|
||||||
|
(ebox-create :id "scroll" :content "zero\none\ntwo\nthree"
|
||||||
|
:width '(120) :height 2 :overflow 'scroll))
|
||||||
|
(let* ((surface (with-current-buffer
|
||||||
|
buffer ebox-surface--buffer-surface))
|
||||||
|
(signals
|
||||||
|
(with-current-buffer buffer
|
||||||
|
ebox-surface--context-signals))
|
||||||
|
(revision (tp-surface-revision surface))
|
||||||
|
(region-id
|
||||||
|
(plist-get
|
||||||
|
(car (ebox-selector-query-buffer buffer "#scroll"))
|
||||||
|
:region-id)))
|
||||||
|
(should (= (ebox--scroll-region-by region-id 1 1) 1))
|
||||||
|
(should (= (tp-surface-revision surface) (1+ revision)))
|
||||||
|
(should (= (alist-get
|
||||||
|
region-id
|
||||||
|
(tp-signal-peek
|
||||||
|
(ebox-surface--signals-scroll signals)))
|
||||||
|
1))
|
||||||
|
(let* ((state (tp-surface-client-state surface))
|
||||||
|
(root (plist-get state :root-node))
|
||||||
|
(text (with-current-buffer buffer
|
||||||
|
(buffer-substring-no-properties
|
||||||
|
(point-min) (point-max)))))
|
||||||
|
(should (= (ebox-get (ebox--root-region-box root region-id)
|
||||||
|
:scroll-offset)
|
||||||
|
1))
|
||||||
|
(should (= (plist-get (ebox-scroll-state region-id)
|
||||||
|
:scroll-offset)
|
||||||
|
1))
|
||||||
|
(should (string-match-p "one" text))
|
||||||
|
(should (string-match-p "two" text))
|
||||||
|
(should-not (string-match-p "zero" text)))))
|
||||||
|
(when (buffer-live-p buffer)
|
||||||
|
(kill-buffer buffer)))))
|
||||||
|
|
||||||
|
(ert-deftest ebox-scroll-update-rejects-a-runtime-replaced-by-its-hook ()
|
||||||
|
"A stale scroll candidate must not overwrite a hook publication."
|
||||||
|
(ebox-surface-test--reset-render-state)
|
||||||
|
(let ((buffer (generate-new-buffer " *ebox-scroll-hook-race*")))
|
||||||
|
(unwind-protect
|
||||||
|
(let ((ebox-viewport-width 240)
|
||||||
|
(ebox-viewport-height 2))
|
||||||
|
(ebox-render-to-buffer
|
||||||
|
buffer
|
||||||
|
(ebox-create :id "scroll" :content "zero\none\ntwo\nthree"
|
||||||
|
:width '(120) :height 2 :overflow 'scroll))
|
||||||
|
(let* ((surface (with-current-buffer
|
||||||
|
buffer ebox-surface--buffer-surface))
|
||||||
|
(revision (tp-surface-revision surface))
|
||||||
|
(handle (ebox-region-resolve buffer "scroll"))
|
||||||
|
(region-id
|
||||||
|
(plist-get
|
||||||
|
(car (ebox-selector-query-buffer buffer "#scroll"))
|
||||||
|
:region-id))
|
||||||
|
(ebox-incremental-before-runtime-mutation-hook
|
||||||
|
(list
|
||||||
|
(lambda (target kind)
|
||||||
|
(when (and (eq target buffer) (eq kind 'scroll))
|
||||||
|
(let ((ebox-incremental--runtime-mutation-hooks-inhibited-p
|
||||||
|
t))
|
||||||
|
(ebox-region-update handle :color "#2563EB")))))))
|
||||||
|
(should-error (ebox--scroll-region-by region-id 1 1))
|
||||||
|
(should (= (tp-surface-revision surface) (1+ revision)))
|
||||||
|
(should (= (plist-get (ebox-scroll-state region-id) :scroll-offset)
|
||||||
|
0))
|
||||||
|
(should (equal
|
||||||
|
(ebox-get
|
||||||
|
(ebox--root-region-box
|
||||||
|
(plist-get (tp-surface-client-state surface) :root-node)
|
||||||
|
region-id)
|
||||||
|
:color)
|
||||||
|
"#2563EB"))))
|
||||||
|
(when (buffer-live-p buffer)
|
||||||
|
(kill-buffer buffer)))))
|
||||||
|
|
||||||
(provide 'ebox-surface-tests)
|
(provide 'ebox-surface-tests)
|
||||||
|
|
||||||
;;; ebox-surface-tests.el ends here
|
;;; ebox-surface-tests.el ends here
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user