Browse Source

Refines PR & issue autolink timeouts and perf

main
Eric Amodio 5 years ago
parent
commit
a661381912
11 changed files with 262 additions and 124 deletions
  1. +12
    -26
      src/annotations/autolinks.ts
  2. +41
    -36
      src/annotations/lineAnnotationController.ts
  3. +4
    -4
      src/git/formatters/commitFormatter.ts
  4. +23
    -4
      src/git/gitService.ts
  5. +18
    -3
      src/git/remotes/provider.ts
  6. +12
    -4
      src/github/github.ts
  7. +74
    -8
      src/hovers/hovers.ts
  8. +9
    -1
      src/logger.ts
  9. +1
    -1
      src/system/decorators/log.ts
  10. +57
    -28
      src/system/promise.ts
  11. +11
    -9
      src/views/nodes/remoteNode.ts

+ 12
- 26
src/annotations/autolinks.ts View File

@ -2,7 +2,7 @@
import { ConfigurationChangeEvent, Disposable } from 'vscode';
import { AutolinkReference, configuration } from '../configuration';
import { Container } from '../container';
import { Dates, Iterables, Promises, Strings } from '../system';
import { Dates, debug, Iterables, Promises, Strings } from '../system';
import { Logger } from '../logger';
import { GitRemote, Issue } from '../git/git';
import { GlyphChars } from '../constants';
@ -47,10 +47,13 @@ export class Autolinks implements Disposable {
}
}
@debug({ args: false })
async getIssueLinks(message: string, remote: GitRemote, { timeout }: { timeout?: number } = {}) {
const provider = remote.provider;
if (!provider?.hasApi()) return undefined;
if (!(await provider.isConnected())) return undefined;
if (!remote.provider?.hasApi()) return undefined;
const { provider } = remote;
const connected = provider.maybeConnected ?? (await provider.isConnected());
if (!connected) return undefined;
const ids = new Set<number>();
@ -75,31 +78,14 @@ export class Autolinks implements Disposable {
if (ids.size === 0) return undefined;
const promises = [
...Iterables.map<number, [number, Promise<Issue | undefined>]>(ids.values(), id => [
id,
provider.getIssue(id)
])
];
const promise =
timeout != null && timeout > 0
? Promises.raceAll(promises, timeout)
: Promise.all(promises.map(([, p]) => p));
const issues = new Map<number, Issue | Promises.CancellationErrorWithId<number>>();
for (const issue of await promise) {
if (issue == null) continue;
issues.set(issue.id, issue);
}
if (issues.size === 0) return undefined;
const issues = await Promises.raceAll(ids.values(), id => provider.getIssue(id), timeout);
if (issues.size === 0 || Iterables.every(issues.values(), pr => pr === undefined)) return undefined;
return issues;
}
linkify(text: string, remotes?: GitRemote[], issues?: Map<number, Issue | Promises.CancellationError>) {
@debug({ args: false })
linkify(text: string, remotes?: GitRemote[], issues?: Map<number, Issue | Promises.CancellationError | undefined>) {
for (const ref of this._references) {
if (this.ensureAutolinkCached(ref, issues)) {
if (ref.linkify != null) {
@ -127,7 +113,7 @@ export class Autolinks implements Disposable {
private ensureAutolinkCached(
ref: CacheableAutolinkReference | DynamicAutolinkReference,
issues?: Map<number, Issue | Promises.CancellationError>
issues?: Map<number, Issue | Promises.CancellationError | undefined>
): ref is CacheableAutolinkReference | DynamicAutolinkReference {
if (isDynamic(ref)) return true;

+ 41
- 36
src/annotations/lineAnnotationController.ts View File

@ -16,7 +16,7 @@ import { LinesChangeEvent } from '../trackers/gitLineTracker';
import { Annotations } from './annotations';
import { debug, Iterables, log, Promises } from '../system';
import { Logger } from '../logger';
import { CommitFormatter, GitBlameCommit, PullRequest } from '../git/gitService';
import { CommitFormatter, GitBlameCommit } from '../git/gitService';
const annotationDecoration: TextEditorDecorationType = window.createTextEditorDecorationType({
after: {
@ -149,11 +149,11 @@ export class LineAnnotationController implements Disposable {
const remotes = await Container.git.getRemotes(repoPath);
const remote = remotes.find(r => r.default);
if (remote === undefined) return undefined;
if (!remote?.provider?.hasApi()) return undefined;
const provider = remote.provider;
if (!provider?.hasApi()) return undefined;
if (!(await providerclass="p">.isConnected())) return undefined;
const { provider } = remote;
const connected = provider.maybeConnected ?? (await provider.isConnected());
if (!connected) return undefined;
const refs = new Set<string>();
@ -163,36 +163,12 @@ export class LineAnnotationController implements Disposable {
if (refs.size === 0) return undefined;
const promises = [
...Iterables.map<string, [string, Promise<[string, PullRequest | undefined]>]>(refs.values(), ref => [
ref,
Container.git.getPullRequestForCommit(ref, remote).then(pr => [ref, pr])
])
];
const promise =
timeout != null && timeout > 0
? Promises.raceAll(promises, timeout)
: Promise.all(promises.map(([, p]) => p));
const prs = new Map<string, PullRequest | Promises.CancellationErrorWithId<string>>();
let ref;
let pr;
for (const result of await promise) {
if (result == null) continue;
if (result instanceof Promises.CancellationErrorWithId) {
prs.set(result.id, result);
} else {
[ref, pr] = result;
if (pr == null) continue;
prs.set(ref, pr);
}
}
if (prs.size === 0) return undefined;
const prs = await Promises.raceAll(
refs.values(),
ref => Container.git.getPullRequestForCommit(ref, provider),
timeout
);
if (prs.size === 0 || Iterables.every(prs.values(), pr => pr === undefined)) return undefined;
return prs;
}
@ -269,6 +245,9 @@ export class LineAnnotationController implements Disposable {
const repoPath = trackedDocument.uri.repoPath;
// TODO: Make this configurable?
const timeout = 100;
const [getBranchAndTagTips, prs] = await Promise.all([
CommitFormatter.has(cfg.format, 'tips') ? Container.git.getBranchesAndTagsTipsFn(repoPath) : undefined,
repoPath != null &&
@ -284,11 +263,37 @@ export class LineAnnotationController implements Disposable {
? this.getPullRequests(
repoPath,
commitLines.filter(([, commit]) => !commit.isUncommitted),
{ timeout: 250 }
{ timeout: timeout }
)
: undefined
]);
if (prs !== undefined) {
const timeouts = [
...Iterables.filterMap(prs.values(), pr =>
pr instanceof Promises.CancellationError ? pr.promise : undefined
)
];
// If there are any PRs that timed out, refresh the annotation(s) once they complete
if (timeouts.length !== 0) {
Logger.debug(
cc,
`${GlyphChars.Dot} pull request queries (${timeouts.length}) took too long (over ${timeout} ms)`
);
Promise.all(timeouts).then(() => {
if (editor === this._editor) {
Logger.debug(
cc,
`${GlyphChars.Dot} pull request queries (${timeouts.length}) completed; refreshing...`
);
this.refresh(editor);
}
});
}
}
const decorations = [];
for (const [l, commit] of commitLines) {

+ 4
- 4
src/git/formatters/commitFormatter.ts View File

@ -25,7 +25,7 @@ const hasTokenRegexMap = new Map();
export interface CommitFormatOptions extends FormatOptions {
annotationType?: FileAnnotationType;
autolinkedIssues?: Map<number, Issue | Promises.CancellationError>;
autolinkedIssues?: Map<number, Issue | Promises.CancellationError | undefined>;
dateStyle?: DateStyle;
getBranchAndTagTips?: (sha: string) => string | undefined;
line?: number;
@ -249,7 +249,7 @@ export class CommitFormatter extends Formatter {
pr.provider
}\n${GlyphChars.Dash.repeat(2)}\n${pr.title}\n${pr.state}, ${pr.formatDateFromNow()}")${separator}`;
} else if (pr instanceof Promises.CancellationError) {
commands += `[\`PR (loading${GlyphChars.Ellipsis})\`](# "Searching for a Pull Request (if any) that introduced this commit...")${separator}`;
commands += `[\`PR ${GlyphChars.Ellipsis}\`](# "Searching for a Pull Request (if any) that introduced this commit...")${separator}`;
} else if (pr.provider != null) {
commands += `[\`Connect to ${pr.provider.name}${
GlyphChars.Ellipsis
@ -378,8 +378,8 @@ export class CommitFormatter extends Formatter {
: `PR #${pr.number}`;
} else if (pr instanceof Promises.CancellationError) {
text = this._options.markdown
? `[PR (loading${GlyphChars.Ellipsis})](# "Searching for a Pull Request (if any) that introduced this commit...")`
: `PR (loading${GlyphChars.Ellipsis})`;
? `[PR ${GlyphChars.Ellipsis}](# "Searching for a Pull Request (if any) that introduced this commit...")`
: `PR ${GlyphChars.Ellipsis}`;
} else {
return emptyStr;
}

+ 23
- 4
src/git/gitService.ts View File

@ -81,7 +81,7 @@ import {
RepositoryChangeEvent
} from './git';
import { GitUri } from './gitUri';
import { RemoteProviderFactory, RemoteProviders } from './remotes/factory';
import { RemoteProviderFactory, RemoteProviders, RemoteProviderWithApi } from './remotes/factory';
import { GitReflogParser, GitShortLogParser } from './parsers/parsers';
import { isWindows } from './shell';
import { PullRequest, PullRequestDateFormatting } from './models/models';
@ -2347,12 +2347,31 @@ export class GitService implements Disposable {
async getPullRequestForCommit(
ref: string,
remote: GitRemote,
options?: { timeout?: number }
): Promise<PullRequest | undefined>;
async getPullRequestForCommit(
ref: string,
provider: RemoteProviderWithApi,
options?: { timeout?: number }
): Promise<PullRequest | undefined>;
@gate()
@debug({ args: { 1: () => false } })
async getPullRequestForCommit(
ref: string,
remoteOrProvider: GitRemote | RemoteProviderWithApi,
{ timeout }: { timeout?: number } = {}
): Promise<PullRequest | undefined> {
if (Git.isUncommitted(ref) || !remote.provider?.hasApi()) return undefined;
if (!(await remote.provider.isConnected())) return undefined;
if (Git.isUncommitted(ref)) return undefined;
let provider;
if (GitRemote.is(remoteOrProvider)) {
({ provider } = remoteOrProvider);
if (!provider?.hasApi()) return undefined;
} else {
provider = remoteOrProvider;
}
let promiseOrPR = remote.provider.getPullRequestForCommit(ref);
let promiseOrPR = provider.getPullRequestForCommit(ref);
if (promiseOrPR == null || !Promises.is(promiseOrPR)) {
return promiseOrPR;
}

+ 18
- 3
src/git/remotes/provider.ts View File

@ -9,7 +9,7 @@ import { Messages } from '../../messages';
import { Issue } from '../models/issue';
import { GitLogCommit } from '../models/logCommit';
import { PullRequest } from '../models/pullRequest';
import { debug, Promises } from '../../system';
import { debug, gate, Promises } from '../../system';
export enum RemoteResourceType {
Branch = 'branch',
@ -230,15 +230,27 @@ export abstract class RemoteProviderWithApi extends
return this.clearCredentials();
}
@gate()
@debug<RemoteProviderWithApi['isConnected']>({
exit: connected => `returned ${connected}`
})
async isConnected(): Promise<boolean> {
return (await this.credentials()) != null;
}
get maybeConnected(): boolean | undefined {
if (this._credentials === undefined) return undefined;
return this._credentials !== null;
}
@gate()
@debug()
async getIssue(id: number): Promise<Issue | undefined> {
const cc = Logger.getCorrelationContext();
if (!(await this.isConnected())) return undefined;
const connected = this.maybeConnected ?? (await this.isConnected());
if (!connected) return undefined;
try {
return await this.onGetIssue(this._credentials!, id);
@ -251,6 +263,7 @@ export abstract class RemoteProviderWithApi extends
private _prsByCommit = new Map<string, Promise<PullRequest | null> | PullRequest | null>();
@gate()
@debug()
getPullRequestForCommit(ref: string): Promise<PullRequest | undefined> | PullRequest | undefined {
let pr = this._prsByCommit.get(ref);
@ -292,11 +305,13 @@ export abstract class RemoteProviderWithApi extends
return this.custom ? `${this.name}:${this.domain}` : this.name;
}
@gate()
@debug()
private async getPullRequestForCommitCore(ref: string) {
const cc = Logger.getCorrelationContext();
if (!(await this.isConnected())) return null;
const connected = this.maybeConnected ?? (await this.isConnected());
if (!connected) return null;
try {
const pr = (await this.onGetPullRequestForCommit(this._credentials!, ref)) ?? null;

+ 12
- 4
src/github/github.ts View File

@ -5,7 +5,11 @@ import { debug } from '../system';
import { Issue, PullRequest, PullRequestState } from '../git/gitService';
export class GitHubApi {
@debug()
@debug({
args: {
1: token => '<token>'
}
})
async getPullRequestForCommit(
provider: string,
token: string,
@ -45,7 +49,7 @@ export class GitHubApi {
}`;
const variables = { owner: owner, repo: repo, sha: ref };
Logger.debug(cc, `variables: ${JSON.stringify(variables)}`);
// Logger.debug(cc, `variables: ${JSON.stringify(variables)}`);
const rsp = await graphql(query, {
...variables,
@ -77,7 +81,11 @@ export class GitHubApi {
}
}
@debug()
@debug({
args: {
1: token => '<token>'
}
})
async getIssue(
provider: string,
token: string,
@ -103,7 +111,7 @@ export class GitHubApi {
}`;
const variables = { owner: owner, repo: repo, number: number };
Logger.debug(cc, `variables: ${JSON.stringify(variables)}`);
// Logger.debug(cc, `variables: ${JSON.stringify(variables)}`);
const rsp = await graphql(query, {
...variables,

+ 74
- 8
src/hovers/hovers.ts View File

@ -14,7 +14,8 @@ import {
GitService,
GitUri
} from '../git/gitService';
import { Promises } from '../system/promise';
import { Logger, TraceLevel } from '../logger';
import { Iterables, Promises, Strings } from '../system';
export namespace Hovers {
export async function changesMessage(
@ -189,25 +190,72 @@ export namespace Hovers {
}
async function getAutoLinkedIssues(message: string, remotes: GitRemote[]) {
const cc = Logger.getNewCorrelationContext('Hovers.getAutoLinkedIssues');
Logger.debug(cc, `${GlyphChars.Dash} message=<message>`);
const start = process.hrtime();
if (
!Container.config.hovers.autolinks.enabled ||
!Container.config.hovers.autolinks.enhanced ||
!CommitFormatter.has(Container.config.hovers.detailsMarkdownFormat, 'message')
) {
Logger.debug(cc, `completed ${GlyphChars.Dot} ${Strings.getDurationMilliseconds(start)} ms`);
return undefined;
}
const remote = remotes.find(r => r.default && r.provider != null);
if (remote === undefined) return undefined;
if (remote === undefined) {
Logger.debug(cc, `completed ${GlyphChars.Dot} ${Strings.getDurationMilliseconds(start)} ms`);
return undefined;
}
// TODO: Make this configurable?
const timeout = 250;
try {
return await Container.autolinks.getIssueLinks(message, remote, { timeout: 250 });
} catch {
const autolinkedIssues = await Container.autolinks.getIssueLinks(message, remote, { timeout: timeout });
if (autolinkedIssues !== undefined && (Logger.level === TraceLevel.Debug || Logger.isDebugging)) {
const timeouts = [
...Iterables.filterMap(autolinkedIssues.values(), issue =>
issue instanceof Promises.CancellationError ? issue.promise : undefined
)
];
// If there are any PRs that timed out, refresh the annotation(s) once they complete
if (timeouts.length !== 0) {
Logger.debug(
cc,
`timed out ${GlyphChars.Dash} issue queries (${
timeouts.length
}) took too long (over ${timeout} ms) ${GlyphChars.Dot} ${Strings.getDurationMilliseconds(
start
)} ms`
);
return autolinkedIssues;
}
}
Logger.debug(cc, `completed ${GlyphChars.Dot} ${Strings.getDurationMilliseconds(start)} ms`);
return autolinkedIssues;
} catch (ex) {
Logger.error(ex, cc, `failed ${GlyphChars.Dot} ${Strings.getDurationMilliseconds(start)} ms`);
return undefined;
}
}
async function getPullRequestForCommit(ref: string, remotes: GitRemote[]) {
const cc = Logger.getNewCorrelationContext('Hovers.getPullRequestForCommit');
Logger.debug(cc, `${GlyphChars.Dash} ref=${ref}`);
const start = process.hrtime();
if (
!Container.config.hovers.pullRequests.enabled ||
!CommitFormatter.has(
@ -219,23 +267,41 @@ export namespace Hovers {
'pullRequestState'
)
) {
Logger.debug(cc, `completed ${GlyphChars.Dot} ${Strings.getDurationMilliseconds(start)} ms`);
return undefined;
}
const remote = remotes.find(r => r.default && r.provider != null);
if (remote === undefined) return undefined;
if (!remote?.provider?.hasApi()) {
Logger.debug(cc, `completed ${GlyphChars.Dot} ${Strings.getDurationMilliseconds(start)} ms`);
return undefined;
}
const { provider } = remote;
const connected = provider.maybeConnected ?? (await provider.isConnected());
if (!connected) {
Logger.debug(cc, `completed ${GlyphChars.Dot} ${Strings.getDurationMilliseconds(start)} ms`);
const provider = remote.provider;
if (provider?.hasApi() && !(await provider.isConnected())) {
return remote;
}
try {
return await Container.git.getPullRequestForCommit(ref, remote, { timeout: 250 });
const pr = await Container.git.getPullRequestForCommit(ref, provider, { timeout: 250 });
Logger.debug(cc, `completed ${GlyphChars.Dot} ${Strings.getDurationMilliseconds(start)} ms`);
return pr;
} catch (ex) {
if (ex instanceof Promises.CancellationError) {
Logger.debug(cc, `timed out ${GlyphChars.Dot} ${Strings.getDurationMilliseconds(start)} ms`);
return ex;
}
Logger.error(ex, cc, `failed ${GlyphChars.Dot} ${Strings.getDurationMilliseconds(start)} ms`);
return undefined;
}
}

+ 9
- 1
src/logger.ts View File

@ -1,7 +1,7 @@
'use strict';
import { ExtensionContext, OutputChannel, Uri, window } from 'vscode';
import { extensionOutputChannelName } from './constants';
import { getCorrelationContext } from './system';
import { getCorrelationContext, getNextCorrelationId } from './system';
// import { Telemetry } from './telemetry';
const emptyStr = '';
@ -113,6 +113,14 @@ export class Logger {
return getCorrelationContext();
}
static getNewCorrelationContext(prefix: string): LogCorrelationContext {
const correlationId = getNextCorrelationId();
return {
correlationId: correlationId,
prefix: `[${correlationId}] ${prefix}`
};
}
static log(message: string, ...params: any[]): void;
static log(context: LogCorrelationContext | undefined, message: string, ...params: any[]): void;
static log(contextOrMessage: LogCorrelationContext | string | undefined, ...params: any[]): void {

+ 1
- 1
src/system/decorators/log.ts View File

@ -17,7 +17,7 @@ export function getCorrelationId() {
return correlationCounter;
}
function getNextCorrelationId() {
export function getNextCorrelationId() {
if (correlationCounter === Number.MAX_SAFE_INTEGER) {
correlationCounter = 0;
}

+ 57
- 28
src/system/promise.ts View File

@ -1,5 +1,6 @@
'use strict';
import { CancellationToken } from 'vscode';
import { Iterables } from './iterable';
export namespace Promises {
export class CancellationError<TPromise = any> extends Error {
@ -88,41 +89,69 @@ export namespace Promises {
export function raceAll<TPromise>(
promises: Promise<TPromise>[],
timeout: number
timeout?: number
): Promise<(TPromise | Promises.CancellationError<Promise<TPromise>>)[]>;
export function raceAll<TPromise, T>(
promises: [T, Promise<TPromise>][],
timeout: number
): Promise<(TPromise | Promises.CancellationErrorWithId<T, Promise<TPromise>>)[]>;
export function raceAll<TPromise, T>(promises: Promise<TPromise>[] | [T, Promise<TPromise>][], timeout: number) {
if (hasIds(promises)) {
return Promise.all(
promises.map(([id, p]) =>
Promise.race([
p,
new Promise<CancellationErrorWithId<T, Promise<TPromise>>>(resolve =>
setTimeout(() => resolve(new CancellationErrorWithId(id, p, 'TIMED OUT')), timeout)
)
])
promises: Map<T, Promise<TPromise>>,
timeout?: number
): Promise<Map<T, TPromise | Promises.CancellationErrorWithId<T, Promise<TPromise>>>>;
export function raceAll<TPromise, T>(
ids: Iterable<T>,
fn: (id: T) => Promise<TPromise>,
timeout?: number
): Promise<Map<T, TPromise | Promises.CancellationErrorWithId<T, Promise<TPromise>>>>;
export async function raceAll<TPromise, T>(
promisesOrIds: Promise<TPromise>[] | Map<T, Promise<TPromise>> | Iterable<T>,
timeoutOrFn?: number | ((id: T) => Promise<TPromise>),
timeout?: number
) {
let promises;
if (timeoutOrFn != null && typeof timeoutOrFn !== 'number') {
promises = new Map(
Iterables.map<T, [T, Promise<TPromise>]>(promisesOrIds as Iterable<T>, id => [id, timeoutOrFn(id)])
);
} else {
timeout = timeoutOrFn;
promises = promisesOrIds as Promise<TPromise>[] | Map<T, Promise<TPromise>>;
}
if (promises instanceof Map) {
return new Map(
await Promise.all(
Iterables.map<
[T, Promise<TPromise>],
Promise<[T, TPromise | CancellationErrorWithId<T, Promise<TPromise>>]>
>(
promises.entries(),
timeout == null
? ([id, promise]) => promise.then(p => [id, p])
: ([id, promise]) =>
Promise.race([
promise,
new Promise<CancellationErrorWithId<T, Promise<TPromise>>>(resolve =>
setTimeout(
() => resolve(new CancellationErrorWithId(id, promise, 'TIMED OUT')),
timeout!
)
)
]).then(p => [id, p])
)
)
);
}
return Promise.all(
promises.map(p =>
Promise.race([
p,
new Promise<CancellationError<Promise<TPromise>>>(resolve =>
setTimeout(() => resolve(new CancellationError(p, 'TIMED OUT')), timeout)
)
])
)
timeout == null
? promises
: promises.map(p =>
Promise.race([
p,
new Promise<CancellationError<Promise<TPromise>>>(resolve =>
setTimeout(() => resolve(new CancellationError(p, 'TIMED OUT')), timeout!)
)
])
)
);
}
function hasIds<TPromise, T>(
promises: Promise<TPromise>[] | [T, Promise<TPromise>][]
): promises is [T, Promise<TPromise>][] {
return Array.isArray(promises[0]);
}
}

+ 11
- 9
src/views/nodes/remoteNode.ts View File

@ -3,7 +3,7 @@ import { TreeItem, TreeItemCollapsibleState } from 'vscode';
import { ViewBranchesLayout } from '../../configuration';
import { GlyphChars } from '../../constants';
import { Container } from '../../container';
import { GitRemote, GitRemoteType, GitUri, RemoteProviderWithApi, Repository } from '../../git/gitService';
import { GitRemote, GitRemoteType, GitUri, Repository } from '../../git/gitService';
import { Arrays, log } from '../../system';
import { RepositoriesView } from '../repositoriesView';
import { BranchNode } from './branchNode';
@ -104,22 +104,24 @@ export class RemoteNode extends ViewNode {
);
if (this.remote.provider != null) {
item.description = `${arrows}${GlyphChars.Space} ${this.remote.provider.name} ${GlyphChars.Space}${GlyphChars.Dot}${GlyphChars.Space} ${this.remote.provider.displayPath}`;
const { provider } = this.remote;
item.description = `${arrows}${GlyphChars.Space} ${provider.name} ${GlyphChars.Space}${GlyphChars.Dot}${GlyphChars.Space} ${provider.displayPath}`;
item.iconPath = {
dark: Container.context.asAbsolutePath(`images/dark/icon-${this.remote.provider.icon}.svg`),
light: Container.context.asAbsolutePath(`images/light/icon-${this.remote.provider.icon}.svg`)
dark: Container.context.asAbsolutePath(`images/dark/icon-${provider.icon}.svg`),
light: Container.context.asAbsolutePath(`images/light/icon-${provider.icon}.svg`)
};
if (this.remote.provider instanceof RemoteProviderWithApi) {
const connected = await this.remote.provider.isConnected();
if (provider.hasApi()) {
const connected = provider.maybeConnected ?? (await provider.isConnected());
item.contextValue += `${ResourceType.Remote}${connected ? '+connected' : '+disconnected'}`;
item.tooltip = `${this.remote.name} (${this.remote.provider.name} ${GlyphChars.Dash} ${
item.tooltip = `${this.remote.name} (${provider.name} ${GlyphChars.Dash} ${
connected ? 'connected' : 'not connected'
})\n${this.remote.provider.displayPath}\n`;
})\n${provider.displayPath}\n`;
} else {
item.contextValue = ResourceType.Remote;
item.tooltip = `${this.remote.name} (${this.remote.provider.name})\n${this.remote.provider.displayPath}\n`;
item.tooltip = `${this.remote.name} (${provider.name})\n${provider.displayPath}\n`;
}
} else {
item.description = `${arrows}${GlyphChars.Space} ${

Loading…
Cancel
Save