fix markdown rendering: protect code blocks/escapes, move links before bold/italic, add task lists and H2-H6 support
This commit is contained in:
parent
ecf7b93f3f
commit
b4b72e2c02
|
|
@ -1,40 +1,65 @@
|
|||
export function renderMarkdown(text: string): string {
|
||||
// Strip code fences (``` and ```language) — we're already in a terminal
|
||||
let result = text.replace(/^```\w*\n?/gm, "")
|
||||
// Extract fenced code blocks before anything else
|
||||
const codeBlocks: string[] = []
|
||||
let result = text.replace(/^```\w*\n([\s\S]*?)^```\s*$/gm, (_, content) => {
|
||||
codeBlocks.push(content)
|
||||
return `\x00CODEBLOCK${codeBlocks.length - 1}\x00`
|
||||
})
|
||||
|
||||
// Extract code spans first so their contents aren't processed as bold/italic
|
||||
// Extract backslash escapes so they aren't processed as markdown
|
||||
const escapes: string[] = []
|
||||
result = result.replace(/\\([\\`*_~\[\]()#>!-])/g, (_, ch) => {
|
||||
escapes.push(ch)
|
||||
return `\x00ESC${escapes.length - 1}\x00`
|
||||
})
|
||||
|
||||
// Extract code spans so their contents aren't processed as bold/italic
|
||||
const codeSpans: string[] = []
|
||||
result = result.replace(/`([^`]+)`/g, (_, code) => {
|
||||
codeSpans.push(code)
|
||||
return `\x00CODE${codeSpans.length - 1}\x00`
|
||||
})
|
||||
|
||||
// Links: [text](url) → OSC 8 terminal hyperlink, underlined blue
|
||||
// Must run before H1/bold/italic so ANSI `[` chars don't confuse the link regex
|
||||
result = result.replace(/(?<!!)\[([^\]]+)\]\(([^)]+)\)/g, (_, text, href) => {
|
||||
const url = href.replace(/\s+"[^"]*"$/, "") // strip optional title
|
||||
return `\x1b]8;;${url}\x07\x1b[4;38;5;75m${text}\x1b[24;39m\x1b]8;;\x07`
|
||||
})
|
||||
|
||||
// H1: # Header → bold+italic+underline
|
||||
result = result.replace(/^# (.+)$/gm, "\x1b[1;3;4m$1\x1b[22;23;24m")
|
||||
|
||||
// H2/H3: ## Header / ### Header → bold
|
||||
result = result.replace(/^#{2,3} (.+)$/gm, "\x1b[1m$1\x1b[22m")
|
||||
result = result.replace(/^#{2,6} (.+)$/gm, "\x1b[1m$1\x1b[22m")
|
||||
|
||||
// Blockquotes: > text → dim+italic
|
||||
result = result.replace(/^> (.+)$/gm, "\x1b[2;3m$1\x1b[22;23m")
|
||||
|
||||
// Bare blockquote lines: > with no text
|
||||
result = result.replace(/^>\s*$/gm, "")
|
||||
|
||||
// Task lists: - [x] → ✓ green, - [ ] → ○ dim
|
||||
result = result.replace(/^(\s*)[-*] \[x\] (.+)$/gm, "$1\x1b[32m✓\x1b[39m $2")
|
||||
result = result.replace(/^(\s*)[-*] \[ \] (.+)$/gm, "$1\x1b[2m☐\x1b[22m $2")
|
||||
|
||||
// Bold: **text**
|
||||
result = result.replace(/\*\*(.+?)\*\*/g, "\x1b[1m$1\x1b[22m")
|
||||
|
||||
// Italic: *text*
|
||||
result = result.replace(/\*(.+?)\*/g, "\x1b[3m$1\x1b[23m")
|
||||
|
||||
// Links: [text](url) → OSC 8 terminal hyperlink, underlined blue
|
||||
result = result.replace(/(?<!!)\[([^\]]+)\]\(([^)]+)\)/g, (_, text, href) => {
|
||||
const url = href.replace(/\s+"[^"]*"$/, "") // strip optional title
|
||||
return `\x1b]8;;${url}\x07\x1b[4;38;5;75m${text}\x1b[24;39m\x1b]8;;\x07`
|
||||
})
|
||||
|
||||
// Restore code spans as light blue
|
||||
result = result.replace(/\x00CODE(\d+)\x00/g, (_, i) => {
|
||||
return `\x1b[38;5;147m${codeSpans[parseInt(i)]}\x1b[39m`
|
||||
})
|
||||
|
||||
// Restore backslash escapes as literal characters
|
||||
result = result.replace(/\x00ESC(\d+)\x00/g, (_, i) => escapes[parseInt(i)])
|
||||
|
||||
// Restore fenced code blocks as plain text
|
||||
result = result.replace(/\x00CODEBLOCK(\d+)\x00/g, (_, i) => codeBlocks[parseInt(i)])
|
||||
|
||||
// Breathe: add blank line before list starts when preceded by non-empty text
|
||||
const lines = result.split("\n")
|
||||
for (let i = lines.length - 1; i > 0; i--) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user