Browse Source

Adds workspace deep link support (#2942)

main
Ramin Tadayon 1 year ago
committed by GitHub
parent
commit
0f83de4acf
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 224 additions and 67 deletions
  1. +16
    -1
      package.json
  2. +9
    -0
      src/commands/base.ts
  3. +15
    -0
      src/commands/copyDeepLink.ts
  4. +1
    -0
      src/constants.ts
  5. +36
    -8
      src/uris/deepLinks/deepLink.ts
  6. +86
    -51
      src/uris/deepLinks/deepLinkService.ts
  7. +24
    -4
      src/views/viewBase.ts
  8. +37
    -3
      src/views/workspacesView.ts

+ 16
- 1
package.json View File

@ -5412,6 +5412,12 @@
"icon": "$(copy)"
},
{
"command": "gitlens.copyDeepLinkToWorkspace",
"title": "Copy Link to Workspace",
"category": "GitLens",
"icon": "$(copy)"
},
{
"command": "gitlens.copyDeepLinkToTag",
"title": "Copy Link to Tag",
"category": "GitLens",
@ -8556,6 +8562,10 @@
"when": "false"
},
{
"command": "gitlens.copyDeepLinkToWorkspace",
"when": "false"
},
{
"command": "gitlens.copyRemoteBranchUrl",
"when": "false"
},
@ -11777,7 +11787,7 @@
},
{
"submenu": "gitlens/share",
"when": "viewItem =~ /gitlens:(branch|commit|compare:results(?!:)|remote|repo-folder|repository|stash|tag|file\\b(?=.*?\\b\\+committed\\b))\\b/",
"when": "viewItem =~ /gitlens:(branch|commit|compare:results(?!:)|remote|repo-folder|repository|stash|tag|workspace|file\\b(?=.*?\\b\\+committed\\b))\\b/",
"group": "7_gitlens_a_share@1"
},
{
@ -13204,6 +13214,11 @@
"group": "1_gitlens@25"
},
{
"command": "gitlens.copyDeepLinkToWorkspace",
"when": "viewItem =~ /gitlens:workspace\\b/",
"group": "1_gitlens@25"
},
{
"command": "gitlens.copyRemoteFileUrlWithoutRange",
"when": "gitlens:hasRemotes && viewItem =~ /gitlens:(file\\b(?=.*?\\b\\+committed\\b)|history:(file|line)|status:file)\\b/",
"group": "2_gitlens@1"

+ 9
- 0
src/commands/base.ts View File

@ -21,6 +21,7 @@ import { GitRemote } from '../git/models/remote';
import { Repository } from '../git/models/repository';
import type { GitTag } from '../git/models/tag';
import { isTag } from '../git/models/tag';
import { CloudWorkspace, LocalWorkspace } from '../plus/workspaces/models';
import { registerCommand } from '../system/command';
import { sequentialize } from '../system/function';
import { ViewNode, ViewRefFileNode, ViewRefNode } from '../views/nodes/viewNode';
@ -212,6 +213,14 @@ export function isCommandContextViewNodeHasTag(
return isTag((context.node as ViewNode & { tag: GitTag }).tag);
}
export function isCommandContextViewNodeHasWorkspace(
context: CommandContext,
): context is CommandViewNodeContext & { node: ViewNode & { workspace: CloudWorkspace | LocalWorkspace } } {
if (context.type !== 'viewItem') return false;
const workspace = (context.node as ViewNode & { workspace?: CloudWorkspace | LocalWorkspace }).workspace;
return workspace instanceof CloudWorkspace || workspace instanceof LocalWorkspace;
}
export type CommandContext =
| CommandEditorLineContext
| CommandGitTimelineItemContext

+ 15
- 0
src/commands/copyDeepLink.ts View File

@ -20,6 +20,7 @@ import {
isCommandContextViewNodeHasComparison,
isCommandContextViewNodeHasRemote,
isCommandContextViewNodeHasTag,
isCommandContextViewNodeHasWorkspace,
} from './base';
export interface CopyDeepLinkCommandArgs {
@ -28,6 +29,7 @@ export interface CopyDeepLinkCommandArgs {
compareWithRef?: StoredNamedRef;
remote?: string;
prePickRemote?: boolean;
workspaceId?: string;
}
@command()
@ -39,6 +41,7 @@ export class CopyDeepLinkCommand extends ActiveEditorCommand {
Commands.CopyDeepLinkToRepo,
Commands.CopyDeepLinkToTag,
Commands.CopyDeepLinkToComparison,
Commands.CopyDeepLinkToWorkspace,
]);
}
@ -58,6 +61,8 @@ export class CopyDeepLinkCommand extends ActiveEditorCommand {
compareRef: context.node.compareRef,
compareWithRef: context.node.compareWithRef,
};
} else if (isCommandContextViewNodeHasWorkspace(context)) {
args = { workspaceId: context.node.workspace.id };
}
}
@ -67,6 +72,16 @@ export class CopyDeepLinkCommand extends ActiveEditorCommand {
async execute(editor?: TextEditor, uri?: Uri, args?: CopyDeepLinkCommandArgs) {
args = { ...args };
if (args.workspaceId != null) {
try {
await this.container.deepLinks.copyDeepLinkUrl(args.workspaceId);
} catch (ex) {
Logger.error(ex, 'CopyDeepLinkCommand');
void showGenericErrorMessage('Unable to copy link');
}
return;
}
let type;
let repoPath;
if (args?.refOrRepoPath == null) {

+ 1
- 0
src/constants.ts View File

@ -126,6 +126,7 @@ export const enum Commands {
CopyDeepLinkToComparison = 'gitlens.copyDeepLinkToComparison',
CopyDeepLinkToRepo = 'gitlens.copyDeepLinkToRepo',
CopyDeepLinkToTag = 'gitlens.copyDeepLinkToTag',
CopyDeepLinkToWorkspace = 'gitlens.copyDeepLinkToWorkspace',
CopyMessageToClipboard = 'gitlens.copyMessageToClipboard',
CopyRemoteBranchesUrl = 'gitlens.copyRemoteBranchesUrl',
CopyRemoteBranchUrl = 'gitlens.copyRemoteBranchUrl',

+ 36
- 8
src/uris/deepLinks/deepLink.ts View File

@ -11,6 +11,7 @@ export enum DeepLinkType {
Comparison = 'compare',
Repository = 'r',
Tag = 't',
Workspace = 'workspace',
}
export function deepLinkTypeToString(type: DeepLinkType): string {
@ -25,6 +26,8 @@ export function deepLinkTypeToString(type: DeepLinkType): string {
return 'Repository';
case DeepLinkType.Tag:
return 'Tag';
case DeepLinkType.Workspace:
return 'Workspace';
default:
debugger;
return 'Unknown';
@ -46,7 +49,7 @@ export function refTypeToDeepLinkType(refType: GitReference['refType']): DeepLin
export interface DeepLink {
type: DeepLinkType;
repoId: string;
mainId: string;
remoteUrl?: string;
repoPath?: string;
targetId?: string;
@ -58,8 +61,10 @@ export function parseDeepLinkUri(uri: Uri): DeepLink | undefined {
// The link target id is everything after the link target.
// For example, if the uri is /link/r/{repoId}/b/{branchName}?url={remoteUrl},
// the link target id is {branchName}
const [, type, prefix, repoId, target, ...rest] = uri.path.split('/');
if (type !== 'link' || prefix !== DeepLinkType.Repository) return undefined;
const [, type, prefix, mainId, target, ...rest] = uri.path.split('/');
if (type !== 'link' || (prefix !== DeepLinkType.Repository && prefix !== DeepLinkType.Workspace)) {
return undefined;
}
const urlParams = new URLSearchParams(uri.query);
let remoteUrl = urlParams.get('url') ?? undefined;
@ -70,12 +75,18 @@ export function parseDeepLinkUri(uri: Uri): DeepLink | undefined {
if (repoPath != null) {
repoPath = decodeURIComponent(repoPath);
}
if (!remoteUrl && !repoPath) return undefined;
if (!remoteUrl && !repoPath && prefix !== DeepLinkType.Workspace) return undefined;
if (prefix === DeepLinkType.Workspace) {
return {
type: DeepLinkType.Workspace,
mainId: mainId,
};
}
if (target == null) {
return {
type: DeepLinkType.Repository,
repoId: repoId,
mainId: mainId,
remoteUrl: remoteUrl,
repoPath: repoPath,
};
@ -103,7 +114,7 @@ export function parseDeepLinkUri(uri: Uri): DeepLink | undefined {
return {
type: target as DeepLinkType,
repoId: repoId,
mainId: mainId,
remoteUrl: remoteUrl,
repoPath: repoPath,
targetId: targetId,
@ -114,6 +125,7 @@ export function parseDeepLinkUri(uri: Uri): DeepLink | undefined {
export const enum DeepLinkServiceState {
Idle,
TypeMatch,
RepoMatch,
CloneOrAddRepo,
OpeningRepo,
@ -125,6 +137,7 @@ export const enum DeepLinkServiceState {
FetchedTargetMatch,
OpenGraph,
OpenComparison,
OpenWorkspace,
}
export const enum DeepLinkServiceAction {
@ -133,6 +146,8 @@ export const enum DeepLinkServiceAction {
DeepLinkResolved,
DeepLinkStored,
DeepLinkErrored,
LinkIsRepoType,
LinkIsWorkspaceType,
OpenRepo,
RepoMatched,
RepoMatchedInLocalMapping,
@ -154,7 +169,7 @@ export type DeepLinkRepoOpenType = 'clone' | 'folder' | 'workspace' | 'current';
export interface DeepLinkServiceContext {
state: DeepLinkServiceState;
url?: string | undefined;
repoId?: string | undefined;
mainId?: string | undefined;
repo?: Repository | undefined;
remoteUrl?: string | undefined;
remote?: GitRemote | undefined;
@ -170,7 +185,14 @@ export interface DeepLinkServiceContext {
export const deepLinkStateTransitionTable: Record<string, Record<string, DeepLinkServiceState>> = {
[DeepLinkServiceState.Idle]: {
[DeepLinkServiceAction.DeepLinkEventFired]: DeepLinkServiceState.RepoMatch,
[DeepLinkServiceAction.DeepLinkEventFired]: DeepLinkServiceState.TypeMatch,
[DeepLinkServiceAction.DeepLinkCancelled]: DeepLinkServiceState.Idle,
},
[DeepLinkServiceState.TypeMatch]: {
[DeepLinkServiceAction.DeepLinkErrored]: DeepLinkServiceState.Idle,
[DeepLinkServiceAction.DeepLinkCancelled]: DeepLinkServiceState.Idle,
[DeepLinkServiceAction.LinkIsRepoType]: DeepLinkServiceState.RepoMatch,
[DeepLinkServiceAction.LinkIsWorkspaceType]: DeepLinkServiceState.OpenWorkspace,
},
[DeepLinkServiceState.RepoMatch]: {
[DeepLinkServiceAction.DeepLinkErrored]: DeepLinkServiceState.Idle,
@ -229,6 +251,10 @@ export const deepLinkStateTransitionTable: Record
[DeepLinkServiceAction.DeepLinkResolved]: DeepLinkServiceState.Idle,
[DeepLinkServiceAction.DeepLinkErrored]: DeepLinkServiceState.Idle,
},
[DeepLinkServiceState.OpenWorkspace]: {
[DeepLinkServiceAction.DeepLinkResolved]: DeepLinkServiceState.Idle,
[DeepLinkServiceAction.DeepLinkErrored]: DeepLinkServiceState.Idle,
},
};
export interface DeepLinkProgress {
@ -238,6 +264,7 @@ export interface DeepLinkProgress {
export const deepLinkStateToProgress: Record<string, DeepLinkProgress> = {
[DeepLinkServiceState.Idle]: { message: 'Done.', increment: 100 },
[DeepLinkServiceState.TypeMatch]: { message: 'Matching link type...', increment: 5 },
[DeepLinkServiceState.RepoMatch]: { message: 'Finding a matching repository...', increment: 10 },
[DeepLinkServiceState.CloneOrAddRepo]: { message: 'Adding repository...', increment: 20 },
[DeepLinkServiceState.OpeningRepo]: { message: 'Opening repository...', increment: 30 },
@ -249,4 +276,5 @@ export const deepLinkStateToProgress: Record = {
[DeepLinkServiceState.FetchedTargetMatch]: { message: 'Finding a matching target...', increment: 90 },
[DeepLinkServiceState.OpenGraph]: { message: 'Opening graph...', increment: 95 },
[DeepLinkServiceState.OpenComparison]: { message: 'Opening comparison...', increment: 95 },
[DeepLinkServiceState.OpenWorkspace]: { message: 'Opening workspace...', increment: 95 },
};

+ 86
- 51
src/uris/deepLinks/deepLinkService.ts View File

@ -21,6 +21,7 @@ import {
deepLinkStateToProgress,
deepLinkStateTransitionTable,
DeepLinkType,
deepLinkTypeToString,
parseDeepLinkUri,
} from './deepLink';
@ -46,7 +47,7 @@ export class DeepLinkService implements Disposable {
await this.container.git.isDiscoveringRepositories;
}
if (!link.type || (!link.repoId && !link.remoteUrl && !link.repoPath)) {
if (!link.type || (!link.mainId && !link.remoteUrl && !link.repoPath)) {
void window.showErrorMessage('Unable to resolve link');
Logger.warn(`Unable to resolve link - missing basic properties: ${uri.toString()}`);
return;
@ -58,9 +59,9 @@ export class DeepLinkService implements Disposable {
return;
}
if (link.type !== DeepLinkType.Repository && link.targetId == null) {
if (link.type !== DeepLinkType.Repository && link.targetId == null && link.mainId == null) {
void window.showErrorMessage('Unable to resolve link');
Logger.warn(`Unable to resolve link - no target id provided: ${uri.toString()}`);
Logger.warn(`Unable to resolve link - no main/target id provided: ${uri.toString()}`);
return;
}
@ -92,7 +93,7 @@ export class DeepLinkService implements Disposable {
this._context = {
state: DeepLinkServiceState.Idle,
url: undefined,
repoId: undefined,
mainId: undefined,
repo: undefined,
remoteUrl: undefined,
remote: undefined,
@ -109,7 +110,7 @@ export class DeepLinkService implements Disposable {
private setContextFromDeepLink(link: DeepLink, url: string) {
this._context = {
...this._context,
repoId: link.repoId,
mainId: link.mainId,
targetType: link.type,
url: url,
remoteUrl: link.remoteUrl,
@ -359,9 +360,13 @@ export class DeepLinkService implements Disposable {
): Promise<void> {
let message = '';
let action = initialAction;
if (action === DeepLinkServiceAction.DeepLinkCancelled && this._context.state === DeepLinkServiceState.Idle) {
return;
}
//Repo match
let matchingLocalRepoPaths: string[] = [];
const { targetType } = this._context;
queueMicrotask(
() =>
@ -369,7 +374,7 @@ export class DeepLinkService implements Disposable {
{
cancellable: true,
location: ProgressLocation.Notification,
title: `Opening repository for link: ${this._context.url}}`,
title: `Opening ${deepLinkTypeToString(targetType ?? DeepLinkType.Repository)} link...`,
},
(progress, token) => {
progress.report({ increment: 0 });
@ -379,14 +384,12 @@ export class DeepLinkService implements Disposable {
resolve();
});
this._disposables.push(
this._onDeepLinkProgressUpdated.event(({ message, increment }) => {
progress.report({ message: message, increment: increment });
if (increment === 100) {
resolve();
}
}),
);
this._onDeepLinkProgressUpdated.event(({ message, increment }) => {
progress.report({ message: message, increment: increment });
if (increment === 100) {
resolve();
}
});
});
},
),
@ -396,7 +399,7 @@ export class DeepLinkService implements Disposable {
this._context.state = deepLinkStateTransitionTable[this._context.state][action];
const {
state,
repoId,
mainId,
repo,
url,
remoteUrl,
@ -422,9 +425,18 @@ export class DeepLinkService implements Disposable {
this.resetContext();
return;
}
case DeepLinkServiceState.TypeMatch: {
if (targetType === DeepLinkType.Workspace) {
action = DeepLinkServiceAction.LinkIsWorkspaceType;
} else {
action = DeepLinkServiceAction.LinkIsRepoType;
}
break;
}
case DeepLinkServiceState.RepoMatch:
case DeepLinkServiceState.AddedRepoMatch: {
if (!repoId && !remoteUrl && !repoPath) {
if (!mainId && !remoteUrl && !repoPath) {
action = DeepLinkServiceAction.DeepLinkErrored;
message = 'No repository id, remote url or path was provided.';
break;
@ -459,10 +471,10 @@ export class DeepLinkService implements Disposable {
}
}
if (repoId != null && repoId !== '-') {
if (mainId != null && mainId !== '-') {
// Repo ID can be any valid SHA in the repo, though standard practice is to use the
// first commit SHA.
if (await this.container.git.validateReference(repo.path, repoId)) {
if (await this.container.git.validateReference(repo.path, mainId)) {
this._context.repo = repo;
action = DeepLinkServiceAction.RepoMatched;
break;
@ -506,7 +518,7 @@ export class DeepLinkService implements Disposable {
break;
}
case DeepLinkServiceState.CloneOrAddRepo: {
if (!repoId && !remoteUrl && !repoPath) {
if (!mainId && !remoteUrl && !repoPath) {
action = DeepLinkServiceAction.DeepLinkErrored;
message = 'Missing repository id, remote url and path.';
break;
@ -872,6 +884,22 @@ export class DeepLinkService implements Disposable {
action = DeepLinkServiceAction.DeepLinkResolved;
break;
}
case DeepLinkServiceState.OpenWorkspace: {
if (!mainId) {
action = DeepLinkServiceAction.DeepLinkErrored;
message = 'Missing workspace id.';
break;
}
await this.container.workspacesView.revealWorkspaceNode(mainId, {
select: true,
focus: true,
expand: true,
});
action = DeepLinkServiceAction.DeepLinkResolved;
break;
}
default: {
action = DeepLinkServiceAction.DeepLinkErrored;
message = 'Unknown state.';
@ -881,6 +909,7 @@ export class DeepLinkService implements Disposable {
}
}
async copyDeepLinkUrl(workspaceId: string): Promise<void>;
async copyDeepLinkUrl(ref: GitReference, remoteUrl: string): Promise<void>;
async copyDeepLinkUrl(
repoPath: string,
@ -889,17 +918,20 @@ export class DeepLinkService implements Disposable {
compareWithRef?: StoredNamedRef,
): Promise<void>;
async copyDeepLinkUrl(
refOrRepoPath: string | GitReference,
remoteUrl: string,
refOrIdOrRepoPath: string | GitReference,
remoteUrl?: string,
compareRef?: StoredNamedRef,
compareWithRef?: StoredNamedRef,
): Promise<void> {
const url = await (typeof refOrRepoPath === 'string'
? this.generateDeepLinkUrl(refOrRepoPath, remoteUrl, compareRef, compareWithRef)
: this.generateDeepLinkUrl(refOrRepoPath, remoteUrl));
const url = await (typeof refOrIdOrRepoPath === 'string'
? remoteUrl != null
? this.generateDeepLinkUrl(refOrIdOrRepoPath, remoteUrl, compareRef, compareWithRef)
: this.generateDeepLinkUrl(refOrIdOrRepoPath)
: this.generateDeepLinkUrl(refOrIdOrRepoPath, remoteUrl!));
await env.clipboard.writeText(url.toString());
}
async generateDeepLinkUrl(workspaceId: string): Promise<URL>;
async generateDeepLinkUrl(ref: GitReference, remoteUrl: string): Promise<URL>;
async generateDeepLinkUrl(
repoPath: string,
@ -908,37 +940,50 @@ export class DeepLinkService implements Disposable {
compareWithRef?: StoredNamedRef,
): Promise<URL>;
async generateDeepLinkUrl(
refOrRepoPath: string | GitReference,
remoteUrl: string,
refOrIdOrRepoPath: string | GitReference,
remoteUrl?: string,
compareRef?: StoredNamedRef,
compareWithRef?: StoredNamedRef,
): Promise<URL> {
const repoPath = typeof refOrRepoPath !== 'string' ? refOrRepoPath.repoPath : refOrRepoPath;
let repoId;
let repoId: string | undefined;
let targetType: DeepLinkType | undefined;
let targetId: string | undefined;
let compareWithTargetId: string | undefined;
const schemeOverride = configuration.get('deepLinks.schemeOverride');
const scheme = !schemeOverride ? 'vscode' : schemeOverride === true ? env.uriScheme : schemeOverride;
let modePrefixString = '';
if (this.container.env === 'dev') {
modePrefixString = 'dev.';
} else if (this.container.env === 'staging') {
modePrefixString = 'staging.';
}
if (remoteUrl == null && typeof refOrIdOrRepoPath === 'string') {
return new URL(`https://${modePrefixString}gitkraken.dev/link/workspaces/${refOrIdOrRepoPath}`);
}
const repoPath = typeof refOrIdOrRepoPath !== 'string' ? refOrIdOrRepoPath.repoPath : refOrIdOrRepoPath;
try {
repoId = await this.container.git.getUniqueRepositoryId(repoPath);
} catch {
repoId = '-';
}
let targetType: DeepLinkType | undefined;
let targetId: string | undefined;
let compareWithTargetId: string | undefined;
if (typeof refOrRepoPath !== 'string') {
switch (refOrRepoPath.refType) {
if (typeof refOrIdOrRepoPath !== 'string') {
switch (refOrIdOrRepoPath.refType) {
case 'branch':
targetType = DeepLinkType.Branch;
targetId = refOrRepoPath.remote
? getBranchNameWithoutRemote(refOrRepoPath.name)
: refOrRepoPath.name;
targetId = refOrIdOrRepoPath.remote
? getBranchNameWithoutRemote(refOrIdOrRepoPath.name)
: refOrIdOrRepoPath.name;
break;
case 'revision':
targetType = DeepLinkType.Commit;
targetId = refOrRepoPath.ref;
targetId = refOrIdOrRepoPath.ref;
break;
case 'tag':
targetType = DeepLinkType.Tag;
targetId = refOrRepoPath.name;
targetId = refOrIdOrRepoPath.name;
break;
}
}
@ -949,9 +994,6 @@ export class DeepLinkService implements Disposable {
compareWithTargetId = compareWithRef.label ?? compareWithRef.ref;
}
const schemeOverride = configuration.get('deepLinks.schemeOverride');
const scheme = !schemeOverride ? 'vscode' : schemeOverride === true ? env.uriScheme : schemeOverride;
let target;
if (targetType === DeepLinkType.Comparison) {
target = `/${targetType}/${compareWithTargetId}...${targetId}`;
@ -968,16 +1010,9 @@ export class DeepLinkService implements Disposable {
}/${repoId}${target}`,
);
// Add the remote URL as a query parameter
deepLink.searchParams.set('url', remoteUrl);
const params = new URLSearchParams();
params.set('url', remoteUrl);
let modePrefixString = '';
if (this.container.env === 'dev') {
modePrefixString = 'dev.';
} else if (this.container.env === 'staging') {
modePrefixString = 'staging.';
if (remoteUrl != null) {
// Add the remote URL as a query parameter
deepLink.searchParams.set('url', remoteUrl);
}
const deepLinkRedirectUrl = new URL(

+ 24
- 4
src/views/viewBase.ts View File

@ -115,6 +115,9 @@ export abstract class ViewBase<
return `gitlens.views.${this.type}`;
}
protected _onDidInitialize = new EventEmitter<void>();
private initialized = false;
protected _onDidChangeTreeData = new EventEmitter<ViewNode | undefined>();
get onDidChangeTreeData(): Event<ViewNode | undefined> {
return this._onDidChangeTreeData.event;
@ -348,7 +351,22 @@ export abstract class ViewBase<
if (node != null) return node.getChildren();
const root = this.ensureRoot();
return root.getChildren();
const children = root.getChildren();
if (!this.initialized) {
if (isPromise(children)) {
void children.then(() => {
if (!this.initialized) {
this.initialized = true;
setTimeout(() => this._onDidInitialize.fire(), 1);
}
});
} else {
this.initialized = true;
setTimeout(() => this._onDidInitialize.fire(), 1);
}
}
return children;
}
getParent(node: ViewNode): ViewNode | undefined {
@ -455,12 +473,14 @@ export abstract class ViewBase<
}
}
if (this.root != null) return find.call(this);
if (this.initialized) return find.call(this);
// If we have no root (e.g. never been initialized) force it so the tree will load properly
await this.show({ preserveFocus: true });
void this.show({ preserveFocus: true });
// Since we have to show the view, give the view time to load and let the callstack unwind before we try to find the node
return new Promise<ViewNode | undefined>(resolve => setTimeout(() => resolve(find.call(this)), 100));
return new Promise<ViewNode | undefined>(resolve =>
once(this._onDidInitialize.event)(() => resolve(find.call(this)), this),
);
}
private async findNodeCoreBFS(

+ 37
- 3
src/views/workspacesView.ts View File

@ -1,4 +1,4 @@
import type { Disposable } from 'vscode';
import type { CancellationToken, Disposable } from 'vscode';
import { env, ProgressLocation, Uri, window } from 'vscode';
import type { RepositoriesViewConfig } from '../config';
import { Commands } from '../constants';
@ -45,8 +45,42 @@ export class WorkspacesView extends ViewBase<'workspaces', WorkspacesViewNode, R
return super.show(options);
}
override get canReveal(): boolean {
return false;
async findWorkspaceNode(workspaceId: string, token?: CancellationToken) {
return this.findNode((n: any) => n.workspace?.id === workspaceId, {
allowPaging: false,
maxDepth: 2,
canTraverse: n => {
if (n instanceof WorkspacesViewNode) return true;
return false;
},
token: token,
});
}
async revealWorkspaceNode(
workspaceId: string,
options?: {
select?: boolean;
focus?: boolean;
expand?: boolean | number;
},
) {
return window.withProgress(
{
location: ProgressLocation.Notification,
title: `Revealing workspace ${workspaceId} in the side bar...`,
cancellable: true,
},
async (progress, token) => {
const node = await this.findWorkspaceNode(workspaceId, token);
if (node == null) return undefined;
await this.ensureRevealNode(node, options);
return node;
},
);
}
protected registerCommands(): Disposable[] {

Loading…
Cancel
Save