Skip to content

Commit

Permalink
fix: prism highlighted line breaking (#1029)
Browse files Browse the repository at this point in the history
Co-authored-by: Anthony Fu <[email protected]>
  • Loading branch information
AlbertBrand and antfu authored Jun 18, 2023
1 parent 0390ee7 commit 4845174
Show file tree
Hide file tree
Showing 5 changed files with 155 additions and 21 deletions.
66 changes: 60 additions & 6 deletions packages/slidev/node/plugins/markdown-it-prism.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type { Grammar } from 'prismjs'
import Prism from 'prismjs'
import loadLanguages from 'prismjs/components/'
import type MarkdownIt from 'markdown-it'
import * as htmlparser2 from 'htmlparser2'
import { escapeVueInCode } from './markdown'

interface Options {
Expand All @@ -29,6 +30,30 @@ interface Options {
defaultLanguage?: string
}

interface Attributes {
[s: string]: string
}

class Tag {
tagname: string
attributes: Attributes

constructor(tagname: string, attributes: Attributes) {
this.tagname = tagname
this.attributes = attributes
}

asOpen() {
return `<${this.tagname} ${Object.entries(this.attributes)
.map(([key, value]) => `${key}="${value}"`)
.join(' ')}>`
}

asClosed() {
return `</${this.tagname}>`
}
}

const DEFAULTS: Options = {
plugins: [],
init: () => {
Expand Down Expand Up @@ -111,12 +136,11 @@ function selectLanguage(options: Options, lang: string): [string, Grammar | unde
*/
function highlight(markdownit: MarkdownIt, options: Options, text: string, lang: string): string {
const [langToUse, prismLang] = selectLanguage(options, lang)
const code = text
.trimEnd()
.split(/\r?\n/g)
.map(line => prismLang
? Prism.highlight(line, prismLang, langToUse)
: markdownit.utils.escapeHtml(line))
let code = text.trimEnd()
code = prismLang
? highlightPrism(code, prismLang, langToUse)
: markdownit.utils.escapeHtml(code)
code = code.split(/\r?\n/g)
.map(line => `<span class="line">${line}</span>`)
.join('\n')
const classAttribute = langToUse
Expand All @@ -125,6 +149,36 @@ function highlight(markdownit: MarkdownIt, options: Options, text: string, lang:
return escapeVueInCode(`<pre${classAttribute}><code>${code}</code></pre>`)
}

function highlightPrism(code: string, prismLang: Grammar, langToUse: string) {
// keep track of open tags
const openTags: Tag[] = []
// create parser
const parser = new htmlparser2.Parser({
onopentag(tagname, attributes) {
openTags.push(new Tag(tagname, attributes))
},
onclosetag() {
openTags.pop()
},
})
// highlight code
code = Prism.highlight(code, prismLang, langToUse)
// split result by line
code = code.split(/\r?\n/g).map((line) => {
// if tag hierarchy is not empty, open elements at start of line
const prefix = openTags.map(tag => tag.asOpen()).join('')
// parse line
parser.write(line)
// if tag hierarchy is not empty, close elements at end of line
const postfix = openTags.reverse().map(tag => tag.asClosed()).join('')
// concatenate result
return prefix + line + postfix
}).join('\n')
// clear parser
parser.end()
return code
}

/**
* Checks whether an option represents a valid Prism language
*
Expand Down
1 change: 1 addition & 0 deletions packages/slidev/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
"fs-extra": "^11.1.1",
"get-port-please": "^3.0.1",
"global-dirs": "^3.0.1",
"htmlparser2": "^9.0.0",
"import-from": "^4.0.0",
"is-installed-globally": "^0.4.0",
"jiti": "^1.18.2",
Expand Down
37 changes: 22 additions & 15 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 25 additions & 0 deletions test/__snapshots__/markdown-it-prism.test.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`markdown-it-prism > should render multi-line JS strings and comments correctly 1`] = `
"<pre class=\\"slidev-code language-js\\"><code><span class=\\"line\\"><span class=\\"token template-string\\"><span class=\\"token template-punctuation string\\">\`</span><span class=\\"token string\\">Multi</span></span></span>
<span class=\\"line\\"><span class=\\"token string\\"><span class=\\"token template-string\\">line</span><span class=\\"token template-punctuation string\\">\`</span></span></span>
<span class=\\"line\\"><span class=\\"token comment\\">/**</span></span>
<span class=\\"line\\"><span class=\\"token comment\\"> * Hello</span></span>
<span class=\\"line\\"><span class=\\"token comment\\"> */</span></span></code></pre>
"
`;
exports[`markdown-it-prism > should render multi-line XML nodes correctly 1`] = `
"<pre class=\\"slidev-code language-xml\\"><code><span class=\\"line\\"><span class=\\"token tag\\"><span class=\\"token tag\\"><span class=\\"token punctuation\\">&lt;</span>a</span><span class=\\"token punctuation\\">/></span></span></span>
<span class=\\"line\\"><span class=\\"token tag\\"><span class=\\"token tag\\"><span class=\\"token punctuation\\">&lt;</span>b</span></span></span>
<span class=\\"line\\"><span class=\\"token tag\\"> <span class=\\"token attr-name\\">attr</span><span class=\\"token attr-value\\"><span class=\\"token punctuation attr-equals\\">=</span><span class=\\"token punctuation\\">\\"</span>value<span class=\\"token punctuation\\">\\"</span></span></span></span>
<span class=\\"line\\"><span class=\\"token tag\\"><span class=\\"token punctuation\\">/></span></span></span></code></pre>
"
`;
exports[`markdown-it-prism > should render non-multiline statements correctly 1`] = `
"<pre class=\\"slidev-code language-ts\\"><code><span class=\\"line\\"><span class=\\"token keyword\\">const</span> a <span class=\\"token operator\\">=</span> <span class=\\"token number\\">1</span><span class=\\"token punctuation\\">;</span></span>
<span class=\\"line\\"><span class=\\"token keyword\\">const</span> b <span class=\\"token operator\\">=</span> <span class=\\"token number\\">2</span><span class=\\"token punctuation\\">;</span></span>
<span class=\\"line\\"><span class=\\"token keyword\\">const</span> c <span class=\\"token operator\\">=</span> a <span class=\\"token operator\\">+</span> b<span class=\\"token punctuation\\">;</span></span></code></pre>
"
`;
47 changes: 47 additions & 0 deletions test/markdown-it-prism.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { describe, expect, it } from 'vitest'
import Markdown from 'markdown-it'
import markdownItPrism from '../packages/slidev/node/plugins/markdown-it-prism'

describe('markdown-it-prism', () => {
const md = Markdown()
markdownItPrism(md, { plugins: [], init: () => {} })

it('should render multi-line JS strings and comments correctly', () => {
const text = `
\`\`\`js
\`Multi
line\`
/**
* Hello
*/
\`\`\`
`
const actual = md.render(text)
expect(actual).toMatchSnapshot()
})

it('should render multi-line XML nodes correctly', () => {
const text = `
\`\`\`xml
<a/>
<b
attr="value"
/>
\`\`\`
`
const actual = md.render(text)
expect(actual).toMatchSnapshot()
})

it('should render non-multiline statements correctly', () => {
const text = `
\`\`\`ts
const a = 1;
const b = 2;
const c = a + b;
\`\`\`
`
const actual = md.render(text)
expect(actual).toMatchSnapshot()
})
})

0 comments on commit 4845174

Please sign in to comment.