changeset 6:114d7e6d2633

Fix various issues around keeping references to editor objects - Don't keep references to view. Instead, generate unique view IDs and use that to find the correct uninstaller when needed. - Don't keep a reference to the plugin itself in registered callbacks on views because that could keep the plugin alive after it has been unloaded.
author Ludovic Chabant <ludovic@chabant.com>
date Mon, 14 Feb 2022 12:35:45 -0800
parents e8300db708b6
children b1cb0474bb18
files src/main.ts
diffstat 1 files changed, 64 insertions(+), 11 deletions(-) [+]
line wrap: on
line diff
--- a/src/main.ts	Thu Feb 10 23:00:06 2022 -0800
+++ b/src/main.ts	Mon Feb 14 12:35:45 2022 -0800
@@ -42,9 +42,14 @@
 	settings: RememberFileStatePluginSettings;
 	data: RememberFileStatePluginData;
 
+	// Don't restore state on the next file being opened.
 	private _suppressNextFileOpen: boolean = false;
+	// Next unique ID to identify views without keeping references to them.
+	private _nextUniqueViewId: number = 0;
 
+	// Functions to unregister any monkey-patched view hooks on plugin unload.
 	private _viewUninstallers = {};
+	// Functions to unregister any global callbacks on plugin unload.
 	private _globalUninstallers = [];
 
 	async onload() {
@@ -83,11 +88,32 @@
 	}
 
 	onunload() {
-		var uninstallers = Object.values(this._viewUninstallers);
-		console.debug(`Unregistering ${uninstallers.length} view callbacks`);
-		uninstallers.forEach((cb) => cb());
+		// Run view uninstallers on all current views.
+		var numViews: number = 0;
+		this.app.workspace.getLeavesOfType("markdown").forEach(
+			(leaf) => {
+				const filePath = leaf.view.file.path;
+				const viewId = this.getUniqueViewId(leaf.view);
+				if (viewId != undefined) {
+					var uninstaller = this._viewUninstallers[viewId];
+					if (uninstaller) {
+						console.debug(`Uninstalling hooks for view ${viewId}`, filePath);
+						uninstaller(leaf.view);
+						++numViews;
+					} else {
+						console.debug("Found markdown view without an uninstaller!", filePath);
+					}
+					// Clear the ID so we don't get confused if the plugin
+					// is re-enabled later.
+					this.clearUniqueViewId(leaf.view);
+				} else {
+					console.debug("Found markdown view without an ID!", filePath);
+				}
+			});
+		console.debug(`Unregistered ${numViews} view callbacks`);
 		this._viewUninstallers = {};
 
+		// Run global unhooks.
 		this._globalUninstallers.forEach((cb) => cb());
 	}
 
@@ -100,11 +126,14 @@
 	}
 
 	private readonly registerOnUnloadFile = function(view) {
-		if (view in this._viewUninstallers) {
+		var filePath = view.file.path;
+		var viewId = this.getUniqueViewId(view, true);
+		if (viewId in this._viewUninstallers) {
+			console.debug(`View ${viewId} is already registered`, filePath);
 			return;
 		}
 
-		console.debug("Registering view callback");
+		console.debug(`Registering callback on view ${viewId}`, filePath);
 		const _this = this;
 		var uninstall = around(view, {
 			onUnloadFile: function(next) {
@@ -114,12 +143,21 @@
 				};
 			}
 		});
-		this._viewUninstallers[view] = uninstall;
+		this._viewUninstallers[viewId] = uninstall;
 
 		view.register(() => {
-			console.debug("Unregistering view callback");
-			delete this._viewUninstallers[view];
-			uninstall();
+			// Don't hold a reference to this plugin here because this callback
+			// will outlive it if it gets deactivated. So let's find it, and
+			// do nothing if we don't find it.
+			var plugin = app.plugins.getPlugin("remember-file-state");
+			if (plugin) {
+				console.debug(`Unregistering view ${viewId} callback`, filePath);
+				delete plugin._viewUninstallers[viewId];
+				uninstall();
+			} else {
+				console.debug(
+					"Plugin remember-file-state has been unloaded, ignoring unregister");
+			}
 		});
 	}
 
@@ -167,7 +205,7 @@
 		console.debug("Remember file state for:", file.path);
 	}
 
-	private restoreFileState(file: TFile, view: View) {
+	private readonly restoreFileState = function(file: TFile, view: View) {
 		const existingFile = this.data.rememberedFiles.find(
 			(curFile) => curFile.path === file.path
 		);
@@ -181,7 +219,7 @@
 		}
 	}
 
-	private forgetExcessFiles() {
+	private readonly forgetExcessFiles = function() {
 		const keepMax = this.settings.rememberMaxFiles;
 		if (keepMax <= 0) {
 			return;
@@ -194,6 +232,21 @@
 		}
 	}
 
+	private readonly getUniqueViewId = function(view: View, autocreateId: boolean = false) {
+		if (view.__uniqueId == undefined) {
+			if (!autocreateId) {
+				return -1;
+			}
+			view.__uniqueId = (this._nextUniqueViewId++);
+			return view.__uniqueId;
+		}
+		return view.__uniqueId;
+	}
+
+	private readonly clearUniqueViewId = function(view: View) {
+		delete view["__uniqueId"];
+	}
+
 	private readonly onFileRename = async (
 		file: TAbstractFile,
 		oldPath: string,