termcast 1.3.35 → 1.3.37
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/dist/components/detail.d.ts.map +1 -1
- package/dist/components/detail.js +12 -2
- package/dist/components/detail.js.map +1 -1
- package/dist/components/list.d.ts.map +1 -1
- package/dist/components/list.js +11 -2
- package/dist/components/list.js.map +1 -1
- package/dist/diagram-parser.d.ts +34 -0
- package/dist/diagram-parser.d.ts.map +1 -0
- package/dist/diagram-parser.js +114 -0
- package/dist/diagram-parser.js.map +1 -0
- package/dist/examples/simple-detail-markdown.d.ts +2 -0
- package/dist/examples/simple-detail-markdown.d.ts.map +1 -0
- package/dist/examples/simple-detail-markdown.js +94 -0
- package/dist/examples/simple-detail-markdown.js.map +1 -0
- package/dist/internal/scrollbox.d.ts.map +1 -1
- package/dist/internal/scrollbox.js +1 -2
- package/dist/internal/scrollbox.js.map +1 -1
- package/dist/markdown-utils.d.ts +14 -0
- package/dist/markdown-utils.d.ts.map +1 -0
- package/dist/markdown-utils.js +138 -0
- package/dist/markdown-utils.js.map +1 -0
- package/dist/theme.d.ts.map +1 -1
- package/dist/theme.js +5 -24
- package/dist/theme.js.map +1 -1
- package/dist/themes/termcast.json +4 -4
- package/dist/themes.d.ts +12 -0
- package/dist/themes.d.ts.map +1 -1
- package/dist/themes.js +79 -0
- package/dist/themes.js.map +1 -1
- package/package.json +7 -4
- package/src/components/detail.tsx +16 -2
- package/src/components/list.tsx +15 -2
- package/src/diagram-parser.tsx +141 -0
- package/src/examples/list-with-detail.vitest.tsx +34 -34
- package/src/examples/list-with-sections.vitest.tsx +1 -1
- package/src/examples/simple-detail-markdown.tsx +96 -0
- package/src/examples/simple-detail-markdown.vitest.tsx +156 -0
- package/src/examples/simple-grid.vitest.tsx +2 -2
- package/src/examples/swift-extension.vitest.tsx +1 -1
- package/src/internal/scrollbox.tsx +1 -3
- package/src/markdown-utils.tsx +182 -0
- package/src/theme.tsx +5 -24
- package/src/themes/termcast.json +4 -4
- package/src/themes.ts +98 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { test, expect, afterEach, beforeEach } from 'vitest'
|
|
2
|
+
import { launchTerminal, Session } from 'tuistory/src'
|
|
3
|
+
|
|
4
|
+
let session: Session
|
|
5
|
+
|
|
6
|
+
beforeEach(async () => {
|
|
7
|
+
session = await launchTerminal({
|
|
8
|
+
command: 'bun',
|
|
9
|
+
args: ['src/examples/simple-detail-markdown.tsx'],
|
|
10
|
+
cols: 80,
|
|
11
|
+
rows: 70,
|
|
12
|
+
})
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
session?.close()
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
test('detail renders markdown with headings, lists, links, tables, code and diagrams', async () => {
|
|
20
|
+
const text = await session.text({
|
|
21
|
+
waitFor: (text) => {
|
|
22
|
+
return text.includes('Architecture') && text.includes('Configuration Table') && text.includes('Process')
|
|
23
|
+
},
|
|
24
|
+
timeout: 10000,
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
expect(text).toMatchInlineSnapshot(`
|
|
28
|
+
"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
█
|
|
32
|
+
█
|
|
33
|
+
Architecture Overview ▀
|
|
34
|
+
|
|
35
|
+
This document describes the system architecture.
|
|
36
|
+
|
|
37
|
+
Components
|
|
38
|
+
|
|
39
|
+
The system has three main components:
|
|
40
|
+
|
|
41
|
+
- Client - handles user interaction
|
|
42
|
+
- Server - processes requests
|
|
43
|
+
- Database - stores data
|
|
44
|
+
|
|
45
|
+
Links
|
|
46
|
+
|
|
47
|
+
Check out the GitHub repository for the source code.
|
|
48
|
+
|
|
49
|
+
See the API documentation for more details.
|
|
50
|
+
|
|
51
|
+
A paragraph with multiple links inline here.
|
|
52
|
+
|
|
53
|
+
Nested formatting: bold with link inside and italic with link.
|
|
54
|
+
|
|
55
|
+
Configuration Table
|
|
56
|
+
|
|
57
|
+
┌───────────┬───────────┬───────────────────────┐
|
|
58
|
+
│Setting │Default │Description │
|
|
59
|
+
│───────────│───────────│───────────────────────│
|
|
60
|
+
│Host │localhost │Database host address │
|
|
61
|
+
│───────────│───────────│───────────────────────│
|
|
62
|
+
│Port │5432 │Database port number │
|
|
63
|
+
│───────────│───────────│───────────────────────│
|
|
64
|
+
│SSL │false │Enable TLS encryption │
|
|
65
|
+
│───────────│───────────│───────────────────────│
|
|
66
|
+
│Pool Size │10 │Max connections │
|
|
67
|
+
└───────────┴───────────┴───────────────────────┘
|
|
68
|
+
|
|
69
|
+
Flow Diagram
|
|
70
|
+
|
|
71
|
+
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
|
|
72
|
+
│ Client │────▶│ Server │────▶│ Database │
|
|
73
|
+
└─────────────┘ └─────────────┘ └─────────────┘
|
|
74
|
+
|
|
75
|
+
Vertical Flow
|
|
76
|
+
|
|
77
|
+
┌─────────┐
|
|
78
|
+
│ Start │
|
|
79
|
+
└────┬────┘
|
|
80
|
+
│
|
|
81
|
+
▼
|
|
82
|
+
┌─────────┐
|
|
83
|
+
│ Process │
|
|
84
|
+
└────┬────┘
|
|
85
|
+
│
|
|
86
|
+
▼
|
|
87
|
+
┌─────────┐
|
|
88
|
+
│ End │
|
|
89
|
+
└─────────┘
|
|
90
|
+
|
|
91
|
+
Code Example
|
|
92
|
+
|
|
93
|
+
interface Config {
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
esc go back powered by termcast
|
|
97
|
+
|
|
98
|
+
"
|
|
99
|
+
`)
|
|
100
|
+
// Headings
|
|
101
|
+
expect(text).toContain('Architecture')
|
|
102
|
+
expect(text).toContain('Components')
|
|
103
|
+
expect(text).toContain('Configuration Table')
|
|
104
|
+
// List items
|
|
105
|
+
expect(text).toContain('Client')
|
|
106
|
+
expect(text).toContain('Server')
|
|
107
|
+
expect(text).toContain('Database')
|
|
108
|
+
// Links - title text visible, URLs hidden
|
|
109
|
+
expect(text).toContain('GitHub repository')
|
|
110
|
+
expect(text).toContain('API documentation')
|
|
111
|
+
expect(text).toContain('link inside')
|
|
112
|
+
// Table
|
|
113
|
+
expect(text).toContain('Setting')
|
|
114
|
+
expect(text).toContain('localhost')
|
|
115
|
+
expect(text).toContain('5432')
|
|
116
|
+
// Diagram box-drawing chars
|
|
117
|
+
expect(text).toContain('┌─')
|
|
118
|
+
expect(text).toContain('─┐')
|
|
119
|
+
expect(text).toContain('Process')
|
|
120
|
+
// Code block header visible (content may need scroll)
|
|
121
|
+
expect(text).toContain('Code Example')
|
|
122
|
+
expect(text).toContain('interface Config')
|
|
123
|
+
}, 30000)
|
|
124
|
+
|
|
125
|
+
test('links have distinct cyan color from bold/heading text', async () => {
|
|
126
|
+
await session.text({
|
|
127
|
+
waitFor: (text) => text.includes('GitHub repository'),
|
|
128
|
+
timeout: 10000,
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
// Links should be cyan (#56b6c2)
|
|
132
|
+
const linkText = await session.text({
|
|
133
|
+
only: { foreground: '#56b6c2' },
|
|
134
|
+
timeout: 5000,
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
// Bold/heading text should be primary yellow (#ffc000)
|
|
138
|
+
const boldText = await session.text({
|
|
139
|
+
only: { foreground: '#ffc000' },
|
|
140
|
+
timeout: 5000,
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
// Verify links are rendered in cyan
|
|
144
|
+
expect(linkText).toContain('GitHub repository')
|
|
145
|
+
expect(linkText).toContain('API documentation')
|
|
146
|
+
expect(linkText).toContain('multiple')
|
|
147
|
+
expect(linkText).toContain('inline')
|
|
148
|
+
|
|
149
|
+
// Verify headings are rendered in primary color (not cyan)
|
|
150
|
+
expect(boldText).toContain('Architecture Overview')
|
|
151
|
+
expect(boldText).toContain('Components')
|
|
152
|
+
|
|
153
|
+
// Links should NOT appear in bold color
|
|
154
|
+
expect(boldText).not.toContain('GitHub repository')
|
|
155
|
+
expect(boldText).not.toContain('API documentation')
|
|
156
|
+
}, 30000)
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
// Markdown renderNode hook for terminal rendering.
|
|
2
|
+
// Overrides paragraph rendering to hide URLs from links,
|
|
3
|
+
// showing only the link title text with distinct cyan color and underline.
|
|
4
|
+
// Uses opentui's renderNode callback on the <markdown> element.
|
|
5
|
+
//
|
|
6
|
+
// Link text gets TextChunk.link = { url } which encodes as OSC 8 terminal
|
|
7
|
+
// hyperlinks when the terminal supports it. Supported terminals include:
|
|
8
|
+
// - Ghostty, kitty, WezTerm, Alacritty, iTerm2
|
|
9
|
+
// In these terminals, links are clickable natively (cmd+click or hover).
|
|
10
|
+
// In unsupported terminals, links still display with distinct color/underline
|
|
11
|
+
// but won't be clickable.
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
TextRenderable,
|
|
15
|
+
StyledText,
|
|
16
|
+
type TextChunk,
|
|
17
|
+
parseColor,
|
|
18
|
+
createTextAttributes,
|
|
19
|
+
type Renderable,
|
|
20
|
+
type RenderContext,
|
|
21
|
+
} from '@opentui/core'
|
|
22
|
+
import { getResolvedTheme } from './themes'
|
|
23
|
+
import { useStore } from './state'
|
|
24
|
+
|
|
25
|
+
// Minimal token types from marked (dependency of opentui, not termcast directly)
|
|
26
|
+
interface Token {
|
|
27
|
+
type: string
|
|
28
|
+
text?: string
|
|
29
|
+
raw?: string
|
|
30
|
+
href?: string
|
|
31
|
+
tokens?: Token[]
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Matches RenderNodeContext from @opentui/core/renderables/Markdown
|
|
35
|
+
interface RenderNodeContext {
|
|
36
|
+
defaultRender: () => Renderable | null
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface LinkInfo {
|
|
40
|
+
text: string
|
|
41
|
+
href: string
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Recursively check if a token or any of its children contain link tokens
|
|
45
|
+
function hasLinks(token: Token): boolean {
|
|
46
|
+
if (!Array.isArray(token.tokens)) {
|
|
47
|
+
return false
|
|
48
|
+
}
|
|
49
|
+
return token.tokens.some((t) => {
|
|
50
|
+
return t.type === 'link' || hasLinks(t)
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Recursively flatten inline tokens into chunks, stripping link URLs.
|
|
55
|
+
// Handles nested structures like **[link](url)** or *[link](url)*.
|
|
56
|
+
function flattenInlineTokens({
|
|
57
|
+
tokens,
|
|
58
|
+
chunks,
|
|
59
|
+
links,
|
|
60
|
+
primaryColor,
|
|
61
|
+
linkColor,
|
|
62
|
+
textColor,
|
|
63
|
+
defaultAttr,
|
|
64
|
+
}: {
|
|
65
|
+
tokens: Token[]
|
|
66
|
+
chunks: TextChunk[]
|
|
67
|
+
links: LinkInfo[]
|
|
68
|
+
primaryColor: ReturnType<typeof parseColor>
|
|
69
|
+
linkColor: ReturnType<typeof parseColor>
|
|
70
|
+
textColor: ReturnType<typeof parseColor>
|
|
71
|
+
defaultAttr?: number
|
|
72
|
+
}): void {
|
|
73
|
+
for (const token of tokens) {
|
|
74
|
+
if (token.type === 'link') {
|
|
75
|
+
links.push({ text: token.text || '', href: token.href || '' })
|
|
76
|
+
// Render link title only with distinct link color, underline, and OSC 8 terminal hyperlink
|
|
77
|
+
const linkAttr = createTextAttributes({ underline: true })
|
|
78
|
+
chunks.push({
|
|
79
|
+
__isChunk: true,
|
|
80
|
+
text: token.text || '',
|
|
81
|
+
fg: linkColor,
|
|
82
|
+
attributes: linkAttr,
|
|
83
|
+
link: { url: token.href || '' },
|
|
84
|
+
})
|
|
85
|
+
} else if (token.type === 'strong') {
|
|
86
|
+
const boldAttr = createTextAttributes({ bold: true })
|
|
87
|
+
// Recurse into strong children to handle nested links like **[link](url)**
|
|
88
|
+
flattenInlineTokens({
|
|
89
|
+
tokens: token.tokens || [],
|
|
90
|
+
chunks,
|
|
91
|
+
links,
|
|
92
|
+
primaryColor,
|
|
93
|
+
linkColor,
|
|
94
|
+
textColor,
|
|
95
|
+
defaultAttr: boldAttr,
|
|
96
|
+
})
|
|
97
|
+
} else if (token.type === 'em') {
|
|
98
|
+
const italicAttr = createTextAttributes({ italic: true })
|
|
99
|
+
// Recurse into em children to handle nested links like *[link](url)*
|
|
100
|
+
flattenInlineTokens({
|
|
101
|
+
tokens: token.tokens || [],
|
|
102
|
+
chunks,
|
|
103
|
+
links,
|
|
104
|
+
primaryColor,
|
|
105
|
+
linkColor,
|
|
106
|
+
textColor,
|
|
107
|
+
defaultAttr: italicAttr,
|
|
108
|
+
})
|
|
109
|
+
} else if (token.type === 'del') {
|
|
110
|
+
const strikeAttr = createTextAttributes({ strikethrough: true })
|
|
111
|
+
flattenInlineTokens({
|
|
112
|
+
tokens: token.tokens || [],
|
|
113
|
+
chunks,
|
|
114
|
+
links,
|
|
115
|
+
primaryColor,
|
|
116
|
+
linkColor,
|
|
117
|
+
textColor,
|
|
118
|
+
defaultAttr: strikeAttr,
|
|
119
|
+
})
|
|
120
|
+
} else if (token.type === 'codespan') {
|
|
121
|
+
chunks.push({
|
|
122
|
+
__isChunk: true,
|
|
123
|
+
text: token.text || '',
|
|
124
|
+
fg: primaryColor,
|
|
125
|
+
attributes: defaultAttr,
|
|
126
|
+
})
|
|
127
|
+
} else if (token.type === 'br') {
|
|
128
|
+
chunks.push({
|
|
129
|
+
__isChunk: true,
|
|
130
|
+
text: '\n',
|
|
131
|
+
fg: textColor,
|
|
132
|
+
})
|
|
133
|
+
} else {
|
|
134
|
+
// text, escape, etc.
|
|
135
|
+
chunks.push({
|
|
136
|
+
__isChunk: true,
|
|
137
|
+
text: token.text ?? token.raw ?? '',
|
|
138
|
+
fg: textColor,
|
|
139
|
+
attributes: defaultAttr,
|
|
140
|
+
})
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Create a renderNode function that overrides paragraph rendering
|
|
146
|
+
// to hide link URLs and make link text bold + bright.
|
|
147
|
+
// Links get OSC 8 terminal hyperlinks via TextChunk.link so terminals
|
|
148
|
+
// can handle click-to-open natively (cmd+click in iTerm2, kitty, Ghostty).
|
|
149
|
+
export function createMarkdownRenderNode(renderer: RenderContext): (token: Token, context: RenderNodeContext) => Renderable | undefined {
|
|
150
|
+
let nodeCounter = 0
|
|
151
|
+
|
|
152
|
+
return (token: Token, context: RenderNodeContext) => {
|
|
153
|
+
// Only override paragraphs that contain links (including nested)
|
|
154
|
+
if (token.type !== 'paragraph' || !hasLinks(token)) {
|
|
155
|
+
return undefined // use default rendering
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const themeName = useStore.getState().currentThemeName
|
|
159
|
+
const theme = getResolvedTheme(themeName)
|
|
160
|
+
const primaryColor = parseColor(theme.primary)
|
|
161
|
+
const linkColor = parseColor(theme.markdownLinkText)
|
|
162
|
+
const textColor = parseColor(theme.text)
|
|
163
|
+
|
|
164
|
+
const chunks: TextChunk[] = []
|
|
165
|
+
const links: LinkInfo[] = []
|
|
166
|
+
flattenInlineTokens({
|
|
167
|
+
tokens: token.tokens || [],
|
|
168
|
+
chunks,
|
|
169
|
+
links,
|
|
170
|
+
primaryColor,
|
|
171
|
+
linkColor,
|
|
172
|
+
textColor,
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
return new TextRenderable(renderer, {
|
|
176
|
+
id: `para-links-${nodeCounter++}`,
|
|
177
|
+
content: new StyledText(chunks),
|
|
178
|
+
width: '100%',
|
|
179
|
+
marginBottom: 1,
|
|
180
|
+
})
|
|
181
|
+
}
|
|
182
|
+
}
|
package/src/theme.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { SyntaxStyle
|
|
2
|
-
import { getResolvedTheme, type ResolvedTheme, defaultThemeName, themeNames } from './themes'
|
|
1
|
+
import { SyntaxStyle } from '@opentui/core'
|
|
2
|
+
import { getResolvedTheme, getSyntaxTheme, type ResolvedTheme, defaultThemeName, themeNames } from './themes'
|
|
3
3
|
import { useStore } from './state'
|
|
4
4
|
import { Cache } from './apis/cache'
|
|
5
5
|
|
|
@@ -51,30 +51,11 @@ export function useTheme(): ResolvedTheme {
|
|
|
51
51
|
return getResolvedTheme(themeName)
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
// Returns a full SyntaxStyle with all code + markdown scopes.
|
|
55
|
+
// Code blocks inside markdown get proper syntax highlighting (keywords, strings, etc.)
|
|
54
56
|
export function getMarkdownSyntaxStyle(): SyntaxStyle {
|
|
55
57
|
const themeName = useStore.getState().currentThemeName
|
|
56
|
-
|
|
57
|
-
return SyntaxStyle.fromStyles({
|
|
58
|
-
default: { fg: RGBA.fromHex(t.markdownText) },
|
|
59
|
-
'markup.heading.1': { fg: RGBA.fromHex(t.markdownHeading), bold: true },
|
|
60
|
-
'markup.heading.2': { fg: RGBA.fromHex(t.markdownHeading), bold: true },
|
|
61
|
-
'markup.heading.3': { fg: RGBA.fromHex(t.markdownHeading), bold: true },
|
|
62
|
-
'markup.heading.4': { fg: RGBA.fromHex(t.markdownHeading), bold: true },
|
|
63
|
-
'markup.heading.5': { fg: RGBA.fromHex(t.markdownHeading), bold: true },
|
|
64
|
-
'markup.heading.6': { fg: RGBA.fromHex(t.markdownHeading), bold: true },
|
|
65
|
-
'markup.heading': { fg: RGBA.fromHex(t.markdownHeading), bold: true },
|
|
66
|
-
'markup.raw.block': { fg: RGBA.fromHex(t.markdownCode) },
|
|
67
|
-
'markup.link.url': { fg: RGBA.fromHex(t.markdownLink) },
|
|
68
|
-
'markup.link.label': { fg: RGBA.fromHex(t.markdownLinkText) },
|
|
69
|
-
'markup.list': { fg: RGBA.fromHex(t.markdownListItem) },
|
|
70
|
-
'markup.list.checked': { fg: RGBA.fromHex(t.success) },
|
|
71
|
-
'markup.list.unchecked': { fg: RGBA.fromHex(t.textMuted) },
|
|
72
|
-
'markup.quote': { fg: RGBA.fromHex(t.markdownBlockQuote), italic: true },
|
|
73
|
-
'punctuation.special': { fg: RGBA.fromHex(t.syntaxPunctuation) },
|
|
74
|
-
'punctuation.delimiter': { fg: RGBA.fromHex(t.syntaxPunctuation) },
|
|
75
|
-
'string.escape': { fg: RGBA.fromHex(t.syntaxString) },
|
|
76
|
-
label: { fg: RGBA.fromHex(t.accent) },
|
|
77
|
-
})
|
|
58
|
+
return SyntaxStyle.fromStyles(getSyntaxTheme(themeName))
|
|
78
59
|
}
|
|
79
60
|
|
|
80
61
|
// For backward compatibility - some code imports markdownSyntaxStyle directly
|
package/src/themes/termcast.json
CHANGED
|
@@ -140,12 +140,12 @@
|
|
|
140
140
|
"light": "darkStep9"
|
|
141
141
|
},
|
|
142
142
|
"markdownLink": {
|
|
143
|
-
"dark": "
|
|
144
|
-
"light": "
|
|
143
|
+
"dark": "darkCyan",
|
|
144
|
+
"light": "darkCyan"
|
|
145
145
|
},
|
|
146
146
|
"markdownLinkText": {
|
|
147
|
-
"dark": "
|
|
148
|
-
"light": "
|
|
147
|
+
"dark": "darkCyan",
|
|
148
|
+
"light": "darkCyan"
|
|
149
149
|
},
|
|
150
150
|
"markdownCode": {
|
|
151
151
|
"dark": "darkStep9",
|
package/src/themes.ts
CHANGED
|
@@ -55,6 +55,7 @@ export interface ResolvedTheme {
|
|
|
55
55
|
// Text colors
|
|
56
56
|
text: string
|
|
57
57
|
textMuted: string
|
|
58
|
+
conceal: string
|
|
58
59
|
|
|
59
60
|
// Background colors
|
|
60
61
|
background: string
|
|
@@ -122,6 +123,17 @@ export interface ResolvedTheme {
|
|
|
122
123
|
transparent: undefined
|
|
123
124
|
}
|
|
124
125
|
|
|
126
|
+
export interface SyntaxThemeStyle {
|
|
127
|
+
fg: RGBA
|
|
128
|
+
bold?: boolean
|
|
129
|
+
italic?: boolean
|
|
130
|
+
underline?: boolean
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export interface SyntaxTheme {
|
|
134
|
+
[key: string]: SyntaxThemeStyle
|
|
135
|
+
}
|
|
136
|
+
|
|
125
137
|
// Note: lucent-orng excluded because it uses transparent backgrounds
|
|
126
138
|
const DEFAULT_THEMES: Record<string, ThemeJson> = {
|
|
127
139
|
aura,
|
|
@@ -210,6 +222,7 @@ function resolveTheme(
|
|
|
210
222
|
// Text
|
|
211
223
|
text: resolveColorToHex(t.text ?? fallbackText),
|
|
212
224
|
textMuted: resolveColorToHex(t.textMuted ?? fallbackGray),
|
|
225
|
+
conceal: resolveColorToHex(t.conceal ?? t.textMuted ?? fallbackGray),
|
|
213
226
|
|
|
214
227
|
// Background
|
|
215
228
|
background: resolveColorToHex(t.background ?? fallbackBg),
|
|
@@ -286,6 +299,91 @@ export function getResolvedTheme(
|
|
|
286
299
|
return resolveTheme(themeJson, mode)
|
|
287
300
|
}
|
|
288
301
|
|
|
302
|
+
// Full syntax theme with tree-sitter scope names for both code and markdown.
|
|
303
|
+
// Ported from critique's getSyntaxTheme() for consistent rendering.
|
|
304
|
+
export function getSyntaxTheme(
|
|
305
|
+
name: string,
|
|
306
|
+
mode: 'dark' | 'light' = 'dark',
|
|
307
|
+
): SyntaxTheme {
|
|
308
|
+
const resolved = getResolvedTheme(name, mode)
|
|
309
|
+
|
|
310
|
+
const h = (hex: string): RGBA => {
|
|
311
|
+
return parseColor(hex)
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return {
|
|
315
|
+
// Default text style
|
|
316
|
+
default: { fg: h(resolved.text) },
|
|
317
|
+
|
|
318
|
+
// Code syntax styles
|
|
319
|
+
keyword: { fg: h(resolved.syntaxKeyword), italic: true },
|
|
320
|
+
'keyword.import': { fg: h(resolved.syntaxKeyword) },
|
|
321
|
+
'keyword.return': { fg: h(resolved.syntaxKeyword), italic: true },
|
|
322
|
+
'keyword.conditional': { fg: h(resolved.syntaxKeyword), italic: true },
|
|
323
|
+
'keyword.repeat': { fg: h(resolved.syntaxKeyword), italic: true },
|
|
324
|
+
'keyword.type': { fg: h(resolved.syntaxType), bold: true, italic: true },
|
|
325
|
+
'keyword.function': { fg: h(resolved.syntaxFunction) },
|
|
326
|
+
'keyword.operator': { fg: h(resolved.syntaxOperator) },
|
|
327
|
+
'keyword.modifier': { fg: h(resolved.syntaxKeyword), italic: true },
|
|
328
|
+
'keyword.exception': { fg: h(resolved.syntaxKeyword), italic: true },
|
|
329
|
+
string: { fg: h(resolved.syntaxString) },
|
|
330
|
+
symbol: { fg: h(resolved.syntaxString) },
|
|
331
|
+
comment: { fg: h(resolved.syntaxComment), italic: true },
|
|
332
|
+
'comment.documentation': { fg: h(resolved.syntaxComment), italic: true },
|
|
333
|
+
number: { fg: h(resolved.syntaxNumber) },
|
|
334
|
+
boolean: { fg: h(resolved.syntaxNumber) },
|
|
335
|
+
constant: { fg: h(resolved.syntaxNumber) },
|
|
336
|
+
function: { fg: h(resolved.syntaxFunction) },
|
|
337
|
+
'function.call': { fg: h(resolved.syntaxFunction) },
|
|
338
|
+
'function.method': { fg: h(resolved.syntaxFunction) },
|
|
339
|
+
'function.method.call': { fg: h(resolved.syntaxVariable) },
|
|
340
|
+
constructor: { fg: h(resolved.syntaxFunction) },
|
|
341
|
+
type: { fg: h(resolved.syntaxType) },
|
|
342
|
+
module: { fg: h(resolved.syntaxType) },
|
|
343
|
+
class: { fg: h(resolved.syntaxType) },
|
|
344
|
+
operator: { fg: h(resolved.syntaxOperator) },
|
|
345
|
+
variable: { fg: h(resolved.syntaxVariable) },
|
|
346
|
+
'variable.parameter': { fg: h(resolved.syntaxVariable) },
|
|
347
|
+
'variable.member': { fg: h(resolved.syntaxFunction) },
|
|
348
|
+
property: { fg: h(resolved.syntaxVariable) },
|
|
349
|
+
parameter: { fg: h(resolved.syntaxVariable) },
|
|
350
|
+
bracket: { fg: h(resolved.syntaxPunctuation) },
|
|
351
|
+
punctuation: { fg: h(resolved.syntaxPunctuation) },
|
|
352
|
+
'punctuation.bracket': { fg: h(resolved.syntaxPunctuation) },
|
|
353
|
+
'punctuation.delimiter': { fg: h(resolved.syntaxOperator) },
|
|
354
|
+
'punctuation.special': { fg: h(resolved.syntaxOperator) },
|
|
355
|
+
|
|
356
|
+
// Markdown styles - tree-sitter scope names for markdown
|
|
357
|
+
'markup.heading': { fg: h(resolved.markdownHeading), bold: true },
|
|
358
|
+
'markup.heading.1': { fg: h(resolved.markdownHeading), bold: true },
|
|
359
|
+
'markup.heading.2': { fg: h(resolved.markdownHeading), bold: true },
|
|
360
|
+
'markup.heading.3': { fg: h(resolved.markdownHeading), bold: true },
|
|
361
|
+
'markup.heading.4': { fg: h(resolved.markdownHeading), bold: true },
|
|
362
|
+
'markup.heading.5': { fg: h(resolved.markdownHeading), bold: true },
|
|
363
|
+
'markup.heading.6': { fg: h(resolved.markdownHeading), bold: true },
|
|
364
|
+
'markup.bold': { fg: h(resolved.markdownStrong), bold: true },
|
|
365
|
+
'markup.strong': { fg: h(resolved.markdownStrong), bold: true },
|
|
366
|
+
'markup.italic': { fg: h(resolved.markdownEmph), italic: true },
|
|
367
|
+
'markup.list': { fg: h(resolved.markdownListItem) },
|
|
368
|
+
'markup.list.checked': { fg: h(resolved.success) },
|
|
369
|
+
'markup.list.unchecked': { fg: h(resolved.textMuted) },
|
|
370
|
+
'markup.quote': { fg: h(resolved.markdownBlockQuote), italic: true },
|
|
371
|
+
'markup.raw': { fg: h(resolved.markdownCode) },
|
|
372
|
+
'markup.raw.block': { fg: h(resolved.markdownCode) },
|
|
373
|
+
'markup.raw.inline': { fg: h(resolved.markdownCode) },
|
|
374
|
+
'markup.link': { fg: h(resolved.markdownLink), underline: true },
|
|
375
|
+
'markup.link.label': { fg: h(resolved.markdownLinkText), underline: true },
|
|
376
|
+
'markup.link.url': { fg: h(resolved.markdownLink), underline: true },
|
|
377
|
+
label: { fg: h(resolved.markdownLinkText) },
|
|
378
|
+
spell: { fg: h(resolved.text) },
|
|
379
|
+
nospell: { fg: h(resolved.text) },
|
|
380
|
+
conceal: { fg: h(resolved.conceal) },
|
|
381
|
+
'string.special': { fg: h(resolved.markdownLink), underline: true },
|
|
382
|
+
'string.special.url': { fg: h(resolved.markdownLink), underline: true },
|
|
383
|
+
'string.escape': { fg: h(resolved.syntaxString) },
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
289
387
|
export const themeNames = Object.keys(DEFAULT_THEMES).sort()
|
|
290
388
|
|
|
291
389
|
export const defaultThemeName = 'termcast'
|