sugarcraft / sugar-glow
PHP port of charmbracelet/glow — Markdown CLI viewer / pager composing CandyShine + Viewport. 8 stock themes, word-wrap, OSC 8 hyperlinks.
Requires
- php: ^8.3
- sugarcraft/candy-core: dev-master
- sugarcraft/candy-shine: dev-master
- sugarcraft/sugar-bits: dev-master
- symfony/console: ^6.4 || ^7.0
Requires (Dev)
- phpunit/phpunit: ^10.5
- sugarcraft/candy-testing: dev-master
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-09-21 21:17:20 UTC
README
SugarGlow
PHP port of charmbracelet/glow — a Markdown CLI viewer that composes CandyShine (rendering) and SugarBits Viewport (scrolling).
composer require sugarcraft/sugar-glow
CLI
sugarglow README.md # render to stdout (default) sugarglow -p README.md # open in a fullscreen pager git log -1 --pretty=%B | sugarglow -p # pipe stdin sugarglow --theme dracula README.md sugarglow --width 80 --no-hyperlinks README.md sugarglow --theme-config ./my-theme.json README.md
Flags:
--theme {ansi|plain|dark|light|notty|dracula|tokyo-night|pink|solarized|monokai|github}— picks a CandyShine preset.solarized,monokai, andgithubload JSON theme files fromthemes/.--style/-s— alias for--theme(glamour-compat).--theme-config <path>— load a custom JSON theme. Two shapes are detected automatically: a glamour config (top-level"document"block with nested element/chroma maps) is mapped byGlamourThemeonto a CandyShineTheme; the historic flat colour map goes throughTheme::fromJson. Overrides--theme.--width/-w <N>— word-wrap paragraphs / blockquotes / list bodies. 0 = no wrap.--no-hyperlinks— disable OSC 8 link envelopes; render links astext (url)instead.--pager/-p— open in a fullscreen pager.
Pager keys
Standard reader keys come from Viewport:
| Key | Action |
|---|---|
↑ / k |
line up |
↓ / j |
line down |
PgUp / b |
page up |
PgDn / space / f |
page down |
Ctrl+U / Ctrl+D |
half page |
Home / g |
top |
End / G |
bottom |
q / Esc / Ctrl+C |
exit |
When the pager is opened on a file (not stdin) it auto-reloads: the
model polls the source with FileWatcher::pollTuple() on a 0.5 s idle
tick and re-renders in place whenever the mtime/size tuple changes, so
edits to the Markdown appear without restarting. A delete-and-recreate
keeps the last good frame until the file returns.
Demos
Render to stdout
Fullscreen pager
Library API
Beyond the CLI, sugar-glow exposes two utility helpers for integrating its behaviour into other PHP projects.
FileWatcher
File watching via mtime polling — works cross-platform with no extensions.
use SugarCraft\Glow\FileWatcher; $watcher = new FileWatcher('/path/to/file.md'); // Check if modified since a given mtime if ($watcher->hasChangedSince($lastMtime)) { // re-render } // Generator-based watch loop (e.g., in a ReactPHP stream) foreach (FileWatcher::watch('/path/to/file.md', 500) as $changed) { // $changed === true each time the file is modified } // Stateless tuple API (drives the pager's auto-reload): $base = FileWatcher::snapshot($path) ?? [0, 0]; // [mtime, size] $changed = FileWatcher::pollTuple($path, ...$base); // new tuple or null
GlamourTheme
GlamourTheme parses charmbracelet/glamour style JSON (a "document"
block, per-element StylePrimitive maps, and a chroma token map) and
projects it onto CandyShine's Theme: document block affixes, indent and
margin, heading/paragraph/inline element styles, and the Keyword /
LiteralString / LiteralNumber / Comment chroma families. Unmappable fields
are exposed verbatim through element() / chroma() for integrations
that need them.
Width helpers
CJK and emoji width handling lives in SugarCraft\Core\Util\Width. Use it
directly for visual truncation, padding, and ANSI-aware measurement:
use SugarCraft\Core\Util\Width; Width::string('hello'); // 5 Width::string('你好'); // 4 (full-width) Width::string('📦'); // 2 (emoji) Width::padRight('hi', 8); // "hi " Width::truncate('hello world', 8); // "hello wo"
Shared foundations
sugar-glow uses candy-palette for terminal capability probing. The
RenderCommand::terminalSupportsColor() wrapper calls
\SugarCraft\Palette\Probe\TerminalProbe::run() and falls back to
true (assume color) if the probe throws — ensuring graceful degradation
on Windows, over SSH, and in old terminals.
Snapshot tests
Render output is covered by golden-file snapshot tests. Fixture files live
in tests/fixtures/ with a .golden extension and are compared against
actual ANSI byte output via SugarCraft\Testing\Snapshot\Assertions::assertGoldenAnsi().
To re-record fixtures after intentional output changes:
UPDATE_GOLDENS=1 vendor/bin/phpunit
Test
cd sugar-glow && composer install && vendor/bin/phpunit

