Replace the physical ekp-region renderer with ekp-buffer text-property projection backed by shared semantic layout plans and stable live-edit transactions. Optimize the C-backed resize hot path, and add deterministic performance, ERT, GUI, fuzz, release, and documentation coverage for task017 through task032. BREAKING CHANGE: require ekp-buffer instead of ekp-region and rename module-owned ekp-region-* settings to ekp-buffer-*.
9.6 KiB
Spec: Source-Clean Text-Property KP Layout 2026-07-29
Summary
ekp-buffer.el presents Knuth-Plass layout without rewriting the buffer's
character stream. The core computes a reusable semantic layout plan;
manual buffer commands and ekp-auto-justify-mode project that plan using
text properties on existing source characters only.
The character sequence remains the document. Visual glue, breaks, indentation, and discretionary hyphens are display metadata: EKP creates no overlay and inserts no layout character.
Goals
- Keep
buffer-string,buffer-substring-no-properties, direct character access, search, syntax, markers, saving, and ordinary editing on the original character sequence. - Use the same core glue, break, indentation, and hyphen decisions for the compatible string renderer and the non-mutating buffer renderer.
- Make automatic layout stable while editing: committed prefix lines keep their projection until a structural commit, the current unfinished visual row stays under Emacs redisplay ownership, and a middle-line edit naturalizes only its local dirty island while preserving unaffected break anchors.
- Preserve undo, modified state, character-modified tick, point, mark, copy/kill, isearch, IME composition, narrowing, and mode lifecycle.
- Fail closed around display ownership: unsupported or conflicting hard paragraphs remain naturally displayed and are diagnosable.
Non-Goals
- Hiding EKP metadata from Lisp that explicitly inspects text properties. The clean-text guarantee concerns the character sequence; copy/kill strips EKP-owned projection metadata.
- Independent simultaneous layouts for different-width windows showing the same buffer. The narrowest live window is authoritative.
- Shrinking tabs or non-ASCII whitespace with
space-width. - Claiming verified bidi, Arabic, Hebrew, Indic shaping, ligatures, or arbitrary multi-codepoint grapheme behavior beyond the tested LTR Latin/CJK boundary.
- Retaining
(require 'ekp-region)or old private/configuration names.
User Flows
Load and use buffer commands
- The user adds the package to
load-path. - The user loads
(require 'ekp-buffer). M-x ekp-justify-regionprojects the active region, or the hard paragraph at point when no region is active.M-x ekp-justify-bufferprojects the accessible buffer.M-x ekp-unjustify-regionorekp-unjustify-bufferremoves only EKP-owned projection properties; source characters do not need restoration.
Edit with automatic layout
- The user enables
M-x ekp-auto-justify-mode. - A committed projection records the complete source baseline, one
whole-hard-line
ekp-layout-plan, line signatures, projected spans, and break anchors. Core DP receives no point, marker, window, or edit-state input. - Ordinary input within the current native visual row performs no whole-hard-line planning. Previously committed rows remain property-identical and the current row stays natural.
- Editing a committed middle row starts a dirty transaction. Only the affected local island becomes natural; following and preceding unaffected break anchors remain installed. Native wrapping absorbs local push/pull.
- Crossing into the next native visual row atomically replans the completed hard-line prefix once. Line-signature diffing limits property writes; the new current row remains natural.
- A hard newline/paragraph end, the next real edit outside the dirty island, explicit refill, or width/font/layout-context change commits the transaction. Point-only motion anywhere performs zero plan, cache, property, or layout work.
- Restoring the baseline source restores the saved projection immediately and exactly, including text properties, plan signatures, and spans.
- Completed paragraphs use the existing full Knuth-Plass projection.
- Disabling the mode cancels pending work, detaches markers, restores any prior copy filter, and removes every EKP-owned projection.
Use logical text during projection
- Search and isearch operate directly on source characters, including across display-only breaks and discretionary hyphens.
- Save and
write-regionserialize source characters; visual layout never reaches disk. - Copy/kill composes with an existing
filter-buffer-substring-function, removes only EKP-owned projection metadata from the copied string, and preserves delete semantics. - Lisp that needs a property-free string uses
buffer-substring-no-properties; property-aware APIs may inspect the EKP display metadata without seeing synthesized layout characters.
Diagnose an unsupported paragraph
- EKP detects foreign
display,line-prefix,wrap-prefix,composition, orinvisibleownership, or an exact shrink request for unsupported whitespace. - EKP leaves only that hard paragraph natural instead of stealing or approximating the foreign representation.
M-x ekp-diagnosereports the authoritative width and recorded conflicts. The user may remove the conflict or explicitly refill after changing the content/configuration.
Display Contract
-
ASCII source spaces use:
((space-width FACTOR) (min-width ((TARGET-PIXELS)))) -
A zero-source CJK/mixed gap applies
min-widthto the preceding complete grapheme, with a target equal to its natural advance plus the planned glue. -
Indentation uses
line-prefix. -
A source-whitespace break displays the first boundary character as a newline and any remaining boundary whitespace as empty.
-
A CJK or Latin discretionary break uses a replacing display string on an existing complete grapheme. It reproduces that grapheme, appends the optional hyphen, then a visual newline.
-
Every public projection value has an EKP owner property. Cleanup removes a public value only while it is still identical to the owned value.
-
All projection updates use
with-silent-modifications; owned properties are nonsticky.
Edge Cases
- Active IME composition defers projection and retries only for the current generation.
- Stale resize/background generations cannot publish.
- A hard paragraph over
ekp-auto-justify-paragraph-limitremains natural during automatic work; explicitekp-refill-paragraphis unbounded. - Large buffers reflow visible-first in time-bounded hard-paragraph chunks.
- Foreign property conflicts are isolated to their hard paragraph.
- Narrowing does not cause projection cleanup or installation outside the intended accessible operation; mode teardown widens to remove all owned state.
- Major-mode changes and mode disable leave the logical text and prior integrations intact.
- Reprojection preserves point, the mark marker, and
mark-activeindependently. An inactive historical mark must never become a visible selection merely because width or layout options changed. - Semantic live-prefix editing still requires real Emacs soft wrapping for
the active natural suffix.
ekp-auto-justify-modetemporarily disables bothtruncate-linesand narrow partial-window truncation, then restores their prior values and local/global binding state when the mode ends.
Acceptance Criteria
ekp.elexposes a semantic layout plan containing source offsets, per-line glue targets, break kinds, indentation, and discretionary hyphen decisions; the public string API remainsequal-including-propertiescompatible.- No EKP buffer path creates an overlay or inserts glue spaces, soft newlines, or discretionary hyphens into source text.
- ASCII gaps use the exact combined
space-width/absolute-pixelmin-widthform; zero-source gaps, indentation, breaks, and hyphens use the display contract above. - Source characters, positions, point/mark, modified state, undo history, character-modified tick, save output, search, and syntax remain logical.
- Automatic editing proves committed-projection and dirty-transaction
ownership: ordinary same-row edits perform no whole-hard-line planning or
unaffected property writes; middle-row edits preserve unrelated anchors;
reversible edits restore the complete projection
equal-including-properties; soft-wrap crossing publishes one atomic whole-hard-line prefix update; structural commit events use the existingekp-layout-plan; and point-only motion performs zero layout work. IME deferral, stale resize rejection, and exactly one complete KP pass after hard-paragraph completion remain required. Narrow side-by-side windows soft-wrap rather than horizontally scroll. A single leading/trailing space or tab is visible in the same command-loop turn, including whitespace exposed by deletion. - Copy/kill, isearch, foreign ownership, multiwindow narrowest-width policy, narrowing, resize, major-mode change, and teardown have deterministic tests.
- Exact GUI probes cover 1–64px ASCII and zero-source CJK glue, display-only break/hyphen/indentation, point, vertical motion, region, mouse, scaling/remapping/fringes, unchanged source, and zero overlays.
- Dynamic per-keystroke evidence proves mixed Latin/CJK same-row typing preserves the committed projection, a real visual-row crossing atomically publishes all completed rows from one hard-line plan, exact source reversal restores the baseline projection, point-only motion preserves every state object and property exactly, and paragraph completion performs the full completed-paragraph KP transition.
- Default, permuted, and isolated ERT; C/Elisp fuzz; warning-as-error Elisp/C builds; checkdoc; package/static/release gates; full diff review; independent code review; and independent architecture review pass.