Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: use magic-string-stack #1545

Merged
merged 5 commits into from
Apr 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
24 changes: 21 additions & 3 deletions packages/slidev/node/syntax/markdown-it/markdown-it-v-drag.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,30 @@
import type MarkdownIt from 'markdown-it'
import type { SourceMapConsumer } from 'source-map-js'
import type MagicString from 'magic-string-stack'
import { SourceMapConsumer } from 'source-map-js'

export default function markdownItVDrag(md: MarkdownIt, sourceMapConsumers: Record<string, SourceMapConsumer>) {
export default function markdownItVDrag(md: MarkdownIt, markdownTransformMap: Map<string, MagicString>) {
const visited = new WeakSet()
const sourceMapConsumers = new WeakMap<MagicString, SourceMapConsumer>()

function getSourceMapConsumer(id: string) {
const s = markdownTransformMap.get(id)
if (!s)
return undefined
let smc = sourceMapConsumers.get(s)
if (smc)
return smc
const sourceMap = s.generateMap()
smc = new SourceMapConsumer({
...sourceMap,
version: sourceMap.version.toString(),
})
sourceMapConsumers.set(s, smc)
return smc
}

const _parse = md.parse
md.parse = function (src, env) {
const smc = sourceMapConsumers[env.id]
const smc = getSourceMapConsumer(env.id)
const toOriginalPos = smc
? (line: number) => smc.originalPositionFor({ line, column: 0 }).line
: (line: number) => line
Expand Down
5 changes: 1 addition & 4 deletions packages/slidev/node/syntax/transform/code-wrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,10 @@ export const reCodeBlock = /^```([\w'-]+?)(?:\s*{([\d\w*,\|-]+)}\s*?({.*?})?(.*?
export function transformCodeWrapper(ctx: MarkdownTransformContext) {
ctx.s.replace(
reCodeBlock,
(full, lang = '', rangeStr: string = '', options = '', attrs = '', code: string, index: number) => {
if (ctx.isIgnored(index))
return full
(full, lang = '', rangeStr: string = '', options = '', attrs = '', code: string) => {
const ranges = normalizeRangeStr(rangeStr)
code = code.trimEnd()
options = options.trim() || '{}'
ctx.ignores.push([index, index + full.length])
return `\n<CodeBlockWrapper v-bind="${options}" :ranges='${JSON.stringify(ranges)}'>\n\n\`\`\`${lang}${attrs}\n${code}\n\`\`\`\n\n</CodeBlockWrapper>`
},
)
Expand Down
10 changes: 6 additions & 4 deletions packages/slidev/node/syntax/transform/in-page-css.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
import type { MarkdownTransformContext } from '@slidev/types'
import { getCodeBlocks } from './utils'

/**
* Transform <style> in markdown to scoped style with page selector
*/
export function transformPageCSS(ctx: MarkdownTransformContext, id: string) {
const page = id.match(/(\d+)\.md$/)?.[1]
export function transformPageCSS(ctx: MarkdownTransformContext) {
const page = ctx.id.match(/(\d+)\.md$/)?.[1]
if (!page)
return

const codeBlocks = getCodeBlocks(ctx.s.original)

ctx.s.replace(
/(\n<style[^>]*?>)([\s\S]+?)(<\/style>)/g,
(full, start, css, end, index) => {
// don't replace `<style>` inside code blocks, #101
if (ctx.isIgnored(index))
if (codeBlocks.isInsideCodeblocks(index))
return full
if (!start.includes('scoped'))
start = start.replace('<style', '<style scoped')
Expand Down
3 changes: 1 addition & 2 deletions packages/slidev/node/syntax/transform/katex-wrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,10 @@ import type { MarkdownTransformContext } from '@slidev/types'
export function transformKaTexWrapper(ctx: MarkdownTransformContext) {
ctx.s.replace(
/^\$\$(?:\s*{([\d\w*,\|-]+)}\s*?({.*?})?\s*?)?\n([\s\S]+?)^\$\$/mg,
(full, rangeStr: string = '', options = '', code: string, index: number) => {
(full, rangeStr: string = '', options = '', code: string) => {
const ranges = !rangeStr.trim() ? [] : rangeStr.trim().split(/\|/g).map(i => i.trim())
code = code.trimEnd()
options = options.trim() || '{}'
ctx.ignores.push([index, index + full.length])
return `<KaTexBlockWrapper v-bind="${options}" :ranges='${JSON.stringify(ranges)}'>\n\n\$\$\n${code}\n\$\$\n</KaTexBlockWrapper>\n`
},
)
Expand Down
42 changes: 20 additions & 22 deletions packages/slidev/node/syntax/transform/magic-move.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,32 +12,30 @@ const reMagicMoveBlock = /^````(?:md|markdown) magic-move(?:[ ]*(\{.*?\})?([^\n]
* Transform magic-move code blocks
*/
export function transformMagicMove(
ctx: MarkdownTransformContext,
shiki: Highlighter | undefined,
shikiOptions: MarkdownItShikiOptions | undefined,
) {
ctx.s.replace(
reMagicMoveBlock,
(full, options = '{}', _attrs = '', body: string, index: number) => {
if (!shiki || !shikiOptions)
throw new Error('Shiki is required for Magic Move. You may need to set `highlighter: shiki` in your Slidev config.')
return (ctx: MarkdownTransformContext) => {
ctx.s.replace(
reMagicMoveBlock,
(full, options = '{}', _attrs = '', body: string) => {
if (!shiki || !shikiOptions)
throw new Error('Shiki is required for Magic Move. You may need to set `highlighter: shiki` in your Slidev config.')

const matches = Array.from(body.matchAll(reCodeBlock))
const matches = Array.from(body.matchAll(reCodeBlock))

if (!matches.length)
throw new Error('Magic Move block must contain at least one code block')
if (!matches.length)
throw new Error('Magic Move block must contain at least one code block')

const ranges = matches.map(i => normalizeRangeStr(i[2]))
const steps = matches.map(i => codeToKeyedTokens(shiki, i[5].trimEnd(), {
...shikiOptions,
lang: i[1] as any,
}),
)
const compressed = lz.compressToBase64(JSON.stringify(steps))

ctx.ignores.push([index, index + full.length])

return `<ShikiMagicMove v-bind="${options}" steps-lz="${compressed}" :step-ranges='${JSON.stringify(ranges)}' />`
},
)
const ranges = matches.map(i => normalizeRangeStr(i[2]))
const steps = matches.map(i => codeToKeyedTokens(shiki, i[5].trimEnd(), {
...shikiOptions,
lang: i[1] as any,
}),
)
const compressed = lz.compressToBase64(JSON.stringify(steps))
return `<ShikiMagicMove v-bind="${options}" steps-lz="${compressed}" :step-ranges='${JSON.stringify(ranges)}' />`
},
)
}
}
3 changes: 1 addition & 2 deletions packages/slidev/node/syntax/transform/mermaid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,10 @@ import type { MarkdownTransformContext } from '@slidev/types'
* Transform Mermaid code blocks (render done on client side)
*/
export function transformMermaid(ctx: MarkdownTransformContext) {
ctx.s.replace(/^```mermaid\s*?({.*?})?\n([\s\S]+?)\n```/mg, (full, options = '', code = '', index: number) => {
ctx.s.replace(/^```mermaid\s*?({.*?})?\n([\s\S]+?)\n```/mg, (full, options = '', code = '') => {
code = code.trim()
options = options.trim() || '{}'
const encoded = lz.compressToBase64(code)
ctx.ignores.push([index, index + full.length])
return `<Mermaid code-lz="${encoded}" v-bind="${options}" />`
})
}
13 changes: 6 additions & 7 deletions packages/slidev/node/syntax/transform/monaco.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import lz from 'lz-string'
import type { MarkdownTransformContext } from '@slidev/types'

export function transformMonaco(ctx: MarkdownTransformContext, enabled = true) {
export function transformMonaco(ctx: MarkdownTransformContext) {
const enabled = (ctx.options.data.config.monaco === true || ctx.options.data.config.monaco === ctx.options.mode)

if (!enabled) {
ctx.s.replace(/{monaco([\w:,-]*)}/g, '')
return
Expand All @@ -10,32 +12,29 @@ export function transformMonaco(ctx: MarkdownTransformContext, enabled = true) {
// transform monaco
ctx.s.replace(
/^```(\w+?)\s*{monaco-diff}\s*?({.*?})?\s*?\n([\s\S]+?)^~~~\s*?\n([\s\S]+?)^```/mg,
(full, lang = 'ts', options = '{}', code: string, diff: string, index: number) => {
(full, lang = 'ts', options = '{}', code: string, diff: string) => {
lang = lang.trim()
options = options.trim() || '{}'
const encoded = lz.compressToBase64(code)
const encodedDiff = lz.compressToBase64(diff)
ctx.ignores.push([index, index + full.length])
return `<Monaco code-lz="${encoded}" diff-lz="${encodedDiff}" lang="${lang}" v-bind="${options}" />`
},
)
ctx.s.replace(
/^```(\w+?)\s*{monaco}\s*?({.*?})?\s*?\n([\s\S]+?)^```/mg,
(full, lang = 'ts', options = '{}', code: string, index: number) => {
(full, lang = 'ts', options = '{}', code: string) => {
lang = lang.trim()
options = options.trim() || '{}'
const encoded = lz.compressToBase64(code)
ctx.ignores.push([index, index + full.length])
return `<Monaco code-lz="${encoded}" lang="${lang}" v-bind="${options}" />`
},
)
ctx.s.replace(
/^```(\w+?)\s*{monaco-run}\s*?({.*?})?\s*?\n([\s\S]+?)^```/mg,
(full, lang = 'ts', options = '{}', code: string, index: number) => {
(full, lang = 'ts', options = '{}', code: string) => {
lang = lang.trim()
options = options.trim() || '{}'
const encoded = lz.compressToBase64(code)
ctx.ignores.push([index, index + full.length])
return `<Monaco runnable code-lz="${encoded}" lang="${lang}" v-bind="${options}" />`
},
)
Expand Down
6 changes: 3 additions & 3 deletions packages/slidev/node/syntax/transform/plant-uml.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { encode as encodePlantUml } from 'plantuml-encoder'
import type { MarkdownTransformContext } from '@slidev/types'

export function transformPlantUml(ctx: MarkdownTransformContext, server: string) {
ctx.s.replace(/^```plantuml\s*?({.*?})?\n([\s\S]+?)\n```/mg, (full, options = '', content = '', index: number) => {
export function transformPlantUml(ctx: MarkdownTransformContext) {
const server = ctx.options.data.config.plantUmlServer
ctx.s.replace(/^```plantuml\s*?({.*?})?\n([\s\S]+?)\n```/mg, (full, options = '', content = '') => {
const code = encodePlantUml(content.trim())
options = options.trim() || '{}'
ctx.ignores.push([index, index + full.length])
return `<PlantUml :code="'${code}'" :server="'${server}'" v-bind="${options}" />`
})
}
6 changes: 4 additions & 2 deletions packages/slidev/node/syntax/transform/slot-sugar.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import type { MarkdownTransformContext } from '@slidev/types'
import { getCodeBlocks } from './utils'

export function transformSlotSugar(
ctx: MarkdownTransformContext,
) {
const linesWithNewline = ctx.s.original.split(/(\r?\n)/g)
const codeBlocks = getCodeBlocks(ctx.s.original)

const lines: string[] = []
for (let i = 0; i < linesWithNewline.length; i += 2) {
const line = linesWithNewline[i]
Expand All @@ -17,9 +20,8 @@ export function transformSlotSugar(
lines.forEach((line) => {
const start = offset
offset += line.length
if (ctx.isIgnored(start))
if (codeBlocks.isInsideCodeblocks(offset))
return

const match = line.match(/^::\s*([\w\.\-\:]+)\s*::(\s*)?$/)
if (match) {
ctx.s.overwrite(start, offset - match[2].length, `${prevSlot ? '\n\n</template>\n' : '\n'}<template v-slot:${match[1]}="slotProps">\n`)
Expand Down
7 changes: 4 additions & 3 deletions packages/slidev/node/syntax/transform/snippet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import path from 'node:path'
import fs from 'fs-extra'
import type { MarkdownTransformContext, ResolvedSlidevOptions } from '@slidev/types'
import type { MarkdownTransformContext } from '@slidev/types'
import { slash } from '@antfu/utils'

function dedent(text: string): string {
Expand Down Expand Up @@ -81,8 +81,9 @@ function findRegion(lines: Array<string>, regionName: string) {
*
* captures: ['/path/to/file.extension', '#region', 'language', '{meta}']
*/
export function transformSnippet(ctx: MarkdownTransformContext, options: ResolvedSlidevOptions, id: string) {
const slideId = (id as string).match(/(\d+)\.md$/)?.[1]
export function transformSnippet(ctx: MarkdownTransformContext) {
const options = ctx.options
const slideId = (ctx.id as string).match(/(\d+)\.md$/)?.[1]
if (!slideId)
return

Expand Down
22 changes: 22 additions & 0 deletions packages/slidev/node/syntax/transform/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,28 @@ export function normalizeRangeStr(rangeStr = '') {
return !rangeStr.trim() ? [] : rangeStr.trim().split(/\|/g).map(i => i.trim())
}

export function getCodeBlocks(md: string) {
const codeblocks = Array
.from(md.matchAll(/^```[\s\S]*?^```/mg))
.map((m) => {
const start = m.index!
const end = m.index! + m[0].length
const startLine = md.slice(0, start).match(/\n/g)?.length || 0
const endLine = md.slice(0, end).match(/\n/g)?.length || 0
return [start, end, startLine, endLine]
})

return {
codeblocks,
isInsideCodeblocks(idx: number) {
return codeblocks.some(([s, e]) => s <= idx && idx <= e)
},
isLineInsideCodeblocks(line: number) {
return codeblocks.some(([, , s, e]) => s <= line && line <= e)
},
}
}

/**
* Escape `{{` in code block to prevent Vue interpret it, #99, #1316
*/
Expand Down