feat: fixed arrangement, text renderer, standalone compositor core - #12
Merged
Conversation
Split the renderer-independent compositor into svg-margin-core.el and add a second arrangement mode and a second render backend, all driven through a clean compositor seam so the core can be proposed for Emacs upstream. - core: extract the provider registry, indicator collection, column arrangement, and the `svg-margin--compose' layout seam into svg-margin-core.el. It has no svg.el dependency and loads standalone; the renderer requires it and draws the layout it returns. A `svg-margin-refresh-function' indirection keeps the core renderer-agnostic. - arrangement: add `svg-margin-arrangement' -- `fill' (default, unchanged) or `fixed', which treats `:column' as a dedicated per-provider lane kept on every line. `svg-margin-provider-columns' assigns lanes declaratively and `svg-margin-fixed-collision' (drop/float) resolves same-lane clashes. - renderer: add `svg-margin-renderer' -- `svg' (default) or `text', which draws each indicator's glyph straight into the built-in margin (no image). The `text' renderer works in a terminal (emacs -nw) while `svg' stays gated to graphical frames. `svg-margin-shape-characters'/`svg-margin-text-fallback' supply glyphs; the built-in margin needs single-cell-advance glyphs. - commands: `svg-margin-set-arrangement'/`svg-margin-set-renderer' and their toggles, plus a refreshing defcustom `:set', apply changes live. Public API is preserved (requiring svg-margin still works and pulls in the core). Docs and tests updated across the compositor, both arrangements, both renderers, the terminal gate, and the commands.
There was a problem hiding this comment.
Pull request overview
This PR factors svg-margin’s provider/collection/arrangement logic into a renderer-independent compositor (svg-margin-core.el), then builds new functionality on top: a fixed column arrangement (dedicated lanes) and a text renderer that works in terminal frames (emacs -nw), plus runtime commands to switch arrangement/renderer live.
Changes:
- Introduces
svg-margin-core.elto host provider registry, indicator normalization/collection, column arrangement (fill/fixed), and the renderer seamsvg-margin--compose. - Adds a
textrendering backend alongside the existing SVG backend, and wires renderer selection + terminal gating. - Expands test coverage and updates README documentation for arrangement/renderer options and runtime switch commands.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
test/svg-margin-test.el |
Adds ERT coverage for fixed arrangement, arrangement dispatch, compose seam, provider-columns, text renderer helpers, renderer usability, and setter/toggle commands. |
svg-margin.el |
Converts to “renderer + window/margin engine” built on the compositor core; adds text renderer implementation, runtime commands, and updated renderer gating. |
svg-margin-core.el |
New compositor core: provider registry, indicator collection/normalization, per-side arrangement selection, fixed-lane arrangement, and svg-margin--compose layout seam. |
README.md |
Documents fixed-vs-fill arrangement, SVG-vs-text renderers, and runtime switching commands; updates terminal compatibility statement. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+85
to
+87
| (defcustom svg-margin-default-side 'left | ||
| "Default margin side for indicators that do not specify `:side'." | ||
| :type '(choice (const left) (const right))) |
Comment on lines
+101
to
+115
| :type '(choice (const :tag "Fill (dense, priority-ordered)" fill) | ||
| (const :tag "Fixed (dedicated columns)" fixed) | ||
| (alist :key-type (choice (const left) (const right)) | ||
| :value-type (choice (const fill) (const fixed)))) | ||
| :set #'svg-margin--custom-set) | ||
|
|
||
| (defcustom svg-margin-fixed-collision 'drop | ||
| "What the `fixed' arrangement does when indicators claim the same column. | ||
| `drop' keeps the higher-`:priority' indicator in the lane and discards the | ||
| other (reported when `svg-margin-debug'). `float' keeps the winner in the lane | ||
| and re-flows the loser into the lowest free column instead of dropping it. | ||
| Only consulted for a `fixed' `svg-margin-arrangement'." | ||
| :type '(choice (const :tag "Drop the loser" drop) | ||
| (const :tag "Re-flow the loser to a free lane" float)) | ||
| :set #'svg-margin--custom-set) |
Comment on lines
+117
to
+127
| (defcustom svg-margin-min-left-columns 0 | ||
| "Minimum number of columns to always reserve in the left margin. | ||
| The left margin still grows past this when a line needs more columns, but | ||
| never shrinks below it -- so reserving a baseline keeps buffer text from | ||
| shifting left/right as indicators come and go (up to this width)." | ||
| :type 'integer) | ||
|
|
||
| (defcustom svg-margin-min-right-columns 0 | ||
| "Minimum number of columns to always reserve in the right margin. | ||
| See `svg-margin-min-left-columns'." | ||
| :type 'integer) |
Comment on lines
+129
to
+153
| (defcustom svg-margin-provider-sides nil | ||
| "Alist of (PROVIDER-NAME . SIDE) overriding where a provider draws. | ||
| SIDE is `left' or `right'. An entry here forces every indicator from that | ||
| provider onto SIDE, even one that stamps its own `:side' -- so you can move | ||
| any provider (including a third-party one) to the other margin declaratively, | ||
| without editing its source. See also the `:side' argument to | ||
| `svg-margin-register-provider'." | ||
| :type '(alist :key-type symbol :value-type (choice (const left) (const right)))) | ||
|
|
||
| (defcustom svg-margin-provider-columns nil | ||
| "Alist of (PROVIDER-NAME . COLUMN) forcing a provider into a fixed lane. | ||
| COLUMN is a 0-based column index (0 nearest the text). An entry here overrides | ||
| every indicator's own `:column', so you can arrange any provider (including a | ||
| third-party one) into a dedicated column declaratively, without editing its | ||
| source. Most useful with a `fixed' `svg-margin-arrangement', where each column | ||
| is a dedicated lane. See also `svg-margin-provider-sides'." | ||
| :type '(alist :key-type symbol :value-type integer)) | ||
|
|
||
| (defcustom svg-margin-debug nil | ||
| "When non-nil, report indicators that are dropped. | ||
| A provider whose indicator has no `:pos'/`:line' (or an out-of-range one) has | ||
| that indicator silently skipped; likewise a `fixed'-arrangement indicator that | ||
| collides on an already-taken column. Enable this to get a message naming the | ||
| provider or the collision, which helps when writing a provider." | ||
| :type 'boolean) |
Comment on lines
772
to
775
| (let ((buffer (or buffer (current-buffer)))) | ||
| (when (and (buffer-live-p buffer) (display-graphic-p)) | ||
| (when (and (buffer-live-p buffer) (svg-margin--renderer-usable-p)) | ||
| (with-current-buffer buffer | ||
| (when (bound-and-true-p svg-margin-mode) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Makes svg-margin's column-allocation logic a self-contained, renderer-independent
compositor (
svg-margin-core.el) and builds two new capabilities on top of theresulting seam:
fixedarrangement — each indicator gets a dedicated, stable columninstead of being densely packed by priority.
textrenderer — draws glyphs straight into the built-in margin (noimage), which also works in a terminal (
emacs -nw).Motivated by Juri Linkov's "Shared margins" thread on emacs-devel
(2025-10 msg00188),
which asks that each margin indicator get its own dedicated column. Decoupling the
compositor is a prerequisite for proposing it upstream.
What's new
Compositor split (
svg-margin-core.el)new
svg-margin--composelayout seam move into the core. Nosvg.eldependency;it loads standalone. The renderer requires it and draws the returned layout.
svg-margin-refresh-functionindirection, so the core never names a renderersymbol. Dependency is strictly one-directional (renderer → core).
fixedarrangementsvg-margin-arrangement—fill(default, unchanged) orfixed(also accepts aper-side alist). In
fixed,:columnis a dedicated lane kept on every line;uncolumned indicators fill free lanes by priority.
svg-margin-provider-columns— declarative(provider . column)lanes (mirrorssvg-margin-provider-sides).svg-margin-fixed-collision—drop(default) orfloat(re-flow thelower-priority loser into a free lane) for same-lane clashes.
textrenderersvg-margin-renderer—svg(default) ortext.textdraws:text/svg-margin-shape-characters/svg-margin-text-fallbackglyphs, coloured by:color/:face.emacs -nw;svgstays gated to graphical frames (with a nudge toswitch renderer). Reuses the existing hover/tooltip/click machinery.
single-cell-advance glyphs align (wide Nerd Font/emoji/CJK icons overflow — keep
svgfor those).Runtime commands
svg-margin-set-arrangement/svg-margin-set-rendererand their toggles, plus arefreshing defcustom
:set, so changes apply live.Compatibility
No breaking changes —
(require 'svg-margin)still works and pulls in the core;existing provider/indicator API is unchanged.
Verification
eask compile— clean, no warnings.eask lint package/checkdoc/elisp-lint/relint— all pass.eask test— 56 tests, 55 as expected, 1 skipped (graphical-onlyimage-builds).Added coverage for the fixed arrangement + collision, the compose seam,
provider-columns, the text renderer, the terminal gate, and the commands.
Deferred (follow-ups, not in this PR)
svg-margin-/SVG-specific prefix for an actualemacs-devel submission (core Emacs will want its own name + packaging).