11import type { SourceSlideInfo } from '@slidev/types'
22import 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'
66import { useProjectFromDoc } from '../composables/useProjectFromDoc'
7- import { displayAnnotations } from '../configs'
7+ import { displayAnnotations , displayCodeBlockLineNumbers } from '../configs'
88import { activeProject } from '../projects'
99import { toRelativePath } from '../utils/toRelativePath'
1010
@@ -30,6 +30,10 @@ const frontmatterEndDecoration = window.createTextEditorDecorationType(dividerCo
3030const errorDecoration = window . createTextEditorDecorationType ( {
3131 isWholeLine : true ,
3232} )
33+ const codeBlockLineNumberDecoration = window . createTextEditorDecorationType ( {
34+ isWholeLine : false ,
35+ rangeBehavior : 1 ,
36+ } )
3337
3438function 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+
46139export 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 )
0 commit comments