diff --git a/package.json b/package.json index d1630f1..0b632cc 100644 --- a/package.json +++ b/package.json @@ -232,7 +232,8 @@ "description": "Specifies whether case should be ignored when matching the prefix", "default": false } - } + }, + "additionalProperties": false }, "uniqueItems": true, "markdownDescription": "Specifies autolinks to external resources in commit messages. Use `` as the variable for the reference number", @@ -1164,7 +1165,8 @@ } ] } - } + }, + "additionalProperties": false } ], "default": { @@ -1398,6 +1400,27 @@ "markdownDescription": "Specifies how much (if any) output will be sent to the GitLens output channel", "scope": "window" }, + "gitlens.partners": { + "type": [ + "object", + "null" + ], + "additionalProperties": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "default": true, + "description": "Specifies whether the partner integration should be shown" + } + }, + "additionalProperties": true, + "description": "Specifies the configuration of a partner integration" + }, + "default": null, + "description": "Specifies the configuration of a partner integration", + "scope": "window" + }, "gitlens.remotes": { "type": [ "array", @@ -1488,7 +1511,8 @@ } }, "description": "Specifies the url formats of the custom remote service" - } + }, + "additionalProperties": false }, "uniqueItems": true, "markdownDescription": "Specifies user-defined remote (code-hosting) services or custom domains for built-in remote services", @@ -2474,6 +2498,7 @@ "default": false } }, + "additionalProperties": false, "markdownDescription": "Specifies which messages should be suppressed", "scope": "window" }, diff --git a/src/api/actionRunners.ts b/src/api/actionRunners.ts index d007167..0b2f5ea 100644 --- a/src/api/actionRunners.ts +++ b/src/api/actionRunners.ts @@ -1,20 +1,34 @@ 'use strict'; -import { commands, Disposable, QuickPickItem, window } from 'vscode'; +import { commands, Disposable, Event, EventEmitter, QuickPickItem, window } from 'vscode'; import { Commands } from '../commands/common'; +import { configuration } from '../configuration'; import { ContextKeys, setContext } from '../constants'; +import { Container } from '../container'; import { getQuickPickIgnoreFocusOut } from '../quickpicks'; import { Action, ActionContext, ActionRunner } from './gitlens'; type Actions = ActionContext['type']; -const actions: Actions[] = ['createPullRequest', 'openPullRequest']; +const actions: Actions[] = ['createPullRequest', 'openPullRequest', 'hover.commands']; + +// The order here determines the sorting of these actions when shown to the user +export const enum ActionRunnerType { + BuiltIn = 0, + BuiltInPartner = 1, + Partner = 2, + BuiltInPartnerInstaller = 3, +} + +export const builtInActionRunnerName = 'Built In'; -export const defaultActionRunnerName = 'Built In'; +class ActionRunnerQuickPickItem implements QuickPickItem { + private readonly _label: string; -export class ActionRunnerQuickPickItem implements QuickPickItem { - constructor(public readonly runner: RegisteredActionRunner) {} + constructor(public readonly runner: RegisteredActionRunner, context: ActionContext) { + this._label = typeof runner.label === 'string' ? runner.label : runner.label(context); + } get label(): string { - return this.runner.label; + return this._label; } get detail(): string | undefined { @@ -22,8 +36,39 @@ export class ActionRunnerQuickPickItem implements QuickPickItem { } } -class RegisteredActionRunner implements ActionRunner, Disposable { - constructor(private readonly runner: ActionRunner, private readonly unregister: () => void) {} +class NoActionRunnersQuickPickItem implements QuickPickItem { + public readonly runner: RegisteredActionRunner | undefined; + + get label(): string { + return 'No actions were found'; + } + + get detail(): string | undefined { + return undefined; + } +} + +let runnerId = 0; +function nextRunnerId() { + if (runnerId === Number.MAX_SAFE_INTEGER) { + runnerId = 1; + } else { + runnerId++; + } + + return runnerId; +} + +class RegisteredActionRunner implements ActionRunner, Disposable { + readonly id: number; + + constructor( + public readonly type: ActionRunnerType, + private readonly runner: ActionRunner, + private readonly unregister: () => void, + ) { + this.id = nextRunnerId(); + } dispose() { this.unregister(); @@ -33,25 +78,70 @@ class RegisteredActionRunner implements ActionRunner, Disposable { return this.runner.name; } - get label(): string { + get label(): string | ((context: T) => string) { return this.runner.label; } - run(context: ActionContext): void | Promise { + get order(): number { + switch (this.type) { + case ActionRunnerType.BuiltIn: + return 0; + + case ActionRunnerType.BuiltInPartner: + return 1; + + case ActionRunnerType.Partner: + // Sort built-in partners and partners with ids the same + return this.partnerId ? 1 : 2; + + case ActionRunnerType.BuiltInPartnerInstaller: + return 3; + + default: + return 100; + } + } + + get partnerId(): string { + return this.runner.partnerId; + } + + run(context: T): void | Promise { return this.runner.run(context); } + + // when(context: ActionContext): boolean { + // try { + // return this.runner.when?.(context) ?? true; + // } catch { + // return false; + // } + // } } export class ActionRunners implements Disposable { - private readonly _actionRunners = new Map(); + private _onDidChange = new EventEmitter(); + get onDidChange(): Event { + return this._onDidChange.event; + } + + private readonly _actionRunners = new Map[]>(); private readonly _disposable: Disposable; + constructor() { - const subscriptions: Disposable[] = []; + const subscriptions: Disposable[] = [ + configuration.onDidChange(e => { + if (!configuration.changed(e, 'partners')) return; + + void this._updateAllContextKeys(); + }), + ]; for (const action of actions) { subscriptions.push( - commands.registerCommand(`${Commands.ActionPrefix}${action}`, (context: ActionContext) => - this.run(context), + commands.registerCommand( + `${Commands.ActionPrefix}${action}`, + (context: ActionContext, runnerId?: number) => this.run(context, runnerId), ), ); } @@ -71,26 +161,39 @@ export class ActionRunners implements Disposable { } count(action: Actions): number { - return this._actionRunners.get(action)?.length ?? 0; + return this.get(action)?.length ?? 0; + } + + get(action: Actions): RegisteredActionRunner[] | undefined { + return filterOnlyEnabledRunners(this._actionRunners.get(action)); } has(action: Actions): boolean { return this.count(action) > 0; } - register(action: Action, runner: ActionRunner): Disposable { + register( + action: Action, + runner: ActionRunner, + type: ActionRunnerType = ActionRunnerType.Partner, + ): Disposable { let runners = this._actionRunners.get(action); if (runners == null) { runners = []; this._actionRunners.set(action, runners); } + const onChanged = (action: Actions) => { + void this._updateContextKeys(action); + this._onDidChange.fire(action); + }; + const runnersMap = this._actionRunners; - const updateContextKeys = this._updateContextKeys.bind(this); - const registeredRunner = new RegisteredActionRunner(runner, function (this: RegisteredActionRunner) { + + const registeredRunner = new RegisteredActionRunner(type, runner, function (this: RegisteredActionRunner) { if (runners!.length === 1) { runnersMap.delete(action); - void updateContextKeys(action); + onChanged(action); } else { const index = runners!.indexOf(this); if (index !== -1) { @@ -98,64 +201,117 @@ export class ActionRunners implements Disposable { } } }); - runners.push(registeredRunner); - void this._updateContextKeys(action); + runners.push(registeredRunner); + onChanged(action); return { dispose: () => registeredRunner.dispose(), }; } - registerDefault(action: Action, runner: Omit): Disposable { - return this.register(action, { ...runner, name: defaultActionRunnerName }); + registerBuiltIn( + action: Action, + runner: Omit, 'partnerId' | 'name'>, + ): Disposable { + return this.register( + action, + { ...runner, partnerId: undefined!, name: builtInActionRunnerName }, + ActionRunnerType.BuiltIn, + ); + } + + registerBuiltInPartner( + partnerId: string, + action: Action, + runner: Omit, 'partnerId'>, + ): Disposable { + return this.register(action, { ...runner, partnerId: partnerId }, ActionRunnerType.BuiltInPartner); } - async run(context: T) { - const runners = this._actionRunners.get(context.type); + registerBuiltInPartnerInstaller( + partnerId: string, + action: Action, + runner: Omit, 'partnerId'>, + ): Disposable { + return this.register( + action, + { ...runner, partnerId: partnerId, name: `${runner.name} (Not Installed)` }, + ActionRunnerType.BuiltInPartnerInstaller, + ); + } + + async run(context: T, runnerId?: number) { + let runners = this.get(context.type); if (runners == null || runners.length === 0) return; + if (runnerId != null) { + runners = runners.filter(r => r.id === runnerId); + } + if (runners.length === 0) return; + let runner; - if (runners.length > 1) { - const items = runners.map(r => new ActionRunnerQuickPickItem(r)); + if (runners.length > 1 || runners.every(r => r.type !== ActionRunnerType.BuiltIn)) { + const items: (ActionRunnerQuickPickItem | NoActionRunnersQuickPickItem)[] = runners + // .filter(r => r.when(context)) + .sort( + (a, b) => + a.order - b.order || + a.name.localeCompare(b.name, undefined, { numeric: true, sensitivity: 'base' }), + ) + .map(r => new ActionRunnerQuickPickItem(r, context)); + + if (items.length === 0) { + items.push(new NoActionRunnersQuickPickItem()); + } - const quickpick = window.createQuickPick(); + const quickpick = window.createQuickPick(); quickpick.ignoreFocusOut = getQuickPickIgnoreFocusOut(); const disposables: Disposable[] = []; try { - const pick = await new Promise(resolve => { - disposables.push( - quickpick.onDidHide(() => resolve(undefined)), - quickpick.onDidAccept(() => { - if (quickpick.activeItems.length !== 0) { - resolve(quickpick.activeItems[0]); - } - }), - ); - - let title; - let placeholder; - switch (context.type) { - case 'createPullRequest': - title = 'Create Pull Request'; - placeholder = 'Choose how to create a pull request'; - break; - case 'openPullRequest': - title = 'Open Pull Request'; - placeholder = 'Choose how to open the pull request'; - break; - } - - quickpick.title = title; - quickpick.placeholder = placeholder; - quickpick.matchOnDetail = true; - quickpick.items = items; - - quickpick.show(); - }); + const pick = await new Promise( + resolve => { + disposables.push( + quickpick.onDidHide(() => resolve(undefined)), + quickpick.onDidAccept(() => { + if (quickpick.activeItems.length !== 0) { + resolve(quickpick.activeItems[0]); + } + }), + ); + + let title; + let placeholder; + switch (context.type) { + case 'createPullRequest': + title = 'Create Pull Request'; + placeholder = 'Choose how to create a pull request'; + break; + case 'openPullRequest': + title = 'Open Pull Request'; + placeholder = 'Choose how to open the pull request'; + break; + case 'hover.commands': + title = 'Want to Discuss or Collaborate? Have Comments, Questions, or Need Help?'; + placeholder = 'Choose what you would like to do'; + break; + default: + // eslint-disable-next-line no-debugger + debugger; + break; + } + + quickpick.title = title; + quickpick.placeholder = placeholder; + quickpick.matchOnDetail = true; + quickpick.items = items; + + quickpick.show(); + }, + ); if (pick == null) return; runner = pick.runner; @@ -167,10 +323,27 @@ export class ActionRunners implements Disposable { [runner] = runners; } - await runner.run(context); + await runner?.run(context); } private async _updateContextKeys(action: Actions) { await setContext(`${ContextKeys.ActionPrefix}${action}`, this.count(action)); } + + private async _updateAllContextKeys() { + for (const action of actions) { + await this._updateContextKeys(action); + } + } +} + +function filterOnlyEnabledRunners(runners: RegisteredActionRunner[] | undefined) { + if (runners == null || runners.length === 0) return undefined; + + const partners = Container.config.partners; + if (partners == null) return runners; + + return runners.filter( + r => r.partnerId == null || (r.partnerId != null && partners[r.partnerId]?.enabled !== false), + ); } diff --git a/src/api/api.ts b/src/api/api.ts index 144d400..a8e4979 100644 --- a/src/api/api.ts +++ b/src/api/api.ts @@ -2,7 +2,7 @@ import { Disposable } from 'vscode'; import { Container } from '../container'; import { Logger } from '../logger'; -import { defaultActionRunnerName } from './actionRunners'; +import { builtInActionRunnerName } from './actionRunners'; import { Action, ActionContext, ActionRunner, GitLensApi } from './gitlens'; const emptyDisposable = Object.freeze({ @@ -13,8 +13,12 @@ const emptyDisposable = Object.freeze({ export class Api implements GitLensApi { registerActionRunner(action: Action, runner: ActionRunner): Disposable { - if (runner.name === defaultActionRunnerName) { - throw new Error(`Cannot use the reserved name '${defaultActionRunnerName}'`); + if (runner.name === builtInActionRunnerName) { + throw new Error(`Cannot use the reserved name '${builtInActionRunnerName}'`); + } + + if ((action as string) === 'hover.commandHelp') { + action = 'hover.commands'; } return Container.actionRunners.register(action, runner); } diff --git a/src/api/gitlens.d.ts b/src/api/gitlens.d.ts index 0cdeaf1..85d6cbc 100644 --- a/src/api/gitlens.d.ts +++ b/src/api/gitlens.d.ts @@ -11,28 +11,12 @@ export interface RemoteProvider { export interface CreatePullRequestActionContext { readonly type: 'createPullRequest'; - readonly runnerId?: number; readonly repoPath: string; readonly branch: { readonly name: string; readonly upstream: string | undefined; readonly isRemote: boolean; - - /** - * @deprecated Use the root [repoPath](#CreatePullRequestActionContext.repoPath) property instead - */ - readonly repoPath: string; - /** - * @deprecated Use the root [remote](#CreatePullRequestActionContext.remote) property instead - */ - readonly remote: - | { - readonly name: string; - readonly provider?: RemoteProvider; - readonly url?: string; - } - | undefined; }; readonly remote: | { @@ -45,33 +29,56 @@ export interface CreatePullRequestActionContext { export interface OpenPullRequestActionContext { readonly type: 'openPullRequest'; - readonly runnerId?: number; readonly repoPath: string; readonly provider: RemoteProvider | undefined; readonly pullRequest: { readonly id: string; readonly url: string; + }; +} - /** - * @deprecated Use the root [repoPath](#OpenPullRequestActionContext.repoPath) property instead - */ - readonly repoPath: string; - /** - * @deprecated Use the root [provider](#OpenPullRequestActionContext.provider) property instead - */ - readonly provider: RemoteProvider | undefined; +export interface HoverCommandsActionContext { + readonly type: 'hover.commands'; + + readonly repoPath: string; + readonly commit: { + sha: string; + author: { + name: string; + email: string | undefined; + [key: string]: any; + }; }; + readonly file: + | { + uri: string; + line: number | undefined; + } + | undefined; } -export type ActionContext = CreatePullRequestActionContext | OpenPullRequestActionContext; +export type ActionContext = CreatePullRequestActionContext | OpenPullRequestActionContext | HoverCommandsActionContext; export type Action = T['type']; -export interface ActionRunner { +export interface ActionRunner { + /* + * A unique key to identify the extension/product/company to which the runner belongs + */ + readonly partnerId: string; + + /* + * A user-friendly name to which the runner belongs, i.e. your extension/product/company name. Will be shown, less prominently, to the user when offering this action + */ readonly name: string; - readonly label: string; - run(context: ActionContext): void | Promise; + /* + * A user-friendly string which describes the action that will be taken. Will be shown to the user when offering this action + */ + readonly label: string | ((context: T) => string); + + run(context: T): void | Promise; + // when?(context: T): boolean; } export interface GitLensApi { diff --git a/src/config.ts b/src/config.ts index 57f5658..346d682 100644 --- a/src/config.ts +++ b/src/config.ts @@ -111,6 +111,13 @@ export interface Config { }; modes: Record; outputLevel: TraceLevel; + partners: Record< + string, + { + enabled: boolean; + [key: string]: any; + } + > | null; remotes: RemotesConfig[] | null; showWelcomeOnInstall: boolean; showWhatsNewAfterUpgrades: boolean; diff --git a/src/extension.ts b/src/extension.ts index d00a21a..216d5c1 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,7 +1,7 @@ 'use strict'; import * as paths from 'path'; import { commands, ExtensionContext, extensions, window, workspace } from 'vscode'; -import { GitLensApi } from '../src/api/gitlens'; +import { GitLensApi, OpenPullRequestActionContext } from '../src/api/gitlens'; import { Api } from './api/api'; import { Commands, executeCommand, OpenPullRequestOnRemoteCommandArgs, registerCommands } from './commands'; import { configuration, Configuration } from './configuration'; @@ -12,6 +12,7 @@ import { GitService } from './git/gitService'; import { GitUri } from './git/gitUri'; import { Logger } from './logger'; import { Messages } from './messages'; +import { registerPartnerActionRunners } from './partners'; import { Strings, Versions } from './system'; import { ViewNode } from './views/nodes'; @@ -135,7 +136,8 @@ export async function activate(context: ExtensionContext): Promise('openPullRequest', { + label: ctx => `Open Pull Request on ${ctx.provider?.name ?? 'Remote'}`, run: async ctx => { if (ctx.type !== 'openPullRequest') return; diff --git a/src/git/formatters/commitFormatter.ts b/src/git/formatters/commitFormatter.ts index 9c72e29..7921d64 100644 --- a/src/git/formatters/commitFormatter.ts +++ b/src/git/formatters/commitFormatter.ts @@ -1,12 +1,12 @@ 'use strict'; -import { OpenPullRequestActionContext } from '../../api/gitlens'; +import { Uri } from 'vscode'; +import { HoverCommandsActionContext, OpenPullRequestActionContext } from '../../api/gitlens'; import { getPresenceDataUri } from '../../avatars'; import { Commands, ConnectRemoteProviderCommand, DiffWithCommand, getMarkdownActionCommand, - InviteToLiveShareCommand, OpenCommitOnRemoteCommand, OpenFileAtRevisionCommand, ShowQuickCommitCommand, @@ -36,9 +36,9 @@ export interface CommitFormatOptions extends FormatOptions { autolinkedIssuesOrPullRequests?: Map; avatarSize?: number; dateStyle?: DateStyle; + editor?: { line: number; uri: Uri }; footnotes?: Map; getBranchAndTagTips?: (sha: string) => string | undefined; - line?: number; markdown?: boolean; messageAutolinks?: boolean; messageIndent?: number; @@ -282,7 +282,7 @@ export class CommitFormatter extends Formatter { uri: diffUris.current.documentUri(), }, repoPath: this._item.repoPath, - line: this._options.line, + line: this._options.editor?.line, })} "Open Changes")** `; } else { commands = `\`${this._padOrTruncate( @@ -310,15 +310,9 @@ export class CommitFormatter extends Formatter { }](${getMarkdownActionCommand('openPullRequest', { repoPath: this._item.repoPath, provider: { id: pr.provider.id, name: pr.provider.name, domain: pr.provider.domain }, - pullRequest: { - id: pr.id, - url: pr.url, - - provider: { id: pr.provider.id, name: pr.provider.name, domain: pr.provider.domain }, - repoPath: this._item.repoPath, - }, + pullRequest: { id: pr.id, url: pr.url }, })} "Open Pull Request \\#${pr.id}${ - Container.actionRunners.count('openPullRequest') == 1 ? ` on ${pr.provider.name}` : '' + Container.actionRunners.count('openPullRequest') == 1 ? ` on ${pr.provider.name}` : '...' }\n${GlyphChars.Dash.repeat(2)}\n${Strings.escapeMarkdown(pr.title).replace(/"/g, '\\"')}\n${ pr.state }, ${pr.formatDateFromNow()}")${separator}`; @@ -335,7 +329,7 @@ export class CommitFormatter extends Formatter { commands += `[$(compare-changes)](${DiffWithCommand.getMarkdownCommandArgs( this._item, - this._options.line, + this._options.editor?.line, )} "Open Changes")${separator}`; if (this._item.previousSha != null) { @@ -347,7 +341,7 @@ export class CommitFormatter extends Formatter { commands += `[$(history)](${OpenFileAtRevisionCommand.getMarkdownCommandArgs( uri, FileAnnotationType.Blame, - this._options.line, + this._options.editor?.line, )} "Blame Previous Revision")${separator}`; } @@ -359,13 +353,27 @@ export class CommitFormatter extends Formatter { )} "Open Commit on ${providers?.length ? providers[0].name : 'Remote'}")${separator}`; } - if (this._item.author !== 'You') { - const presence = this._options.presence; - if (presence != null) { - commands += `[$(live-share)](${InviteToLiveShareCommand.getMarkdownCommandArgs( - this._item.email, - )} "Invite ${this._item.author} (${presence.statusText}) to a Live Share Session")${separator}`; - } + if (Container.actionRunners.count('hover.commands') > 0) { + commands += `[$(feedback) Discuss / Collab${ + GlyphChars.Ellipsis + }](${getMarkdownActionCommand('hover.commands', { + repoPath: this._item.repoPath, + commit: { + sha: this._item.sha, + author: { + name: this._item.author, + email: this._item.email, + presence: this._options.presence, + }, + }, + file: + this._options.editor != null + ? { + uri: this._options.editor?.uri.toString(), + line: this._options.editor?.line, + } + : undefined, + })} "Want to Discuss or Collaborate? Have Comments, Questions, or Need Help?")${separator}`; } commands += `[$(ellipsis)](${ShowQuickCommitFileCommand.getMarkdownCommandArgs({ @@ -479,15 +487,9 @@ export class CommitFormatter extends Formatter { text = `[PR #${pr.id}](${getMarkdownActionCommand('openPullRequest', { repoPath: this._item.repoPath, provider: { id: pr.provider.id, name: pr.provider.name, domain: pr.provider.domain }, - pullRequest: { - id: pr.id, - url: pr.url, - - repoPath: this._item.repoPath, - provider: { id: pr.provider.id, name: pr.provider.name, domain: pr.provider.domain }, - }, + pullRequest: { id: pr.id, url: pr.url }, })} "Open Pull Request \\#${pr.id}${ - Container.actionRunners.count('openPullRequest') == 1 ? ` on ${pr.provider.name}` : '' + Container.actionRunners.count('openPullRequest') == 1 ? ` on ${pr.provider.name}` : '...' }\n${GlyphChars.Dash.repeat(2)}\n${Strings.escapeMarkdown(pr.title).replace(/"/g, '\\"')}\n${ pr.state }, ${pr.formatDateFromNow()}")`; diff --git a/src/git/models/repository.ts b/src/git/models/repository.ts index 5d166ec..9d578ff 100644 --- a/src/git/models/repository.ts +++ b/src/git/models/repository.ts @@ -733,32 +733,28 @@ export class Repository implements Disposable { if (!(await Messages.showCreatePullRequestPrompt(branch.name))) return; const remote = await this.getRemote(remoteName); - const remoteInfo = - remote != null - ? { - name: remote.name, - provider: - remote.provider != null - ? { - id: remote.provider.id, - name: remote.provider.name, - domain: remote.provider.domain, - } - : undefined, - url: remote.url, - } - : { name: remoteName }; void executeActionCommand('createPullRequest', { repoPath: this.path, - remote: remoteInfo, + remote: + remote != null + ? { + name: remote.name, + provider: + remote.provider != null + ? { + id: remote.provider.id, + name: remote.provider.name, + domain: remote.provider.domain, + } + : undefined, + url: remote.url, + } + : { name: remoteName }, branch: { name: branch.name, isRemote: branch.remote, upstream: branch.tracking, - - remote: remoteInfo, - repoPath: this.path, }, }); } diff --git a/src/hovers/hovers.ts b/src/hovers/hovers.ts index 80ec9f0..4bf5705 100644 --- a/src/hovers/hovers.ts +++ b/src/hovers/hovers.ts @@ -199,7 +199,10 @@ export namespace Hovers { const details = await CommitFormatter.fromTemplateAsync(Container.config.hovers.detailsMarkdownFormat, commit, { autolinkedIssuesOrPullRequests: autolinkedIssuesOrPullRequests, dateFormat: dateFormat, - line: editorLine, + editor: { + line: editorLine, + uri: uri, + }, markdown: true, messageAutolinks: Container.config.hovers.autolinks.enabled, pullRequestOrRemote: pr, diff --git a/src/partners.ts b/src/partners.ts new file mode 100644 index 0000000..5336bc2 --- /dev/null +++ b/src/partners.ts @@ -0,0 +1,42 @@ +'use strict'; +import { ExtensionContext } from 'vscode'; +import { ActionContext, HoverCommandsActionContext } from './api/gitlens'; +import { Commands, executeCommand, InviteToLiveShareCommandArgs } from './commands'; +import { Container } from './container'; + +export function registerPartnerActionRunners(context: ExtensionContext): void { + registerLiveShare(context); +} + +function registerLiveShare(context: ExtensionContext) { + context.subscriptions.push( + Container.actionRunners.registerBuiltInPartner('liveshare', 'hover.commands', { + name: 'Live Share', + label: (context: ActionContext) => { + if (context.type === 'hover.commands') { + if (context.commit.author.name !== 'You') { + return `$(live-share) Invite ${context.commit.author.name}${ + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions + context.commit.author.presence?.statusText + ? ` (${context.commit.author.presence?.statusText})` + : '' + } to a Live Share Session`; + } + } + + return '$(live-share) Start a Live Share Session'; + }, + run: async (context: ActionContext) => { + if (context.type !== 'hover.commands' || context.commit.author.name === 'You') { + await executeCommand(Commands.InviteToLiveShare, {}); + + return; + } + + await executeCommand(Commands.InviteToLiveShare, { + email: context.commit.author.email, + }); + }, + }), + ); +} diff --git a/src/views/viewCommands.ts b/src/views/viewCommands.ts index fea5123..99fe242 100644 --- a/src/views/viewCommands.ts +++ b/src/views/viewCommands.ts @@ -283,32 +283,28 @@ export class ViewCommands { } const remote = await node.branch.getRemote(); - const remoteInfo = - remote != null - ? { - name: remote.name, - provider: - remote.provider != null - ? { - id: remote.provider.id, - name: remote.provider.name, - domain: remote.provider.domain, - } - : undefined, - url: remote.url, - } - : undefined; return executeActionCommand('createPullRequest', { repoPath: node.repoPath, - remote: remoteInfo, + remote: + remote != null + ? { + name: remote.name, + provider: + remote.provider != null + ? { + id: remote.provider.id, + name: remote.provider.name, + domain: remote.provider.domain, + } + : undefined, + url: remote.url, + } + : undefined, branch: { name: node.branch.name, upstream: node.branch.tracking, isRemote: node.branch.remote, - - remote: remoteInfo, - repoPath: node.repoPath, }, }); } @@ -420,25 +416,16 @@ export class ViewCommands { private openPullRequest(node: PullRequestNode) { if (!(node instanceof PullRequestNode)) return Promise.resolve(); - const provider = { - id: node.pullRequest.provider.id, - name: node.pullRequest.provider.name, - domain: node.pullRequest.provider.domain, - }; - return executeActionCommand('openPullRequest', { repoPath: node.uri.repoPath!, - provider: provider, + provider: { + id: node.pullRequest.provider.id, + name: node.pullRequest.provider.name, + domain: node.pullRequest.provider.domain, + }, pullRequest: { id: node.pullRequest.id, url: node.pullRequest.url, - - provider: { - id: node.pullRequest.provider.id, - name: node.pullRequest.provider.name, - domain: node.pullRequest.provider.domain, - }, - repoPath: node.uri.repoPath!, }, }); }