Skip to content

feat: fixed arrangement, text renderer, standalone compositor core - #12

Merged
chiply merged 1 commit into
mainfrom
feat/compositor-core-fixed-arrangement
Jul 1, 2026
Merged

feat: fixed arrangement, text renderer, standalone compositor core#12
chiply merged 1 commit into
mainfrom
feat/compositor-core-fixed-arrangement

Conversation

@chiply

@chiply chiply commented Jul 1, 2026

Copy link
Copy Markdown
Owner

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 the
resulting seam:

  1. A fixed arrangement — each indicator gets a dedicated, stable column
    instead of being densely packed by priority.
  2. A text renderer — draws glyphs straight into the built-in margin (no
    image), 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)

  • Provider registry, indicator collection/normalisation, column arrangement, and a
    new svg-margin--compose layout seam move into the core. No svg.el dependency;
    it loads standalone. The renderer requires it and draws the returned layout.
  • The one back-reference (registry → refresh) is broken via a
    svg-margin-refresh-function indirection, so the core never names a renderer
    symbol. Dependency is strictly one-directional (renderer → core).

fixed arrangement

  • svg-margin-arrangementfill (default, unchanged) or fixed (also accepts a
    per-side alist). In fixed, :column is a dedicated lane kept on every line;
    uncolumned indicators fill free lanes by priority.
  • svg-margin-provider-columns — declarative (provider . column) lanes (mirrors
    svg-margin-provider-sides).
  • svg-margin-fixed-collisiondrop (default) or float (re-flow the
    lower-priority loser into a free lane) for same-lane clashes.

text renderer

  • svg-margin-renderersvg (default) or text. text draws :text /
    svg-margin-shape-characters / svg-margin-text-fallback glyphs, coloured by
    :color/:face.
  • Works in emacs -nw; svg stays gated to graphical frames (with a nudge to
    switch renderer). Reuses the existing hover/tooltip/click machinery.
  • Caveat documented: the built-in margin reserves whole character cells, so only
    single-cell-advance glyphs align (wide Nerd Font/emoji/CJK icons overflow — keep
    svg for those).

Runtime commands

  • svg-margin-set-arrangement / svg-margin-set-renderer and their toggles, plus a
    refreshing 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-only image-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)

  • Renaming the core off the svg-margin-/SVG-specific prefix for an actual
    emacs-devel submission (core Emacs will want its own name + packaging).

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.
Copilot AI review requested due to automatic review settings July 1, 2026 14:01

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.el to host provider registry, indicator normalization/collection, column arrangement (fill/fixed), and the renderer seam svg-margin--compose.
  • Adds a text rendering 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 thread svg-margin-core.el
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 thread svg-margin-core.el
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 thread svg-margin-core.el
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 thread svg-margin-core.el
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 thread svg-margin.el
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)
@chiply
chiply merged commit fd066de into main Jul 1, 2026
8 checks passed
@chiply
chiply deleted the feat/compositor-core-fixed-arrangement branch July 1, 2026 14:08
@chiply chiply mentioned this pull request Jul 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants