opentui-markdown-render 0.2.1 → 0.2.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opentui-markdown-render",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "OpenTUI Markdown renderer with optional native LaTeX math",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -56,12 +56,12 @@
56
56
  "string-width": "7.2.0"
57
57
  },
58
58
  "devDependencies": {
59
- "@opentui/core": "0.5.12",
60
- "@opentui/solid": "0.5.12",
59
+ "@opentui/core": "0.8.2",
60
+ "@opentui/solid": "0.8.2",
61
61
  "@tsconfig/bun": "1.0.9",
62
62
  "@types/bun": "1.4.0",
63
63
  "@typescript/native-preview": "7.0.0-dev.20251207.1",
64
- "solid-js": "1.9.15"
64
+ "solid-js": "1.9.18"
65
65
  },
66
66
  "engines": {
67
67
  "bun": ">=1.4.0"
package/src/app.tsx CHANGED
@@ -1,5 +1,5 @@
1
- import type { CliRenderer } from "@opentui/core"
2
- import { createMemo, createSignal } from "solid-js"
1
+ import type { CliRenderer, SyntaxStyle } from "@opentui/core"
2
+ import { createMemo, createSignal, onCleanup } from "solid-js"
3
3
  import { useCanvasGraphics } from "./graphics"
4
4
  import { resolveHeadingStyles } from "./headings"
5
5
  import { rgbaToHex } from "./math"
@@ -9,6 +9,7 @@ import { parseDocument, type Document, type FrameConfig, type TablesConfig } fro
9
9
  import { MarkdownFrame } from "./view/frame"
10
10
  import { DocumentView, type RenderUnit } from "./view/document"
11
11
  import type { Theme } from "./theme/types"
12
+ import { generateSyntax } from "./theme/syntax"
12
13
 
13
14
  export type { ThemeModeSetting } from "./config"
14
15
  export { loadRendererConfig } from "./config"
@@ -154,6 +155,14 @@ export function MarkdownRenderer(props: MarkdownRendererProps) {
154
155
  // The look of the heading levels is a configuration of its own: the colours
155
156
  // come from the theme, the attributes and the spacing from the config file.
156
157
  const headings = createMemo(() => resolveHeadingStyles(config().headings))
158
+ let currentSyntax: SyntaxStyle | undefined
159
+ const syntaxStyle = createMemo(() => {
160
+ currentSyntax?.destroy()
161
+ const nativeScene = (renderer as CliRenderer & { readonly nativeScene?: object }).nativeScene
162
+ currentSyntax = generateSyntax(theme(), headings(), theme().markdown.codeBlock, nativeScene)
163
+ return currentSyntax
164
+ })
165
+ onCleanup(() => currentSyntax?.destroy())
157
166
  return (
158
167
  <box
159
168
  width="100%"
@@ -178,6 +187,7 @@ export function MarkdownRenderer(props: MarkdownRendererProps) {
178
187
  tableOptions={tableOptions()}
179
188
  mermaidOptions={config().mermaid}
180
189
  mathScale={mathScale()}
190
+ syntaxStyle={syntaxStyle()}
181
191
  onDiagnostic={props.onDiagnostic}
182
192
  renderUnit={props.renderUnit}
183
193
  />
package/src/index.ts CHANGED
@@ -89,6 +89,7 @@ export {
89
89
  bundledThemeNames,
90
90
  themeFileNames,
91
91
  generateSyntax,
92
+ type SyntaxResourceOwner,
92
93
  resolveTheme,
93
94
  mergeTheme,
94
95
  deriveStatusLineTheme,
@@ -1,7 +1,7 @@
1
1
  export { defaultTheme } from "./defaults"
2
2
  export { loadTheme, bundledThemeNames, themeFileNames } from "./load"
3
3
  export type { ThemeEnvironment } from "./load"
4
- export { generateSyntax } from "./syntax"
4
+ export { generateSyntax, type SyntaxResourceOwner } from "./syntax"
5
5
  export { resolveTheme, mergeTheme, deriveStatusLineTheme } from "./resolve"
6
6
  export { mixColors } from "./colors"
7
7
  export type { Theme, ThemeMode, ThemePreset, ThemeOverride, StatusLineTheme, StatusLineThemeOverride, FrameTheme, FrameThemeOverride, MarkdownThemeOverride, HeadingLevel } from "./types"
@@ -2,7 +2,14 @@ import { SyntaxStyle, type RGBA, type ThemeTokenStyle } from "@opentui/core"
2
2
  import { defaultHeadingStyles, headingColor, headingLevels, type HeadingStyles } from "../headings"
3
3
  import type { HeadingLevel, Theme } from "./types"
4
4
 
5
- export function generateSyntax(theme: Theme, headings: HeadingStyles = defaultHeadingStyles, defaultColor: RGBA = theme.text.base) {
5
+ export type SyntaxResourceOwner = object
6
+
7
+ export function generateSyntax(
8
+ theme: Theme,
9
+ headings: HeadingStyles = defaultHeadingStyles,
10
+ defaultColor: RGBA = theme.text.base,
11
+ owner?: SyntaxResourceOwner,
12
+ ) {
6
13
  const { markdown, syntax, text } = theme
7
14
  const feedback = text.feedback
8
15
 
@@ -22,7 +29,7 @@ export function generateSyntax(theme: Theme, headings: HeadingStyles = defaultHe
22
29
  rule([`markup.heading.${level}`], headingColor(theme, level), headingStyle(level)),
23
30
  )
24
31
 
25
- return SyntaxStyle.fromTheme([
32
+ const rules = [
26
33
  rule(["default"], defaultColor),
27
34
  rule(["prompt"], theme.accent),
28
35
  rule(["comment", "comment.documentation"], syntax.comment, { italic: true }),
@@ -84,7 +91,11 @@ export function generateSyntax(theme: Theme, headings: HeadingStyles = defaultHe
84
91
  rule(["error"], feedback.error, { bold: true }),
85
92
  rule(["warning"], feedback.warning, { bold: true }),
86
93
  rule(["info"], feedback.info),
87
- ])
94
+ ]
95
+ // OpenTUI 0.8 makes native resource ownership explicit. Passing the owner is
96
+ // harmless on older supported releases, whose factory ignores extra args.
97
+ const fromTheme = SyntaxStyle.fromTheme as (rules: ThemeTokenStyle[], owner?: SyntaxResourceOwner) => SyntaxStyle
98
+ return fromTheme(rules, owner)
88
99
  }
89
100
 
90
101
  function rule(
package/src/view/code.tsx CHANGED
@@ -1,4 +1,6 @@
1
- import { infoStringToFiletype } from "@opentui/core"
1
+ import { infoStringToFiletype, type SyntaxStyle } from "@opentui/core"
2
+ import { useRenderer } from "@opentui/solid"
3
+ import { onCleanup } from "solid-js"
2
4
  import { generateSyntax } from "../theme/syntax"
3
5
  import type { Theme } from "../theme/types"
4
6
  import type { HeadingStyles } from "../headings"
@@ -8,14 +10,19 @@ export type CodeBlockProps = {
8
10
  language: string | null
9
11
  theme: Theme
10
12
  headings: HeadingStyles
13
+ syntaxStyle?: SyntaxStyle
11
14
  }
12
15
 
13
16
  export function CodeBlock(props: CodeBlockProps) {
17
+ const renderer = useRenderer()
18
+ const nativeScene = (renderer as typeof renderer & { readonly nativeScene?: object }).nativeScene
19
+ const owned = props.syntaxStyle ? undefined : generateSyntax(props.theme, props.headings, props.theme.markdown.codeBlock, nativeScene)
20
+ onCleanup(() => owned?.destroy())
14
21
  return (
15
22
  <code
16
23
  content={props.content}
17
24
  filetype={infoStringToFiletype(props.language ?? "")}
18
- syntaxStyle={generateSyntax(props.theme, props.headings, props.theme.markdown.codeBlock)}
25
+ syntaxStyle={props.syntaxStyle ?? owned!}
19
26
  fg={props.theme.markdown.codeBlock}
20
27
  bg={props.theme.background.base}
21
28
  conceal={false}
@@ -1,6 +1,6 @@
1
1
  import { createMemo, For, type Accessor, type JSX } from "solid-js"
2
2
  import stringWidth from "string-width"
3
- import { type TextChunk } from "@opentui/core"
3
+ import { type SyntaxStyle, type TextChunk } from "@opentui/core"
4
4
  import { assertNever, type Block, type Document, type ListItem, type UnitInfo } from "../core/document"
5
5
  import type { ResolvedTableOptions } from "../core/tables"
6
6
  import type { ImagesConfig, MermaidConfig } from "../config/types"
@@ -31,6 +31,7 @@ export type DocumentViewProps = {
31
31
  tableOptions: ResolvedTableOptions
32
32
  mermaidOptions?: MermaidConfig
33
33
  mathScale?: number
34
+ syntaxStyle?: SyntaxStyle
34
35
  onDiagnostic?: (diagnostic: { level: "warning" | "error"; message: string }) => void
35
36
  renderUnit?: RenderUnit
36
37
  }
@@ -97,9 +98,11 @@ export function DocumentView(props: DocumentViewProps) {
97
98
  columns={columns > 2 ? columns - 2 : columns} quote />}</For>
98
99
  </box>
99
100
  case "list": return <ListBlockView block={() => block} columns={columns} quote={input.quote} wrapItems={false} />
100
- case "code": return <CodeBlock content={block.content} language={block.language} theme={props.theme} headings={props.headings} />
101
+ case "code": return <CodeBlock content={block.content} language={block.language} theme={props.theme}
102
+ headings={props.headings} syntaxStyle={props.syntaxStyle} />
101
103
  case "mermaid": return <MermaidDiagramView source={block.content} theme={props.theme} columns={columns}
102
- mermaidOptions={props.mermaidOptions} headings={props.headings} onDiagnostic={props.onDiagnostic} />
104
+ mermaidOptions={props.mermaidOptions} headings={props.headings} syntaxStyle={props.syntaxStyle}
105
+ onDiagnostic={props.onDiagnostic} />
103
106
  case "table": return <TableBlock block={block} options={props.tableOptions} theme={props.theme} headings={props.headings}
104
107
  hyperlinks={props.graphics.hyperlinks?.() ?? true} />
105
108
  case "literal": return <text fg={props.theme.markdown.text} wrapMode="word" content={block.text} />
@@ -1,5 +1,5 @@
1
1
  import { createEffect, createMemo, Show, type Accessor } from "solid-js"
2
- import { type MouseEvent, type TextRenderable } from "@opentui/core"
2
+ import { type MouseEvent, type SyntaxStyle, type TextRenderable } from "@opentui/core"
3
3
  import { measureMermaidDiagram, type PreparedDiagram } from "../merman/diagram"
4
4
  import { detectMermaidDiagram } from "../merman/detect"
5
5
  import { MermaidSyntaxError } from "../merman/diagnostics"
@@ -16,6 +16,7 @@ export type MermaidDiagramViewProps = {
16
16
  columns: number
17
17
  mermaidOptions?: MermaidConfig
18
18
  headings?: HeadingStyles
19
+ syntaxStyle?: SyntaxStyle
19
20
  onDiagnostic?: (diagnostic: { level: "error"; message: string }) => void
20
21
  }
21
22
 
@@ -121,7 +122,7 @@ export function MermaidDiagramView(props: MermaidDiagramViewProps) {
121
122
  })
122
123
  return <box width="100%" flexDirection="column" flexShrink={0}>
123
124
  <Show when={prepared()} fallback={<CodeBlock content={props.source} language="mermaid"
124
- theme={props.theme} headings={props.headings ?? defaultHeadingStyles} />}>
125
+ theme={props.theme} headings={props.headings ?? defaultHeadingStyles} syntaxStyle={props.syntaxStyle} />}>
125
126
  {(value) => <DiagramText prepared={value} />}
126
127
  </Show>
127
128
  </box>