@@ -39,7 +39,7 @@ import {SessionLanguageProvider} from "./session-language-provider";
3939import { popupManager } from "./ace/popupManager" ;
4040
4141export class LanguageProvider {
42- activeEditor : Ace . Editor ;
42+ activeEditor : Ace . Editor | null ;
4343 private readonly $messageController : IMessageController ;
4444 private $signatureTooltip : SignatureTooltip ;
4545 $sessionLanguageProviders : { [ sessionID : string ] : SessionLanguageProvider } = { } ;
@@ -57,6 +57,18 @@ export class LanguageProvider {
5757 doLiveAutocomplete : ( e ) => void ;
5858 validateAceInlineCompleterWithEditor : ( editor : Ace . Editor ) => void ;
5959 } ;
60+ private $editorEventHandlers : { [ editorId : string ] : {
61+ changeSession ?: ( e : any ) => void ;
62+ focus ?: ( ) => void ;
63+ changeSelectionForHighlights ?: ( ) => void ;
64+ changeSelectionForCodeActions ?: ( ) => void ;
65+ afterExec ?: ( e : any ) => void ;
66+ } } = { } ;
67+ private $editorOriginalState : { [ editorId : string ] : {
68+ completers ?: Ace . Completer [ ] ;
69+ inlineCompleters ?: Ace . Completer [ ] ;
70+ inlineAutocompleteCommand ?: any ;
71+ } } = { } ;
6072
6173 private constructor ( worker : Worker , options ?: ProviderOptions ) {
6274 this . $messageController = new MessageController ( worker , this ) ;
@@ -226,6 +238,20 @@ export class LanguageProvider {
226238 this . registerSession ( editor . session , editor , config ) ;
227239 }
228240
241+ /**
242+ * Unregisters an Ace editor instance, removing all event listeners, completers, tooltips,
243+ * and cleaning up associated resources. This is the counterpart to registerEditor.
244+ *
245+ * @param editor - The Ace editor instance to be unregistered.
246+ * @param cleanupSession - Optional flag to also dispose the current session. When true,
247+ * calls closeDocument on the editor's session, cleaning up all
248+ * session-related resources. Default: false.
249+ */
250+ unregisterEditor ( editor : Ace . Editor , cleanupSession : boolean = false ) {
251+ if ( this . editors . includes ( editor ) )
252+ this . $unregisterEditor ( editor , cleanupSession ) ;
253+ }
254+
229255
230256 codeActionCallback : ( codeActions : CodeActionsByService [ ] ) => void ;
231257
@@ -303,22 +329,27 @@ export class LanguageProvider {
303329
304330 editor . setOption ( "useWorker" , false ) ;
305331
332+ this . $editorEventHandlers [ editor . id ] = { } ;
306333
307334 if ( ! this . options . manualSessionControl ) {
308- editor . on ( "changeSession" , ( { session} ) => this . registerSession ( session , editor , session . lspConfig ) ) ;
335+ const changeSessionHandler = ( { session} ) => this . registerSession ( session , editor , session . lspConfig ) ;
336+ this . $editorEventHandlers [ editor . id ] . changeSession = changeSessionHandler ;
337+ editor . on ( "changeSession" , changeSessionHandler ) ;
309338 }
310339
311340 if ( this . options . functionality ! . completion || this . options . functionality ! . inlineCompletion ) {
312341 this . $registerCompleters ( editor ) ;
313342 }
314343 this . activeEditor ??= editor ;
315- editor . on ( "focus" , ( ) => {
344+ const focusHandler = ( ) => {
316345 this . activeEditor = editor ;
317- } ) ;
346+ } ;
347+ this . $editorEventHandlers [ editor . id ] . focus = focusHandler ;
348+ editor . on ( "focus" , focusHandler ) ;
318349
319350 if ( this . options . functionality ! . documentHighlights ) {
320351 var $timer
321- editor . on ( "changeSelection" , ( ) => {
352+ const changeSelectionForHighlights = ( ) => {
322353 if ( ! $timer )
323354 $timer =
324355 setTimeout ( ( ) => {
@@ -332,7 +363,9 @@ export class LanguageProvider {
332363 this . $messageController . findDocumentHighlights ( this . $getFileName ( editor . session ) , fromPoint ( cursor ) , sessionLanguageProvider . $applyDocumentHighlight ) ;
333364 $timer = undefined ;
334365 } , 50 ) ;
335- } ) ;
366+ } ;
367+ this . $editorEventHandlers [ editor . id ] . changeSelectionForHighlights = changeSelectionForHighlights ;
368+ editor . on ( "changeSelection" , changeSelectionForHighlights ) ;
336369 }
337370
338371 if ( this . options . functionality ! . codeActions ) {
@@ -353,6 +386,86 @@ export class LanguageProvider {
353386 this . setStyles ( editor ) ;
354387 }
355388
389+ $unregisterEditor ( editor : Ace . Editor , cleanupSession : boolean = false ) {
390+ const editorIndex = this . editors . indexOf ( editor ) ;
391+ if ( editorIndex > - 1 ) {
392+ this . editors . splice ( editorIndex , 1 ) ;
393+ }
394+
395+ const handlers = this . $editorEventHandlers [ editor . id ] ;
396+
397+ if ( handlers ) {
398+ if ( handlers . changeSession ) {
399+ editor . off ( "changeSession" , handlers . changeSession ) ;
400+ }
401+
402+ if ( handlers . focus ) {
403+ editor . off ( "focus" , handlers . focus ) ;
404+ }
405+
406+ if ( handlers . changeSelectionForHighlights ) {
407+ editor . off ( "changeSelection" , handlers . changeSelectionForHighlights ) ;
408+ }
409+
410+ if ( handlers . changeSelectionForCodeActions ) {
411+ editor . off ( "changeSelection" , handlers . changeSelectionForCodeActions ) ;
412+ }
413+
414+ if ( handlers . afterExec ) {
415+ editor . commands . off ( 'afterExec' , handlers . afterExec ) ;
416+ }
417+
418+ delete this . $editorEventHandlers [ editor . id ] ;
419+ }
420+
421+ const originalState = this . $editorOriginalState [ editor . id ] ;
422+
423+ if ( originalState ) {
424+ if ( this . options . functionality ?. completion && originalState . completers !== undefined ) {
425+ editor . completers = originalState . completers ;
426+ }
427+
428+ if ( this . options . functionality ?. inlineCompletion && originalState . inlineCompleters !== undefined ) {
429+ editor . inlineCompleters = originalState . inlineCompleters ;
430+ }
431+
432+ if ( this . options . functionality ?. inlineCompletion ) {
433+ if ( originalState . inlineAutocompleteCommand ) {
434+ editor . commands . addCommand ( originalState . inlineAutocompleteCommand ) ;
435+ } else {
436+ try {
437+ editor . commands . removeCommand ( "startInlineAutocomplete" ) ;
438+ } catch ( e ) {
439+ }
440+ }
441+ }
442+
443+ delete this . $editorOriginalState [ editor . id ] ;
444+ }
445+
446+ if ( this . options . functionality ?. signatureHelp ) {
447+ this . $signatureTooltip . unregisterEditor ( editor ) ;
448+ }
449+ if ( this . options . functionality ?. hover && this . $hoverTooltip ) {
450+ this . $hoverTooltip . removeFromEditor ( editor ) ;
451+ }
452+ if ( this . options . functionality ?. codeActions ) {
453+ const lightBulb = this . $lightBulbWidgets [ editor . id ] ;
454+ if ( lightBulb ) {
455+ lightBulb . dispose ( ) ;
456+ delete this . $lightBulbWidgets [ editor . id ] ;
457+ }
458+ }
459+
460+ if ( this . activeEditor === editor ) {
461+ this . activeEditor = this . editors . length > 0 ? this . editors [ 0 ] : null ;
462+ }
463+
464+ if ( cleanupSession && editor . session ) {
465+ this . closeDocument ( editor . session ) ;
466+ }
467+ }
468+
356469 private $provideCodeActions ( editor : Ace . Editor ) {
357470 const lightBulb = new LightbulbWidget ( editor ) ;
358471 this . $lightBulbWidgets [ editor . id ] = lightBulb ;
@@ -372,7 +485,7 @@ export class LanguageProvider {
372485 } ) ;
373486
374487 var actionTimer
375- editor . on ( "changeSelection" , ( ) => {
488+ const changeSelectionForCodeActions = ( ) => {
376489 if ( ! actionTimer )
377490 actionTimer =
378491 setTimeout ( ( ) => {
@@ -391,7 +504,9 @@ export class LanguageProvider {
391504 } ) ;
392505 actionTimer = undefined ;
393506 } , 500 ) ;
394- } ) ;
507+ } ;
508+ this . $editorEventHandlers [ editor . id ] . changeSelectionForCodeActions = changeSelectionForCodeActions ;
509+ editor . on ( "changeSelection" , changeSelectionForCodeActions ) ;
395510 }
396511
397512 private $initHoverTooltip ( editor ) {
@@ -531,16 +646,20 @@ export class LanguageProvider {
531646 if ( ! this . options . functionality ! . format )
532647 return ;
533648
534- let sessionLanguageProvider = this . $getSessionLanguageProvider ( this . activeEditor . session ) ;
535- sessionLanguageProvider . $sendDeltaQueue ( sessionLanguageProvider . format ) ;
649+ if ( this . activeEditor ) {
650+ let sessionLanguageProvider = this . $getSessionLanguageProvider ( this . activeEditor . session ) ;
651+ sessionLanguageProvider . $sendDeltaQueue ( sessionLanguageProvider . format ) ;
652+ }
536653 }
537654
538655 getSemanticTokens ( ) {
539656 if ( ! this . options . functionality ! . semanticTokens )
540657 return ;
541658
542- let sessionLanguageProvider = this . $getSessionLanguageProvider ( this . activeEditor . session ) ;
543- sessionLanguageProvider . getSemanticTokens ( ) ;
659+ if ( this . activeEditor ) {
660+ let sessionLanguageProvider = this . $getSessionLanguageProvider ( this . activeEditor . session ) ;
661+ sessionLanguageProvider . getSemanticTokens ( ) ;
662+ }
544663 }
545664
546665 doComplete ( editor : Ace . Editor , session : Ace . EditSession , callback : ( completionList : Ace . Completion [ ] | null ) => void ) {
@@ -564,11 +683,21 @@ export class LanguageProvider {
564683 if ( ! this . options . functionality ?. completion && ! this . options . functionality ?. inlineCompletion ) {
565684 return ;
566685 }
567- if ( this . options . functionality ?. completion && this . options . functionality ?. completion . overwriteCompleters ) {
568- editor . completers = [ ] ;
686+
687+ this . $editorOriginalState [ editor . id ] = { } ;
688+
689+ if ( this . options . functionality ?. completion ) {
690+ this . $editorOriginalState [ editor . id ] . completers = editor . completers ? [ ...editor . completers ] : [ ] ;
691+ if ( this . options . functionality . completion . overwriteCompleters ) {
692+ editor . completers = [ ] ;
693+ }
569694 }
570- if ( this . options . functionality ?. inlineCompletion && this . options . functionality ?. inlineCompletion . overwriteCompleters ) {
571- editor . inlineCompleters = [ ] ;
695+
696+ if ( this . options . functionality ?. inlineCompletion ) {
697+ this . $editorOriginalState [ editor . id ] . inlineCompleters = editor . inlineCompleters ? [ ...editor . inlineCompleters ] : [ ] ;
698+ if ( this . options . functionality . inlineCompletion . overwriteCompleters ) {
699+ editor . inlineCompleters = [ ] ;
700+ }
572701 }
573702 if ( this . options . functionality . completion ) {
574703 completer = {
@@ -633,6 +762,9 @@ export class LanguageProvider {
633762 }
634763
635764 if ( this . options . functionality ?. inlineCompletion ) {
765+ const existingCommand = editor . commands . commands [ "startInlineAutocomplete" ] ;
766+ this . $editorOriginalState [ editor . id ] . inlineAutocompleteCommand = existingCommand || null ;
767+
636768 editor . commands . addCommand ( {
637769 name : "startInlineAutocomplete" ,
638770 exec : ( editor , options ) => {
@@ -641,6 +773,7 @@ export class LanguageProvider {
641773 } ,
642774 bindKey : { win : "Alt-C" , mac : "Option-C" }
643775 } ) ;
776+ this . $editorEventHandlers [ editor . id ] . afterExec = this . doLiveAutocomplete ;
644777 editor . commands . on ( 'afterExec' , this . doLiveAutocomplete ) ;
645778
646779 inlineCompleter = {
@@ -672,14 +805,15 @@ export class LanguageProvider {
672805 }
673806
674807 /**
675- * Removes document from all linked services by session id
676- * @param session
677- * @param [callback]
808+ * Removes document from all linked services by session id and cleans up all associated resources.
809+ * This includes removing event listeners, clearing marker groups, annotations, and notifying the server.
810+ * @param session - The Ace EditSession to close
811+ * @param [callback] - Optional callback to execute after the document is closed
678812 */
679813 closeDocument ( session : Ace . EditSession , callback ?) {
680814 let sessionProvider = this . $getSessionLanguageProvider ( session ) ;
681815 if ( sessionProvider ) {
682- sessionProvider . closeDocument ( callback ) ;
816+ sessionProvider . dispose ( callback ) ;
683817 delete this . $sessionLanguageProviders [ session [ "id" ] ] ;
684818 }
685819 }
0 commit comments