@@ -55,7 +55,14 @@ export function createCm6Plugin(plugin: ShikiPlugin) {
5555 * @param update
5656 */
5757 update ( update : ViewUpdate ) : void {
58- this . decorations = this . decorations . map ( update . changes ) ;
58+ try {
59+ this . decorations = this . decorations . map ( update . changes ) ;
60+ } catch ( e ) {
61+ // Decorations may have stale positions if the document changed while an async
62+ // updateWidgets call was in flight. Reset them so the next update can rebuild.
63+ this . decorations = Decoration . none ;
64+ console . warn ( 'Resetting decorations due to error:' , e ) ;
65+ }
5966
6067 // we handle doc changes and selection changes here
6168 if ( update . docChanged || update . selectionSet ) {
@@ -79,6 +86,9 @@ export function createCm6Plugin(plugin: ShikiPlugin) {
7986 let lang = '' ;
8087 let state : SyntaxNode [ ] = [ ] ;
8188 const decorationUpdates : DecorationUpdate [ ] = [ ] ;
89+ // Capture the state at the time of the syntax tree traversal so we can
90+ // detect if the document changed while async decoration building was in flight.
91+ const capturedState = view . state ;
8292
8393 // const t1 = performance.now();
8494
@@ -172,6 +182,11 @@ export function createCm6Plugin(plugin: ShikiPlugin) {
172182 this . removeDecoration ( node . from , node . to ) ;
173183 } else if ( node . type === DecorationUpdateType . Insert ) {
174184 const decorations = await this . buildDecorations ( node . hideTo ?? node . from , node . to , node . lang , node . content ) ;
185+ // If the document changed while we were awaiting, the positions we captured
186+ // from the syntax tree are stale. Abort to avoid applying out-of-range decorations.
187+ if ( this . view . state !== capturedState ) {
188+ return ;
189+ }
175190 this . removeDecoration ( node . from , node . to ) ;
176191 if ( node . hideLang ) {
177192 // add the decoration that hides the language tag
0 commit comments