Skip to content

Commit 8c7d23a

Browse files
authored
Merge pull request #207 from mkslanc/provide-cleanups
Provide cleanups
2 parents 8a5c593 + 8b8c38d commit 8c7d23a

File tree

9 files changed

+777
-35
lines changed

9 files changed

+777
-35
lines changed

package-lock.json

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "ace-linters-root",
3-
"version": "1.8.5",
3+
"version": "1.8.6",
44
"scripts": {
55
"build:parts": "npm run build -ws",
66
"build": "npm run build:parts && webpack",

packages/ace-linters/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "ace-linters",
33
"author": "Azat Alimov <mkslanc@gmail.com>",
4-
"version": "1.8.5",
4+
"version": "1.8.6",
55
"scripts": {
66
"clean": "rimraf build",
77
"postbuild": "node postbuild.js",

packages/ace-linters/src/components/lightbulb.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ export class LightbulbWidget implements ILightbulbWidget {
5555
removeListeners() {
5656
this.editor.off("changeSelection", this.hideAll);
5757
this.editor.off("focus", this.hideAll);
58+
this.editor.renderer.off("afterRender", this.setPosition);
5859
this.editor.session.off("changeScrollTop", this.setPosition);
5960
this.editor.session.off("changeScrollLeft", this.setPosition);
6061
}
@@ -164,6 +165,9 @@ export class LightbulbWidget implements ILightbulbWidget {
164165

165166
dispose() {
166167
this.removeListeners();
167-
document.body.removeChild(this.lightbulb);
168+
if (this.lightbulb && this.lightbulb.parentNode) {
169+
this.lightbulb.parentNode.removeChild(this.lightbulb);
170+
}
171+
this.popup.destroy();
168172
}
169173
}

packages/ace-linters/src/components/signature-tooltip.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,24 @@ import {Ace} from "ace-code";
22
import {BaseTooltip} from "./base-tooltip";
33

44
export class SignatureTooltip extends BaseTooltip {
5-
5+
editorHandlers: Map<Ace.Editor, () => void> = new Map();
6+
67
registerEditor(editor: Ace.Editor) {
7-
editor.on("changeSelection", () => this.onChangeSelection(editor));
8+
const handler = () => this.onChangeSelection(editor);
9+
this.editorHandlers.set(editor, handler);
10+
editor.on("changeSelection", handler);
11+
}
12+
13+
unregisterEditor(editor: Ace.Editor) {
14+
const handler = this.editorHandlers.get(editor);
15+
if (handler) {
16+
editor.off("changeSelection", handler);
17+
this.editorHandlers.delete(editor);
18+
}
19+
// Clean up if this was the active editor
20+
if (this.$activeEditor === editor) {
21+
this.$inactivateEditor();
22+
}
823
}
924

1025

packages/ace-linters/src/language-provider.ts

Lines changed: 154 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ import {SessionLanguageProvider} from "./session-language-provider";
3939
import {popupManager} from "./ace/popupManager";
4040

4141
export 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

Comments
 (0)