@@ -8,7 +8,15 @@ import { Cm6_Util } from 'src/codemirror/Cm6_Util';
88import { type ThemedToken } from 'shiki' ;
99import { editorLivePreviewField } from 'obsidian' ;
1010
11- interface DecoQueueNode {
11+ enum DecorationUpdateType {
12+ Insert ,
13+ Remove ,
14+ }
15+
16+ type DecorationUpdate = InsertDecoration | RemoveDecoration ;
17+
18+ interface InsertDecoration {
19+ type : DecorationUpdateType . Insert ;
1220 from : number ;
1321 to : number ;
1422 lang : string ;
@@ -17,6 +25,12 @@ interface DecoQueueNode {
1725 hideTo ?: number ;
1826}
1927
28+ interface RemoveDecoration {
29+ type : DecorationUpdateType . Remove ;
30+ from : number ;
31+ to : number ;
32+ }
33+
2034// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
2135export function createCm6Plugin ( plugin : ShikiPlugin ) {
2236 return ViewPlugin . fromClass (
@@ -30,7 +44,7 @@ export function createCm6Plugin(plugin: ShikiPlugin) {
3044 void this . updateWidgets ( view ) ;
3145
3246 plugin . updateCm6Plugin = ( ) : Promise < void > => {
33- return this . forceUpdate ( ) ;
47+ return this . updateWidgets ( this . view ) ;
3448 } ;
3549 }
3650
@@ -50,12 +64,6 @@ export function createCm6Plugin(plugin: ShikiPlugin) {
5064 }
5165 }
5266
53- async forceUpdate ( ) : Promise < void > {
54- await this . updateWidgets ( this . view ) ;
55-
56- this . view . dispatch ( this . view . state . update ( { } ) ) ;
57- }
58-
5967 isLivePreview ( state : EditorState ) : boolean {
6068 // @ts -ignore some strange private field not being assignable
6169 return state . field ( editorLivePreviewField ) ;
@@ -70,7 +78,7 @@ export function createCm6Plugin(plugin: ShikiPlugin) {
7078 async updateWidgets ( view : EditorView , docChanged : boolean = true ) : Promise < void > {
7179 let lang = '' ;
7280 let state : SyntaxNode [ ] = [ ] ;
73- const decoQueue : DecoQueueNode [ ] = [ ] ;
81+ const decorationUpdates : DecorationUpdate [ ] = [ ] ;
7482
7583 // const t1 = performance.now();
7684
@@ -92,7 +100,8 @@ export function createCm6Plugin(plugin: ShikiPlugin) {
92100 if ( match ) {
93101 const hasSelectionOverlap = Cm6_Util . checkSelectionAndRangeOverlap ( view . state . selection , node . from - 1 , node . to + 1 ) ;
94102
95- decoQueue . push ( {
103+ decorationUpdates . push ( {
104+ type : DecorationUpdateType . Insert ,
96105 from : node . from ,
97106 to : node . to ,
98107 lang : match [ 1 ] ,
@@ -123,42 +132,63 @@ export function createCm6Plugin(plugin: ShikiPlugin) {
123132 if ( props . has ( 'HyperMD-codeblock-begin' ) ) {
124133 const content = Cm6_Util . getContent ( view . state , node . from , node . to ) ;
125134
126- lang = / ^ ` ` ` ( \S + ) / . exec ( content ) ?. [ 1 ] ?? '' ;
135+ lang = / ^ ` ` ` \s * ( \S + ) / . exec ( content ) ?. [ 1 ] ?? '' ;
127136 }
128137
129138 if ( props . has ( 'HyperMD-codeblock-end' ) ) {
130139 if ( state . length > 0 && lang !== '' ) {
131140 const start = state [ 0 ] . from ;
132141 const end = state [ state . length - 1 ] . to ;
133142
134- decoQueue . push ( {
143+ decorationUpdates . push ( {
144+ type : DecorationUpdateType . Insert ,
135145 from : start ,
136146 to : end ,
137147 lang,
138148 content : Cm6_Util . getContent ( view . state , start , end ) ,
139149 } ) ;
140150 }
141151
152+ if ( state . length > 0 && lang === '' ) {
153+ const start = state [ 0 ] . from ;
154+ const end = state [ state . length - 1 ] . to ;
155+
156+ decorationUpdates . push ( {
157+ type : DecorationUpdateType . Remove ,
158+ from : start ,
159+ to : end ,
160+ } ) ;
161+ }
162+
142163 lang = '' ;
143164 state = [ ] ;
144165 }
145166 } ,
146167 } ) ;
147168
148- for ( const node of decoQueue ) {
169+ for ( const node of decorationUpdates ) {
149170 try {
150- const decorations = await this . buildDecorations ( node . hideTo ?? node . from , node . to , node . lang , node . content ) ;
151- this . removeDecoration ( node . from , node . to ) ;
152- if ( node . hideLang ) {
153- // add the decoration that hides the language tag
154- decorations . unshift ( Decoration . replace ( { } ) . range ( node . from , node . hideTo ) ) ;
171+ if ( node . type === DecorationUpdateType . Remove ) {
172+ this . removeDecoration ( node . from , node . to ) ;
173+ } else if ( node . type === DecorationUpdateType . Insert ) {
174+ const decorations = await this . buildDecorations ( node . hideTo ?? node . from , node . to , node . lang , node . content ) ;
175+ this . removeDecoration ( node . from , node . to ) ;
176+ if ( node . hideLang ) {
177+ // add the decoration that hides the language tag
178+ decorations . unshift ( Decoration . replace ( { } ) . range ( node . from , node . hideTo ) ) ;
179+ }
180+ // add the highlight decorations
181+ this . addDecoration ( node . from , node . to , decorations ) ;
155182 }
156- // add the highlight decorations
157- this . addDecoration ( node . from , node . to , decorations ) ;
158183 } catch ( e ) {
159184 console . error ( e ) ;
160185 }
161186 }
187+
188+ if ( decorationUpdates . length > 0 ) {
189+ this . view . dispatch ( this . view . state . update ( { } ) ) ;
190+ }
191+
162192 // console.log('Traversed syntax tree in', performance.now() - t1, 'ms');
163193 }
164194
0 commit comments