feat(tp): isolate stylesheet rule domains

Give independent consumers their own rules, cascade layer ordering, and source-order counters so packages such as Ebox cannot pollute TP's default stylesheet or each other. Match generic class and state tokens by value and document caller-owned stylesheet lifecycle.

Verified: make clean; make test (728/728); make compile WERROR=t; checkdoc tp-style.el; git diff --check; Ebox make test against ../tp.
This commit is contained in:
Kinneyzhang 2026-08-06 13:59:08 +08:00
parent 07b84b0685
commit 1195297011
6 changed files with 139 additions and 43 deletions

View File

@ -174,7 +174,7 @@
Since 0.2.0 the library is organized as a family of layered modules (`tp-core`, `tp-style`, `tp-reactive`, `tp-layer`, `tp-ops`, `tp-search`, `tp-render`, `tp-stack`, `tp-query`, `tp-palette`, `tp-builtins`) behind the umbrella file `tp.el``(require 'tp)` still loads everything, so nothing changes for users. See [Installation](#installation) for the module map. Since 0.2.0 the library is organized as a family of layered modules (`tp-core`, `tp-style`, `tp-reactive`, `tp-layer`, `tp-ops`, `tp-search`, `tp-render`, `tp-stack`, `tp-query`, `tp-palette`, `tp-builtins`) behind the umbrella file `tp.el``(require 'tp)` still loads everything, so nothing changes for users. See [Installation](#installation) for the module map.
The TP 1.0 migration has begun with a pure schema-driven cascade kernel. `tp-style.el` adds namespaced property schemas, structured selectors, origin/importance/layer/specificity/scope/source-order precedence, property-specific inheritance, tagged CSS-wide values, inherited custom properties, explicit `tp-computed` sources, projection to Emacs properties, and read-only winner provenance. It deliberately owns no buffers, markers, mounts, or reactive subscriptions; retained surfaces will build on this kernel in later migration phases. The TP 1.0 migration has begun with a pure schema-driven cascade kernel. `tp-style.el` adds namespaced property schemas, structured selectors, origin/importance/layer/specificity/scope/source-order precedence, property-specific inheritance, tagged CSS-wide values, inherited custom properties, explicit `tp-computed` sources, projection to Emacs properties, and read-only winner provenance. `tp-stylesheet-create` gives each independent consumer its own rule, layer, and source-order domain instead of forcing unrelated packages through one process-global stylesheet. It deliberately owns no buffers, markers, mounts, or reactive subscriptions; retained surfaces will build on this kernel in later migration phases.
`tp-reactive.el` now also provides the TP 1.0 exact dependency runtime: signals invalidate only their subscribed bindings, binding reads form memoized binding→binding edges, conditional computations replace obsolete dependencies, and the outermost `tp-with-transaction` flushes each dirty binding once. Candidate signal writes and binding values/dependencies commit together; compute, cycle, publication, or transaction-participant failures roll them back. `tp-transaction-participate` lets a client promote opaque side state after surfaces publish while supplying its inverse for the same rollback boundary. Global and buffer-local variable adapters reuse this graph without making text properties or buffer scans the runtime database. `tp-reactive.el` now also provides the TP 1.0 exact dependency runtime: signals invalidate only their subscribed bindings, binding reads form memoized binding→binding edges, conditional computations replace obsolete dependencies, and the outermost `tp-with-transaction` flushes each dirty binding once. Candidate signal writes and binding values/dependencies commit together; compute, cycle, publication, or transaction-participant failures roll them back. `tp-transaction-participate` lets a client promote opaque side state after surfaces publish while supplying its inverse for the same rollback boundary. Global and buffer-local variable adapters reuse this graph without making text properties or buffer scans the runtime database.
@ -516,11 +516,11 @@ A complete overview of all tp.el functions organized by category:
| `tp-subject-create` / `tp-subject-set-children` | Build consumer-independent selector subjects and relations | | `tp-subject-create` / `tp-subject-set-children` | Build consumer-independent selector subjects and relations |
| `tp-selector-match-p` / `tp-selector-specificity` | Match the structured selector AST and compute its specificity | | `tp-selector-match-p` / `tp-selector-specificity` | Match the structured selector AST and compute its specificity |
| `tp-define-style` / `tp-style-declarations` / `tp-undefine-style` | Register, inspect, and remove defensive named declarations | | `tp-define-style` / `tp-style-declarations` / `tp-undefine-style` | Register, inspect, and remove defensive named declarations |
| `tp-stylesheet-add-rule` | Add an origin/layer/scope-aware structured rule | | `tp-stylesheet-create` / `tp-stylesheet-add-rule` | Create an isolated rule/layer domain and add origin/layer/scope-aware structured rules |
| `tp-wide-value` / `tp-important` / `tp-var` | Construct unambiguous cascade values without reserving ordinary Elisp symbols | | `tp-wide-value` / `tp-important` / `tp-var` | Construct unambiguous cascade values without reserving ordinary Elisp symbols |
| `tp-computed` | Mark the only function values TP should execute | | `tp-computed` | Mark the only function values TP should execute |
| `tp-compute-style` / `tp-project-style` | Compute canonical values/provenance and project final Emacs text properties | | `tp-compute-style` / `tp-project-style` | Compute canonical values/provenance and project final Emacs text properties |
| `tp-style-reset-rules` / `tp-style-reset` | Reset stylesheet-only state or the complete style registry | | `tp-style-reset-rules` / `tp-style-reset` | Reset one isolated/default stylesheet, or TP's global schemas, named styles, and default stylesheet |
#### Signals and Bindings #### Signals and Bindings

View File

@ -173,7 +173,7 @@
自 0.2.0 起,本库被组织为一组分层模块(`tp-core`、`tp-style`、`tp-reactive`、`tp-layer`、`tp-ops`、`tp-search`、`tp-render`、`tp-stack`、`tp-query`、`tp-palette`、`tp-builtins`),由伞形文件 `tp.el` 统一加载 — `(require 'tp)` 仍会加载全部模块,对用户没有任何变化。模块一览见[安装](#安装)。 自 0.2.0 起,本库被组织为一组分层模块(`tp-core`、`tp-style`、`tp-reactive`、`tp-layer`、`tp-ops`、`tp-search`、`tp-render`、`tp-stack`、`tp-query`、`tp-palette`、`tp-builtins`),由伞形文件 `tp.el` 统一加载 — `(require 'tp)` 仍会加载全部模块,对用户没有任何变化。模块一览见[安装](#安装)。
TP 1.0 迁移已经从纯 schema-driven cascade kernel 开始。`tp-style.el` 新增 namespaced property schema、结构化 selector、origin/importance/layer/specificity/scope/source-order 优先级、逐属性继承、带 tag 的 CSS-wide 值、可继承 custom property、显式 `tp-computed` source、Emacs 属性投影和只读 winner provenance。这个模块刻意不拥有 buffer、marker、mount 或响应式订阅;后续 retained surface 会建立在同一个 kernel 上。 TP 1.0 迁移已经从纯 schema-driven cascade kernel 开始。`tp-style.el` 新增 namespaced property schema、结构化 selector、origin/importance/layer/specificity/scope/source-order 优先级、逐属性继承、带 tag 的 CSS-wide 值、可继承 custom property、显式 `tp-computed` source、Emacs 属性投影和只读 winner provenance。`tp-stylesheet-create` 让每个独立 consumer 拥有自己的 rule、layer 与 source-order domain不必让无关包共享一份进程全局 stylesheet。这个模块刻意不拥有 buffer、marker、mount 或响应式订阅;后续 retained surface 会建立在同一个 kernel 上。
`tp-reactive.el` 现在也提供 TP 1.0 的精确依赖 runtimesignal 只 invalidates 真实订阅的 bindingbinding read 建立 memoized binding→binding edge条件计算会替换已经失效的旧依赖最外层 `tp-with-transaction` 对每个 dirty binding 最多 flush 一次。candidate signal write 与 binding value/dependencies 一起提交compute、cycle、publication 或 transaction participant 失败时一起回滚。`tp-transaction-participate` 允许 client 在 surface 发布后晋升 opaque side state同时为同一 rollback boundary 提供逆操作。global 和 buffer-local variable adapter 复用同一 graph不再把文本属性或 buffer scan 当作新 runtime database。 `tp-reactive.el` 现在也提供 TP 1.0 的精确依赖 runtimesignal 只 invalidates 真实订阅的 bindingbinding read 建立 memoized binding→binding edge条件计算会替换已经失效的旧依赖最外层 `tp-with-transaction` 对每个 dirty binding 最多 flush 一次。candidate signal write 与 binding value/dependencies 一起提交compute、cycle、publication 或 transaction participant 失败时一起回滚。`tp-transaction-participate` 允许 client 在 surface 发布后晋升 opaque side state同时为同一 rollback boundary 提供逆操作。global 和 buffer-local variable adapter 复用同一 graph不再把文本属性或 buffer scan 当作新 runtime database。
@ -511,11 +511,11 @@ tp.el 所有函数按类别组织的完整概览:
| `tp-subject-create` / `tp-subject-set-children` | 构造与具体 consumer 无关的 selector subject 和关系 | | `tp-subject-create` / `tp-subject-set-children` | 构造与具体 consumer 无关的 selector subject 和关系 |
| `tp-selector-match-p` / `tp-selector-specificity` | 匹配结构化 selector AST 并计算 specificity | | `tp-selector-match-p` / `tp-selector-specificity` | 匹配结构化 selector AST 并计算 specificity |
| `tp-define-style` / `tp-style-declarations` / `tp-undefine-style` | 注册、查询并移除防御性复制的 named declarations | | `tp-define-style` / `tp-style-declarations` / `tp-undefine-style` | 注册、查询并移除防御性复制的 named declarations |
| `tp-stylesheet-add-rule` | 添加支持 origin/layer/scope 的结构化 rule | | `tp-stylesheet-create` / `tp-stylesheet-add-rule` | 创建隔离的 rule/layer domain添加支持 origin/layer/scope 的结构化 rule |
| `tp-wide-value` / `tp-important` / `tp-var` | 构造无歧义 cascade 值,不占用普通 Elisp symbol | | `tp-wide-value` / `tp-important` / `tp-var` | 构造无歧义 cascade 值,不占用普通 Elisp symbol |
| `tp-computed` | 标记 TP 唯一应该执行的 function value | | `tp-computed` | 标记 TP 唯一应该执行的 function value |
| `tp-compute-style` / `tp-project-style` | 计算 canonical values/provenance 并投影最终 Emacs text properties | | `tp-compute-style` / `tp-project-style` | 计算 canonical values/provenance 并投影最终 Emacs text properties |
| `tp-style-reset-rules` / `tp-style-reset` | 仅重置 stylesheet state或重置完整 style registry | | `tp-style-reset-rules` / `tp-style-reset` | 重置某个隔离/default stylesheet或重置 TP 的全局 schema、named style 与 default stylesheet |
#### Signals 与 Bindings #### Signals 与 Bindings

View File

@ -177,6 +177,8 @@ tp 不会把外部属性收编成新的匿名层,也不会静默覆盖它。
- `tp-define-property` 只接受带 namespace 的 symbol id例如 `text/face``ebox/width`schema replacement 在完整验证后一次写入,失败不会破坏旧 definition。 - `tp-define-property` 只接受带 namespace 的 symbol id例如 `text/face``ebox/width`schema replacement 在完整验证后一次写入,失败不会破坏旧 definition。
- structured selector 原生支持 type/id/class/attribute/state、compound、descendant/child/adjacent/general sibling以及 `:is`、`:where` 和 `:not``tp-selector-specificity` 与 rule matching 使用同一 AST。 - structured selector 原生支持 type/id/class/attribute/state、compound、descendant/child/adjacent/general sibling以及 `:is`、`:where` 和 `:not``tp-selector-specificity` 与 rule matching 使用同一 AST。
- cascade 顺序固定为 importance、origin、layer、specificity、scope proximity、source ordernormal 与 important declaration 的 layer 顺序按 CSS 规则相反unlayered normal 高于 layered normal。 - cascade 顺序固定为 importance、origin、layer、specificity、scope proximity、source ordernormal 与 important declaration 的 layer 顺序按 CSS 规则相反unlayered normal 高于 layered normal。
- `tp-stylesheet-create` 建立相互隔离的 rule、layer order 与 source order domain`tp-stylesheet-add-rule :stylesheet SHEET` 只写入该实例,`tp-compute-style :rules SHEET` 只读取该实例,避免不同 consumer 通过默认全局 stylesheet 相互污染。
- 独立 stylesheet 的生命周期归创建者所有;`tp-style-reset` 只清理 TP 的全局 schema、named style 与 default stylesheet调用方必须用 `tp-style-reset-rules SHEET` 显式清理自己的实例。
- `initial`、`inherit`、`unset`、`revert` 和 `revert-layer` 必须由 `tp-wide-value` 显式构造,普通同名 Elisp symbol 保持 literal。 - `initial`、`inherit`、`unset`、`revert` 和 `revert-layer` 必须由 `tp-wide-value` 显式构造,普通同名 Elisp symbol 保持 literal。
- 普通 function value 永远不执行;只有 `tp-computed` 包装的 function 在计算时执行一次,其返回值不二次调用。 - 普通 function value 永远不执行;只有 `tp-computed` 包装的 function 在计算时执行一次,其返回值不二次调用。
- `--name` custom property 默认继承;`tp-var` 支持 fallback 和 cycle invalidation。属性 schema 的 normalizer/validator 在变量与 wide value 求值后执行。 - `--name` custom property 默认继承;`tp-var` 支持 fallback 和 cycle invalidation。属性 schema 的 normalizer/validator 在变量与 wide value 求值后执行。

View File

@ -169,7 +169,7 @@ Stage 2 canonical façade 是内部模型,不改变公开入口和历史返回
### tp-style.el纯 schema 与 cascade kernel ### tp-style.el纯 schema 与 cascade kernel
只依赖 tp-core不接触 buffer、marker、mount 或订阅。它拥有 namespaced property schema、structured subject/selector、origin/importance/layer/specificity/scope/source-order cascade、逐属性继承、tagged CSS-wide values、custom property/`tp-var`、显式 `tp-computed`、named style、provenance 和最终 Emacs property projection。 只依赖 tp-core不接触 buffer、marker、mount 或订阅。它拥有 namespaced property schema、structured subject/selector、origin/importance/layer/specificity/scope/source-order cascade、逐属性继承、tagged CSS-wide values、custom property/`tp-var`、显式 `tp-computed`、named style、provenance 和最终 Emacs property projection。默认 stylesheet 只服务便利入口;独立 consumer 使用 `tp-stylesheet-create` 持有隔离的 rule、layer 与 source-order domain。
普通 function value 保持 literal只有 `tp-computed` 才会求值一次。schema/rule 注册先完整校验再原子替换。shorthand 在进入候选集前展开一次,计算结果随后经过 variable/wide-value resolution、normalizer 和 validator。该模块提供后续 signal/binding 与 retained surface 共用的唯一 style 语义,不建立第二套 renderer。 普通 function value 保持 literal只有 `tp-computed` 才会求值一次。schema/rule 注册先完整校验再原子替换。shorthand 在进入候选集前展开一次,计算结果随后经过 variable/wide-value resolution、normalizer 和 validator。该模块提供后续 signal/binding 与 retained surface 共用的唯一 style 语义,不建立第二套 renderer。
@ -582,7 +582,7 @@ insert/copy/yank/stickiness/narrowing/indirect buffer 均不在 tp-query 中封
| 模块 | 状态 | 描述 | 清理 | | 模块 | 状态 | 描述 | 清理 |
|------|------|------|------| |------|------|------|------|
| tp-core | —— | **无可变状态**(仅 `tp-debug-mode`/`tp-debug-echo` 两个用户选项;调试日志写入 *tp-debug* 缓冲区,由 `tp-debug-clear` 清除) | - | | tp-core | —— | **无可变状态**(仅 `tp-debug-mode`/`tp-debug-echo` 两个用户选项;调试日志写入 *tp-debug* 缓冲区,由 `tp-debug-clear` 清除) | - |
| tp-style | schema、named style、stylesheet rule、cascade layer/source order registries | 纯 style definition 和 rule 状态;不保存 object、buffer 或 mount | `tp-style-reset` / `tp-style-reset-rules` | | tp-style | schema、named style、default stylesheet 与调用方持有的独立 stylesheet instances | 纯 style definition 和 rule/layer/source-order 状态;不保存 object、buffer 或 mount独立实例不被全局 reset 暗中清理 | `tp-style-reset` / `tp-style-reset-rules` |
| tp-reactive | signals、owner bindings、dependency subscriber sets、variable adapters、transaction-local scheduler state、public counters | TP 1.0 exact reactive graphnormal update 不扫描 buffer | `tp-reactive-reset`buffer-scoped signal 随 buffer kill | | tp-reactive | signals、owner bindings、dependency subscriber sets、variable adapters、transaction-local scheduler state、public counters | TP 1.0 exact reactive graphnormal update 不扫描 buffer | `tp-reactive-reset`buffer-scoped signal 随 buffer kill |
| tp-surface | buffer-local surfaces、object/mount/index、range anchors、property ledgers、root/scoped publication、plan/client-state/revision/report | TP 1.0 retained publicationglobal registry 仅 weak-reference | `tp-surface-unmount`buffer kill authoritative teardown | | tp-surface | buffer-local surfaces、object/mount/index、range anchors、property ledgers、root/scoped publication、plan/client-state/revision/report | TP 1.0 retained publicationglobal registry 仅 weak-reference | `tp-surface-unmount`buffer kill authoritative teardown |
| tp-reactive | `tp-reactive-deps` | 变量 → 依赖层 注册表 | `tp-reactive-reset` | | tp-reactive | `tp-reactive-deps` | 变量 → 依赖层 注册表 | `tp-reactive-reset` |

View File

@ -168,6 +168,51 @@
(should (tp-selector-match-p (should (tp-selector-match-p
'(:where (:type button) (:class missing)) second))))) '(:where (:type button) (:class missing)) second)))))
(ert-deftest tp-style-test-subject-class-and-state-tokens-use-value-equality ()
"Selectors match generic string tokens without requiring symbol identity."
(tp-style-test--isolated
(let ((subject (tp-subject-create
:type 'button
:classes (list (copy-sequence "primary"))
:state (list (copy-sequence "active")))))
(should (tp-selector-match-p '(:class "primary") subject))
(should (tp-selector-match-p '(:state "active") subject)))))
(ert-deftest tp-style-test-stylesheet-instances-isolate-rules-and-layers ()
"Independent stylesheets cannot leak rules or layer order into each other."
(tp-style-test--isolated
(tp-style-test--color-schema)
(let ((left (tp-stylesheet-create))
(right (tp-stylesheet-create))
(subject (tp-subject-create :type 'button)))
(tp-stylesheet-add-rule '(:type button) '(demo/color "blue")
:layer 'base :stylesheet left)
(tp-stylesheet-add-rule '(:type button) '(demo/color "red")
:layer 'components :stylesheet left)
(tp-stylesheet-add-rule '(:type button) '(demo/color "green")
:layer 'components :stylesheet right)
(tp-stylesheet-add-rule '(:type button) '(demo/color "purple")
:layer 'base :stylesheet right)
(should (equal (plist-get
(tp-style-test--values subject :rules left)
'demo/color)
"red"))
(should (equal (plist-get
(tp-style-test--values subject :rules right)
'demo/color)
"purple"))
(should (equal (plist-get (tp-style-test--values subject) 'demo/color)
"black"))
(tp-style-reset-rules left)
(should (equal (plist-get
(tp-style-test--values subject :rules left)
'demo/color)
"black"))
(should (equal (plist-get
(tp-style-test--values subject :rules right)
'demo/color)
"purple")))))
(ert-deftest tp-style-test-origin-importance-and-specificity-are-ordered () (ert-deftest tp-style-test-origin-importance-and-specificity-are-ordered ()
"Importance, origin, and selector specificity decide the winner in order." "Importance, origin, and selector specificity decide the winner in order."
(tp-style-test--isolated (tp-style-test--isolated

View File

@ -40,6 +40,12 @@
"Computed values, active properties, custom properties, and provenance." "Computed values, active properties, custom properties, and provenance."
values custom-properties active-properties provenance) values custom-properties active-properties provenance)
(cl-defstruct (tp-stylesheet
(:constructor tp-stylesheet-create ())
(:conc-name tp--stylesheet-))
"Independent ordered rule and cascade-layer collection."
rules layers (source-order 0))
(cl-defstruct (tp--computed-source (:constructor tp--make-computed-source)) (cl-defstruct (tp--computed-source (:constructor tp--make-computed-source))
function) function)
@ -49,10 +55,10 @@
name fallback-present-p fallback) name fallback-present-p fallback)
(cl-defstruct (tp--style-rule (:constructor tp--make-style-rule)) (cl-defstruct (tp--style-rule (:constructor tp--make-style-rule))
selector declarations origin layer scope specificity source-order) selector declarations origin layer layer-rank scope specificity source-order)
(cl-defstruct (tp--candidate (:constructor tp--make-candidate)) (cl-defstruct (tp--candidate (:constructor tp--make-candidate))
property value origin important layer specificity scope-distance property value origin important layer layer-rank specificity scope-distance
source-order declaration-order selector) source-order declaration-order selector)
(defconst tp--style-origin-order (defconst tp--style-origin-order
@ -178,14 +184,24 @@ OPTIONS support :initial, :inherits, :normalizer, :validator, :equality,
(tp-register-text-property property)) (tp-register-text-property property))
(copy-tree value)))) (copy-tree value))))
(defun tp-style-reset-rules () (defun tp-style-reset-rules (&optional stylesheet)
"Clear stylesheet rules and their cascade-layer order." "Clear rules and cascade-layer order in optional STYLESHEET.
(setq tp--stylesheet-rules nil With nil STYLESHEET, reset TP's process-wide default stylesheet."
tp--cascade-layers nil (if stylesheet
tp--style-source-order 0)) (progn
(unless (tp-stylesheet-p stylesheet)
(signal 'wrong-type-argument
(list 'tp-stylesheet-p stylesheet)))
(setf (tp--stylesheet-rules stylesheet) nil
(tp--stylesheet-layers stylesheet) nil
(tp--stylesheet-source-order stylesheet) 0))
(setq tp--stylesheet-rules nil
tp--cascade-layers nil
tp--style-source-order 0)))
(defun tp-style-reset () (defun tp-style-reset ()
"Clear all TP style schemas, definitions, rules, and ordering state." "Clear TP's schemas, named styles, and default stylesheet state.
Caller-owned stylesheet instances retain their rules until reset explicitly."
(clrhash tp--property-schemas) (clrhash tp--property-schemas)
(clrhash tp--named-styles) (clrhash tp--named-styles)
(setq tp--property-schema-order nil) (setq tp--property-schema-order nil)
@ -229,8 +245,8 @@ OPTIONS support :initial, :inherits, :normalizer, :validator, :equality,
(cl-defun tp-subject-create (&key type id classes attributes state parent children) (cl-defun tp-subject-create (&key type id classes attributes state parent children)
"Create a generic cascade subject from TYPE, ID, and metadata. "Create a generic cascade subject from TYPE, ID, and metadata.
CLASSES and STATE are symbol lists. ATTRIBUTES is an alist. PARENT and CLASSES and STATE are token lists compared with `equal'. ATTRIBUTES is an
CHILDREN must be TP subjects when present." alist. PARENT and CHILDREN must be TP subjects when present."
(when (and parent (not (tp-subject-p parent))) (when (and parent (not (tp-subject-p parent)))
(signal 'wrong-type-argument (list 'tp-subject-p parent))) (signal 'wrong-type-argument (list 'tp-subject-p parent)))
(let ((subject (tp--make-subject (let ((subject (tp--make-subject
@ -330,8 +346,8 @@ CHILDREN must be TP subjects when present."
(:universal t) (:universal t)
(:type (equal (nth 1 selector) (tp-subject-type subject))) (:type (equal (nth 1 selector) (tp-subject-type subject)))
(:id (equal (nth 1 selector) (tp-subject-id subject))) (:id (equal (nth 1 selector) (tp-subject-id subject)))
(:class (memq (nth 1 selector) (tp-subject-classes subject))) (:class (member (nth 1 selector) (tp-subject-classes subject)))
(:state (memq (nth 1 selector) (tp-subject-state subject))) (:state (member (nth 1 selector) (tp-subject-state subject)))
(:attr (tp--selector-match-attribute selector subject)) (:attr (tp--selector-match-attribute selector subject))
(:and (cl-every (lambda (item) (tp-selector-match-p item subject)) (:and (cl-every (lambda (item) (tp-selector-match-p item subject))
(cdr selector))) (cdr selector)))
@ -456,31 +472,57 @@ CHILDREN must be TP subjects when present."
(signal 'tp-invalid-style (list :origin origin))) (signal 'tp-invalid-style (list :origin origin)))
origin) origin)
(defun tp--register-cascade-layer (layer) (defun tp--register-cascade-layer (layer stylesheet)
"Register LAYER in first-seen order when non-nil." "Register LAYER in first-seen order for optional STYLESHEET.
(when (and layer (not (memq layer tp--cascade-layers))) Return LAYER's zero-based rank, or nil for an unlayered rule."
(setq tp--cascade-layers (append tp--cascade-layers (list layer))))) (when layer
(let ((layers (if stylesheet
(tp--stylesheet-layers stylesheet)
tp--cascade-layers)))
(unless (memq layer layers)
(setq layers (append layers (list layer)))
(if stylesheet
(setf (tp--stylesheet-layers stylesheet) layers)
(setq tp--cascade-layers layers)))
(cl-position layer layers))))
(defun tp--next-style-source-order (stylesheet)
"Increment and return source order for optional STYLESHEET."
(if stylesheet
(cl-incf (tp--stylesheet-source-order stylesheet))
(cl-incf tp--style-source-order)))
(defun tp--append-stylesheet-rule (rule stylesheet)
"Append RULE to optional STYLESHEET and return RULE."
(if stylesheet
(setf (tp--stylesheet-rules stylesheet)
(append (tp--stylesheet-rules stylesheet) (list rule)))
(setq tp--stylesheet-rules
(append tp--stylesheet-rules (list rule))))
rule)
;;;###autoload ;;;###autoload
(cl-defun tp-stylesheet-add-rule (cl-defun tp-stylesheet-add-rule
(selector declarations &key (origin 'author) layer scope) (selector declarations &key (origin 'author) layer scope stylesheet)
"Add a structured SELECTOR rule with DECLARATIONS. "Add a structured SELECTOR rule with DECLARATIONS.
ORIGIN defaults to `author'. LAYER is ordered by first appearance. SCOPE, ORIGIN defaults to `author'. LAYER is ordered by first appearance. SCOPE,
when non-nil, is a selector that must match the subject or an ancestor." when non-nil, is a selector that must match the subject or an ancestor.
STYLESHEET isolates rules and layer order from TP's default stylesheet."
(tp--validate-selector selector) (tp--validate-selector selector)
(when scope (tp--validate-selector scope)) (when scope (tp--validate-selector scope))
(tp--validate-rule-origin origin) (tp--validate-rule-origin origin)
(let ((expanded (tp--expand-declarations declarations))) (when (and stylesheet (not (tp-stylesheet-p stylesheet)))
(tp--register-cascade-layer layer) (signal 'wrong-type-argument (list 'tp-stylesheet-p stylesheet)))
(let ((rule (tp--make-style-rule (let* ((expanded (tp--expand-declarations declarations))
:selector (copy-tree selector) (layer-rank (tp--register-cascade-layer layer stylesheet))
:declarations (copy-tree expanded) (rule (tp--make-style-rule
:origin origin :layer layer :scope (copy-tree scope) :selector (copy-tree selector)
:specificity (tp-selector-specificity selector) :declarations (copy-tree expanded)
:source-order (cl-incf tp--style-source-order)))) :origin origin :layer layer :layer-rank layer-rank
(setq tp--stylesheet-rules :scope (copy-tree scope)
(append tp--stylesheet-rules (list rule))) :specificity (tp-selector-specificity selector)
rule))) :source-order (tp--next-style-source-order stylesheet))))
(tp--append-stylesheet-rule rule stylesheet)))
(defun tp--scope-distance (scope subject) (defun tp--scope-distance (scope subject)
"Return distance from SUBJECT to matching SCOPE, or nil." "Return distance from SUBJECT to matching SCOPE, or nil."
@ -504,6 +546,7 @@ DECLARATION-ORDER is the property's position within RULE."
:property property :value raw :property property :value raw
:origin (tp--style-rule-origin rule) :important important :origin (tp--style-rule-origin rule) :important important
:layer (tp--style-rule-layer rule) :layer (tp--style-rule-layer rule)
:layer-rank (tp--style-rule-layer-rank rule)
:specificity (tp--style-rule-specificity rule) :specificity (tp--style-rule-specificity rule)
:scope-distance (tp--scope-distance (tp--style-rule-scope rule) subject) :scope-distance (tp--scope-distance (tp--style-rule-scope rule) subject)
:source-order (tp--style-rule-source-order rule) :source-order (tp--style-rule-source-order rule)
@ -529,7 +572,8 @@ DECLARATION-ORDER is the property's position within RULE."
(pcase-let ((`(,raw . ,important) (tp--unwrap-important value))) (pcase-let ((`(,raw . ,important) (tp--unwrap-important value)))
(tp--make-candidate (tp--make-candidate
:property property :value raw :origin 'inline :property property :value raw :origin 'inline
:important important :layer nil :specificity '(1 0 0) :important important :layer nil :layer-rank nil
:specificity '(1 0 0)
:scope-distance most-positive-fixnum :scope-distance most-positive-fixnum
:source-order (1+ tp--style-source-order) :source-order (1+ tp--style-source-order)
:declaration-order declaration-order :declaration-order declaration-order
@ -557,12 +601,13 @@ DECLARATION-ORDER is the property's position within RULE."
(defun tp--layer-rank (candidate) (defun tp--layer-rank (candidate)
"Return cascade layer rank for CANDIDATE." "Return cascade layer rank for CANDIDATE."
(let ((layer (tp--candidate-layer candidate)) (let ((layer (tp--candidate-layer candidate))
(rank (tp--candidate-layer-rank candidate))
(important (tp--candidate-important candidate))) (important (tp--candidate-important candidate)))
(cond (cond
((and important (null layer)) -1000000) ((and important (null layer)) -1000000)
((null layer) 1000000) ((null layer) 1000000)
(important (- (or (cl-position layer tp--cascade-layers) 0))) (important (- (or rank 0)))
(t (or (cl-position layer tp--cascade-layers) 0))))) (t (or rank 0)))))
(defun tp--compare-number (left right) (defun tp--compare-number (left right)
"Compare LEFT and RIGHT, returning 1, -1, or 0." "Compare LEFT and RIGHT, returning 1, -1, or 0."
@ -868,12 +913,16 @@ When PROVENANCE-P is non-nil, also retain winning declaration facts."
parent-style provenance) parent-style provenance)
"Compute a deterministic style for SUBJECT. "Compute a deterministic style for SUBJECT.
DECLARATIONS are inline values. RULES defaults to the registered stylesheet; DECLARATIONS are inline values. RULES defaults to the registered stylesheet;
explicit nil disables stylesheet rules. PARENT-STYLE may be a computed style an isolated stylesheet instance selects only its rules, and explicit nil
or values plist. When PROVENANCE is non-nil, winner metadata is retained." disables stylesheet rules. PARENT-STYLE may be a computed style or values
plist. When PROVENANCE is non-nil, winner metadata is retained."
(unless (tp-subject-p subject) (unless (tp-subject-p subject)
(signal 'wrong-type-argument (list 'tp-subject-p subject))) (signal 'wrong-type-argument (list 'tp-subject-p subject)))
(let* ((active-rules (if (eq rules tp--style-all-rules) (let* ((active-rules (if (eq rules tp--style-all-rules)
tp--stylesheet-rules rules)) tp--stylesheet-rules
(if (tp-stylesheet-p rules)
(tp--stylesheet-rules rules)
rules)))
(candidate-table (candidate-table
(tp--group-candidates (tp--group-candidates
(tp--collect-candidates subject declarations active-rules))) (tp--collect-candidates subject declarations active-rules)))