diff --git a/CHANGELOG.md b/CHANGELOG.md index a4fa991b1ccc2..fb5b1b117906b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p ### Fixed - Fixes [#3592](https://github.com/gitkraken/vscode-gitlens/issues/3592) - Connecting to an integration via Remotes view (but likely others) doesn't work +- Fixes [#3571](https://github.com/gitkraken/vscode-gitlens/issues/3571) - Gitlens fails to register buttons on top-right corner — thanks to [PR #3605](https://github.com/gitkraken/vscode-gitlens/pull/3605) by Jean Pierre ([@jeanp413](https://github.com/jeanp413)) - Fixes an issue where virtual repositories for GitHub PRs from forks wouldn't load properly - Fixes an issue where deleting a worktree would not always remove the worktree from the view diff --git a/README.md b/README.md index ec0f4f53a1d8f..867373cedea28 100644 --- a/README.md +++ b/README.md @@ -449,6 +449,7 @@ A big thanks to the people that have contributed to this project 🙏❤️: - may ([@m4rch3n1ng](https://github.com/m4rch3n1ng)) — [contributions](https://github.com/gitkraken/vscode-gitlens/commits?author=m4rch3n1ng) - bm-w ([@bm-w](https://github.com/bm-w)) — [contributions](https://github.com/gitkraken/vscode-gitlens/commits?author=bm-w) - Tyler Johnson ([@TJohnsonSE](https://github.com/TJohnsonSE)) — [contributions](https://github.com/gitkraken/vscode-gitlens/commits?author=TJohnsonSE) +- Jean Pierre ([@jeanp413](https://github.com/jeanp413)) — [contributions](https://github.com/gitkraken/vscode-gitlens/commits?author=jeanp413) Also special thanks to the people that have provided support, testing, brainstorming, etc: diff --git a/src/annotations/fileAnnotationController.ts b/src/annotations/fileAnnotationController.ts index c17e5aa297ede..e361a4a56fd60 100644 --- a/src/annotations/fileAnnotationController.ts +++ b/src/annotations/fileAnnotationController.ts @@ -33,7 +33,8 @@ import { registerCommand } from '../system/vscode/command'; import { configuration } from '../system/vscode/configuration'; import { setContext } from '../system/vscode/context'; import type { KeyboardScope } from '../system/vscode/keyboard'; -import { getResourceContextKeyValue, isTrackableTextEditor } from '../system/vscode/utils'; +import { UriSet } from '../system/vscode/uriMap'; +import { isTrackableTextEditor } from '../system/vscode/utils'; import type { DocumentBlameStateChangeEvent, DocumentDirtyIdleTriggerEvent, @@ -327,8 +328,8 @@ export class FileAnnotationController implements Disposable { debouncedRestore(editor); } - private readonly _annotatedUris = new Set(); - private readonly _computingUris = new Set(); + private readonly _annotatedUris = new UriSet(); + private readonly _computingUris = new UriSet(); async onProviderEditorStatusChanged(editor: TextEditor | undefined, status: AnnotationStatus | undefined) { if (editor == null) return; @@ -345,20 +346,16 @@ export class FileAnnotationController implements Disposable { } else { windowStatus = undefined; - let key = getResourceContextKeyValue(editor.document.uri); - if (typeof key !== 'string') { - key = await key; - } - + const uri = editor.document.uri; switch (status) { case 'computing': - if (!this._annotatedUris.has(key)) { - this._annotatedUris.add(key); + if (!this._annotatedUris.has(uri)) { + this._annotatedUris.add(uri); changed = true; } - if (!this._computingUris.has(key)) { - this._computingUris.add(key); + if (!this._computingUris.has(uri)) { + this._computingUris.add(uri); changed = true; } @@ -366,29 +363,29 @@ export class FileAnnotationController implements Disposable { case 'computed': { const provider = this.getProvider(editor); if (provider == null) { - if (this._annotatedUris.has(key)) { - this._annotatedUris.delete(key); + if (this._annotatedUris.has(uri)) { + this._annotatedUris.delete(uri); changed = true; } - } else if (!this._annotatedUris.has(key)) { - this._annotatedUris.add(key); + } else if (!this._annotatedUris.has(uri)) { + this._annotatedUris.add(uri); changed = true; } - if (this._computingUris.has(key)) { - this._computingUris.delete(key); + if (this._computingUris.has(uri)) { + this._computingUris.delete(uri); changed = true; } break; } default: - if (this._annotatedUris.has(key)) { - this._annotatedUris.delete(key); + if (this._annotatedUris.has(uri)) { + this._annotatedUris.delete(uri); changed = true; } - if (this._computingUris.has(key)) { - this._computingUris.delete(key); + if (this._computingUris.has(uri)) { + this._computingUris.delete(uri); changed = true; } break; diff --git a/src/constants.context.ts b/src/constants.context.ts index 9d41f64a531a0..cde5c853dbf1f 100644 --- a/src/constants.context.ts +++ b/src/constants.context.ts @@ -1,3 +1,4 @@ +import type { Uri } from 'vscode'; import type { AnnotationStatus } from './annotations/annotationProvider'; import type { Keys, PromoKeys } from './constants'; import type { CustomEditorTypes, WebviewTypes, WebviewViewTypes } from './constants.views'; @@ -26,10 +27,10 @@ export type ContextKeys = { 'gitlens:repos:withHostingIntegrations': string[]; 'gitlens:repos:withHostingIntegrationsConnected': string[]; 'gitlens:schemes:trackable': string[]; - 'gitlens:tabs:annotated': string[]; - 'gitlens:tabs:annotated:computing': string[]; - 'gitlens:tabs:blameable': string[]; - 'gitlens:tabs:tracked': string[]; + 'gitlens:tabs:annotated': Uri[]; + 'gitlens:tabs:annotated:computing': Uri[]; + 'gitlens:tabs:blameable': Uri[]; + 'gitlens:tabs:tracked': Uri[]; 'gitlens:untrusted': boolean; 'gitlens:views:canCompare': boolean; 'gitlens:views:canCompare:file': boolean; diff --git a/src/system/vscode/uriMap.ts b/src/system/vscode/uriMap.ts new file mode 100644 index 0000000000000..975f106d871cf --- /dev/null +++ b/src/system/vscode/uriMap.ts @@ -0,0 +1,136 @@ +import type { Uri } from 'vscode'; + +type UriMapEntry = { + readonly uri: Uri; + readonly value: T; +}; + +export class UriMap implements Map { + private static readonly defaultToKey = (resource: Uri) => resource.toString(); + + readonly [Symbol.toStringTag] = 'UriMap'; + private readonly _map: Map>; + + constructor(entries?: readonly (readonly [Uri, T])[]) { + this._map = new Map(); + if (entries?.length) { + for (const [uri, value] of entries) { + this.set(uri, value); + } + } + } + + set(uri: Uri, value: T): this { + this._map.set(UriMap.defaultToKey(uri), { uri: uri, value: value }); + return this; + } + + get(uri: Uri): T | undefined { + return this._map.get(UriMap.defaultToKey(uri))?.value; + } + + has(uri: Uri): boolean { + return this._map.has(UriMap.defaultToKey(uri)); + } + + get size(): number { + return this._map.size; + } + + clear(): void { + this._map.clear(); + } + + delete(uri: Uri): boolean { + return this._map.delete(UriMap.defaultToKey(uri)); + } + + forEach(callbackfn: (value: T, key: Uri, map: Map) => void, thisArg?: any): void { + if (typeof thisArg !== 'undefined') { + callbackfn = callbackfn.bind(thisArg); + } + for (const [_, entry] of this._map) { + callbackfn(entry.value, entry.uri, this); + } + } + + *values(): MapIterator { + for (const entry of this._map.values()) { + yield entry.value; + } + } + + *keys(): MapIterator { + for (const entry of this._map.values()) { + yield entry.uri; + } + } + + *entries(): MapIterator<[Uri, T]> { + for (const entry of this._map.values()) { + yield [entry.uri, entry.value]; + } + } + + *[Symbol.iterator](): MapIterator<[Uri, T]> { + for (const [, entry] of this._map) { + yield [entry.uri, entry.value]; + } + } +} + +export class UriSet implements Set { + readonly [Symbol.toStringTag]: string = 'UriSet'; + + private readonly _map: UriMap; + + constructor(entries?: readonly Uri[]) { + this._map = new UriMap(); + if (entries?.length) { + for (const uri of entries) { + this.add(uri); + } + } + } + + get size(): number { + return this._map.size; + } + + add(uri: Uri): this { + this._map.set(uri, uri); + return this; + } + + clear(): void { + this._map.clear(); + } + + delete(uri: Uri): boolean { + return this._map.delete(uri); + } + + forEach(callbackfn: (value: Uri, value2: Uri, set: Set) => void, thisArg?: any): void { + this._map.forEach((_value, key) => callbackfn.call(thisArg, key, key, this)); + } + + has(uri: Uri): boolean { + return this._map.has(uri); + } + + entries(): SetIterator<[Uri, Uri]> { + return this._map.entries(); + } + + keys(): SetIterator { + return this._map.keys(); + } + + values(): SetIterator { + return this._map.keys(); + } + + [Symbol.iterator](): SetIterator { + return this.keys(); + } +} diff --git a/src/system/vscode/utils.ts b/src/system/vscode/utils.ts index 4a6741ae66d16..2ab4c14137848 100644 --- a/src/system/vscode/utils.ts +++ b/src/system/vscode/utils.ts @@ -352,26 +352,3 @@ export function tabContainsUri(tab: Tab | undefined, uri: Uri | undefined): bool return false; } - -const resourceContextKeyValueCache = new Map>(); - -export function getResourceContextKeyValue(uri: Uri) { - // If we are on a remote connection, VS Code's `TextDocument.uri` uses a `file://` scheme, - // but VS Code sets the `resource` context key to a "remote" url in the form of `vscode-remote://+/` - // So we need to try to generate that `vscode-remote://` version, which seems to work by getting the querystring from `env.asExternalUri` - if (uri.scheme === 'file' && env.remoteName) { - const uriKey = uri.toString(); - let promise = resourceContextKeyValueCache.get(uriKey); - if (promise == null) { - promise = env.asExternalUri(uri).then(u => u.query); - resourceContextKeyValueCache.set(uriKey, promise); - promise.then( - () => resourceContextKeyValueCache.delete(uriKey), - () => resourceContextKeyValueCache.delete(uriKey), - ); - } - return promise; - } - - return uri.toString(); -} diff --git a/src/trackers/documentTracker.ts b/src/trackers/documentTracker.ts index c6104d37d695b..33f67fc7e474b 100644 --- a/src/trackers/documentTracker.ts +++ b/src/trackers/documentTracker.ts @@ -22,7 +22,8 @@ import type { Deferrable } from '../system/function'; import { debounce } from '../system/function'; import { configuration } from '../system/vscode/configuration'; import { setContext } from '../system/vscode/context'; -import { findTextDocument, getResourceContextKeyValue, isVisibleDocument } from '../system/vscode/utils'; +import { UriSet } from '../system/vscode/uriMap'; +import { findTextDocument, isVisibleDocument } from '../system/vscode/utils'; import type { TrackedGitDocument } from './trackedDocument'; import { createTrackedGitDocument } from './trackedDocument'; @@ -414,33 +415,33 @@ export class GitDocumentTracker implements Disposable { (tracked ?? (await docPromise))?.dispose(); } - private readonly _openUrisBlameable = new Set(); - private readonly _openUrisTracked = new Set(); + private readonly _openUrisBlameable = new UriSet(); + private readonly _openUrisTracked = new UriSet(); private _updateContextDebounced: Deferrable<() => void> | undefined; updateContext(uri: Uri, blameable: boolean, tracked: boolean) { let changed = false; - function updateContextCore(this: GitDocumentTracker, key: string, blameable: boolean, tracked: boolean) { + function updateContextCore(this: GitDocumentTracker, uri: Uri, blameable: boolean, tracked: boolean) { if (tracked) { - if (!this._openUrisTracked.has(key)) { + if (!this._openUrisTracked.has(uri)) { changed = true; - this._openUrisTracked.add(key); + this._openUrisTracked.add(uri); } - } else if (this._openUrisTracked.has(key)) { + } else if (this._openUrisTracked.has(uri)) { changed = true; - this._openUrisTracked.delete(key); + this._openUrisTracked.delete(uri); } if (blameable) { - if (!this._openUrisBlameable.has(key)) { + if (!this._openUrisBlameable.has(uri)) { changed = true; - this._openUrisBlameable.add(key); + this._openUrisBlameable.add(uri); } - } else if (this._openUrisBlameable.has(key)) { + } else if (this._openUrisBlameable.has(uri)) { changed = true; - this._openUrisBlameable.delete(key); + this._openUrisBlameable.delete(uri); } if (!changed) return; @@ -452,13 +453,7 @@ export class GitDocumentTracker implements Disposable { this._updateContextDebounced(); } - const key = getResourceContextKeyValue(uri); - if (typeof key !== 'string') { - void key.then(u => updateContextCore.call(this, u, blameable, tracked)); - return; - } - - updateContextCore.call(this, key, blameable, tracked); + updateContextCore.call(this, uri, blameable, tracked); } private fireDocumentDirtyStateChanged(e: DocumentDirtyStateChangeEvent) {