From 711b0b1abe9419c836103c4cf483345ff726edd7 Mon Sep 17 00:00:00 2001 From: Kinneyzhang Date: Tue, 8 Sep 2026 21:33:14 +0800 Subject: [PATCH] fix: preserve property defaults and Emacs 31 compatibility --- .gitignore | 1 + .phrase/docs/CHANGE.md | 7 + .phrase/docs/ISSUES.md | 1 + .../change_post_audit_hardening_20260820.md | 33 ++++ .../issue_emacs31_strict_compile_20260901.md | 44 ++++++ .../plan_post_audit_hardening_20260820.md | 12 ++ .../task_post_audit_hardening_20260820.md | 18 +++ AGENTS.md | 144 ++++++++++++++++++ ekp-buffer.el | 2 +- ekp-hyphen.el | 4 +- ekp-utils.el | 8 +- ekp.el | 14 +- tests/ekp-buffer-tests.el | 12 +- tests/ekp-live-commit-evaluator.el | 6 +- tests/ekp-tests.el | 20 ++- 15 files changed, 302 insertions(+), 24 deletions(-) create mode 100644 .phrase/phases/phase-kp-overhaul-20260726/issue_emacs31_strict_compile_20260901.md create mode 100644 AGENTS.md diff --git a/.gitignore b/.gitignore index 35ea26b..cf3cbd1 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ archive *.o *.elc AGENTS.md +!AGENTS.md diff --git a/.phrase/docs/CHANGE.md b/.phrase/docs/CHANGE.md index eb9c298..eefd371 100644 --- a/.phrase/docs/CHANGE.md +++ b/.phrase/docs/CHANGE.md @@ -2,6 +2,13 @@ ## phase-kp-overhaul-20260726 +- 2026-09-01 restore Emacs 31.1 strict byte compilation without behavior + changes (`task044`, `issue029`): + `.phrase/phases/phase-kp-overhaul-20260726/change_post_audit_hardening_20260820.md` +- 2026-08-31 restore the Emacs 31 fresh-source baseline while preserving + multibyte policy ownership and real-buffer marker noninheritance + (`task043`): + `.phrase/phases/phase-kp-overhaul-20260726/change_post_audit_hardening_20260820.md` - 2026-08-20 plan and implement post-audit hardening (`task037`–`task042`, `task030` source-fresh evaluator continuation; `issue022`–`issue028`): `.phrase/phases/phase-kp-overhaul-20260726/change_post_audit_hardening_20260820.md` diff --git a/.phrase/docs/ISSUES.md b/.phrase/docs/ISSUES.md index 536a140..96a8f41 100644 --- a/.phrase/docs/ISSUES.md +++ b/.phrase/docs/ISSUES.md @@ -28,3 +28,4 @@ - issue026 [ ] [Showcase loses automatic inline policy and test runners can false-green.](../phases/phase-kp-overhaul-20260726/issue_showcase_test_freshness_20260820.md) - issue027 [x] [Manual integrations remain installed with no projection owner.](../phases/phase-kp-overhaul-20260726/issue_integration_no_projection_20260820.md) - issue028 [x] [Locked live evaluator can measure stale candidate bytecode.](../phases/phase-kp-overhaul-20260726/issue_live_evaluator_source_freshness_20260820.md) +- issue029 [x] [Emacs 31.1 WERROR rejects obsolete single-binding let macros.](../phases/phase-kp-overhaul-20260726/issue_emacs31_strict_compile_20260901.md) diff --git a/.phrase/phases/phase-kp-overhaul-20260726/change_post_audit_hardening_20260820.md b/.phrase/phases/phase-kp-overhaul-20260726/change_post_audit_hardening_20260820.md index fe8f550..9c6d187 100644 --- a/.phrase/phases/phase-kp-overhaul-20260726/change_post_audit_hardening_20260820.md +++ b/.phrase/phases/phase-kp-overhaul-20260726/change_post_audit_hardening_20260820.md @@ -1,5 +1,38 @@ # Change: Post-Audit Hardening 2026-08-20 +## 2026-09-01 — Restore Emacs 31.1 strict byte compilation + +- **Modify** `ekp.el`, `ekp-utils.el`, and `ekp-hyphen.el` by replacing six + obsolete single-binding `if-let`/`when-let` forms with the behavior-identical + starred variants. +- **Modify** `tests/ekp-live-commit-evaluator.el` in the same mechanical way so + maintained performance tooling is strict-compile clean too. +- **Add** `issue029` and close `task044` with exact environment and evidence. +- **Behavior/Risk:** no data flow, branch, public API, layout, hyphenation, + font, module-path, or evaluator behavior changes. +- **Evidence:** root `make`; Emacs 31.1 WERROR production and changed-tool + compilation; normal and seed-20260901 random ERT 296/296; C build; 300-case + parity fuzz; release invariants; local 49-entry dictionary manifest/hash; + checkdoc; and diff-check. The user explicitly excluded the network-backed + fixed-upstream dictionary fetch, so it is not claimed. + +## 2026-08-31 — Restore the Emacs 31 fresh-source baseline + +- **Modify** `ekp.el` and `ekp-buffer.el` so layout and projection marker + properties update the toplevel default of Emacs 31's automatically + buffer-local `text-property-default-nonsticky`; new and existing buffers + that have no explicit override now inherit the nonsticky protocol. +- **Modify** `tests/ekp-tests.el` and `tests/ekp-buffer-tests.el` so policy + ownership fixtures remain mutable multibyte strings while mutating ASCII + code points that Emacs 31 permits in place. The tests still prove that + cache keys own the original policy values. +- **Behavior/Risk:** no layout algorithm or public policy changes. This fixes + marker noninheritance in real buffers and removes an Emacs 31-only fixture + mutation error. +- **Evidence:** focused ERT 3/3; source-first normal and seed-20260831 random + ERT 296/296; source load, release invariants, 49-entry dictionary manifest, + and pinned dictionary update checks pass. + ## 2026-08-20 — Lock follow-up goals and task boundaries - **Add** `plan_post_audit_hardening_20260820.md` with the evidence-backed diff --git a/.phrase/phases/phase-kp-overhaul-20260726/issue_emacs31_strict_compile_20260901.md b/.phrase/phases/phase-kp-overhaul-20260726/issue_emacs31_strict_compile_20260901.md new file mode 100644 index 0000000..fd152da --- /dev/null +++ b/.phrase/phases/phase-kp-overhaul-20260726/issue_emacs31_strict_compile_20260901.md @@ -0,0 +1,44 @@ +# issue029 — Emacs 31.1 strict compilation rejects obsolete let macros + +Status: resolved + +## Environment + +- GNU Emacs 31.1 development build `fac653279dcb` +- macOS Apple Silicon +- EKP HEAD `97cb6a6e3506` + +## Reproduction + +Run the repository CI byte-compilation command with +`byte-compile-error-on-warn` set to `t` against the four production files. + +## Expected vs actual + +- Expected: production and maintained test tools compile with warnings treated + as errors on the recorded Emacs executable. +- Actual: Emacs 31.1 rejects six single-binding `if-let`/`when-let` forms in + production; strict changed-tool compilation also finds three such forms in + `tests/ekp-live-commit-evaluator.el`. + +## Investigation and fix + +The macros became obsolete in Emacs 31.1. Every affected form has exactly one +binding, so the starred replacement has identical binding, truth, body, and +fallback behavior and needs no compatibility branch. Replace only those nine +macro names; do not alter layout or evaluator data flow. + +## Verification + +`task044` passed root integration compilation; WERROR production and changed +evaluator compilation; normal and seed-20260901 random ERT 296/296; warning-clean +C build; 300-case C/Elisp parity fuzz; release invariants; local 49-entry +dictionary manifest/hash; checkdoc; and diff-check. The network-backed +fixed-upstream dictionary fetch was stopped and excluded at the user's explicit +direction; no upstream-provenance claim is made. + +Resolved At: 2026-09-01 + +Resolved By: C1a nine-repository verification + +Commit: pending (protected pre-existing dirty worktree) diff --git a/.phrase/phases/phase-kp-overhaul-20260726/plan_post_audit_hardening_20260820.md b/.phrase/phases/phase-kp-overhaul-20260726/plan_post_audit_hardening_20260820.md index 7fc256b..130e8fa 100644 --- a/.phrase/phases/phase-kp-overhaul-20260726/plan_post_audit_hardening_20260820.md +++ b/.phrase/phases/phase-kp-overhaul-20260726/plan_post_audit_hardening_20260820.md @@ -80,3 +80,15 @@ Stop only when all tasks in this plan have current issue/task evidence, closed issues have user-visible or contract-level verification, `issue018` is either genuinely closed by its locked gate or explicitly remains open with fresh measurements, and the complete diff is committed and reviewed. + +## 2026-09-01 Emacs 31 strict-compile follow-up + +- `task044` / `issue029` replace the six remaining single-binding + `if-let`/`when-let` forms in production and the three forms in the live + evaluator with their semantically identical starred variants. +- No layout, hyphenation, font-selection, module-path, or evaluator behavior + may change. Existing ERT coverage locks those paths; Emacs 31.1 WERROR byte + compilation is the regression gate for the compatibility defect itself. +- Closure requires root integration compilation, strict production and changed + test-tool compilation, the complete normal and seeded-random ERT suites, C + build/parity fuzz, release/dictionary checks, checkdoc, and diff-check. diff --git a/.phrase/phases/phase-kp-overhaul-20260726/task_post_audit_hardening_20260820.md b/.phrase/phases/phase-kp-overhaul-20260726/task_post_audit_hardening_20260820.md index 54483b4..ba7e145 100644 --- a/.phrase/phases/phase-kp-overhaul-20260726/task_post_audit_hardening_20260820.md +++ b/.phrase/phases/phase-kp-overhaul-20260726/task_post_audit_hardening_20260820.md @@ -1,5 +1,23 @@ # Tasks: Post-Audit Hardening 2026-08-20 +- task044 [x] Restore Emacs 31.1 WERROR compilation by replacing obsolete + single-binding `if-let`/`when-let` forms with behavior-identical + `if-let*`/`when-let*` forms in production and the live evaluator. + - Source: `issue029` and the C1a nine-repository strict-compile gate. + - Validation: root `make`; Emacs 31.1 WERROR production and changed-tool + compilation; normal/random ERT 296/296; C build and 300-case fuzz; + release, local 49-entry dictionary manifest/hash, checkdoc, and diff gates. + The network-backed fixed-upstream dictionary fetch was explicitly excluded + by the user and is not claimed as evidence. + +- task043 [x] Restore the Emacs 31 fresh-source baseline without weakening + cache-ownership or marker-noninheritance contracts. Keep policy fixtures + mutable and multibyte, and install EKP marker properties in the global + `text-property-default-nonsticky` default used by real buffers. + - Source: Emacs 31 fresh-source failures in the M0a baseline. + - Validation: focused ERT 3/3, normal and seeded-random ERT 296/296, + source-load, release, and 49-entry dictionary gates. + - task037 [x] Validate public buffer widths before mutation and make rejected `ekp-justify-region` requests failure-atomic. Add red tests for zero, negative, non-integer, and projection-preservation cases. diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..e33a9c0 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,144 @@ +# 核心协议:意图识别(强制执行) + +在处理任何请求之前,你必须先识别用户的意图并遵循相应的协议。 + +## 1. 🌱 启动 / 立项 / 模糊想法 +**触发条件**:用户想要开启新项目、新阶段,或者只有一个模糊的想法。 +**行动**: +1. **扫描**:读取 `.phrase/modules/pr_faq.md` 的 YAML 元数据以确认匹配。 +2. **加载**:仅当匹配成功时,完整读取该文件内容。 +3. **执行**:扮演“严格的产品经理”角色。进行访谈以起草亚马逊风格的 PR/FAQ。 +4. **约束**:在 PR/FAQ 最终确定之前,禁止开始编写代码或拆解任务。 + +## 2. 🔨 编码 / 重构 / 审查 +**触发条件**:用户请求代码实现、Bug 修复、重构或代码审查。 +**行动**: +1. **扫描**:读取 `.phrase/modules/linus_coding.md` 的 YAML 元数据以确认匹配。 +2. **加载**:仅当匹配成功时,完整读取该文件内容。 +3. **执行**:扮演“Linus Torvalds”角色。 +4. **约束**:在编码前和编码过程中,严格执行“5 层思考模型”和“好品味”判断。 + +## 3. ✍️ 文案 / 营销 / 文档 +**触发条件**:用户需要撰写 README、发布说明、产品介绍或营销文案。 +**行动**: +1. **扫描**:读取 `.phrase/modules/copywriting.md` 的 YAML 元数据以确认匹配。 +2. **加载**:仅当匹配成功时,完整读取该文件内容。 +3. **执行**:扮演“转化率文案专家”角色。 +4. **约束**:遵循“结论先行”、“降低成本”、“可感知的具体”等 10 大原则。 + +## 4. 🌐 浏览器 / 网页自动化 / 爬虫 +**触发条件**:用户需要访问网页、抓取数据、截图、测试 Web UI 或填写表单。 +**行动**: +1. **扫描**:读取 `.phrase/modules/agent-browser.md` 的 YAML 元数据以确认匹配。 +2. **检查**:确保环境中已安装 `agent-browser` 依赖。 +3. **加载**:仅当匹配成功且依赖满足时,完整读取该文件内容。 +4. **执行**:使用 CLI 工具进行浏览器自动化操作。 + +## 5. 📋 任务执行(默认) +**触发条件**:用户想要执行一个具体的、已定义的任务。 +**行动**:遵循下方的“文档驱动开发”工作流。 + +--- + +“文档驱动开发(Doc-Driven Development)”:先锁定文档 → 拆 `taskNNN` → 实现与验证 → 回写文档。 + +--- + +## 0. 原则(按优先级) +- 仓库既有规范 > 本文;冲突时按 `README`/`STYLEGUIDE` 等执行,并在 `issue_*`/`change_*` 记录取舍。 +- 文档为事实来源:需求、交互、接口只能来自 `spec/plan/tech-refer/adr`。 +- 单次仅处理一个原子任务;所有改动可追溯到 `taskNNN` 与其依据(`spec`/`issue`/`adr`)。 +- 每个 `taskNNN` 必须说明验证方式(测试或手动步骤)。 +- 实现完成必须回写:`task_*`、`change_*`,必要时更新 `spec_*`/`issue_*`/`adr_*`。 + +--- + +## 1. 仓库结构与文档 +- 代码根:`App/`, `Core/`, `UI/`, `Shared/`, `Tests/`, `Assets/`, `Samples/`, `Schemas/`, `StackWM-Bridging-Header.h`。保持分层清晰,`Tests/` 镜像核心模块。 +- 文档根:`.phrase/` + - 阶段:`.phrase/phases/phase--/` + - 全局索引:`.phrase/docs/` +- `Docs/` 为外部文档,可继续独立存放。 + +--- + +## 2. Phase 工作流 +1. **Phase Gate**(仅当用户明确开启新阶段):在新 `phase-*` 目录创建最小集 `spec_*`, `plan_*`, `task_*`, 视需求补 `tech-refer_*`/`adr_*`,`issue_*` 可后置。 +2. **In-Phase Loop**(默认): + - 新需求 → 更新当前 `plan_*` → 拆 `taskNNN`。 + - 实现 → 在 `task_*` 中新增/更新并执行对应任务。 + - Bug → 在 `.phrase/docs/ISSUES.md` 登记 `issueNNN`,在 phase 写详情,再拆 `taskNNN`。 + - 不可逆决策 → 先写 `adr_*` 或在 `tech-refer_*` 增 “Decision”。 +3. **Task 闭环**:完成后需 + 1) 将 `task_*` 条目标记 `[x]` + 2) 在 phase `change_*` 记录条目,并于 `.phrase/docs/CHANGE.md` 加索引 + 3) 若影响交互,更新对应 `spec_*` + 4) 若解决问题,更新 `ISSUES.md` 和 issue 详情(含验证结论) + +当目标与当前 phase purpose 明显不同、需要独立里程碑或架构大重构时,可建议开启新 phase,但需用户确认。 + +### Phase 生命周期 +- 开启阶段:在 `.phrase/phases/phase--/` 下创建 `spec/plan/task/...`。 +- 阶段完结:用户确认后,将整个目录重命名为 `DONE-phase--/`,同时把主要文档也按规则改为 `DONE-PLAN-*`、`DONE-TASK-*` 等,确保一眼可见结项状态。 + +--- + +## 3. Task / Issue 规范 +- `taskNNN` 为三位递增 ID(`task001` 起),不可重排或复用;拆分/合并需创建新 ID 并在原任务注明流向。 +- 任何对 `task_*` 的增删改/勾选都要在当前 phase `change_*` 记录一次,可批量合并但必须可追溯。 +- 原子任务标准:一次工作会话可完成、产出可观察、可独立验证,既不过细也不过粗。 +- Issue: + - 全局索引:`.phrase/docs/ISSUES.md` 用 `issueNNN [ ]/[x]` 并链接 phase 详情。 + - 详情文件 `issue__.md` 需含环境、复现、调查、根因、修复、验证、关联的 `taskNNN`/提交。 + - 用户可感知问题需在标记 `[x]` 前获得确认,并记录 `Resolved At/By/Commit`。 + +--- + +## 4. Build / Test / Dev +- 首选仓库入口:若提供 Makefile、GitHub Actions、或 scripts/,优先使用。 +- windows系统下 emacs 路径: "C:\Users\26289\Apps\emacs-30.2\emacs-30.2\bin" +- 常见 Elisp 验证方式: + - 运行 ERT 测试:emacs -Q --batch -L . -l -f ert-run-tests-batch-and-exit + - 交互手动验证:emacs -Q -L . -l .el 后在 UI 中 M-x 执行命令 +- 可选:lint/格式(按仓库约定),例如 package-lint、checkdoc、byte-compile(若项目采用)。 +- 测试用例全部写在根目录的 tests/ 目录下 + +--- + +## 5. 编码与验证 +- 遵循仓库已有编码规范(缩进、命名、lexical-binding 等)。 +- 明确支持的 Emacs 版本范围;涉及 API 差异时要写清楚 fallback 或条件分支策略。 +- 尽量保持改动最小化:除非任务是“清理”,否则避免批量格式化与无关重排。 +- 关键路径加可诊断日志(遵循项目 logging 方案)。 +- 测试优先覆盖核心逻辑;UI/系统胶水可提供手动验证步骤。测试必须确定性,必要时注入依赖或 mock。 + +--- + +## 6. 文档更新与 Changelog +- `change_*`:phase 内的真实变更记录;每个完成的 `taskNNN` 至少一条,包含日期、文件/路径、Add|Modify|Delete、受影响函数、行为/风险说明,按时间倒序。 +- `.phrase/docs/CHANGE.md`:仅索引与摘要,指向对应 phase `change_*` 条目;可按工作会话批量更新。 +- `spec_*`/`plan_*`/`tech-refer_*`/`adr_*`/`issue_*` 均需随变更回写(增量即可),保持单一事实来源。 + +--- + +## 7. 提交、PR 与安全 +- 默认使用 Conventional Commits(`feat:`, `fix:`, `docs:`, `test:`, `chore:` 等),一份提交聚焦单个 `taskNNN`。 +- PR 描述需列出关联的 `taskNNN`/`issueNNN`、动机、行为变化、验证方式、风险/回滚方案,并在 UI 变化时附截图/GIF。 +- 禁止提交密钥、token、证书、真实用户数据;涉及权限/配置的任务,需在 `spec_*` 和 `tech-refer_*` 清楚描述失败反馈、API 边界与排查方式。 + +--- + +## 8. 模板速览 +- `spec`: Summary / Goals & Non-goals / User Flows(操作→反馈→回退)/ Edge Cases / Acceptance Criteria +- `plan`: Milestones / Scope / Priorities / Risks & Dependencies /(可选)Rollback +- `tech-refer`: Options / Proposed Approach / Interfaces & APIs / Trade-offs / Risks & Mitigations +- `task`: `task001 [ ] 产出 + 验证方式 + 影响范围` +- `issue`: `issueNNN [ ] Summary + Environment + Repro + Expected vs Actual + Investigation + Fix + Verification + User Confirmation + Resolved At/By/Commit` +- `adr`: Context / Decision / Alternatives / Consequences / Rollback + +--- + +## 9. 协作表达提示 +- 解释方案时优先描述用户操作(快捷键/鼠标/命令)、可见反馈、撤销/失败路径、边界情况。 +- 引用文档时用“文件名 + 小节”口语化说明,不逐字背诵。 +- 提供可选方案时说明它们属于当前还是后续里程碑,帮助用户决策。 diff --git a/ekp-buffer.el b/ekp-buffer.el index 1be87d1..6348c3a 100644 --- a/ekp-buffer.el +++ b/ekp-buffer.el @@ -253,7 +253,7 @@ caps the narrowest live window at N pixels." "Unicode whitespace that cannot use the ASCII-space projection path.") (dolist (property ekp-buffer--owned-properties) - (setf (alist-get property text-property-default-nonsticky) t)) + (ekp--make-text-property-default-nonsticky property)) ;;; Setup diff --git a/ekp-hyphen.el b/ekp-hyphen.el index b211acc..371bd48 100644 --- a/ekp-hyphen.el +++ b/ekp-hyphen.el @@ -169,7 +169,7 @@ LANGUAGE identifies the dictionary in that condition." (lambda (m) (string (string-to-number (match-string 1 m) 16))) line)) - (when-let ((parsed (ekp-hyphen--parse-pattern line))) + (when-let* ((parsed (ekp-hyphen--parse-pattern line))) (puthash (car parsed) (cdr parsed) patterns) (setq maxlen (max maxlen (length (car parsed)))))))) (forward-line 1))) @@ -227,7 +227,7 @@ by default the dictionary's own LEFTHYPHENMIN/RIGHTHYPHENMIN apply (let ((path (or (and lang (ekp-hyphen--resolve-lang lang)) file))) (unless path (signal 'ekp-hyphen-dictionary-not-found (list lang))) - (when-let ((count (gethash path ekp-hyphen--unsupported-cache))) + (when-let* ((count (gethash path ekp-hyphen--unsupported-cache))) (signal 'ekp-hyphen-unsupported-pattern (list (or lang path) path count))) (let ((h (or (gethash path ekp-hyphen--cache) diff --git a/ekp-utils.el b/ekp-utils.el index c15d6ab..eb6184f 100644 --- a/ekp-utils.el +++ b/ekp-utils.el @@ -122,7 +122,7 @@ Use the blank glyph of STRING's Latin font; for a monospace font that width is the space's own advance." ;; font is monospace, use the pixel of blank ;; as word spacing pixel - (if-let ((font-family (ekp-monospace-p string))) + (if-let* ((font-family (ekp-monospace-p string))) (ekp--measured-width (propertize " " 'face `(:family ,font-family))) (let* ((letter (ekp-get-latin-letter string)) @@ -134,14 +134,14 @@ that width is the space's own advance." (defun ekp-latin-font (string) "Return the font family used for STRING's Latin letters. Fall back to the default face family when STRING has no Latin letter." - (if-let ((letter (ekp-get-latin-letter string))) + (if-let* ((letter (ekp-get-latin-letter string))) (ekp-font-family letter) (face-attribute 'default :family))) (defun ekp-cjk-font (string) "Return the font family used for STRING's CJK characters. Fall back to the family of a sample CJK glyph when STRING has none." - (if-let ((letter (ekp-get-cjk-letter string))) + (if-let* ((letter (ekp-get-cjk-letter string))) (ekp-font-family letter) (ekp-font-family "牛"))) @@ -344,7 +344,7 @@ by merging boxes." (defun ekp-c-module-dir () "Return the C module directory." - (when-let ((root-dir (ekp-root-dir))) + (when-let* ((root-dir (ekp-root-dir))) (expand-file-name "ekp_c" root-dir))) (defun ekp-c-module-file () diff --git a/ekp.el b/ekp.el index a3616da..c15d1ee 100644 --- a/ekp.el +++ b/ekp.el @@ -1790,8 +1790,8 @@ POLICY-ANALYSIS is a precomputed result from `ekp--analyze-policies'." (while (and (>= tail 0) (= (aref old tail) ?\s)) (setq tail (1- tail))) (when (>= tail 0) - (when-let ((space - (cl-position ?\s old :from-end t :end (1+ tail)))) + (when-let* ((space + (cl-position ?\s old :from-end t :end (1+ tail)))) (1+ space)))))) (defun ekp--para-has-box-type-p (para type) @@ -3606,11 +3606,19 @@ Only property-free, context-stable 1D layouts take this fast path." '(ekp-glue ekp-soft-break ekp-soft-hyphen ekp-hidden ekp-justified) "Text properties owned by the lossless render/inversion protocol.") +(defun ekp--make-text-property-default-nonsticky (property) + "Make PROPERTY nonsticky by default in every buffer." + (let ((defaults (copy-tree + (default-toplevel-value + 'text-property-default-nonsticky)))) + (setf (alist-get property defaults) t) + (set-default-toplevel-value 'text-property-default-nonsticky defaults))) + ;; Text typed next to a marker character must never inherit the ;; marker: a self-inserted char inheriting `ekp-glue' would be treated ;; as a synthesized space by the next unjustification and deleted. (dolist (prop ekp--layout-marker-properties) - (setf (alist-get prop text-property-default-nonsticky) t)) + (ekp--make-text-property-default-nonsticky prop)) (defun ekp--hide-string (string) "Return STRING marked `ekp-hidden' and displayed as nothing." diff --git a/tests/ekp-buffer-tests.el b/tests/ekp-buffer-tests.el index 40868e8..9f7e21e 100644 --- a/tests/ekp-buffer-tests.el +++ b/tests/ekp-buffer-tests.el @@ -3481,8 +3481,8 @@ a zero budget still makes progress (exactly one chunk)." (ekp-buffer-test--with-text "" (text-mode) (let* ((ekp-use-c-module nil) - (suffix (copy-sequence "uX")) - (line-start-extra (copy-sequence "《")) + (suffix (copy-sequence (string-as-multibyte "uX"))) + (line-start-extra (copy-sequence "《X")) (ekp-number-unit-suffixes (list suffix)) (ekp-token-break-policies '((number-unit . no-break))) (ekp-kinsoku-profile 'custom) @@ -3499,18 +3499,20 @@ a zero budget still makes progress (exactly one chunk)." (apply original arguments)))) (setq first (cdr (ekp-buffer--live-plan-entry text width))) (setq key (caar ekp-buffer--live-plan-cache)) + (should (multibyte-string-p suffix)) + (should (multibyte-string-p line-start-extra)) (should (seq-some (lambda (string) (equal string "uX")) (ekp-buffer-test--strings-in-tree key))) - (should (seq-some (lambda (string) (equal string "《")) + (should (seq-some (lambda (string) (equal string "《X")) (ekp-buffer-test--strings-in-tree key))) (store-substring suffix 1 "Y") - (store-substring line-start-extra 0 "》") + (store-substring line-start-extra 1 "Y") (let ((changed (cdr (ekp-buffer--live-plan-entry text width)))) (should-not (eq changed first)) (should (= calls 2))) (should (seq-some (lambda (string) (equal string "uX")) (ekp-buffer-test--strings-in-tree key))) - (should (seq-some (lambda (string) (equal string "《")) + (should (seq-some (lambda (string) (equal string "《X")) (ekp-buffer-test--strings-in-tree key))))))) (provide 'ekp-buffer-tests) diff --git a/tests/ekp-live-commit-evaluator.el b/tests/ekp-live-commit-evaluator.el index 92f78c8..7877aee 100644 --- a/tests/ekp-live-commit-evaluator.el +++ b/tests/ekp-live-commit-evaluator.el @@ -71,19 +71,19 @@ (defun ekp-live-commit-evaluator--env-number (name fallback) "Return numeric environment variable NAME, or FALLBACK." - (if-let ((value (getenv name))) + (if-let* ((value (getenv name))) (string-to-number value) fallback)) (defun ekp-live-commit-evaluator--env-numbers (name fallback) "Return comma-separated numeric environment variable NAME, or FALLBACK." - (if-let ((value (getenv name))) + (if-let* ((value (getenv name))) (mapcar #'string-to-number (split-string value "," t "[ \t]+")) fallback)) (defun ekp-live-commit-evaluator--env-strings (name fallback) "Return comma-separated string environment variable NAME, or FALLBACK." - (if-let ((value (getenv name))) + (if-let* ((value (getenv name))) (split-string value "," t "[ \t]+") fallback)) diff --git a/tests/ekp-tests.el b/tests/ekp-tests.el index 046b59e..0910654 100644 --- a/tests/ekp-tests.el +++ b/tests/ekp-tests.el @@ -2052,7 +2052,13 @@ module is bypassed automatically (it has no looseness support)." '(ekp-glue ekp-soft-break ekp-soft-hyphen ekp-hidden ekp-justified))) (dolist (property ekp--layout-marker-properties) - (should (eq (alist-get property text-property-default-nonsticky) t)))) + (should + (eq (alist-get property + (default-value 'text-property-default-nonsticky)) + t)) + (with-temp-buffer + (should + (eq (alist-get property text-property-default-nonsticky) t))))) (ert-deftest ekp-test-layout-plan-maps-source-gaps-and-breaks () "The semantic plan must retain source offsets for every visual decision." @@ -2813,8 +2819,8 @@ returned the stale looseness-0 layout for the same (string, width)." "Policy-analysis cache keys must own mutable public policy strings." (ekp-tests--with-clean-state (let* ((ekp-use-c-module nil) - (suffix (copy-sequence "uX")) - (line-start-extra (copy-sequence "《")) + (suffix (copy-sequence (string-as-multibyte "uX"))) + (line-start-extra (copy-sequence "《X")) (ekp-number-unit-suffixes (list suffix)) (ekp-token-break-policies '((number-unit . no-break))) (ekp-kinsoku-profile 'custom) @@ -2823,6 +2829,8 @@ returned the stale looseness-0 layout for the same (string, width)." (width 16) cache-key first-plan) (setq first-plan (ekp-layout-plan text width)) + (should (multibyte-string-p suffix)) + (should (multibyte-string-p line-start-extra)) (should (seq-some (lambda (interval) @@ -2835,13 +2843,13 @@ returned the stale looseness-0 layout for the same (string, width)." (should cache-key) (should (seq-some (lambda (string) (equal string "uX")) (ekp-tests--strings-in-tree cache-key))) - (should (seq-some (lambda (string) (equal string "《")) + (should (seq-some (lambda (string) (equal string "《X")) (ekp-tests--strings-in-tree cache-key))) (store-substring suffix 1 "Y") - (store-substring line-start-extra 0 "》") + (store-substring line-start-extra 1 "Y") (should (seq-some (lambda (string) (equal string "uX")) (ekp-tests--strings-in-tree cache-key))) - (should (seq-some (lambda (string) (equal string "《")) + (should (seq-some (lambda (string) (equal string "《X")) (ekp-tests--strings-in-tree cache-key))) (let ((changed-plan (ekp-layout-plan text width))) (should-not