Mercurial > obsidian-remember-file-state
comparison src/main.ts @ 40:96e86650043b
Fix issues with files opened in multiple panes.
The previous code was problematic since it opened files at the top for
no obvious reason. Now we always restore any saved state if we have it,
and if not we restore the current state from another pane if we find
the same file is already open.
author | Ludovic Chabant <ludovic@chabant.com> |
---|---|
date | Wed, 20 Sep 2023 17:18:47 -0700 |
parents | 8be02002ed66 |
children | aa9bc7754c5d |
comparison
equal
deleted
inserted
replaced
39:fdc14798c221 | 40:96e86650043b |
---|---|
224 private readonly onFileOpen = async ( | 224 private readonly onFileOpen = async ( |
225 openedFile: TFile | 225 openedFile: TFile |
226 ): Promise<void> => { | 226 ): Promise<void> => { |
227 // If `openedFile` is null, it's because the last pane was closed | 227 // If `openedFile` is null, it's because the last pane was closed |
228 // and there is now an empty pane. | 228 // and there is now an empty pane. |
229 if (openedFile) { | 229 if (!openedFile) { |
230 var activeView: MarkdownView = this.app.workspace.getActiveViewOfType(MarkdownView); | 230 return; |
231 if (activeView) { | 231 } |
232 this.registerOnUnloadFile(activeView); | 232 |
233 | 233 var shouldSuppressThis: bool = this._suppressNextFileOpen; |
234 var isRealFileOpen = true; | 234 this._suppressNextFileOpen = false; |
235 const viewId = this.getUniqueViewId(activeView as unknown as ViewWithID); | 235 if (shouldSuppressThis) { |
236 if (viewId != undefined) { | 236 console.debug("RememberFileState: not restoring file state because of explicit suppression"); |
237 const lastOpenFileInView = this._lastOpenFiles[viewId]; | 237 return; |
238 isRealFileOpen = (lastOpenFileInView != openedFile.path); | 238 } |
239 this._lastOpenFiles[viewId] = openedFile.path; | 239 |
240 } | 240 // Check that the file is handled by a markdown editor, which is the |
241 | 241 // only editor we support for now. |
242 // Don't restore the file state if: | 242 var activeView: MarkdownView = this.app.workspace.getActiveViewOfType(MarkdownView); |
243 // - We are suppressing it explicitly (such as if the file was | 243 if (!activeView) { |
244 // opened via clicking a hyperlink) | 244 console.debug("RememberFileState: not restoring file state, it's not a markdown view"); |
245 // - The file is already currently open in another pane | 245 return; |
246 // - The file was already opened in this pane, and we're just | 246 } |
247 // returning to it. | 247 |
248 if (!this._suppressNextFileOpen && | 248 this.registerOnUnloadFile(activeView); |
249 !this.isFileMultiplyOpen(openedFile) && | 249 |
250 isRealFileOpen | 250 // Check if this is a genuine file open, and not returning to pane that |
251 ) { | 251 // already had this file opened in it. |
252 try { | 252 var isRealFileOpen = true; |
253 this.restoreFileState(openedFile, activeView); | 253 const viewId = this.getUniqueViewId(activeView as unknown as ViewWithID); |
254 } catch (err) { | 254 if (viewId != undefined) { |
255 console.error("RememberedFileState: couldn't restore file state: ", err); | 255 const lastOpenFileInView = this._lastOpenFiles[viewId]; |
256 } | 256 isRealFileOpen = (lastOpenFileInView != openedFile.path); |
257 } | 257 this._lastOpenFiles[viewId] = openedFile.path; |
258 else { | 258 } |
259 console.debug("RememberedFileState: not restoring file state because:"); | 259 if (!isRealFileOpen) { |
260 if (this._suppressNextFileOpen) { | 260 console.debug("RememberFileState: not restoring file state, that file was already open in this pane."); |
261 console.debug("...we were told to not do it."); | 261 return; |
262 } else if (this.isFileMultiplyOpen(openedFile)) { | 262 } |
263 console.debug("...it's open in multiple panes."); | 263 |
264 } else if (!isRealFileOpen) { | 264 // Restore the state! |
265 console.debug("...that file was already open in this pane."); | 265 try { |
266 } else { | 266 const existingFile = this.data.rememberedFiles[openedFile.path]; |
267 console.debug("...unknown reason."); | 267 if (existingFile) { |
268 } | 268 const savedStateData = existingFile.stateData; |
269 console.debug("RememberFileState: restoring saved state for:", openedFile.path, savedStateData); | |
270 this.restoreState(savedStateData, activeView); | |
271 } else { | |
272 // If we don't have any saved state for this file, let's see if | |
273 // it's opened in another pane. If so, restore that. | |
274 const otherPaneState = this.findFileStateFromOtherPane(openedFile, activeView); | |
275 if (otherPaneState) { | |
276 console.debug("RememberFileState: restoring other pane state for:", openedFile.path, otherPaneState); | |
277 this.restoreState(otherPaneState, activeView); | |
269 } | 278 } |
270 } | 279 } |
271 // else: the file isn't handled by a markdown editor. | 280 } catch (err) { |
272 else { | 281 console.error("RememberFileState: couldn't restore file state: ", err); |
273 console.debug("RememberedFileState: not restoring anything, it's not a markdown view"); | |
274 } | |
275 | |
276 this._suppressNextFileOpen = false; | |
277 } | 282 } |
278 } | 283 } |
279 | 284 |
280 private readonly rememberFileState = async (file: TFile, view: MarkdownView): Promise<void> => { | 285 private readonly rememberFileState = async (file: TFile, view: MarkdownView): Promise<void> => { |
281 // Save scrolling position (Obsidian API only gives vertical position). | 286 const stateData = this.getState(view); |
282 const scrollInfo = {top: view.currentMode.getScroll(), left: 0}; | |
283 | |
284 // Save current selection. | |
285 // If state selection is undefined, we have a legacy editor. Just ignore that part. | |
286 const cm6editor = view.editor as EditorWithCM6; | |
287 const stateSelection: EditorSelection = cm6editor.cm.state.selection; | |
288 const stateSelectionJSON = (stateSelection !== undefined) ? stateSelection.toJSON() : ""; | |
289 | |
290 const stateData = {'scrollInfo': scrollInfo, 'selection': stateSelectionJSON}; | |
291 | |
292 var existingFile = this.data.rememberedFiles[file.path]; | 287 var existingFile = this.data.rememberedFiles[file.path]; |
293 if (existingFile) { | 288 if (existingFile) { |
294 existingFile.lastSavedTime = Date.now(); | 289 existingFile.lastSavedTime = Date.now(); |
295 existingFile.stateData = stateData; | 290 existingFile.stateData = stateData; |
296 } else { | 291 } else { |
306 this.forgetExcessFiles(); | 301 this.forgetExcessFiles(); |
307 } | 302 } |
308 console.debug("RememberedFileState: remembered state for:", file.path, stateData); | 303 console.debug("RememberedFileState: remembered state for:", file.path, stateData); |
309 } | 304 } |
310 | 305 |
311 private readonly restoreFileState = function(file: TFile, view: MarkdownView) { | 306 private readonly getState = function(view: MarkdownView) { |
312 const existingFile = this.data.rememberedFiles[file.path]; | 307 // Save scrolling position (Obsidian API only gives vertical position). |
313 if (existingFile) { | 308 const scrollInfo = {top: view.currentMode.getScroll(), left: 0}; |
314 console.debug("RememberedFileState: restoring state for:", file.path, existingFile.stateData); | 309 |
315 const stateData = existingFile.stateData; | 310 // Save current selection. |
316 | 311 // If state selection is undefined, we have a legacy editor. Just ignore that part. |
317 // Restore scrolling position (Obsidian API only allows setting vertical position). | 312 const cm6editor = view.editor as EditorWithCM6; |
318 view.currentMode.applyScroll(stateData.scrollInfo.top); | 313 const stateSelection: EditorSelection = cm6editor.cm.state.selection; |
319 | 314 const stateSelectionJSON = (stateSelection !== undefined) ? stateSelection.toJSON() : ""; |
320 // Restore last known selection, if any. | 315 |
321 if (stateData.selection != "") { | 316 const stateData = {'scrollInfo': scrollInfo, 'selection': stateSelectionJSON}; |
322 const cm6editor = view.editor as EditorWithCM6; | 317 |
323 var transaction = cm6editor.cm.state.update({ | 318 return stateData; |
324 selection: EditorSelection.fromJSON(stateData.selection)}) | 319 } |
325 | 320 |
326 cm6editor.cm.dispatch(transaction); | 321 private readonly restoreState = function(stateData: StateData, view: MarkdownView) { |
327 } | 322 // Restore scrolling position (Obsidian API only allows setting vertical position). |
323 view.currentMode.applyScroll(stateData.scrollInfo.top); | |
324 | |
325 // Restore last known selection, if any. | |
326 if (stateData.selection != "") { | |
327 const cm6editor = view.editor as EditorWithCM6; | |
328 var transaction = cm6editor.cm.state.update({ | |
329 selection: EditorSelection.fromJSON(stateData.selection)}) | |
330 | |
331 cm6editor.cm.dispatch(transaction); | |
328 } | 332 } |
329 } | 333 } |
330 | 334 |
331 private readonly isFileMultiplyOpen = function(file: TFile) { | 335 private readonly findFileStateFromOtherPane = function(file: TFile, activeView: MarkdownView) { |
332 var numFound: number = 0; | 336 var otherView = null; |
333 this.app.workspace.getLeavesOfType("markdown").forEach( | 337 this.app.workspace.getLeavesOfType("markdown").every( |
334 (leaf: WorkspaceLeaf) => { | 338 (leaf: WorkspaceLeaf) => { |
335 const filePath = (leaf.view as MarkdownView).file.path; | 339 var curView = leaf.view as MarkdownView; |
336 if (filePath == file.path) { | 340 if (curView != activeView && |
337 ++numFound; | 341 curView.file.path == file.path && |
342 this.getUniqueViewId(curView) >= 0 // Skip views that have never been activated. | |
343 ) { | |
344 console.debug(`FFFFOOOOOUNNNNDD!!!!! ${file.path}`, curView, activeView); | |
345 otherView = curView; | |
346 return false; // Stop iterating leaves. | |
338 } | 347 } |
339 }); | 348 return true; |
340 return numFound >= 2; | 349 }, |
350 this // thisArg | |
351 ); | |
352 return otherView ? this.getState(otherView) : null; | |
341 } | 353 } |
342 | 354 |
343 private readonly forgetExcessFiles = function() { | 355 private readonly forgetExcessFiles = function() { |
344 const keepMax = this.settings.rememberMaxFiles; | 356 const keepMax = this.settings.rememberMaxFiles; |
345 if (keepMax <= 0) { | 357 if (keepMax <= 0) { |