termrender 0.7.3__tar.gz → 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- termrender-1.0.0/.git +1 -0
- {termrender-0.7.3 → termrender-1.0.0}/CHANGELOG.md +103 -28
- termrender-1.0.0/CLAUDE.md +25 -0
- {termrender-0.7.3 → termrender-1.0.0}/PKG-INFO +5 -5
- {termrender-0.7.3 → termrender-1.0.0}/README.md +4 -4
- termrender-1.0.0/src/termrender/CLAUDE.md +15 -0
- {termrender-0.7.3 → termrender-1.0.0}/src/termrender/__main__.py +15 -5
- {termrender-0.7.3 → termrender-1.0.0}/src/termrender/emit.py +13 -1
- {termrender-0.7.3 → termrender-1.0.0}/src/termrender/layout.py +10 -4
- {termrender-0.7.3 → termrender-1.0.0}/src/termrender/parser.py +13 -35
- termrender-1.0.0/src/termrender/renderers/mermaid.py +98 -0
- {termrender-0.7.3 → termrender-1.0.0}/src/termrender/renderers/text.py +1 -1
- {termrender-0.7.3 → termrender-1.0.0}/src/termrender/renderers/timeline.py +7 -9
- {termrender-0.7.3 → termrender-1.0.0}/src/termrender/style.py +9 -2
- {termrender-0.7.3 → termrender-1.0.0}/tests/test_column_alignment.py +8 -8
- termrender-1.0.0/tests/test_linebreak.py +79 -0
- termrender-1.0.0/tests/test_mermaid_compat.py +115 -0
- {termrender-0.7.3 → termrender-1.0.0}/tests/test_myst_gaps.py +25 -38
- termrender-0.7.3/CLAUDE.md +0 -64
- termrender-0.7.3/src/termrender/CLAUDE.md +0 -73
- termrender-0.7.3/src/termrender/renderers/mermaid.py +0 -48
- {termrender-0.7.3 → termrender-1.0.0}/.github/workflows/publish.yml +0 -0
- {termrender-0.7.3 → termrender-1.0.0}/.gitignore +0 -0
- {termrender-0.7.3 → termrender-1.0.0}/LICENSE +0 -0
- {termrender-0.7.3 → termrender-1.0.0}/design.json +0 -0
- {termrender-0.7.3 → termrender-1.0.0}/pyproject.toml +0 -0
- {termrender-0.7.3 → termrender-1.0.0}/requirements.json +0 -0
- {termrender-0.7.3 → termrender-1.0.0}/src/termrender/__init__.py +0 -0
- {termrender-0.7.3 → termrender-1.0.0}/src/termrender/blocks.py +0 -0
- {termrender-0.7.3 → termrender-1.0.0}/src/termrender/py.typed +0 -0
- {termrender-0.7.3 → termrender-1.0.0}/src/termrender/renderers/CLAUDE.md +0 -0
- {termrender-0.7.3 → termrender-1.0.0}/src/termrender/renderers/__init__.py +0 -0
- {termrender-0.7.3 → termrender-1.0.0}/src/termrender/renderers/borders.py +0 -0
- {termrender-0.7.3 → termrender-1.0.0}/src/termrender/renderers/charts.py +0 -0
- {termrender-0.7.3 → termrender-1.0.0}/src/termrender/renderers/code.py +0 -0
- {termrender-0.7.3 → termrender-1.0.0}/src/termrender/renderers/columns.py +0 -0
- {termrender-0.7.3 → termrender-1.0.0}/src/termrender/renderers/diff.py +0 -0
- {termrender-0.7.3 → termrender-1.0.0}/src/termrender/renderers/divider.py +0 -0
- {termrender-0.7.3 → termrender-1.0.0}/src/termrender/renderers/panel.py +0 -0
- {termrender-0.7.3 → termrender-1.0.0}/src/termrender/renderers/quote.py +0 -0
- {termrender-0.7.3 → termrender-1.0.0}/src/termrender/renderers/stat.py +0 -0
- {termrender-0.7.3 → termrender-1.0.0}/src/termrender/renderers/table.py +0 -0
- {termrender-0.7.3 → termrender-1.0.0}/src/termrender/renderers/tree.py +0 -0
- {termrender-0.7.3 → termrender-1.0.0}/tests/__init__.py +0 -0
- {termrender-0.7.3 → termrender-1.0.0}/tests/test_charts.py +0 -0
- {termrender-0.7.3 → termrender-1.0.0}/tests/test_diff.py +0 -0
- {termrender-0.7.3 → termrender-1.0.0}/tests/test_inline_badge.py +0 -0
- {termrender-0.7.3 → termrender-1.0.0}/tests/test_stat.py +0 -0
- {termrender-0.7.3 → termrender-1.0.0}/tests/test_tasklist.py +0 -0
- {termrender-0.7.3 → termrender-1.0.0}/tests/test_timeline.py +0 -0
- {termrender-0.7.3 → termrender-1.0.0}/tests/test_variable_colons.py +0 -0
termrender-1.0.0/.git
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
gitdir: /Users/silasrhyneer/Code/cli/termrender/.git/worktrees/tr-v100
|
|
@@ -1,18 +1,93 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
## v1.0.0 (2026-04-27)
|
|
5
|
+
|
|
6
|
+
### Documentation
|
|
7
|
+
|
|
8
|
+
- **claude-md**: Tighten root and src CLAUDE.md
|
|
9
|
+
([`7fe01ec`](https://github.com/crouton-labs/termrender/commit/7fe01ec2da0c7a0c1aa0f4eccf9b958496562be8))
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
- **mermaid**: Switch to :::mermaid directive, drop backtick fence forms
|
|
14
|
+
([`083f590`](https://github.com/crouton-labs/termrender/commit/083f5900b4b516f9df599dd08129afee34310e3d))
|
|
15
|
+
|
|
16
|
+
BREAKING CHANGE: ```mermaid fenced code blocks are no longer rendered as mermaid diagrams — they now
|
|
17
|
+
render as plain code blocks. Mermaid diagrams must use the new :::mermaid directive. The
|
|
18
|
+
MyST-style ```{name} backtick directive form is also removed; backtick fences now always produce a
|
|
19
|
+
code block, regardless of the language tag. Every directive uses ::: exclusively.
|
|
20
|
+
|
|
21
|
+
### Breaking Changes
|
|
22
|
+
|
|
23
|
+
- **mermaid**: ```mermaid fenced code blocks are no longer rendered as mermaid diagrams — they now
|
|
24
|
+
render as plain code blocks. Mermaid diagrams must use the new :::mermaid directive. The
|
|
25
|
+
MyST-style ```{name} backtick directive form is also removed; backtick fences now always produce a
|
|
26
|
+
code block, regardless of the language tag. Every directive uses ::: exclusively.
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
## v0.9.1 (2026-04-25)
|
|
30
|
+
|
|
31
|
+
### Bug Fixes
|
|
32
|
+
|
|
33
|
+
- **timeline**: Wrap event text instead of truncating with ellipsis
|
|
34
|
+
([`5af0e04`](https://github.com/crouton-labs/termrender/commit/5af0e04c3256075a2016e2cf8ae3d44e9e78c8fc))
|
|
35
|
+
|
|
36
|
+
Long event entries previously got clipped with `…` when they exceeded event_w. Now they wrap across
|
|
37
|
+
multiple lines, with continuation lines indented under the bullet and prefixed by the accent bar.
|
|
38
|
+
Layout height sums per-entry wrapped line counts so the block reserves the right space.
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
## v0.9.0 (2026-04-21)
|
|
42
|
+
|
|
43
|
+
### Bug Fixes
|
|
44
|
+
|
|
45
|
+
- **wrap**: Honor hard line breaks in wrap_text
|
|
46
|
+
([`a383f4d`](https://github.com/crouton-labs/termrender/commit/a383f4de66b3d8370bda500dd4c3771a591563aa))
|
|
47
|
+
|
|
48
|
+
Markdown hard breaks were parsed as \n spans but wrap_text only split on spaces, leaking raw \n into
|
|
49
|
+
wrapped output. Inside panels and columns this broke border alignment because visual_ljust padded
|
|
50
|
+
the string once, not per visual line.
|
|
51
|
+
|
|
52
|
+
wrap_text now recursively wraps each \n-separated segment; the text-renderer offset heuristic skips
|
|
53
|
+
\n as well as space between lines. Layout height calcs pick up the extra lines automatically.
|
|
54
|
+
|
|
55
|
+
### Features
|
|
56
|
+
|
|
57
|
+
- **spacing**: Add blank lines between hard breaks and top-level blocks
|
|
58
|
+
([`7610189`](https://github.com/crouton-labs/termrender/commit/761018928504cf9626678fe46b5ee66d5e899d5d))
|
|
59
|
+
|
|
60
|
+
Hard line breaks now render a blank line between the two sides (parser emits \n\n so wrap_text
|
|
61
|
+
naturally produces the gap), and DOCUMENT-level siblings are separated by a blank padded line so
|
|
62
|
+
paragraphs, headings, and blocks no longer visually run together.
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
## v0.8.0 (2026-04-18)
|
|
66
|
+
|
|
67
|
+
### Features
|
|
68
|
+
|
|
69
|
+
- **mermaid**: Preprocess sequence diagrams for mermaid-ascii compatibility
|
|
70
|
+
([`a642576`](https://github.com/crouton-labs/termrender/commit/a642576d41d5dbde372d7de2ab47745296a78e32))
|
|
71
|
+
|
|
72
|
+
mermaid-ascii only parses ->> / -->> arrows, participants, and self-loops; every other common
|
|
73
|
+
sequence-diagram construct made it fail and fall back to raw source. Rewrite Note lines into
|
|
74
|
+
self-loops, map -> / -x / --x / -) / --) / -- > onto the supported arrow pair, drop block keywords
|
|
75
|
+
(loop/alt/activate/ autonumber/end/…), and flatten <br/> to ' / '. Non-sequence diagrams pass
|
|
76
|
+
through unchanged.
|
|
77
|
+
|
|
78
|
+
|
|
4
79
|
## v0.7.3 (2026-04-15)
|
|
5
80
|
|
|
6
81
|
### Bug Fixes
|
|
7
82
|
|
|
8
83
|
- **code**: Wrap long code lines to fit layout width
|
|
9
|
-
([`31c6e59`](https://github.com/
|
|
84
|
+
([`31c6e59`](https://github.com/crouton-labs/termrender/commit/31c6e595a438c4ced8c61fff679b59d4ae55f938))
|
|
10
85
|
|
|
11
86
|
Code blocks previously used raw line count for height and let render_box grow beyond the layout
|
|
12
87
|
allocation. Now wraps source lines to the available content width in both layout and renderer.
|
|
13
88
|
|
|
14
89
|
- **parser**: Add directive trace and file-absolute line numbers to error messages
|
|
15
|
-
([`0f99ea0`](https://github.com/
|
|
90
|
+
([`0f99ea0`](https://github.com/crouton-labs/termrender/commit/0f99ea0310116f8fa06e933cd26126246d7a3b43))
|
|
16
91
|
|
|
17
92
|
Stray-closer and unclosed-directive errors now print the full open/close trace and, when nested
|
|
18
93
|
directives share a colon count, name the specific cause and suggest the fix. Recursive body
|
|
@@ -25,7 +100,7 @@ Stray-closer and unclosed-directive errors now print the full open/close trace a
|
|
|
25
100
|
### Bug Fixes
|
|
26
101
|
|
|
27
102
|
- **cli**: Default --tmux pane to 1/3 window width
|
|
28
|
-
([`d9c1bcc`](https://github.com/
|
|
103
|
+
([`d9c1bcc`](https://github.com/crouton-labs/termrender/commit/d9c1bccbe95a4e5cf1f975b82cbafde6d9d3807a))
|
|
29
104
|
|
|
30
105
|
Instead of preview-rendering at 80 cols to measure content width, default to (window_width - 2) // 3
|
|
31
106
|
for a consistent 1/3 split.
|
|
@@ -36,7 +111,7 @@ Instead of preview-rendering at 80 cols to measure content width, default to (wi
|
|
|
36
111
|
### Bug Fixes
|
|
37
112
|
|
|
38
113
|
- **cli**: Give --pane error paths actionable recovery guidance
|
|
39
|
-
([`f857c32`](https://github.com/
|
|
114
|
+
([`f857c32`](https://github.com/crouton-labs/termrender/commit/f857c32c89afe32a3a668f03a3d570b0f14dae97))
|
|
40
115
|
|
|
41
116
|
The two --pane error paths now tell the agent how to recover instead of restating the problem.
|
|
42
117
|
"Check that the pane id is valid" is a dead end for an agent — it needs either a command to list
|
|
@@ -48,7 +123,7 @@ The two --pane error paths now tell the agent how to recover instead of restatin
|
|
|
48
123
|
### Features
|
|
49
124
|
|
|
50
125
|
- **cli**: Add --pane for in-place tmux pane updates
|
|
51
|
-
([`4ab1d77`](https://github.com/
|
|
126
|
+
([`4ab1d77`](https://github.com/crouton-labs/termrender/commit/4ab1d77b996aa356926407dcc11c1b408e68e0ee))
|
|
52
127
|
|
|
53
128
|
--tmux now prints the newly-created pane id to stdout (via split-window -P -F) so callers can
|
|
54
129
|
capture it for subsequent updates. --pane <ID> targets an existing pane via tmux respawn-pane -k
|
|
@@ -68,7 +143,7 @@ Also in this commit: - Expand -h epilog to cover the 8 visualization directives
|
|
|
68
143
|
### Bug Fixes
|
|
69
144
|
|
|
70
145
|
- **borders**: Grow render_box to fit overflowing content and titles
|
|
71
|
-
([`dc108c8`](https://github.com/
|
|
146
|
+
([`dc108c8`](https://github.com/crouton-labs/termrender/commit/dc108c8242763828245569f719abce64b26ddf5b))
|
|
72
147
|
|
|
73
148
|
mermaid-ascii's --maxWidth is non-strict, so a child mermaid block can return lines wider than the
|
|
74
149
|
panel's allocated content area. Previously the side walls floated outward to accommodate the
|
|
@@ -85,7 +160,7 @@ render_box now measures the widest content line (and the title) and grows its ef
|
|
|
85
160
|
### Features
|
|
86
161
|
|
|
87
162
|
- Add diff, charts, stat, timeline, tasklist, and inline badges
|
|
88
|
-
([`e14f615`](https://github.com/
|
|
163
|
+
([`e14f615`](https://github.com/crouton-labs/termrender/commit/e14f615ae8d0723405db61c79b0f858d7bf0f863))
|
|
89
164
|
|
|
90
165
|
New block-level directives: - :::diff — colored unified diff with +/- gutters - :::bar — multi-bar
|
|
91
166
|
chart with sub-cell precision via eighth blocks - :::progress — single-line progress bar (auto
|
|
@@ -108,7 +183,7 @@ Cross-cutting changes: - InlineSpan gained fg/bg fields; render_spans and span-s
|
|
|
108
183
|
63 new tests across six test files. All 94 tests pass.
|
|
109
184
|
|
|
110
185
|
- **cli**: Add --watch mode for live re-rendering
|
|
111
|
-
([`4223ad8`](https://github.com/
|
|
186
|
+
([`4223ad8`](https://github.com/crouton-labs/termrender/commit/4223ad86805b0b3ad45450bd7ca4441a668f0e23))
|
|
112
187
|
|
|
113
188
|
Re-renders the file whenever its mtime changes, with terminal-resize detection and inline error
|
|
114
189
|
display so the watcher survives malformed input. Uses the alternate screen buffer so Ctrl+C
|
|
@@ -120,7 +195,7 @@ Composes with --tmux: --tmux --watch points the spawned pane at the real file pa
|
|
|
120
195
|
### Refactoring
|
|
121
196
|
|
|
122
197
|
- **parser**: Require strictly more colons on outer fences
|
|
123
|
-
([`4a501d9`](https://github.com/
|
|
198
|
+
([`4a501d9`](https://github.com/crouton-labs/termrender/commit/4a501d917db191f758874bb6c3d922c879a763be))
|
|
124
199
|
|
|
125
200
|
Drops the depth-counter that allowed `:::outer ... :::inner ... ::: ... :::` nesting with same colon
|
|
126
201
|
counts. Termrender now matches the standard followed by MyST, Pandoc fenced divs,
|
|
@@ -140,7 +215,7 @@ Fixtures in test_column_alignment.py rewritten to ascending colon counts (7/6/5/
|
|
|
140
215
|
### Features
|
|
141
216
|
|
|
142
217
|
- **table**: Render horizontal separator lines between data rows
|
|
143
|
-
([`3e4c74a`](https://github.com/
|
|
218
|
+
([`3e4c74a`](https://github.com/crouton-labs/termrender/commit/3e4c74a10d63470f2eb2ec096bb47cf41f0b7f70))
|
|
144
219
|
|
|
145
220
|
|
|
146
221
|
## v0.4.0 (2026-04-05)
|
|
@@ -148,7 +223,7 @@ Fixtures in test_column_alignment.py rewritten to ascending colon counts (7/6/5/
|
|
|
148
223
|
### Features
|
|
149
224
|
|
|
150
225
|
- **parser**: Variable colon counts, backtick fence directives, and gloam-inspired theming
|
|
151
|
-
([`47fac7f`](https://github.com/
|
|
226
|
+
([`47fac7f`](https://github.com/crouton-labs/termrender/commit/47fac7fcf13d33e5d9986d3f9ca42ddaf5e7207d))
|
|
152
227
|
|
|
153
228
|
Parser changes: - Support 3+ colon openers/closers with stack-based matching - Backtick fence
|
|
154
229
|
directive syntax (```{name}) via mistune AST interception - Option line stripping (:key: value)
|
|
@@ -170,18 +245,18 @@ Theming (gloam-inspired defaults): - Headings: depth-based colored fg + dim tint
|
|
|
170
245
|
### Documentation
|
|
171
246
|
|
|
172
247
|
- Update CLAUDE.md notes for mermaid, tmux, and layout
|
|
173
|
-
([`9e104d5`](https://github.com/
|
|
248
|
+
([`9e104d5`](https://github.com/crouton-labs/termrender/commit/9e104d5ee7bad9a57902e79586c02b0e8d80c589))
|
|
174
249
|
|
|
175
250
|
### Features
|
|
176
251
|
|
|
177
252
|
- **cli**: Auto-size tmux pane to fit rendered content
|
|
178
|
-
([`91f0414`](https://github.com/
|
|
253
|
+
([`91f0414`](https://github.com/crouton-labs/termrender/commit/91f0414d0bf8bfbe4d7167159b928ed9c736db74))
|
|
179
254
|
|
|
180
255
|
- **mermaid**: Pass width and vertical padding to mermaid-ascii
|
|
181
|
-
([`96145c2`](https://github.com/
|
|
256
|
+
([`96145c2`](https://github.com/crouton-labs/termrender/commit/96145c2789a52a4d94e9bc5f4adf7f3a88d8501f))
|
|
182
257
|
|
|
183
258
|
- **table**: Auto-wrap cell content when columns overflow
|
|
184
|
-
([`0fae56f`](https://github.com/
|
|
259
|
+
([`0fae56f`](https://github.com/crouton-labs/termrender/commit/0fae56f8f00260c3263671df9a63a5bea17820bb))
|
|
185
260
|
|
|
186
261
|
When a table exceeds available width, cells now wrap text within their proportionally-shrunk column
|
|
187
262
|
widths instead of overflowing. Layout height calculation updated to account for multi-line cells.
|
|
@@ -192,7 +267,7 @@ When a table exceeds available width, cells now wrap text within their proportio
|
|
|
192
267
|
### Bug Fixes
|
|
193
268
|
|
|
194
269
|
- **mermaid**: Undo double-encoded UTF-8 from mermaid-ascii output
|
|
195
|
-
([`9e0560c`](https://github.com/
|
|
270
|
+
([`9e0560c`](https://github.com/crouton-labs/termrender/commit/9e0560ce46b6dc3f90d2d716a97780713e5e5e53))
|
|
196
271
|
|
|
197
272
|
mermaid-ascii misinterprets UTF-8 bytes as Latin-1 and re-encodes, corrupting multi-byte characters
|
|
198
273
|
(e.g. → renders as â<U+0086><U+0092>). Apply latin-1 round-trip to recover original UTF-8 in both
|
|
@@ -201,7 +276,7 @@ mermaid-ascii misinterprets UTF-8 bytes as Latin-1 and re-encodes, corrupting mu
|
|
|
201
276
|
### Documentation
|
|
202
277
|
|
|
203
278
|
- Add tmux pane lifecycle and --check interaction notes to CLAUDE.md
|
|
204
|
-
([`9400092`](https://github.com/
|
|
279
|
+
([`9400092`](https://github.com/crouton-labs/termrender/commit/9400092e507d470acb97ac5a17b66fcf0e9aa2f6))
|
|
205
280
|
|
|
206
281
|
|
|
207
282
|
## v0.2.0 (2026-04-05)
|
|
@@ -209,27 +284,27 @@ mermaid-ascii misinterprets UTF-8 bytes as Latin-1 and re-encodes, corrupting mu
|
|
|
209
284
|
### Bug Fixes
|
|
210
285
|
|
|
211
286
|
- Handle zero-width and emoji presentation chars in visual width calculation
|
|
212
|
-
([`d0bb8dc`](https://github.com/
|
|
287
|
+
([`d0bb8dc`](https://github.com/crouton-labs/termrender/commit/d0bb8dcfa5ca0d2c16d78a1d7f81825231b9cb59))
|
|
213
288
|
|
|
214
289
|
_char_width now returns 0 for combining marks and format characters (ZWJ, variation selectors).
|
|
215
290
|
visual_len handles VS16 emoji presentation sequences by promoting the preceding character to width
|
|
216
291
|
2. Fixes panel border misalignment when content contains emoji or special Unicode.
|
|
217
292
|
|
|
218
293
|
- **docs**: Update README output examples to match actual rendered output
|
|
219
|
-
([`de6d0cc`](https://github.com/
|
|
294
|
+
([`de6d0cc`](https://github.com/crouton-labs/termrender/commit/de6d0ccfd8a60aca20f2b2659a313f8d8c87d853))
|
|
220
295
|
|
|
221
296
|
### Chores
|
|
222
297
|
|
|
223
298
|
- Add README, design specs, and project CLAUDE.md files
|
|
224
|
-
([`93ac358`](https://github.com/
|
|
299
|
+
([`93ac358`](https://github.com/crouton-labs/termrender/commit/93ac35857981c549797a9359573cacea1478b3ad))
|
|
225
300
|
|
|
226
301
|
- Derive version from git tags via hatch-vcs
|
|
227
|
-
([`33595a0`](https://github.com/
|
|
302
|
+
([`33595a0`](https://github.com/crouton-labs/termrender/commit/33595a0b64363e445b90c9df135a50a4652e2bae))
|
|
228
303
|
|
|
229
304
|
### Continuous Integration
|
|
230
305
|
|
|
231
306
|
- Auto-release and publish via conventional commits
|
|
232
|
-
([`80a456b`](https://github.com/
|
|
307
|
+
([`80a456b`](https://github.com/crouton-labs/termrender/commit/80a456b7301c57f2fd2b0cd30622b78f2d4b931e))
|
|
233
308
|
|
|
234
309
|
Replace manual GitHub release trigger with python-semantic-release. On push to main, conventional
|
|
235
310
|
commits are analyzed to determine version bumps (feat→minor, fix→patch) and publish to PyPI
|
|
@@ -238,12 +313,12 @@ Replace manual GitHub release trigger with python-semantic-release. On push to m
|
|
|
238
313
|
### Documentation
|
|
239
314
|
|
|
240
315
|
- Update README token count and expand CLAUDE.md implementation notes
|
|
241
|
-
([`1f70a53`](https://github.com/
|
|
316
|
+
([`1f70a53`](https://github.com/crouton-labs/termrender/commit/1f70a5352cbced30219012dbede7040c6ac97457))
|
|
242
317
|
|
|
243
318
|
### Features
|
|
244
319
|
|
|
245
320
|
- Add CJK ambiguous-width support, strict directive parsing, and rendering fixes
|
|
246
|
-
([`c000883`](https://github.com/
|
|
321
|
+
([`c000883`](https://github.com/crouton-labs/termrender/commit/c0008835d66b721b0a09c7a34dde11d08b3d3d94))
|
|
247
322
|
|
|
248
323
|
- Add emoji presentation and East Asian ambiguous-width character handling with --cjk flag and
|
|
249
324
|
TERMRENDER_CJK env var - All renderers (borders, divider, quote, tree) now compute box-drawing
|
|
@@ -252,14 +327,14 @@ Replace manual GitHub release trigger with python-semantic-release. On push to m
|
|
|
252
327
|
for inter-column gaps - Support 'author' as alias for 'by' attribute on quote blocks
|
|
253
328
|
|
|
254
329
|
- Add GFM table rendering with box-drawing borders
|
|
255
|
-
([`c3b61cd`](https://github.com/
|
|
330
|
+
([`c3b61cd`](https://github.com/crouton-labs/termrender/commit/c3b61cdd659fdb782089cbca2fd3f74b18486605))
|
|
256
331
|
|
|
257
332
|
Enable mistune table plugin, parse table AST into TABLE blocks, and render with box-drawing
|
|
258
333
|
characters. Supports left/center/right column alignment, bold headers, auto-sized columns, and
|
|
259
334
|
proportional overflow distribution.
|
|
260
335
|
|
|
261
336
|
- **cli**: Add --tmux pane output, --check validation, and structured error handling
|
|
262
|
-
([`36b52ee`](https://github.com/
|
|
337
|
+
([`36b52ee`](https://github.com/crouton-labs/termrender/commit/36b52eed9701cd0acd363db2d0fa3d277244c8b0))
|
|
263
338
|
|
|
264
339
|
- --tmux renders in a new tmux side pane via split-window, piped through less -R - --check validates
|
|
265
340
|
directive syntax without rendering (exit 0/2) - Structured _error() helper with fix/hint guidance
|
|
@@ -269,12 +344,12 @@ Enable mistune table plugin, parse table AST into TABLE blocks, and render with
|
|
|
269
344
|
depth note
|
|
270
345
|
|
|
271
346
|
- **cli**: Improve help output with examples, version flag, and tty detection
|
|
272
|
-
([`cb3e7e2`](https://github.com/
|
|
347
|
+
([`cb3e7e2`](https://github.com/crouton-labs/termrender/commit/cb3e7e2752ae860e5c3cbd4c4f1627e925a9c431))
|
|
273
348
|
|
|
274
349
|
### Testing
|
|
275
350
|
|
|
276
351
|
- Add column alignment and visual width tests
|
|
277
|
-
([`f8b6099`](https://github.com/
|
|
352
|
+
([`f8b6099`](https://github.com/crouton-labs/termrender/commit/f8b60998625977b10dd4697f8e772d80125cb9ce))
|
|
278
353
|
|
|
279
354
|
Covers showpiece rendering, column line width consistency, status marker visual widths (text vs
|
|
280
355
|
emoji presentation), and panel border alignment.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# termrender
|
|
2
|
+
|
|
3
|
+
## Commands
|
|
4
|
+
```bash
|
|
5
|
+
pip install -e .
|
|
6
|
+
pytest tests/
|
|
7
|
+
python -m termrender <file.md>
|
|
8
|
+
python -m build
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
No linter or formatter is configured.
|
|
12
|
+
|
|
13
|
+
## Constraints
|
|
14
|
+
- **Layout pass order is load-bearing**: `resolve_width()` top-down must complete before `resolve_height()` bottom-up — height calls `wrap_text(text, width)`, which requires width already set.
|
|
15
|
+
- **`borders.py` `render_box` width**: takes **total** width including borders, not content width. Passing content width silently overflows.
|
|
16
|
+
- **`wrap_text()` CJK bug**: uses `len()` internally, not `visual_len()` — silently overflows for CJK content.
|
|
17
|
+
- **`_ambiguous_width` is global mutable state** with no reset path — `set_ambiguous_width()` or `TERMRENDER_CJK` env var changes persist for the process lifetime.
|
|
18
|
+
- **Version**: derived from git tags via hatch-vcs — no version in `pyproject.toml`. Adding one will conflict.
|
|
19
|
+
- **Commits**: conventional commits. `feat` → minor, `fix`/`perf` → patch. Auto-released via python-semantic-release on main.
|
|
20
|
+
|
|
21
|
+
## Supplementary CLAUDE.md files
|
|
22
|
+
- `src/termrender/CLAUDE.md` — parser, layout, mermaid, nesting, and `--check`/`--tmux` implementation gotchas
|
|
23
|
+
- `src/termrender/renderers/CLAUDE.md` — renderer contracts, `render_box` width semantics, EAW edge cases
|
|
24
|
+
|
|
25
|
+
Read these before modifying layout, parsing, or renderer code.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: termrender
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 1.0.0
|
|
4
4
|
Summary: Rich terminal rendering of directive-flavored markdown
|
|
5
5
|
Project-URL: Homepage, https://github.com/CaptainCrouton89/termrender
|
|
6
6
|
Project-URL: Repository, https://github.com/CaptainCrouton89/termrender
|
|
@@ -393,17 +393,17 @@ Horizontal rules with optional centered labels.
|
|
|
393
393
|
|
|
394
394
|
### Mermaid diagrams
|
|
395
395
|
|
|
396
|
-
Renders mermaid flowcharts as ASCII art via [mermaid-ascii](https://github.com/mermaid-js/mermaid-ascii).
|
|
396
|
+
Renders mermaid flowcharts as ASCII art via [mermaid-ascii](https://github.com/mermaid-js/mermaid-ascii). Mermaid uses the `:::mermaid` directive — backtick fences (`` ```mermaid ``) are treated as plain code blocks.
|
|
397
397
|
|
|
398
398
|
**Input:**
|
|
399
|
-
|
|
400
|
-
|
|
399
|
+
```markdown
|
|
400
|
+
:::mermaid
|
|
401
401
|
graph LR
|
|
402
402
|
A[Request] --> B{Auth?}
|
|
403
403
|
B -->|Yes| C[Handler]
|
|
404
404
|
B -->|No| D[401]
|
|
405
|
+
:::
|
|
405
406
|
```
|
|
406
|
-
````
|
|
407
407
|
|
|
408
408
|
The diagram gets rendered as ASCII art inline in the terminal output. Requires `mermaid-ascii` to be installed (it's a dependency, so it should be).
|
|
409
409
|
|
|
@@ -365,17 +365,17 @@ Horizontal rules with optional centered labels.
|
|
|
365
365
|
|
|
366
366
|
### Mermaid diagrams
|
|
367
367
|
|
|
368
|
-
Renders mermaid flowcharts as ASCII art via [mermaid-ascii](https://github.com/mermaid-js/mermaid-ascii).
|
|
368
|
+
Renders mermaid flowcharts as ASCII art via [mermaid-ascii](https://github.com/mermaid-js/mermaid-ascii). Mermaid uses the `:::mermaid` directive — backtick fences (`` ```mermaid ``) are treated as plain code blocks.
|
|
369
369
|
|
|
370
370
|
**Input:**
|
|
371
|
-
|
|
372
|
-
|
|
371
|
+
```markdown
|
|
372
|
+
:::mermaid
|
|
373
373
|
graph LR
|
|
374
374
|
A[Request] --> B{Auth?}
|
|
375
375
|
B -->|Yes| C[Handler]
|
|
376
376
|
B -->|No| D[401]
|
|
377
|
+
:::
|
|
377
378
|
```
|
|
378
|
-
````
|
|
379
379
|
|
|
380
380
|
The diagram gets rendered as ASCII art inline in the terminal output. Requires `mermaid-ascii` to be installed (it's a dependency, so it should be).
|
|
381
381
|
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
- `layout.py` imports `fix_mermaid_encoding` and `preprocess_mermaid_for_ascii` from `renderers/mermaid.py` — the only reverse dependency from layout into renderers. Reorganizing `renderers/` must preserve these imports. The two mermaid subprocess call sites differ: layout uses `check=True` (non-zero exit raises → caught → raw-source fallback); the renderer omits `check` (non-zero exit silently reads `stdout`, which may be empty or partial).
|
|
2
|
+
|
|
3
|
+
- `--check` calls `parse()` and exits — it never runs `layout.py`. Layout-time failures (mermaid subprocess missing, column percent overflow, `resolve_width`/`resolve_height` exceptions) pass `--check` cleanly but crash at render time.
|
|
4
|
+
|
|
5
|
+
- Column widths: explicit percent/absolute allocations are subtracted first; remaining space is split among auto-width columns with `max(remaining, 0)`. Two columns each claiming 80% of a 100px terminal leaves auto-width columns with `width = 1` — no error, no proportional scaling.
|
|
6
|
+
|
|
7
|
+
- **QUOTE** height gets `+1` only when `author` or `by` attr is set. Using any other key (`attribution`, `source`) silently omits the extra line — the renderer's attribution line is clipped.
|
|
8
|
+
|
|
9
|
+
- **LIST_ITEM** layout wraps at `max(width - 2, 1)`, hardcoding a 2-column indent. If the renderer changes the indent width, layout height and actual render height diverge silently.
|
|
10
|
+
|
|
11
|
+
- `text.py:32–33`: after each wrapped line, the offset skips one character if the next plain-text character is a space (the space `wrap_text` consumed). If wrapping preserves trailing spaces, this skip shifts subsequent span styling by one character.
|
|
12
|
+
|
|
13
|
+
- `--tmux` exits `EXIT_OK` after the tmux pane command succeeds — the `--check` branch comes later and is never reached. `--check` is silently dropped when combined with `--tmux`.
|
|
14
|
+
|
|
15
|
+
- `_EMOJI_WIDE_RANGES` in `style.py`: `_char_width()` exits early when `cp < lo`, assuming ranges are in ascending codepoint order. Adding a range out of order silently misclassifies all codepoints whose `lo` is higher than the inserted range's `lo`.
|
|
@@ -53,7 +53,8 @@ directives (close each with a matching colon count):
|
|
|
53
53
|
:::tasklist Checkbox list — [x] checked, [ ] unchecked, [!] in-progress
|
|
54
54
|
Plain lists with at least one marker auto-promote; use the directive
|
|
55
55
|
to force unchecked styling on items without explicit markers.
|
|
56
|
-
|
|
56
|
+
:::mermaid Mermaid diagram (via mermaid-ascii)
|
|
57
|
+
body: standard mermaid source (graph TD, flowchart, sequenceDiagram, ...)
|
|
57
58
|
|
|
58
59
|
Inline:
|
|
59
60
|
:badge[text]{color=c} Inline pill badge
|
|
@@ -335,18 +336,27 @@ def main() -> None:
|
|
|
335
336
|
if args.width:
|
|
336
337
|
pane_width = args.width
|
|
337
338
|
else:
|
|
338
|
-
# Default
|
|
339
|
+
# Default sizing aims for a readable side pane (~80 cols) and
|
|
340
|
+
# only falls back to 1/3-of-window when the window is too narrow
|
|
341
|
+
# to give that much without crushing the source pane.
|
|
339
342
|
try:
|
|
340
343
|
result = subprocess.run(
|
|
341
344
|
["tmux", "display-message", "-p", "#{window_width}"],
|
|
342
345
|
capture_output=True, text=True, check=True,
|
|
343
346
|
)
|
|
344
347
|
window_width = int(result.stdout.strip())
|
|
345
|
-
|
|
348
|
+
# Prefer 80 cols if the source pane keeps at least 60.
|
|
349
|
+
# Otherwise split evenly. Floor of 40 keeps text legible.
|
|
350
|
+
if window_width >= 140:
|
|
351
|
+
pane_width = 80
|
|
352
|
+
elif window_width >= 100:
|
|
353
|
+
pane_width = window_width // 2
|
|
354
|
+
else:
|
|
355
|
+
pane_width = max(window_width - 2 - 50, 40)
|
|
346
356
|
except Exception:
|
|
347
|
-
pane_width =
|
|
357
|
+
pane_width = 80
|
|
348
358
|
|
|
349
|
-
pane_width = max(pane_width,
|
|
359
|
+
pane_width = max(pane_width, 40) # absolute minimum for readability
|
|
350
360
|
|
|
351
361
|
# Watch mode points the new pane at the user's real file so edits
|
|
352
362
|
# propagate; non-watch mode snapshots source into a tempfile.
|
|
@@ -7,12 +7,24 @@ from termrender.renderers import (
|
|
|
7
7
|
panel, columns, tree, code, text, divider, quote, mermaid, table,
|
|
8
8
|
diff, charts, stat, timeline,
|
|
9
9
|
)
|
|
10
|
+
from termrender.style import visual_ljust
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
def emit_block(block: Block, color: bool) -> list[str]:
|
|
13
14
|
"""Render a single block and its children, returning output lines."""
|
|
14
15
|
match block.type:
|
|
15
|
-
case BlockType.DOCUMENT
|
|
16
|
+
case BlockType.DOCUMENT:
|
|
17
|
+
# Insert a blank padded line between top-level siblings so
|
|
18
|
+
# paragraphs, headings, and blocks don't visually run together.
|
|
19
|
+
lines: list[str] = []
|
|
20
|
+
sep = visual_ljust("", block.width or 0)
|
|
21
|
+
for i, child in enumerate(block.children):
|
|
22
|
+
if i > 0:
|
|
23
|
+
lines.append(sep)
|
|
24
|
+
lines.extend(emit_block(child, color))
|
|
25
|
+
return lines
|
|
26
|
+
|
|
27
|
+
case BlockType.COL:
|
|
16
28
|
lines: list[str] = []
|
|
17
29
|
for child in block.children:
|
|
18
30
|
lines.extend(emit_block(child, color))
|
|
@@ -5,7 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
import subprocess
|
|
6
6
|
|
|
7
7
|
from termrender.blocks import Block, BlockType
|
|
8
|
-
from termrender.renderers.mermaid import fix_mermaid_encoding
|
|
8
|
+
from termrender.renderers.mermaid import fix_mermaid_encoding, preprocess_mermaid_for_ascii
|
|
9
9
|
from termrender.style import wrap_text, visual_len
|
|
10
10
|
|
|
11
11
|
|
|
@@ -148,7 +148,7 @@ def resolve_height(block: Block) -> None:
|
|
|
148
148
|
try:
|
|
149
149
|
result = subprocess.run(
|
|
150
150
|
["mermaid-ascii", "-f", "-", "-w", str(block.width or 80), "-y", "1"],
|
|
151
|
-
input=source,
|
|
151
|
+
input=preprocess_mermaid_for_ascii(source),
|
|
152
152
|
capture_output=True,
|
|
153
153
|
text=True,
|
|
154
154
|
check=True,
|
|
@@ -192,9 +192,15 @@ def resolve_height(block: Block) -> None:
|
|
|
192
192
|
elif bt == BlockType.TIMELINE:
|
|
193
193
|
entries = block.attrs.get("entries", [])
|
|
194
194
|
title_h = 1 if block.attrs.get("title") else 0
|
|
195
|
-
# Each entry takes 1 line + 1 connector line between entries (none after last)
|
|
196
195
|
if entries:
|
|
197
|
-
|
|
196
|
+
date_w = max(visual_len(e["date"]) for e in entries)
|
|
197
|
+
event_w = max((block.width or 60) - date_w - 4, 5)
|
|
198
|
+
total = 0
|
|
199
|
+
for entry in entries:
|
|
200
|
+
wrapped = wrap_text(entry["event"], event_w) or [""]
|
|
201
|
+
total += len(wrapped)
|
|
202
|
+
total += len(entries) - 1 # connector between entries
|
|
203
|
+
block.height = title_h + total
|
|
198
204
|
else:
|
|
199
205
|
block.height = max(title_h, 1)
|
|
200
206
|
|
|
@@ -69,15 +69,13 @@ _DIRECTIVE_TO_BLOCK: dict[str, BlockType] = {
|
|
|
69
69
|
"gauge": BlockType.GAUGE,
|
|
70
70
|
"stat": BlockType.STAT,
|
|
71
71
|
"timeline": BlockType.TIMELINE,
|
|
72
|
+
"mermaid": BlockType.MERMAID,
|
|
72
73
|
"tasklist": BlockType.LIST, # alias: forces tasklist styling on the inner list
|
|
73
74
|
}
|
|
74
75
|
|
|
75
76
|
_SELF_CLOSING_DIRECTIVES = frozenset({"divider", "progress", "gauge"})
|
|
76
77
|
|
|
77
|
-
#
|
|
78
|
-
_BACKTICK_DIRECTIVE_RE = re.compile(r"^\{(\w[\w-]*)\}(.*)")
|
|
79
|
-
|
|
80
|
-
# MyST option line: :key: value — intentionally requires a value after the key
|
|
78
|
+
# Option line: :key: value — intentionally requires a value after the key
|
|
81
79
|
# (the \s+(.+) part). Flag-style options like :nosandbox: (no value) won't match
|
|
82
80
|
# and will be treated as body content.
|
|
83
81
|
_OPTION_LINE_RE = re.compile(r"^:(\w[\w-]*):\s+(.+)$")
|
|
@@ -134,7 +132,10 @@ def _convert_inline(nodes: list[dict]) -> list[InlineSpan]:
|
|
|
134
132
|
elif ntype == "softbreak":
|
|
135
133
|
spans.append(InlineSpan(text=" "))
|
|
136
134
|
elif ntype == "linebreak":
|
|
137
|
-
|
|
135
|
+
# Two \n so wrap_text emits a blank line between the two sides
|
|
136
|
+
# of the hard break, giving more vertical breathing room than a
|
|
137
|
+
# soft wrap. See tests/test_linebreak.py.
|
|
138
|
+
spans.append(InlineSpan(text="\n\n"))
|
|
138
139
|
else:
|
|
139
140
|
# Fallback: try raw text
|
|
140
141
|
if "raw" in node:
|
|
@@ -245,7 +246,7 @@ def _expand_inline_roles(spans: list[InlineSpan]) -> list[InlineSpan]:
|
|
|
245
246
|
|
|
246
247
|
|
|
247
248
|
def _strip_options(body: str) -> tuple[dict[str, str], str]:
|
|
248
|
-
"""Strip
|
|
249
|
+
"""Strip option lines from the start of a directive body.
|
|
249
250
|
|
|
250
251
|
Option lines have the form `:key: value` and appear at the start of the body.
|
|
251
252
|
Blank lines between option lines are allowed. Scanning stops at the first
|
|
@@ -302,33 +303,10 @@ def _convert_ast(nodes: list[dict], _depth: int = 0) -> list[Block]:
|
|
|
302
303
|
elif ntype == "block_code":
|
|
303
304
|
raw = node.get("raw", "")
|
|
304
305
|
info = node.get("attrs", {}).get("info", "")
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
arg_text = m_directive.group(2).strip()
|
|
310
|
-
if dir_name == "mermaid":
|
|
311
|
-
options, body = _strip_options(raw)
|
|
312
|
-
attrs = dict(options)
|
|
313
|
-
if arg_text:
|
|
314
|
-
attrs["argument"] = arg_text
|
|
315
|
-
attrs["source"] = body
|
|
316
|
-
blocks.append(Block(type=BlockType.MERMAID, attrs=attrs))
|
|
317
|
-
else:
|
|
318
|
-
attrs: dict[str, Any] = {}
|
|
319
|
-
if arg_text:
|
|
320
|
-
attrs["argument"] = arg_text
|
|
321
|
-
blocks.append(_directive_to_block(dir_name, attrs, raw, _depth=_depth))
|
|
322
|
-
elif info == "mermaid":
|
|
323
|
-
blocks.append(Block(
|
|
324
|
-
type=BlockType.MERMAID,
|
|
325
|
-
attrs={"source": raw},
|
|
326
|
-
))
|
|
327
|
-
else:
|
|
328
|
-
blocks.append(Block(
|
|
329
|
-
type=BlockType.CODE,
|
|
330
|
-
attrs={"lang": info, "source": raw},
|
|
331
|
-
))
|
|
306
|
+
blocks.append(Block(
|
|
307
|
+
type=BlockType.CODE,
|
|
308
|
+
attrs={"lang": info, "source": raw},
|
|
309
|
+
))
|
|
332
310
|
|
|
333
311
|
elif ntype == "list":
|
|
334
312
|
ordered = node.get("attrs", {}).get("ordered", False)
|
|
@@ -610,8 +588,8 @@ def _directive_to_block(name: str, attrs: dict[str, Any], body: str, _depth: int
|
|
|
610
588
|
|
|
611
589
|
block_type = _DIRECTIVE_TO_BLOCK.get(name, BlockType.PANEL)
|
|
612
590
|
|
|
613
|
-
# Tree, Code, Diff: store raw body, don't parse as markdown
|
|
614
|
-
if block_type in (BlockType.TREE, BlockType.CODE, BlockType.DIFF):
|
|
591
|
+
# Tree, Code, Diff, Mermaid: store raw body, don't parse as markdown
|
|
592
|
+
if block_type in (BlockType.TREE, BlockType.CODE, BlockType.DIFF, BlockType.MERMAID):
|
|
615
593
|
attrs["source"] = body
|
|
616
594
|
return Block(type=block_type, attrs=attrs)
|
|
617
595
|
|