opentui-markdown-render 0.0.0-stage → 0.2.0
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.
- package/LICENSE +21 -0
- package/NOTICE +27 -0
- package/README.md +223 -2
- package/bin/otmd.tsx +99 -0
- package/native/Cargo.lock +3708 -0
- package/native/Cargo.toml +28 -0
- package/native/build.rs +3 -0
- package/native/src/lib.rs +880 -0
- package/package.json +67 -4
- package/prebuilds/linux-x64-gnu/opentui_markdown_render.node +4 -0
- package/src/app.tsx +180 -0
- package/src/cli.ts +63 -0
- package/src/config/index.ts +46 -0
- package/src/config/load.ts +337 -0
- package/src/config/resolve.ts +36 -0
- package/src/config/theme.ts +110 -0
- package/src/config/types.ts +105 -0
- package/src/core/document.ts +44 -0
- package/src/core/frame.ts +144 -0
- package/src/core/image.ts +9 -0
- package/src/core/index.ts +6 -0
- package/src/core/layout.ts +27 -0
- package/src/core/math.ts +170 -0
- package/src/core/parse.ts +208 -0
- package/src/core/tables.ts +58 -0
- package/src/graphics.ts +85 -0
- package/src/headings.ts +77 -0
- package/src/index.ts +107 -0
- package/src/math/index.ts +86 -0
- package/src/math/native.ts +39 -0
- package/src/math/renderer.ts +84 -0
- package/src/math/validate.ts +44 -0
- package/src/media.tsx +267 -0
- package/src/merman/class/dagre-layout.ts +171 -0
- package/src/merman/class/diagram.ts +8 -0
- package/src/merman/class/drawing.ts +343 -0
- package/src/merman/class/labels.ts +38 -0
- package/src/merman/class/layout.ts +531 -0
- package/src/merman/class/parser.ts +308 -0
- package/src/merman/class/render-grid.ts +18 -0
- package/src/merman/class/style.ts +27 -0
- package/src/merman/class/types.ts +57 -0
- package/src/merman/core/canvas.ts +228 -0
- package/src/merman/core/color/style.ts +56 -0
- package/src/merman/core/drawing.ts +307 -0
- package/src/merman/core/geometry.ts +347 -0
- package/src/merman/core/mermaid.ts +38 -0
- package/src/merman/core/render-grid.ts +51 -0
- package/src/merman/core/spatial.ts +243 -0
- package/src/merman/core/text-lines.ts +32 -0
- package/src/merman/core/text.ts +44 -0
- package/src/merman/detect.ts +19 -0
- package/src/merman/diagnostics.ts +16 -0
- package/src/merman/diagram.ts +268 -0
- package/src/merman/flowchart/drawing.ts +433 -0
- package/src/merman/flowchart/labels.ts +144 -0
- package/src/merman/flowchart/layout.ts +1057 -0
- package/src/merman/flowchart/options.ts +12 -0
- package/src/merman/flowchart/parser.ts +477 -0
- package/src/merman/flowchart/render.ts +10 -0
- package/src/merman/flowchart/routing.ts +1429 -0
- package/src/merman/flowchart/style.ts +79 -0
- package/src/merman/flowchart/types.ts +63 -0
- package/src/merman/gantt/diagram.ts +8 -0
- package/src/merman/gantt/drawing.ts +193 -0
- package/src/merman/gantt/parser.ts +198 -0
- package/src/merman/gantt/render-grid.ts +17 -0
- package/src/merman/gantt/style.ts +36 -0
- package/src/merman/gantt/types.ts +45 -0
- package/src/merman/gitgraph/diagram.ts +8 -0
- package/src/merman/gitgraph/drawing.ts +234 -0
- package/src/merman/gitgraph/parser.ts +234 -0
- package/src/merman/gitgraph/render-grid.ts +17 -0
- package/src/merman/gitgraph/style.ts +37 -0
- package/src/merman/gitgraph/types.ts +36 -0
- package/src/merman/palette.ts +60 -0
- package/src/merman/sequence/diagram.ts +8 -0
- package/src/merman/sequence/drawing.ts +288 -0
- package/src/merman/sequence/options.ts +8 -0
- package/src/merman/sequence/parser.ts +303 -0
- package/src/merman/sequence/placement.ts +692 -0
- package/src/merman/sequence/render-grid.ts +22 -0
- package/src/merman/sequence/style.ts +81 -0
- package/src/merman/sequence/types.ts +74 -0
- package/src/merman/state/diagram.ts +8 -0
- package/src/merman/state/drawing.ts +363 -0
- package/src/merman/state/endpoint.ts +15 -0
- package/src/merman/state/layout.ts +1134 -0
- package/src/merman/state/note.ts +59 -0
- package/src/merman/state/options.ts +11 -0
- package/src/merman/state/parser.ts +245 -0
- package/src/merman/state/render-grid.ts +22 -0
- package/src/merman/state/routing.ts +1522 -0
- package/src/merman/state/search.ts +103 -0
- package/src/merman/state/style.ts +52 -0
- package/src/merman/state/types.ts +63 -0
- package/src/merman/state/visible-model.ts +77 -0
- package/src/merman/timeline/diagram.ts +8 -0
- package/src/merman/timeline/drawing.ts +109 -0
- package/src/merman/timeline/parser.ts +119 -0
- package/src/merman/timeline/render-grid.ts +17 -0
- package/src/merman/timeline/style.ts +36 -0
- package/src/merman/timeline/types.ts +31 -0
- package/src/merman.ts +3 -0
- package/src/statusline.tsx +183 -0
- package/src/theme/colors.ts +11 -0
- package/src/theme/defaults.ts +190 -0
- package/src/theme/index.ts +7 -0
- package/src/theme/load.ts +39 -0
- package/src/theme/resolve.ts +167 -0
- package/src/theme/syntax.ts +96 -0
- package/src/theme/types.ts +151 -0
- package/src/view/code.tsx +27 -0
- package/src/view/document-slots.ts +111 -0
- package/src/view/document-table.tsx +98 -0
- package/src/view/document.tsx +195 -0
- package/src/view/frame.tsx +83 -0
- package/src/view/inline-flow.tsx +113 -0
- package/src/view/inline-layout.ts +149 -0
- package/src/view/inline-math.ts +42 -0
- package/src/view/inline-style.ts +103 -0
- package/src/view/math.tsx +85 -0
- package/src/view/mermaid.tsx +128 -0
- package/src/view/table.ts +100 -0
- package/themes/dark.json +32 -0
- package/themes/example.jsonc +98 -0
- package/themes/light.json +32 -0
- package/themes/solarized-dark.json +32 -0
- package/themes/solarized-light.json +32 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Desiment
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/NOTICE
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
Third-party notices
|
|
2
|
+
===================
|
|
3
|
+
|
|
4
|
+
MiTeX scope
|
|
5
|
+
-----------
|
|
6
|
+
|
|
7
|
+
The Typst helper functions in `native/src/lib.rs` (`MITEX_SCOPE`) are derived
|
|
8
|
+
from the MiTeX project, which is licensed under the Apache License 2.0:
|
|
9
|
+
|
|
10
|
+
MiTeX — LaTeX support for Typst, powered by Rust and WASM
|
|
11
|
+
https://github.com/mitex-rs/mitex
|
|
12
|
+
Copyright (c) 2022-2026 Myriad-Dreamin, OrangeX4, Enter-tainer, Shichien
|
|
13
|
+
|
|
14
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
15
|
+
you may not use this file except in compliance with the License.
|
|
16
|
+
You may obtain a copy of the License at
|
|
17
|
+
|
|
18
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
19
|
+
|
|
20
|
+
Unless required by applicable law or agreed to in writing, software
|
|
21
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
22
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
23
|
+
See the License for the specific language governing permissions and
|
|
24
|
+
limitations under the License.
|
|
25
|
+
|
|
26
|
+
The scope was reduced to the handlers the renderer needs and adapted to the
|
|
27
|
+
Typst version the addon embeds.
|
package/README.md
CHANGED
|
@@ -1,3 +1,224 @@
|
|
|
1
|
-
#
|
|
1
|
+
# OpenTUI Markdown Render
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://github.com/Desiment/opentui-markdown-render/actions/workflows/verify.yml)
|
|
4
|
+
|
|
5
|
+
> [!WARNING]
|
|
6
|
+
> The code in this repository is 100% AI-slop. I disclaim any responsibility for the functionality of this code. I also do not accept pull requests for this repository. You are welcome to open issues if you find any bugs, but it is unlikely that I will fix them. If you would like to develop this project into something more substantial, please let me know by opening an issue.
|
|
7
|
+
|
|
8
|
+
`opentui-markdown-render` is a Markdown renderer for [OpenTUI](https://github.com/anomalyco/opentui) and Solid. It turns Markdown into a terminal-native document with tables, syntax-highlighted code, Mermaid diagrams, local images, animated GIFs, and optional LaTeX-style mathematics.
|
|
9
|
+
|
|
10
|
+

|
|
11
|
+
|
|
12
|
+
## Why this renderer exists
|
|
13
|
+
|
|
14
|
+
Terminal UIs usually have to choose between displaying Markdown as plain text and embedding a web renderer that does not understand terminal layout. This package keeps the document in the OpenTUI layout model instead:
|
|
15
|
+
|
|
16
|
+
- paragraphs, headings, lists, quotes, links, emphasis, code, rules, and task lists;
|
|
17
|
+
- inline and display math, rendered in-process with terminal image protocols (Kitty gives the sharpest output);
|
|
18
|
+
- Mermaid flowcharts, sequence diagrams, state diagrams, Gantt charts, timelines, Git graphs, and class diagrams;
|
|
19
|
+
- content-sized tables with optional horizontal scrolling;
|
|
20
|
+
- local PNG, JPEG, WebP, and animated GIF assets;
|
|
21
|
+
- configurable themes, heading styles, frames, status lines, image limits, and Mermaid layout;
|
|
22
|
+
- a parser-only `core` entry point for hosts that need their own view or document pipeline;
|
|
23
|
+
- source ranges and render units for selection, comments, review tools, and overlays owned by the host.
|
|
24
|
+
|
|
25
|
+
The renderer uses OpenTUI primitives rather than OpenTUI's built-in Markdown parser. Math is rasterized by the native addon using MiTeX and Typst; the renderer does not start a system LaTeX or Typst process.
|
|
26
|
+
|
|
27
|
+
## See it in action
|
|
28
|
+
|
|
29
|
+
### Live themes and configuration
|
|
30
|
+
|
|
31
|
+
Press `c` in the demo to switch between light, dark, and Solarized themes and to adjust frame, table, and class-diagram layout without restarting the viewer.
|
|
32
|
+
|
|
33
|
+

|
|
34
|
+
|
|
35
|
+
### Typeset mathematics
|
|
36
|
+
|
|
37
|
+
Inline formulas participate in normal text layout, while display formulas use display style with large operators and limits. Rendering happens in-process through MiTeX and Typst.
|
|
38
|
+
|
|
39
|
+

|
|
40
|
+
|
|
41
|
+
### Terminal-native Mermaid diagrams
|
|
42
|
+
|
|
43
|
+
Supported Mermaid families are parsed and laid out as terminal diagrams rather than browser SVG. Unsupported syntax remains a readable source fence.
|
|
44
|
+
|
|
45
|
+

|
|
46
|
+
|
|
47
|
+
### Local images and animated GIFs
|
|
48
|
+
|
|
49
|
+
Local assets are decoded below the configured asset root, fitted to terminal cells, and composited against the active background. GIF timing and transparency are retained.
|
|
50
|
+
|
|
51
|
+

|
|
52
|
+
|
|
53
|
+
## Quick start
|
|
54
|
+
|
|
55
|
+
### Try the standalone demo
|
|
56
|
+
|
|
57
|
+
The repository contains a viewer and focused Markdown fixtures. The demo needs Bun, and the native math addon needs a Rust toolchain when building locally.
|
|
58
|
+
|
|
59
|
+
```sh
|
|
60
|
+
bun install
|
|
61
|
+
bun run generate:examples
|
|
62
|
+
bun run build:native
|
|
63
|
+
bun run demo:all
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
The demo accepts a fixture name as a command-line argument:
|
|
67
|
+
|
|
68
|
+
```sh
|
|
69
|
+
bun run demo:plain
|
|
70
|
+
bun run demo:math
|
|
71
|
+
bun run demo:mermaid
|
|
72
|
+
bun run demo:images
|
|
73
|
+
bun run demo:frame
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Press `c` to open the configuration panel. Use Up/Down (or `j`/`k`) to select an option and Left/Right (or `h`/`l`) to change it. Press `q` to open the exit confirmation. The demo's runtime changes are not written to the JSONC file.
|
|
77
|
+
|
|
78
|
+
See [Examples and demo fixtures](examples/README.md) for the complete fixture matrix and [Getting started](docs/getting-started.md) for a step-by-step walkthrough.
|
|
79
|
+
|
|
80
|
+
### Install the `otmd` CLI
|
|
81
|
+
|
|
82
|
+
The package also provides `otmd`, a terminal Markdown viewer. npm can install the
|
|
83
|
+
package globally, but Bun is still required at runtime because the CLI executes
|
|
84
|
+
TypeScript and uses the Bun native addon loader:
|
|
85
|
+
|
|
86
|
+
```sh
|
|
87
|
+
npm install -g opentui-markdown-render
|
|
88
|
+
# or: bun add -g opentui-markdown-render
|
|
89
|
+
|
|
90
|
+
otmd --version
|
|
91
|
+
otmd README.md
|
|
92
|
+
cat README.md | otmd -
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Use `otmd --help` for options such as `--config` and `--no-config`. The current
|
|
96
|
+
native math prebuild targets Linux x64 with glibc; other platforms keep readable
|
|
97
|
+
math source when the addon is unavailable.
|
|
98
|
+
|
|
99
|
+
### Install in an OpenTUI application
|
|
100
|
+
|
|
101
|
+
```sh
|
|
102
|
+
bun add opentui-markdown-render @opentui/core @opentui/solid solid-js
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
`@opentui/core`, `@opentui/solid`, and `solid-js` are peer dependencies. The package currently targets Bun 1.4+, OpenTUI 0.5.12+, and Solid 1.9+. Native prebuild coverage is currently Linux x64 with glibc.
|
|
106
|
+
|
|
107
|
+
### Configuration discovery
|
|
108
|
+
|
|
109
|
+
With automatic discovery enabled, the nearest project configuration wins. The
|
|
110
|
+
loader checks `opentui-markdown-render.jsonc` and then
|
|
111
|
+
`opentui-markdown-render.json` while walking from the current working directory
|
|
112
|
+
up through its parents. If no project file exists, it checks the XDG user
|
|
113
|
+
configuration directory:
|
|
114
|
+
|
|
115
|
+
```text
|
|
116
|
+
$XDG_CONFIG_HOME/otmd/config.jsonc # or ~/.config when XDG_CONFIG_HOME is unset
|
|
117
|
+
$XDG_CONFIG_HOME/otmd/config.json
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Pass `--config PATH` to `otmd` or `configPath` to `MarkdownRenderer` to select a
|
|
121
|
+
file explicitly. Embedded hosts can pass `discoverConfig={false}` when they own
|
|
122
|
+
configuration themselves.
|
|
123
|
+
|
|
124
|
+
Themes are complete JSON/JSONC files. Select a bundled theme or a user theme
|
|
125
|
+
from `$XDG_CONFIG_HOME/otmd/themes` with:
|
|
126
|
+
|
|
127
|
+
```jsonc
|
|
128
|
+
{
|
|
129
|
+
"theme": { "name": "dark" }
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
The repository includes [`themes/example.jsonc`](themes/example.jsonc), a fully
|
|
134
|
+
commented template showing every theme setting. Copy it to
|
|
135
|
+
`~/.config/otmd/themes/my-theme.jsonc` and select it with
|
|
136
|
+
`{ "theme": { "name": "my-theme" } }`.
|
|
137
|
+
|
|
138
|
+
The smallest useful component looks like this:
|
|
139
|
+
|
|
140
|
+
```tsx
|
|
141
|
+
import { createCliRenderer } from "@opentui/core"
|
|
142
|
+
import { render } from "@opentui/solid"
|
|
143
|
+
import { MarkdownRenderer } from "opentui-markdown-render"
|
|
144
|
+
|
|
145
|
+
const renderer = await createCliRenderer({})
|
|
146
|
+
|
|
147
|
+
render(
|
|
148
|
+
() => (
|
|
149
|
+
<MarkdownRenderer
|
|
150
|
+
content={() => "# Hello\n\nProbability is $P(A \\mid B)$."}
|
|
151
|
+
renderer={renderer}
|
|
152
|
+
/>
|
|
153
|
+
),
|
|
154
|
+
renderer,
|
|
155
|
+
)
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
`content` and `document` are reactive accessors. Pass exactly one of them. Use `content` when the renderer should parse Markdown itself, or pass a `parseDocument` result when the host already owns parsing or caching:
|
|
159
|
+
|
|
160
|
+
```tsx
|
|
161
|
+
import { MarkdownRenderer, parseDocument } from "opentui-markdown-render"
|
|
162
|
+
|
|
163
|
+
const document = parseDocument(markdown)
|
|
164
|
+
|
|
165
|
+
<MarkdownRenderer
|
|
166
|
+
document={() => document}
|
|
167
|
+
renderer={renderer}
|
|
168
|
+
/>
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
The component measures the area assigned by its parent. Put it inside an OpenTUI `scrollbox` when the host owns vertical scrolling, and use `assetBase` for local images.
|
|
172
|
+
|
|
173
|
+
## What the output supports
|
|
174
|
+
|
|
175
|
+
| Input | Terminal output |
|
|
176
|
+
| --- | --- |
|
|
177
|
+
| Markdown headings and emphasis | Styled text with per-level colour, attributes, and spacing |
|
|
178
|
+
| Fenced code | OpenTUI code blocks with language-aware highlighting where supported |
|
|
179
|
+
| Tables | Content-sized terminal tables; wide tables can pan horizontally |
|
|
180
|
+
| `$inline$`, `$$display$$`, `\(inline\)`, `\[display\]` | In-process math images; quality depends on the terminal image protocol |
|
|
181
|
+
| Mermaid fences | Native terminal diagrams for the supported diagram families |
|
|
182
|
+
| Local PNG/JPEG/WebP/GIF | Terminal images with size limits and GIF frame timing |
|
|
183
|
+
| Links | Labels retain their `href`; terminals without hyperlinks show the target as text |
|
|
184
|
+
| Unsupported content | A readable source-oriented fallback rather than a silently missing block |
|
|
185
|
+
|
|
186
|
+
The renderer is deliberately not a browser. HTML is displayed literally, network image URLs are rejected, and unsupported Mermaid syntax remains a code fence. See [Features and supported Markdown](docs/features.md), [Mermaid support](docs/MERMAID_SUPPORT.md), and [Fallbacks and limitations](docs/features.md#fallbacks-and-limitations).
|
|
187
|
+
|
|
188
|
+
## Documentation
|
|
189
|
+
|
|
190
|
+
Start with the page that matches the task:
|
|
191
|
+
|
|
192
|
+
- [Documentation index](docs/README.md) — all guides and reference pages.
|
|
193
|
+
- [Getting started](docs/getting-started.md) — installation, demo setup, first integration, and host layout.
|
|
194
|
+
- [Configuration](docs/CONFIGURATION.md) — JSON/JSONC discovery, themes, frames, tables, headings, images, Mermaid, and status lines.
|
|
195
|
+
- [API reference](docs/api.md) — public exports, component props, document types, helpers, and host-owned state.
|
|
196
|
+
- [Integration recipes](docs/recipes.md) — streaming, cached documents, themes, status lines, frames, and overlays.
|
|
197
|
+
- [Features and supported Markdown](docs/features.md) — rendering behaviour, math, images, tables, links, source preservation, and fallbacks.
|
|
198
|
+
- [Mermaid support matrix](docs/MERMAID_SUPPORT.md) — rendered, ignored, approximated, and unsupported syntax by family.
|
|
199
|
+
- [Troubleshooting](docs/troubleshooting.md) — work through common native, terminal, image, config, and layout problems.
|
|
200
|
+
- [Architecture](docs/architecture.md) — parser, layout, view, native math, media, and host boundaries.
|
|
201
|
+
- [Development](docs/development.md) — verification, native builds, fixtures, tests, and documentation captures.
|
|
202
|
+
- [Examples](examples/README.md) — runnable fixtures that act as executable documentation.
|
|
203
|
+
|
|
204
|
+
## Important constraints
|
|
205
|
+
|
|
206
|
+
- The release target is currently Linux x64 with glibc. Other platforms can still render text and leave formula source readable when a native prebuild is unavailable.
|
|
207
|
+
- Kitty graphics gives the best math quality; the image pipeline also supports Sixel and block-character output when available. Missing native math or failed conversion leaves the original LaTeX source readable.
|
|
208
|
+
- The native addon is in-process Rust code containing MiTeX, Typst, embedded fonts, and PNG rasterization. It does not invoke system LaTeX, TeX Live, Typst, or an external rasterizer.
|
|
209
|
+
- Local images are resolved below `assetBase`; network URLs and paths that escape that root are refused.
|
|
210
|
+
- Mermaid support is an intentional subset of Mermaid, not a compatibility claim for the full upstream grammar.
|
|
211
|
+
- The library does not own selection, comments, persistent IDs, or scrolling state. Hosts use `renderUnit`, source ranges, and their own state for those features.
|
|
212
|
+
|
|
213
|
+
## Development
|
|
214
|
+
|
|
215
|
+
```sh
|
|
216
|
+
bun install
|
|
217
|
+
bun run verify
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
`verify` builds the native addon, runs TypeScript type checking, runs the Bun test suite, and runs the Rust tests. See [Development](docs/development.md) before changing parser behaviour, native math, fixtures, or documentation captures.
|
|
221
|
+
|
|
222
|
+
## License
|
|
223
|
+
|
|
224
|
+
The project is released under the [MIT License](LICENSE). The native math pipeline includes MiTeX; its notice is included in [`NOTICE`](NOTICE).
|
package/bin/otmd.tsx
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
import { createCliRenderer } from "@opentui/core"
|
|
3
|
+
import { render } from "@opentui/solid"
|
|
4
|
+
import { createSignal, onCleanup, Show } from "solid-js"
|
|
5
|
+
import path from "node:path"
|
|
6
|
+
import {
|
|
7
|
+
MarkdownRenderer,
|
|
8
|
+
StatusLine,
|
|
9
|
+
cliUsage,
|
|
10
|
+
loadRendererConfig,
|
|
11
|
+
parseCliArgs,
|
|
12
|
+
resolveRendererTheme,
|
|
13
|
+
useCanvasGraphics,
|
|
14
|
+
} from "../src"
|
|
15
|
+
|
|
16
|
+
const manifest = await Bun.file(new URL("../package.json", import.meta.url)).json() as { version: string }
|
|
17
|
+
|
|
18
|
+
function fail(message: string): never {
|
|
19
|
+
console.error(`otmd: ${message}`)
|
|
20
|
+
console.error("Try 'otmd --help' for usage.")
|
|
21
|
+
process.exit(2)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let command
|
|
25
|
+
try {
|
|
26
|
+
command = parseCliArgs(process.argv.slice(2))
|
|
27
|
+
} catch (error) {
|
|
28
|
+
fail(error instanceof Error ? error.message : String(error))
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (command.kind === "help") {
|
|
32
|
+
process.stdout.write(cliUsage)
|
|
33
|
+
process.exit(0)
|
|
34
|
+
}
|
|
35
|
+
if (command.kind === "version") {
|
|
36
|
+
process.stdout.write(`${manifest.version}\n`)
|
|
37
|
+
process.exit(0)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const file = command.file
|
|
41
|
+
const filePath = file && file !== "-" ? path.resolve(file) : undefined
|
|
42
|
+
const content = file === "-"
|
|
43
|
+
? await new Response(Bun.stdin).text()
|
|
44
|
+
: filePath
|
|
45
|
+
? await readFile(filePath)
|
|
46
|
+
: fail("a Markdown file is required (use - to read stdin)")
|
|
47
|
+
const assetBase = filePath ? path.dirname(filePath) : process.cwd()
|
|
48
|
+
const config = loadRendererConfig(command.configPath, process.cwd(), { discover: command.discoverConfig })
|
|
49
|
+
const renderer = await createCliRenderer({})
|
|
50
|
+
|
|
51
|
+
function Otmd() {
|
|
52
|
+
const graphics = useCanvasGraphics(renderer)
|
|
53
|
+
const theme = () => resolveRendererTheme(renderer, { config, cwd: process.cwd() })
|
|
54
|
+
const [position] = createSignal("1/1")
|
|
55
|
+
const onKeypress = (key: { name?: string }) => {
|
|
56
|
+
if (key.name === "q" || key.name === "escape") {
|
|
57
|
+
renderer.destroy()
|
|
58
|
+
process.exit(0)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
renderer.keyInput.on("keypress", onKeypress)
|
|
62
|
+
onCleanup(() => renderer.keyInput.off("keypress", onKeypress))
|
|
63
|
+
const state = () => ({
|
|
64
|
+
title: filePath ? path.basename(filePath) : "stdin",
|
|
65
|
+
protocol: graphics.protocol(),
|
|
66
|
+
cell: graphics.cell() ? `${graphics.cell()!.width}x${graphics.cell()!.height}` : undefined,
|
|
67
|
+
position: position(),
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<box width="100%" height="100%" flexDirection="column" backgroundColor={theme().background.base}>
|
|
72
|
+
<scrollbox width="100%" flexGrow={1} contentOptions={{ paddingLeft: 1, paddingRight: 1, paddingTop: 1, paddingBottom: 1 }}>
|
|
73
|
+
<MarkdownRenderer
|
|
74
|
+
content={() => content}
|
|
75
|
+
renderer={renderer}
|
|
76
|
+
assetBase={() => assetBase}
|
|
77
|
+
config={() => config}
|
|
78
|
+
cwd={process.cwd()}
|
|
79
|
+
discoverConfig={false}
|
|
80
|
+
/>
|
|
81
|
+
</scrollbox>
|
|
82
|
+
<Show when={config.statusline?.enabled !== false}>
|
|
83
|
+
<StatusLine theme={theme()} state={state} fields={config.statusline?.fields} separator={config.statusline?.separator} border={config.statusline?.border} style={config.statusline?.style} highlight={config.statusline?.highlight} />
|
|
84
|
+
</Show>
|
|
85
|
+
</box>
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
await render(() => <Otmd />, renderer)
|
|
90
|
+
|
|
91
|
+
async function readFile(filePath: string) {
|
|
92
|
+
const file = Bun.file(filePath)
|
|
93
|
+
if (!(await file.exists())) fail(`file not found: ${filePath}`)
|
|
94
|
+
try {
|
|
95
|
+
return await file.text()
|
|
96
|
+
} catch (error) {
|
|
97
|
+
fail(`unable to read ${filePath}: ${error instanceof Error ? error.message : String(error)}`)
|
|
98
|
+
}
|
|
99
|
+
}
|