From a4498fdec90dddb09eb5b47f3865d2a0dce39a42 Mon Sep 17 00:00:00 2001 From: AidenLx Date: Tue, 8 Jun 2021 08:40:09 +0800 Subject: [PATCH] perf: use debounce for vault event - now the returned path of all vault event is kept in waitingList and only get update when all consecutive events are finished - add filters to reduce duplicate count update --- package.json | 1 + src/folder-count.ts | 72 +++++++++++++++++++++++++++----------------- src/main.ts | 39 ++++-------------------- src/misc.ts | 11 ++++++- src/vault-handler.ts | 38 +++++++++++++++++++++++ 5 files changed, 99 insertions(+), 62 deletions(-) create mode 100644 src/vault-handler.ts diff --git a/package.json b/package.json index 83eaaaf..29a534c 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "@types/node": "^14.14.37", "@typescript-eslint/eslint-plugin": "^4.23.0", "@typescript-eslint/parser": "^4.23.0", + "assert-never": "^1.2.1", "cz-conventional-changelog": "^3.3.0", "eslint": "^7.26.0", "eslint-config-prettier": "^8.3.0", diff --git a/src/folder-count.ts b/src/folder-count.ts index 892753a..3796afe 100644 --- a/src/folder-count.ts +++ b/src/folder-count.ts @@ -5,10 +5,11 @@ import { AbstractFileFilter, getParentPath, isFolder, + isParent, iterateItems, withSubfolderClass, } from 'misc'; -import { AFItem, FolderItem, TAbstractFile, TFolder } from 'obsidian'; +import { AFItem, FolderItem, TFolder } from 'obsidian'; const countFolderChildren = (folder: TFolder, filter: AbstractFileFilter) => { let count = 0; @@ -19,38 +20,53 @@ const countFolderChildren = (folder: TFolder, filter: AbstractFileFilter) => { return count; }; +/** filter out all path that is the parent of existing path */ +const filterParent = (pathList: string[]): Set => { + const list = Array.from(pathList); + list.sort(); + for (let i = 0; i < list.length; i++) { + if ( + i < list.length - 1 && + (list[i] === list[i + 1] || isParent(list[i], list[i + 1])) + ) { + list.shift(); + i--; + } + } + return new Set(list); +}; +/** get all parents and add to set if not exist */ +const getAllParents = (path: string, set: Set): void => { + let parent = getParentPath(path); + while (parent && !set.has(parent)) { + set.add(parent); + parent = getParentPath(parent); + } +}; /** * Update folder count of target's parent */ export const updateCount = ( - target: string | TAbstractFile, + targetList: string[], plugin: FileExplorerNoteCount, ): void => { - if (!plugin.fileExplorer) throw new Error('fileExplorer not found'); - const explorer = plugin.fileExplorer; - - const iterate = (folder: TFolder) => { - if (!folder.isRoot()) { - setCount( - explorer.fileItems[folder.path] as FolderItem, - plugin.fileFilter, - ); - iterate(folder.parent); - } - }; - - let parent: TFolder; - if (typeof target === 'string' || !target.parent) { - const filePath = typeof target === 'string' ? target : target.path; - const parentPath = getParentPath(filePath); - parent = plugin.app.vault.getAbstractFileByPath(parentPath) as TFolder; - if (!parent) { - console.error('cannot find parent: ' + parentPath); - return; - } - } else parent = target.parent; - - iterate(parent); + const set = filterParent(targetList); + for (const path of targetList) { + getAllParents(path, set); + } + // set count of path + const { fileExplorer, fileFilter } = plugin; + if (!fileExplorer) { + console.error('fileExplorer missing'); + return; + } + for (const path of set) { + // check if path available + if (!fileExplorer.fileItems[path]) continue; + setCount(fileExplorer.fileItems[path] as FolderItem, fileFilter); + } + // empty waitingList + targetList.length = 0; }; export const setupCount = (plugin: FileExplorerNoteCount, revert = false) => { @@ -63,7 +79,7 @@ export const setupCount = (plugin: FileExplorerNoteCount, revert = false) => { }); }; -const setCount = (item: FolderItem, filter: AbstractFileFilter) => { +export const setCount = (item: FolderItem, filter: AbstractFileFilter) => { if (item.file.isRoot()) return; const count = countFolderChildren(item.file, filter); item.titleEl.dataset['count'] = count.toString(); diff --git a/src/main.ts b/src/main.ts index c0b137b..d23b488 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,10 +1,10 @@ import './styles/patch.css'; -import { AbstractFileFilter, getParentPath, withSubfolderClass } from 'misc'; -import { FileExplorer, Plugin, TAbstractFile, TFile, TFolder } from 'obsidian'; -import { dirname } from 'path-browserify'; +import { AbstractFileFilter, withSubfolderClass } from 'misc'; +import { FileExplorer, Plugin, TFile } from 'obsidian'; +import { VaultHandler } from 'vault-handler'; -import { setupCount, updateCount } from './folder-count'; +import { setupCount } from './folder-count'; import { DEFAULT_SETTINGS, FENoteCountSettingTab } from './settings'; export default class FileExplorerNoteCount extends Plugin { @@ -12,34 +12,7 @@ export default class FileExplorerNoteCount extends Plugin { fileExplorer?: FileExplorer; - onRename = (af: TAbstractFile, oldPath: string) => { - // only update when file is moved to other location - // if af is TFolder, its count will be updated when its children are renamed - if (af instanceof TFolder && dirname(af.path) === dirname(oldPath)) - return; - - updateCount(af, this); - const oldParent = getParentPath(oldPath); - // when file is moved alone (not with folder) - if (this.app.vault.getAbstractFileByPath(oldParent)) - updateCount(oldPath, this); - }; - - registerVaultEvent() { - // attach events on new folder - this.registerEvent( - this.app.vault.on('create', (af) => { - updateCount(af, this); - }), - ); - // include mv and rename - this.registerEvent(this.app.vault.on('rename', this.onRename)); - this.registerEvent( - this.app.vault.on('delete', (af) => { - updateCount(af, this); - }), - ); - } + vaultHandler = new VaultHandler(this); initialize = (revert = false) => { const leaves = this.app.workspace.getLeavesOfType('file-explorer'); @@ -49,7 +22,7 @@ export default class FileExplorerNoteCount extends Plugin { if (!this.fileExplorer) this.fileExplorer = leaves[0].view as FileExplorer; setupCount(this, revert); - if (!revert) this.registerVaultEvent(); + if (!revert) this.vaultHandler.registerVaultEvent(); if (revert) { for (const el of document.getElementsByClassName( withSubfolderClass, diff --git a/src/misc.ts b/src/misc.ts index 34615dc..257a9fc 100644 --- a/src/misc.ts +++ b/src/misc.ts @@ -22,7 +22,8 @@ export const iterateItems = ( } }; -export const getParentPath = (src: string) => { +export const getParentPath = (src: string): string | null => { + if (src === '/') return null; const path = dirname(src); if (path === '.') return '/'; else return path; @@ -39,3 +40,11 @@ export const equals = (arr1: any, arr2: any) => { return arr1.every((v, i) => v === arr2[i]); }; + +export const isParent = (parent: string, child: string): boolean => { + if (child === parent) return false; + if (parent === '/') parent = ''; + if (child === '/') child = ''; + const parentTokens = parent.split('/').filter((i) => i.length); + return parentTokens.every((t, i) => child.split('/')[i] === t); +}; diff --git a/src/vault-handler.ts b/src/vault-handler.ts new file mode 100644 index 0000000..f45502d --- /dev/null +++ b/src/vault-handler.ts @@ -0,0 +1,38 @@ +import { updateCount } from 'folder-count'; +import FileExplorerNoteCount from 'main'; +import { getParentPath } from 'misc'; +import { App, debounce, TAbstractFile, Vault } from 'obsidian'; + +export class VaultHandler { + waitingList: string[] = []; + get app(): App { + return this.plugin.app; + } + get vault(): Vault { + return this.plugin.app.vault; + } + plugin: FileExplorerNoteCount; + constructor(plugin: FileExplorerNoteCount) { + this.plugin = plugin; + } + + update = debounce( + () => updateCount(this.waitingList, this.plugin), + 500, + true, + ); + + handler = (...args: (string | TAbstractFile)[]) => { + for (const arg of args) { + const path = arg instanceof TAbstractFile ? arg.path : arg; + this.waitingList.push(getParentPath(path) ?? '/'); + } + this.update(); + }; + + registerVaultEvent = () => { + this.plugin.registerEvent(this.vault.on('create', this.handler)); + this.plugin.registerEvent(this.vault.on('rename', this.handler)); + this.plugin.registerEvent(this.vault.on('delete', this.handler)); + }; +}