- new commands ekp-justify-buffer / ekp-unjustify-buffer
- ekp-justify-region / ekp-unjustify-region without an active region
now act on the hard paragraph at point (DWIM) instead of erroring
- ekp-auto-justify-mode remaps fill-paragraph to the new
ekp-refill-paragraph — plain M-q treated glue spaces and soft
breaks as content and destroyed the original whitespace
- ekp-org-setup / ekp-markdown-setup wire the existing skip-face
presets in one call; enabling the mode in an Org/Markdown buffer
applies them automatically unless the user configured their own.
The markdown setup also stops font-lock from managing the display
property: refontification was stripping the pixel glue and
wrecking the layout permanently.
- justify runs font-lock-ensure over the region first when
face-based verbatim detection is configured — paragraphs jit-lock
had never fontified (off-screen code blocks) were not skipped
- showcase/demo buffers disable undo recording (width sweeps
generated thousands of useless undo entries)
5 new ERT tests (81 total).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- isearch searches the logical text: entering isearch temporarily
un-justifies (spans remembered as markers), exiting restores the
layout. Justified CJK lines contain real injected spaces, which
made even same-line phrases unfindable.
- the kill ring receives the logical text via a buffer-local
filter-buffer-substring-function: yanking justified text elsewhere
transports words, not the source window's pixel layout.
- multi-window: the layout width is now the narrowest window showing
the buffer (was: whichever window resized last, leaving the other
window overflowing), and window-configuration-change-hook catches
the buffer becoming displayed, splits, and closed windows.
- paragraphs containing field or read-only text (comint prompts)
are never re-written; interactive justify/unjustify on read-only
buffers signals instead of editing through inhibit-read-only.
- edit re-flows wait for an active input-method composition (quail
preedit) to finish instead of re-writing the text under it.
4 new ERT tests (76 total).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Justification is a re-layout of the same logical text; it now behaves
that way toward the rest of the editor:
- saving a justified buffer writes the LOGICAL text: before-save-hook
unjustifies (spans remembered as markers), after-save-hook restores
the justified view; the deterministic pair stays off undo history.
Previously soft newlines and break hyphens were silently persisted
to disk and the original whitespace was unrecoverable after reopen.
- renderer marker properties (ekp-glue/soft-break/soft-hyphen/hidden/
justified) are registered in text-property-default-nonsticky: text
typed after a glue no longer inherits the marker and is no longer
deleted as a synthesized space by the next re-flow.
- pure re-layout preserves buffer-modified-p when it was nil: no more
lock files, auto-saves and "buffer modified" prompts from merely
enabling ekp-auto-justify-mode.
- changes applied by undo are not re-dirtied (no timer war against
the user's undo sequence).
- switching major modes tears justified state down cleanly
(change-major-mode-hook), widening first so narrowing never leaves
justified orphans.
5 new ERT tests (72 total).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Buffers past ekp-auto-justify-lazy-threshold (20k chars) no longer
re-justify wholesale on a width change: the visible span updates
synchronously and the rest follows in polite background chunks
(run-with-timer ticks that yield to pending input). Measured on a
300-paragraph, 132k-char article: perceived latency 332 ms → 15 ms,
background completion ~0.5 s.
Fixed in the process: adjacent chunk markers shared a boundary, and
the earlier chunk's delete+insert pushed the next chunk's start
marker back over the freshly inserted text — chunks grew linearly
(2.2k, 4.9k, 7.4k… chars) and background work went quadratic (14 s).
Start markers now use insertion-type t. Regression test asserts the
chunked result equals the one-shot result property-for-property.
Also: org/markdown skip-face presets (ekp-region-org-skip-faces /
ekp-region-markdown-skip-faces), MELPA header hygiene (Version + URL
on the main file, stale Package-Requires dropped from ekp-hyphen.el),
readme notes.
67 ERT green; fuzz 300/300.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Paragraphs stay verbatim (never justified, byte-identical through
justify/auto-mode/unjustify) when they carry the ekp-verbatim text
property, wear a face from ekp-region-skip-faces (point it at
org-block / markdown-code-face and code blocks are exempt), or match
the buffer-local ekp-region-skip-predicate — three layers of the same
general mechanism, no mode-specific code. Inline code inside prose
is the existing ekp-no-break atom (rigid, unbreakable, unhyphenated).
New commands: ekp-verbatim-region / ekp-clear-verbatim-region.
Core engine untouched: block protection lives entirely in the region
layer, so skipped text never even reaches the tokenizer.
Tests: 66 ERT green (property / face / predicate paths, exact
roundtrips around justified prose).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ekp-parshape gives each line its own (INDENT . WIDTH) — TeX \parshape
— with the last entry repeating; ekp-first-line-indent is sugar for
the CJK 2-em paragraph convention (t derives 2 em from the paragraph's
CJK font, or pass pixels). Line-number-dependent widths require the
(position × line-count) DP, so both ride the loose engine and bypass
the C module, exactly like looseness.
Indents render as leading spacers marked ekp-glue, so buffer
roundtrips remain character- and property-exact. Composes with
alignment modes (indent adds to the centering lead) and protrusion.
Tests: 63 ERT green; fuzz 300/300.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The renderer (shared by both engines) is now lossless: synthesized
glue carries the original text it replaced (ekp-glue), soft line
breaks carry the whitespace swallowed around the break
(ekp-soft-break), break hyphens are marked (ekp-soft-hyphen), and
paragraph-edge whitespace survives as zero-display ekp-hidden text.
Orphaned ekp--combine-glues-and-boxes / ekp--interleave removed.
New ekp-region.el:
- ekp-justify-region / ekp-unjustify-region: in-place justification
with exact structural restore (character- and property-exact),
robust to edits made while justified
- ekp-auto-justify-mode: keeps the buffer justified at the window
width; debounced re-flow on window resize, incremental
per-paragraph re-justification after edits (served by the
paragraph cache)
- fix found in live GUI testing: buffer-local members of
window-size-change-functions receive the WINDOW as argument and
may run with an unrelated buffer current; the handler now resolves
window and buffer explicitly (regression test included)
Tests: 47 ERT (36 core + 11 region) passing; 300-case fuzz 0
failures (C/elisp parity unchanged); byte-compile clean with
error-on-warn. Verified interactively in GUI Emacs: justified at
1403px maximized, auto-reflowed to 614px on frame resize.
docs: interactive-use section in readme.md / readme_zh.md
ci: byte-compile list includes ekp-region.el
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>