Skip to content

Commit 55977d3

Browse files
kermanxantfu
authored andcommitted
fix: allow indented code blocks
1 parent adc9c90 commit 55977d3

10 files changed

Lines changed: 68 additions & 84 deletions

File tree

docs/custom/config-transformers.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ The return value should be the custom options for the transformers. The `pre`, `
3535
3. Import snippets syntax and Shiki magic move
3636
4. `preCodeblock` from your project
3737
5. `preCodeblock` from addons and themes
38-
6. Built-in special code blocks like Mermaid, Monaco and PlantUML
38+
6. Built-in special code blocks like Mermaid and PlantUML
3939
7. `postCodeblock` from addons and themes
4040
8. `postCodeblock` from your project
4141
9. Other built-in transformers like code block wrapping

packages/client/builtin/CodeBlockWrapper.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ const props = defineProps({
4848
},
4949
title: {
5050
type: String,
51-
default: undefined,
51+
default: '',
5252
},
5353
})
5454

packages/slidev/node/syntax/markdown-it/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ export async function useMarkdownItPlugins(md: MarkdownExit, options: ResolvedSl
1515
const { data: { features, config }, utils: { katexOptions } } = options
1616

1717
if (config.highlighter === 'shiki') {
18-
// @ts-expect-error @shikijs/markdown-it types expect MarkdownItAsync, but MarkdownExit is API-compatible
1918
md.use(await MarkdownItShiki(options))
2019
}
2120

packages/slidev/node/syntax/markdown-it/markdown-it-shiki.ts

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import type { ResolvedSlidevOptions } from '@slidev/types'
2+
import type { MarkdownExit } from 'markdown-exit'
23
import type { ShikiTransformer } from 'shiki'
34
import { isTruthy } from '@antfu/utils'
45
import { fromAsyncCodeToHtml } from '@shikijs/markdown-it/async'
5-
import { escapeVueInCode } from '../transform/utils'
6+
import lz from 'lz-string'
7+
import { escapeVueInCode, normalizeRangeStr } from '../transform/utils'
68

79
export default async function MarkdownItShiki({ data: { config }, mode, utils: { shiki, shikiOptions } }: ResolvedSlidevOptions) {
810
async function getTwoslashTransformer() {
9-
const [,,{ transformerTwoslash }] = await Promise.all([
11+
const [, , { transformerTwoslash }] = await Promise.all([
1012
// trigger shiki to load the langs
1113
shiki.codeToHast('', { lang: 'js', ...shikiOptions }),
1214
shiki.codeToHast('', { lang: 'ts', ...shikiOptions }),
@@ -31,14 +33,63 @@ export default async function MarkdownItShiki({ data: { config }, mode, utils: {
3133
this.addClassToHast(pre, 'slidev-code')
3234
delete pre.properties.tabindex
3335
},
34-
postprocess(code) {
35-
return escapeVueInCode(code)
36-
},
3736
} satisfies ShikiTransformer,
3837
].filter(isTruthy) as ShikiTransformer[]
3938

40-
return fromAsyncCodeToHtml(shiki.codeToHtml, {
39+
const highlighterPlugin = fromAsyncCodeToHtml(shiki.codeToHtml, {
4140
...shikiOptions,
4241
transformers,
4342
})
43+
44+
const monacoEnabled = config.monaco === true || config.monaco === mode
45+
46+
return (md: MarkdownExit) => {
47+
// @ts-expect-error @shikijs/markdown-it types expect MarkdownItAsync, but MarkdownExit is API-compatible
48+
md.use(highlighterPlugin)
49+
50+
// Apply CodeBlockWrapper
51+
const oldFence = md.renderer.rules.fence
52+
md.renderer.rules.fence = async function (tokens, idx, renderOptions, env, slf) {
53+
const token = tokens[idx]
54+
const { monaco, lang, title, ranges, options, rest } = parseMetaString(token.info)
55+
const optionsProp = options ? `v-bind="${options}"` : ''
56+
token.info = `${lang} ${rest}`
57+
58+
if (monaco) {
59+
if (!monacoEnabled) {
60+
return oldFence?.(tokens, idx, renderOptions, env, slf) || ''
61+
}
62+
63+
let encoded, diff
64+
if (monaco === 'monaco-diff') {
65+
const [code, diffStr] = token.content.split(/^\s*~~~\s*\n/m, 2)
66+
encoded = lz.compressToBase64(code)
67+
diff = diffStr === undefined ? '' : `diff-lz="${lz.compressToBase64(diffStr)}"`
68+
}
69+
else {
70+
encoded = lz.compressToBase64(token.content)
71+
}
72+
73+
const runnable = monaco === 'monaco-run' ? 'runnable' : ''
74+
return `<Monaco ${optionsProp} ${runnable} lang="${lang}" code-lz="${encoded}" ${diff} />`
75+
}
76+
77+
token.info = rest
78+
const code = await oldFence?.(tokens, idx, renderOptions, env, slf) || ''
79+
return `<CodeBlockWrapper ${optionsProp} title=${JSON.stringify(title)} :ranges='${JSON.stringify(ranges)}'>${escapeVueInCode(code)}</CodeBlockWrapper>`
80+
}
81+
}
82+
}
83+
84+
const META_RE = /([\w'-]+)?(?:[ \t]*|[ \t][ \w\t'-]*)(?:\[([^\]]*)\])?[ \t]*(?:\{([\w*,|-]+)\}[ \t]*(\{[^}]*\})?([^\r\n]*))?/
85+
86+
function parseMetaString(meta: string) {
87+
const [, lang = '', title = '', rangeStr = '', options = '', rest = ''] = meta.trim().match(META_RE) ?? []
88+
89+
if (title === '' && rangeStr.startsWith('monaco')) {
90+
return { monaco: rangeStr, lang, options, rest }
91+
}
92+
93+
const ranges = normalizeRangeStr(rangeStr)
94+
return { title, ranges, options, rest }
4495
}

packages/slidev/node/syntax/transform/code-wrapper.ts

Lines changed: 0 additions & 20 deletions
This file was deleted.

packages/slidev/node/syntax/transform/index.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
import type { MarkdownTransformer, ResolvedSlidevOptions } from '@slidev/types'
22
import setupTransformers from '../../setups/transformers'
3-
import { transformCodeWrapper } from './code-wrapper'
43
import { transformPageCSS } from './in-page-css'
54
import { transformKaTexWrapper } from './katex-wrapper'
65
import { transformMagicMove } from './magic-move'
76
import { transformMermaid } from './mermaid'
8-
import { transformMonaco } from './monaco'
97
import { transformPlantUml } from './plant-uml'
108
import { transformSlotSugar } from './slot-sugar'
119
import { transformSnippet } from './snippet'
@@ -22,11 +20,9 @@ export async function getMarkdownTransformers(options: ResolvedSlidevOptions): P
2220

2321
transformMermaid,
2422
transformPlantUml,
25-
options.data.features.monaco && transformMonaco,
2623

2724
...extras.postCodeblock,
2825

29-
transformCodeWrapper,
3026
options.data.features.katex && transformKaTexWrapper,
3127
transformPageCSS,
3228
transformSlotSugar,

packages/slidev/node/syntax/transform/magic-move.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import type { MarkdownTransformContext } from '@slidev/types'
22
import lz from 'lz-string'
33
import { toKeyedTokens } from 'shiki-magic-move/core'
4-
import { reCodeBlock } from './code-wrapper'
54
import { normalizeRangeStr } from './utils'
65

6+
// eslint-disable-next-line regexp/no-super-linear-backtracking
7+
const reCodeBlock = /^```([\w'-]+)?(?:[ \t]*|[ \t][ \w\t'-]*)(?:\[([^\]]*)\])?[ \t]*(?:\{([\w*,|-]+)\}[ \t]*(\{[^}]*\})?([^\r\n]*))?\r?\n((?:(?!^```)[\s\S])*?)^```$/gm
78
// eslint-disable-next-line regexp/no-super-linear-backtracking
89
const reMagicMoveBlock = /^````(?:md|markdown) magic-move(?: *\[([^\]]*)\])?(?: *(\{[^}]*\}))? *([^\n]*)\n([\s\S]+?)^````\s*?$/gm
910

packages/slidev/node/syntax/transform/monaco.ts

Lines changed: 0 additions & 41 deletions
This file was deleted.

packages/vscode/src/views/annotations.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ function mergeSlideNumbers(slides: { index: number }[]): string {
4949
interface CodeBlockInfo {
5050
startLine: number
5151
endLine: number
52-
indent: string
52+
indent: number
5353
}
5454

5555
function findCodeBlocks(docText: string): CodeBlockInfo[] {
@@ -61,7 +61,7 @@ function findCodeBlocks(docText: string): CodeBlockInfo[] {
6161
const trimmedLine = line.trimStart()
6262

6363
if (trimmedLine.startsWith('```')) {
64-
const indent = line.slice(0, line.length - trimmedLine.length)
64+
const indent = line.length - trimmedLine.length
6565
const codeBlockLevel = line.match(/^\s*`+/)![0]
6666
const backtickCount = codeBlockLevel.trim().length
6767
const startLine = i
@@ -115,11 +115,9 @@ function updateCodeBlockLineNumbers(editor: ReturnType<typeof useActiveTextEdito
115115
// \u2800 renders as a space but won't be trimmed
116116
const paddedNumber = String(lineNumber).padStart(numberWidth, '\u2800')
117117

118+
const position = new Position(currentLine, block.indent)
118119
codeBlockLineNumbers.push({
119-
range: new Range(
120-
new Position(currentLine, 0),
121-
new Position(currentLine, 0),
122-
),
120+
range: new Range(position, position),
123121
renderOptions: {
124122
before: {
125123
contentText: `${paddedNumber}│`,

packages/vscode/syntaxes/slidev.example.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,9 @@ text: 1
4040

4141
# Code block
4242

43-
```ts {1,2|3}
44-
const a = 1
45-
```
43+
```ts {1,2|3}
44+
const a = 1
45+
```
4646

4747
```ts twoslash
4848
const a = 1

0 commit comments

Comments
 (0)