Skip to content

Commit 917bca2

Browse files
Sdjuzedeautofix-ci[bot]kermanx
authored
feat: show line numbers in markdown blocks (#2320)
Co-authored-by: zede <zede169778@gmail.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: _Kerman <kermanx@qq.com>
1 parent 6a7c10c commit 917bca2

File tree

4 files changed

+169
-6
lines changed

4 files changed

+169
-6
lines changed

packages/vscode/package.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,12 @@
363363
"description": "Display annotations for slides markdown files",
364364
"default": true
365365
},
366+
"slidev.annotations-line-numbers": {
367+
"type": "boolean",
368+
"scope": "window",
369+
"description": "Display line numbers in code blocks",
370+
"default": true
371+
},
366372
"slidev.preview-sync": {
367373
"type": "boolean",
368374
"scope": "window",

packages/vscode/src/configs.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export const {
55
'force-enabled': forceEnabled,
66
'port': configuredPortInitial,
77
'annotations': displayAnnotations,
8+
'annotations-line-numbers': displayCodeBlockLineNumbers,
89
'preview-sync': previewSyncInitial,
910
include,
1011
exclude,
@@ -13,6 +14,7 @@ export const {
1314
'force-enabled': Boolean,
1415
'port': Number,
1516
'annotations': Boolean,
17+
'annotations-line-numbers': Boolean,
1618
'preview-sync': Boolean,
1719
'include': Object as ConfigType<string[]>,
1820
'exclude': String,

packages/vscode/src/views/annotations.ts

Lines changed: 139 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import type { SourceSlideInfo } from '@slidev/types'
22
import type { DecorationOptions } from 'vscode'
3-
import { clamp, ensurePrefix } from '@antfu/utils'
4-
import { computed, createSingletonComposable, useActiveTextEditor, watch } from 'reactive-vscode'
5-
import { Position, Range, ThemeColor, window } from 'vscode'
3+
import { clamp, debounce, ensurePrefix } from '@antfu/utils'
4+
import { computed, createSingletonComposable, onScopeDispose, useActiveTextEditor, watch } from 'reactive-vscode'
5+
import { Position, Range, ThemeColor, window, workspace } from 'vscode'
66
import { useProjectFromDoc } from '../composables/useProjectFromDoc'
7-
import { displayAnnotations } from '../configs'
7+
import { displayAnnotations, displayCodeBlockLineNumbers } from '../configs'
88
import { activeProject } from '../projects'
99
import { toRelativePath } from '../utils/toRelativePath'
1010

@@ -30,6 +30,10 @@ const frontmatterEndDecoration = window.createTextEditorDecorationType(dividerCo
3030
const errorDecoration = window.createTextEditorDecorationType({
3131
isWholeLine: true,
3232
})
33+
const codeBlockLineNumberDecoration = window.createTextEditorDecorationType({
34+
isWholeLine: false,
35+
rangeBehavior: 1,
36+
})
3337

3438
function mergeSlideNumbers(slides: { index: number }[]): string {
3539
const indexes = slides.map(s => s.index + 1)
@@ -43,13 +47,134 @@ function mergeSlideNumbers(slides: { index: number }[]): string {
4347
return merged.map(([start, end]) => start === end ? `#${start}` : `#${start}-${end}`).join(', ')
4448
}
4549

50+
interface CodeBlockInfo {
51+
startLine: number
52+
endLine: number
53+
indent: string
54+
}
55+
56+
function findCodeBlocks(docText: string): CodeBlockInfo[] {
57+
const lines = docText.split(/\r?\n/)
58+
const codeBlocks: CodeBlockInfo[] = []
59+
60+
for (let i = 0; i < lines.length; i++) {
61+
const line = lines[i]
62+
const trimmedLine = line.trimStart()
63+
64+
if (trimmedLine.startsWith('```')) {
65+
const indent = line.slice(0, line.length - trimmedLine.length)
66+
const codeBlockLevel = line.match(/^\s*`+/)![0]
67+
const backtickCount = codeBlockLevel.trim().length
68+
const startLine = i
69+
70+
if (backtickCount !== 3) {
71+
continue
72+
}
73+
74+
let endLine = i
75+
for (let j = i + 1; j < lines.length; j++) {
76+
if (lines[j].startsWith(codeBlockLevel)) {
77+
endLine = j
78+
break
79+
}
80+
}
81+
82+
if (endLine > startLine) {
83+
codeBlocks.push({
84+
startLine: startLine + 1,
85+
endLine,
86+
indent,
87+
})
88+
}
89+
90+
i = endLine
91+
}
92+
}
93+
94+
return codeBlocks
95+
}
96+
97+
function updateCodeBlockLineNumbers(editor: ReturnType<typeof useActiveTextEditor>['value'], docText: string) {
98+
if (!editor || !displayCodeBlockLineNumbers.value)
99+
return
100+
101+
const codeBlockLineNumbers: DecorationOptions[] = []
102+
const codeBlocks = findCodeBlocks(docText)
103+
104+
for (const block of codeBlocks) {
105+
const lineCount = block.endLine - block.startLine
106+
const maxLineNumber = lineCount
107+
const numberWidth = String(maxLineNumber).length
108+
109+
for (let i = 0; i < lineCount; i++) {
110+
const lineNumber = i + 1
111+
const currentLine = block.startLine + i
112+
113+
if (currentLine >= editor.document.lineCount)
114+
continue
115+
116+
const paddedNumber = String(lineNumber).padStart(numberWidth, '⠀')
117+
118+
codeBlockLineNumbers.push({
119+
range: new Range(
120+
new Position(currentLine, 0),
121+
new Position(currentLine, 0),
122+
),
123+
renderOptions: {
124+
before: {
125+
contentText: `${paddedNumber}│`,
126+
color: new ThemeColor('editorLineNumber.foreground'),
127+
fontWeight: 'normal',
128+
fontStyle: 'normal',
129+
margin: '0 0.5em 0 0',
130+
},
131+
},
132+
})
133+
}
134+
}
135+
136+
editor.setDecorations(codeBlockLineNumberDecoration, codeBlockLineNumbers)
137+
}
138+
46139
export const useAnnotations = createSingletonComposable(() => {
47140
const editor = useActiveTextEditor()
48141
const doc = computed(() => editor.value?.document)
49142
const projectInfo = useProjectFromDoc(doc)
143+
144+
let debouncedUpdateLineNumbers: ((docText: string) => void) | null = null
145+
146+
watch(
147+
[editor, displayCodeBlockLineNumbers],
148+
([currentEditor, lineNumbersEnabled]) => {
149+
debouncedUpdateLineNumbers = null
150+
151+
if (!currentEditor || !lineNumbersEnabled) {
152+
if (currentEditor)
153+
currentEditor.setDecorations(codeBlockLineNumberDecoration, [])
154+
return
155+
}
156+
157+
debouncedUpdateLineNumbers = debounce(150, (docText: string) => {
158+
if (editor.value === currentEditor)
159+
updateCodeBlockLineNumbers(currentEditor, docText)
160+
})
161+
},
162+
{ immediate: true },
163+
)
164+
165+
const textChangeDisposable = workspace.onDidChangeTextDocument((e) => {
166+
if (editor.value?.document === e.document && displayCodeBlockLineNumbers.value && debouncedUpdateLineNumbers) {
167+
debouncedUpdateLineNumbers(e.document.getText())
168+
}
169+
})
170+
171+
onScopeDispose(() => {
172+
textChangeDisposable.dispose()
173+
})
174+
50175
watch(
51-
[editor, doc, projectInfo, activeProject, displayAnnotations],
52-
([editor, doc, projectInfo, activeProject, enabled]) => {
176+
[editor, doc, projectInfo, activeProject, displayAnnotations, displayCodeBlockLineNumbers],
177+
([editor, doc, projectInfo, activeProject, enabled, lineNumbersEnabled]) => {
53178
if (!editor || !doc || !projectInfo)
54179
return
55180

@@ -58,6 +183,7 @@ export const useAnnotations = createSingletonComposable(() => {
58183
editor.setDecorations(dividerDecoration, [])
59184
editor.setDecorations(frontmatterContentDecoration, [])
60185
editor.setDecorations(errorDecoration, [])
186+
editor.setDecorations(codeBlockLineNumberDecoration, [])
61187
return
62188
}
63189

@@ -141,6 +267,13 @@ export const useAnnotations = createSingletonComposable(() => {
141267
})
142268
}
143269
editor.setDecorations(errorDecoration, errors)
270+
271+
if (lineNumbersEnabled) {
272+
updateCodeBlockLineNumbers(editor, docText)
273+
}
274+
else {
275+
editor.setDecorations(codeBlockLineNumberDecoration, [])
276+
}
144277
},
145278
{ immediate: true },
146279
)

packages/vscode/syntaxes/slidev.example.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,30 @@ const a = 1
6161

6262
```ts {monaco-run}{showOutputAt: '+1'} twoslash
6363
const a = 1
64+
const b = 2
6465
```
6566

6667
$$
6768
\lambda = 1
6869
$$
70+
71+
---
72+
layout: center
73+
text: 2
74+
---
75+
76+
# Magic Move
77+
78+
````md magic-move
79+
```ts
80+
const a = 1
81+
```
82+
83+
```ts
84+
const a = 1
85+
const b = 2
86+
const c = 3
87+
88+
1
89+
```
90+
````

0 commit comments

Comments
 (0)