From 300cb231f0a03500570f955e5e1d8d4841f68199 Mon Sep 17 00:00:00 2001 From: Eric Amodio Date: Thu, 2 Nov 2023 04:01:39 -0400 Subject: [PATCH] Adds configurable delay to repo file watching - Subscribers can specify the delay and we use the min delay of current subscriptions - Defaults Graph & Commit Details to 1s delay (rather than 2.5s) --- src/git/models/repository.ts | 95 ++++++++++++++-------- src/plus/webviews/graph/graphWebview.ts | 2 +- src/system/iterable.ts | 42 ++++++++++ src/views/nodes/fileHistoryNode.ts | 2 +- src/views/nodes/lineHistoryNode.ts | 2 +- src/views/nodes/repositoryNode.ts | 2 +- src/webviews/commitDetails/commitDetailsWebview.ts | 2 +- 7 files changed, 108 insertions(+), 39 deletions(-) diff --git a/src/git/models/repository.ts b/src/git/models/repository.ts index 3c15b36..95657e8 100644 --- a/src/git/models/repository.ts +++ b/src/git/models/repository.ts @@ -1,6 +1,6 @@ import type { CancellationToken, ConfigurationChangeEvent, Event, Uri, WorkspaceFolder } from 'vscode'; import { Disposable, EventEmitter, ProgressLocation, RelativePattern, window, workspace } from 'vscode'; -import { md5 } from '@env/crypto'; +import { md5, uuid } from '@env/crypto'; import { ForcePushMode } from '../../@types/vscode.git.enums'; import type { CreatePullRequestActionContext } from '../../api/gitlens'; import type { RepositoriesSorting } from '../../config'; @@ -16,8 +16,9 @@ import { configuration } from '../../system/configuration'; import { formatDate, fromNow } from '../../system/date'; import { gate } from '../../system/decorators/gate'; import { debug, log, logName } from '../../system/decorators/log'; +import type { Deferrable } from '../../system/function'; import { debounce } from '../../system/function'; -import { filter, join, some } from '../../system/iterable'; +import { filter, join, min, some } from '../../system/iterable'; import { getLoggableName, Logger } from '../../system/logger'; import { getLogScope } from '../../system/logger.scope'; import { updateRecordValue } from '../../system/object'; @@ -91,6 +92,9 @@ export const enum RepositoryChangeComparisonMode { Exclusive, } +const defaultFileSystemChangeDelay = 2500; +const defaultRepositoryChangeDelay = 250; + export class RepositoryChangeEvent { private readonly _changes: Set; @@ -235,10 +239,8 @@ export class Repository implements Disposable { private _branch: Promise | undefined; private readonly _disposable: Disposable; - private _fireChangeDebounced: (() => void) | undefined = undefined; - private _fireFileSystemChangeDebounced: (() => void) | undefined = undefined; - private _fsWatchCounter = 0; - private _fsWatcherDisposable: Disposable | undefined; + private _fireChangeDebounced: Deferrable<() => void> | undefined = undefined; + private _fireFileSystemChangeDebounced: Deferrable<() => void> | undefined = undefined; private _pendingFileSystemChange?: RepositoryFileSystemChangeEvent; private _pendingRepoChange?: RepositoryChangeEvent; private _suspended: boolean; @@ -365,7 +367,7 @@ export class Repository implements Disposable { } dispose() { - this.stopWatchingFileSystem(); + this.unWatchFileSystem(true); this._disposable.dispose(); } @@ -1002,7 +1004,7 @@ export class Repository implements Disposable { } if (this._pendingFileSystemChange != null) { - this._fireFileSystemChangeDebounced!(); + this._fireFileSystemChangeDebounced?.(); } } @@ -1138,8 +1140,32 @@ export class Repository implements Disposable { return this._etagFileSystem; } - startWatchingFileSystem(): Disposable { - this._fsWatchCounter++; + suspend() { + this._suspended = true; + } + + @log() + tag(...args: string[]) { + void this.runTerminalCommand('tag', ...args); + } + + @log() + tagDelete(tags: GitTagReference | GitTagReference[]) { + if (!Array.isArray(tags)) { + tags = [tags]; + } + + const args = ['--delete']; + void this.runTerminalCommand('tag', ...args, ...tags.map(t => t.ref)); + } + + private _fsWatcherDisposable: Disposable | undefined; + private _fsWatchers = new Map(); + private _fsChangeDelay: number = defaultFileSystemChangeDelay; + + watchFileSystem(delay: number = defaultFileSystemChangeDelay): Disposable { + const id = uuid(); + this._fsWatchers.set(id, delay); if (this._fsWatcherDisposable == null) { const watcher = workspace.createFileSystemWatcher(new RelativePattern(this.uri, '**')); this._fsWatcherDisposable = Disposable.from( @@ -1152,36 +1178,34 @@ export class Repository implements Disposable { this._etagFileSystem = Date.now(); } - return { dispose: () => this.stopWatchingFileSystem() }; + this.ensureMinFileSystemChangeDelay(); + + return { dispose: () => this.unWatchFileSystem(id) }; } - stopWatchingFileSystem(force: boolean = false) { - if (this._fsWatcherDisposable == null) return; - if (--this._fsWatchCounter > 0 && !force) return; + private unWatchFileSystem(forceOrId: true | string) { + if (typeof forceOrId !== 'boolean') { + this._fsWatchers.delete(forceOrId); + if (this._fsWatchers.size !== 0) { + this.ensureMinFileSystemChangeDelay(); + return; + } + } this._etagFileSystem = undefined; - this._fsWatchCounter = 0; - this._fsWatcherDisposable.dispose(); + this._fsChangeDelay = defaultFileSystemChangeDelay; + this._fsWatchers.clear(); + this._fsWatcherDisposable?.dispose(); this._fsWatcherDisposable = undefined; } - suspend() { - this._suspended = true; - } + private ensureMinFileSystemChangeDelay() { + const minDelay = min(this._fsWatchers.values()); + if (minDelay === this._fsChangeDelay) return; - @log() - tag(...args: string[]) { - void this.runTerminalCommand('tag', ...args); - } - - @log() - tagDelete(tags: GitTagReference | GitTagReference[]) { - if (!Array.isArray(tags)) { - tags = [tags]; - } - - const args = ['--delete']; - void this.runTerminalCommand('tag', ...args, ...tags.map(t => t.ref)); + this._fsChangeDelay = minDelay; + this._fireFileSystemChangeDebounced?.flush(); + this._fireFileSystemChangeDebounced = undefined; } @debug() @@ -1191,7 +1215,7 @@ export class Repository implements Disposable { this._updatedAt = Date.now(); if (this._fireChangeDebounced == null) { - this._fireChangeDebounced = debounce(this.fireChangeCore.bind(this), 250); + this._fireChangeDebounced = debounce(this.fireChangeCore.bind(this), defaultRepositoryChangeDelay); } this._pendingRepoChange = this._pendingRepoChange?.with(changes) ?? new RepositoryChangeEvent(this, changes); @@ -1224,7 +1248,10 @@ export class Repository implements Disposable { this._updatedAt = Date.now(); if (this._fireFileSystemChangeDebounced == null) { - this._fireFileSystemChangeDebounced = debounce(this.fireFileSystemChangeCore.bind(this), 2500); + this._fireFileSystemChangeDebounced = debounce( + this.fireFileSystemChangeCore.bind(this), + this._fsChangeDelay, + ); } if (this._pendingFileSystemChange == null) { diff --git a/src/plus/webviews/graph/graphWebview.ts b/src/plus/webviews/graph/graphWebview.ts index 1f56390..ffd6e62 100644 --- a/src/plus/webviews/graph/graphWebview.ts +++ b/src/plus/webviews/graph/graphWebview.ts @@ -1544,7 +1544,7 @@ export class GraphWebviewProvider implements WebviewProvider { if (key !== 'gitlens:hasConnectedRemotes') return; diff --git a/src/system/iterable.ts b/src/system/iterable.ts index c9bb071..bafe5c1 100644 --- a/src/system/iterable.ts +++ b/src/system/iterable.ts @@ -170,6 +170,48 @@ export function* map( } } +export function max(source: Iterable | IterableIterator): number; +export function max(source: Iterable | IterableIterator, selector: (item: T) => number): number; +export function max(source: Iterable | IterableIterator, selector?: (item: T) => number): number { + let max = Number.NEGATIVE_INFINITY; + if (selector == null) { + for (const item of source as Iterable | IterableIterator) { + if (item > max) { + max = item; + } + } + } else { + for (const item of source) { + const value = selector(item); + if (value > max) { + max = value; + } + } + } + return max; +} + +export function min(source: Iterable | IterableIterator): number; +export function min(source: Iterable | IterableIterator, selector: (item: T) => number): number; +export function min(source: Iterable | IterableIterator, selector?: (item: T) => number): number { + let min = Number.POSITIVE_INFINITY; + if (selector == null) { + for (const item of source as Iterable | IterableIterator) { + if (item < min) { + min = item; + } + } + } else { + for (const item of source) { + const value = selector(item); + if (value < min) { + min = value; + } + } + } + return min; +} + export function next(source: IterableIterator): T { return source.next().value as T; } diff --git a/src/views/nodes/fileHistoryNode.ts b/src/views/nodes/fileHistoryNode.ts index d92170f..a0b2147 100644 --- a/src/views/nodes/fileHistoryNode.ts +++ b/src/views/nodes/fileHistoryNode.ts @@ -189,7 +189,7 @@ export class FileHistoryNode const subscription = Disposable.from( weakEvent(repo.onDidChange, this.onRepositoryChanged, this), - weakEvent(repo.onDidChangeFileSystem, this.onFileSystemChanged, this, [repo.startWatchingFileSystem()]), + weakEvent(repo.onDidChangeFileSystem, this.onFileSystemChanged, this, [repo.watchFileSystem()]), weakEvent( configuration.onDidChange, e => { diff --git a/src/views/nodes/lineHistoryNode.ts b/src/views/nodes/lineHistoryNode.ts index 40cc720..3a915ad 100644 --- a/src/views/nodes/lineHistoryNode.ts +++ b/src/views/nodes/lineHistoryNode.ts @@ -202,7 +202,7 @@ export class LineHistoryNode const subscription = Disposable.from( weakEvent(repo.onDidChange, this.onRepositoryChanged, this), - weakEvent(repo.onDidChangeFileSystem, this.onFileSystemChanged, this, [repo.startWatchingFileSystem()]), + weakEvent(repo.onDidChangeFileSystem, this.onFileSystemChanged, this, [repo.watchFileSystem()]), ); return subscription; diff --git a/src/views/nodes/repositoryNode.ts b/src/views/nodes/repositoryNode.ts index 0e2d454..5c41bb7 100644 --- a/src/views/nodes/repositoryNode.ts +++ b/src/views/nodes/repositoryNode.ts @@ -398,7 +398,7 @@ export class RepositoryNode extends SubscribeableViewNode<'repository', ViewsWit if (this.view.config.includeWorkingTree) { disposables.push( weakEvent(this.repo.onDidChangeFileSystem, this.onFileSystemChanged, this, [ - this.repo.startWatchingFileSystem(), + this.repo.watchFileSystem(), ]), ); } diff --git a/src/webviews/commitDetails/commitDetailsWebview.ts b/src/webviews/commitDetails/commitDetailsWebview.ts index 70f2e8f..a24af9a 100644 --- a/src/webviews/commitDetails/commitDetailsWebview.ts +++ b/src/webviews/commitDetails/commitDetailsWebview.ts @@ -740,7 +740,7 @@ export class CommitDetailsWebviewProvider private subscribeToRepositoryWip(repo: Repository) { return Disposable.from( - repo.startWatchingFileSystem(), + repo.watchFileSystem(1000), repo.onDidChangeFileSystem(() => this.onWipChanged(repo)), repo.onDidChange(e => { if (e.changed(RepositoryChange.Index, RepositoryChangeComparisonMode.Any)) {