@@ -306,6 +306,18 @@ export const diagnostics_databases = new WeakMap<
306306 DiagnosticsDatabase
307307> ( ) ;
308308
309+ interface IMarkerDefinition {
310+ options : CodeMirror . TextMarkerOptions ;
311+ start : IEditorPosition ;
312+ end : IEditorPosition ;
313+ hash : string ;
314+ }
315+
316+ interface IMarkedDiagnostic {
317+ editor : CodeMirror . Editor ;
318+ marker : CodeMirror . TextMarker ;
319+ }
320+
309321export class DiagnosticsCM extends CodeMirrorIntegration {
310322 private last_response : lsProtocol . PublishDiagnosticsParams ;
311323
@@ -339,7 +351,7 @@ export class DiagnosticsCM extends CodeMirrorIntegration {
339351 }
340352
341353 private unique_editor_ids : DefaultMap < CodeMirror . Editor , number > ;
342- private marked_diagnostics : Map < string , CodeMirror . TextMarker > = new Map ( ) ;
354+ private marked_diagnostics : Map < string , IMarkedDiagnostic > = new Map ( ) ;
343355 /**
344356 * Allows access to the most recent diagnostics in context of the editor.
345357 *
@@ -477,6 +489,11 @@ export class DiagnosticsCM extends CodeMirrorIntegration {
477489 this . filterDiagnostics ( response . diagnostics )
478490 ) ;
479491
492+ const markerOptionsByEditor = new Map <
493+ CodeMirror . Editor ,
494+ IMarkerDefinition [ ]
495+ > ( ) ;
496+
480497 diagnostics_by_range . forEach (
481498 ( diagnostics : lsProtocol . Diagnostic [ ] , range : lsProtocol . Range ) => {
482499 const start = PositionConverter . lsp_to_cm (
@@ -641,23 +658,70 @@ export class DiagnosticsCM extends CodeMirrorIntegration {
641658 . join ( '\n' ) ,
642659 className : classNames . join ( ' ' )
643660 } ;
661+
662+ let optionsList = markerOptionsByEditor . get ( cm_editor ) ;
663+ if ( ! optionsList ) {
664+ optionsList = [ ] ;
665+ markerOptionsByEditor . set ( cm_editor , optionsList ) ;
666+ }
667+ optionsList . push ( {
668+ options,
669+ start : start_in_editor ,
670+ end : end_in_editor ,
671+ hash : diagnostic_hash
672+ } ) ;
673+ }
674+ }
675+ ) ;
676+
677+ for ( const [
678+ cmEditor ,
679+ markerDefinitions
680+ ] of markerOptionsByEditor . entries ( ) ) {
681+ // note: could possibly be wrapped in `requestAnimationFrame()`
682+ // at a risk of sometimes having an inconsistent state in database.
683+ // note: using `operation()` significantly improves performance.
684+ // test cases:
685+ // - one cell with 1000 `math.pi` and `import math`; comment out import,
686+ // wait for 1000 diagnostics, then uncomment import, wait for removal:
687+ // - before:
688+ // - diagnostics show up in: 13.6s (blocking!)
689+ // - diagnostics removal in: 13.2s (blocking!)
690+ // - after:
691+ // - diagnostics show up in: 254ms
692+ // - diagnostics removal in: 160.4ms
693+ // - first open of a notebook with 10 cells, each with 88 diagnostics:
694+ // - before: 2.75s (each time)
695+ // - after 208.75ms (order of magnitude faster!)
696+ // - first open of a notebook with 100 cells, each with 1 diagnostic
697+ // this scenario is expected to have no gain (measures overhead)
698+ // - before 280.34ms, 377ms, 399ms
699+ // - after 385.29ms, 301.97ms, 309.4ms
700+ cmEditor . operation ( ( ) => {
701+ const doc = cmEditor . getDoc ( ) ;
702+ for ( const definition of markerDefinitions ) {
644703 let marker ;
645704 try {
646- marker = cm_editor
647- . getDoc ( )
648- . markText ( start_in_editor , end_in_editor , options ) ;
705+ marker = doc . markText (
706+ definition . start ,
707+ definition . end ,
708+ definition . options
709+ ) ;
649710 } catch ( e ) {
650711 this . console . warn (
651712 'Marking inspection (diagnostic text) failed:' ,
652- diagnostics ,
713+ definition ,
653714 e
654715 ) ;
655716 return ;
656717 }
657- this . marked_diagnostics . set ( diagnostic_hash , marker ) ;
718+ this . marked_diagnostics . set ( definition . hash , {
719+ marker,
720+ editor : cmEditor
721+ } ) ;
658722 }
659- }
660- ) ;
723+ } ) ;
724+ }
661725
662726 // remove the markers which were not included in the new message
663727 this . removeUnusedDiagnosticMarkers ( markers_to_retain ) ;
@@ -696,14 +760,36 @@ export class DiagnosticsCM extends CodeMirrorIntegration {
696760 }
697761
698762 protected removeUnusedDiagnosticMarkers ( to_retain : Set < string > ) {
699- this . marked_diagnostics . forEach (
700- ( marker : CodeMirror . TextMarker , diagnostic_hash : string ) => {
701- if ( ! to_retain . has ( diagnostic_hash ) ) {
702- this . marked_diagnostics . delete ( diagnostic_hash ) ;
703- marker . clear ( ) ;
763+ const toRemoveByEditor = new Map <
764+ CodeMirror . Editor ,
765+ { marker : CodeMirror . TextMarker ; hash : string } [ ]
766+ > ( ) ;
767+
768+ for ( const [
769+ diagnosticHash ,
770+ markedDiagnostic
771+ ] of this . marked_diagnostics . entries ( ) ) {
772+ if ( ! to_retain . has ( diagnosticHash ) ) {
773+ let diagnosticsList = toRemoveByEditor . get ( markedDiagnostic . editor ) ;
774+ if ( ! diagnosticsList ) {
775+ diagnosticsList = [ ] ;
776+ toRemoveByEditor . set ( markedDiagnostic . editor , diagnosticsList ) ;
704777 }
778+ diagnosticsList . push ( {
779+ marker : markedDiagnostic . marker ,
780+ hash : diagnosticHash
781+ } ) ;
705782 }
706- ) ;
783+ }
784+
785+ for ( const [ cmEditor , markers ] of toRemoveByEditor . entries ( ) ) {
786+ cmEditor . operation ( ( ) => {
787+ for ( const markerData of markers ) {
788+ markerData . marker . clear ( ) ;
789+ this . marked_diagnostics . delete ( markerData . hash ) ;
790+ }
791+ } ) ;
792+ }
707793 }
708794
709795 remove ( ) : void {
0 commit comments