From c85718eb812fb2b38b00262ed88e8e234b744a19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Tue, 14 Jul 2020 13:56:56 +0000 Subject: [PATCH] git: remote source provider picker command fixes #102394 --- extensions/git/src/api/api1.ts | 88 +++++++------- extensions/git/src/api/extension.ts | 4 + extensions/git/src/commands.ts | 171 ++-------------------------- extensions/git/src/main.ts | 2 +- extensions/git/src/remoteSource.ts | 133 ++++++++++++++++++++++ 5 files changed, 199 insertions(+), 199 deletions(-) create mode 100644 extensions/git/src/remoteSource.ts diff --git a/extensions/git/src/api/api1.ts b/extensions/git/src/api/api1.ts index a6d2cd4b3d3..b05e5ebf92e 100644 --- a/extensions/git/src/api/api1.ts +++ b/extensions/git/src/api/api1.ts @@ -5,10 +5,12 @@ import { Model } from '../model'; import { Repository as BaseRepository, Resource } from '../repository'; -import { InputBox, Git, API, Repository, Remote, RepositoryState, Branch, Ref, Submodule, Commit, Change, RepositoryUIState, Status, LogOptions, APIState, CommitOptions, GitExtension, RefType, RemoteSourceProvider, CredentialsProvider, BranchQuery } from './git'; +import { InputBox, Git, API, Repository, Remote, RepositoryState, Branch, Ref, Submodule, Commit, Change, RepositoryUIState, Status, LogOptions, APIState, CommitOptions, RefType, RemoteSourceProvider, CredentialsProvider, BranchQuery } from './git'; import { Event, SourceControlInputBox, Uri, SourceControl, Disposable, commands } from 'vscode'; import { mapEvent } from '../util'; import { toGitUri } from '../uri'; +import { pickRemoteSource, PickRemoteSourceOptions } from '../remoteSource'; +import { GitExtensionImpl } from './extension'; class ApiInputBox implements InputBox { set value(value: string) { this._inputBox.value = value; } @@ -308,41 +310,51 @@ function getStatus(status: Status): string { return 'UNKNOWN'; } -export function registerAPICommands(extension: GitExtension): Disposable { - return Disposable.from( - commands.registerCommand('git.api.getRepositories', () => { - const api = extension.getAPI(1); - return api.repositories.map(r => r.rootUri.toString()); - }), - - commands.registerCommand('git.api.getRepositoryState', (uri: string) => { - const api = extension.getAPI(1); - const repository = api.getRepository(Uri.parse(uri)); - - if (!repository) { - return null; - } - - const state = repository.state; - - const ref = (ref: Ref | undefined) => (ref && { ...ref, type: getRefType(ref.type) }); - const change = (change: Change) => ({ - uri: change.uri.toString(), - originalUri: change.originalUri.toString(), - renameUri: change.renameUri?.toString(), - status: getStatus(change.status) - }); - - return { - HEAD: ref(state.HEAD), - refs: state.refs.map(ref), - remotes: state.remotes, - submodules: state.submodules, - rebaseCommit: state.rebaseCommit, - mergeChanges: state.mergeChanges.map(change), - indexChanges: state.indexChanges.map(change), - workingTreeChanges: state.workingTreeChanges.map(change) - }; - }) - ); +export function registerAPICommands(extension: GitExtensionImpl): Disposable { + const disposables: Disposable[] = []; + + disposables.push(commands.registerCommand('git.api.getRepositories', () => { + const api = extension.getAPI(1); + return api.repositories.map(r => r.rootUri.toString()); + })); + + disposables.push(commands.registerCommand('git.api.getRepositoryState', (uri: string) => { + const api = extension.getAPI(1); + const repository = api.getRepository(Uri.parse(uri)); + + if (!repository) { + return null; + } + + const state = repository.state; + + const ref = (ref: Ref | undefined) => (ref && { ...ref, type: getRefType(ref.type) }); + const change = (change: Change) => ({ + uri: change.uri.toString(), + originalUri: change.originalUri.toString(), + renameUri: change.renameUri?.toString(), + status: getStatus(change.status) + }); + + return { + HEAD: ref(state.HEAD), + refs: state.refs.map(ref), + remotes: state.remotes, + submodules: state.submodules, + rebaseCommit: state.rebaseCommit, + mergeChanges: state.mergeChanges.map(change), + indexChanges: state.indexChanges.map(change), + workingTreeChanges: state.workingTreeChanges.map(change) + }; + })); + + disposables.push(commands.registerCommand('git.api.getRemoteSources', (opts?: PickRemoteSourceOptions) => { + if (!extension.model) { + return; + } + + return pickRemoteSource(extension.model, opts); + })); + + return Disposable.from(...disposables); } diff --git a/extensions/git/src/api/extension.ts b/extensions/git/src/api/extension.ts index f7598fe2f8c..bfedc0cc909 100644 --- a/extensions/git/src/api/extension.ts +++ b/extensions/git/src/api/extension.ts @@ -42,6 +42,10 @@ export class GitExtensionImpl implements GitExtension { this._onDidChangeEnablement.fire(this.enabled); } + get model(): Model | undefined { + return this._model; + } + constructor(model?: Model) { if (model) { this.enabled = true; diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 4aa583ab32c..c9fe2503d22 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -6,10 +6,10 @@ import { lstat, Stats } from 'fs'; import * as os from 'os'; import * as path from 'path'; -import { commands, Disposable, LineChange, MessageOptions, OutputChannel, Position, ProgressLocation, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder, TimelineItem, env, QuickPick } from 'vscode'; +import { commands, Disposable, LineChange, MessageOptions, OutputChannel, Position, ProgressLocation, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder, TimelineItem, env } from 'vscode'; import TelemetryReporter from 'vscode-extension-telemetry'; import * as nls from 'vscode-nls'; -import { Branch, GitErrorCodes, Ref, RefType, Status, CommitOptions, RemoteSourceProvider, RemoteSource } from './api/git'; +import { Branch, GitErrorCodes, Ref, RefType, Status, CommitOptions, RemoteSourceProvider } from './api/git'; import { ForcePushMode, Git, Stash } from './git'; import { Model } from './model'; import { Repository, Resource, ResourceGroupType } from './repository'; @@ -18,8 +18,8 @@ import { fromGitUri, toGitUri, isGitUri } from './uri'; import { grep, isDescendant, pathEquals } from './util'; import { Log, LogLevel } from './log'; import { GitTimelineItem } from './timelineProvider'; -import { throttle, debounce } from './decorators'; import { ApiRepository } from './api/api1'; +import { pickRemoteSource } from './remoteSource'; const localize = nls.loadMessageBundle(); @@ -240,72 +240,6 @@ interface PushOptions { silent?: boolean; } -async function getQuickPickResult(quickpick: QuickPick): Promise { - const result = await new Promise(c => { - quickpick.onDidAccept(() => c(quickpick.selectedItems[0])); - quickpick.onDidHide(() => c(undefined)); - quickpick.show(); - }); - - quickpick.hide(); - return result; -} - -class RemoteSourceProviderQuickPick { - - private quickpick: QuickPick; - - constructor(private provider: RemoteSourceProvider) { - this.quickpick = window.createQuickPick(); - this.quickpick.ignoreFocusOut = true; - - if (provider.supportsQuery) { - this.quickpick.placeholder = localize('type to search', "Repository name (type to search)"); - this.quickpick.onDidChangeValue(this.onDidChangeValue, this); - } else { - this.quickpick.placeholder = localize('type to filter', "Repository name"); - } - } - - @debounce(300) - onDidChangeValue(): void { - this.query(); - } - - @throttle - async query(): Promise { - this.quickpick.busy = true; - - try { - const remoteSources = await this.provider.getRemoteSources(this.quickpick.value) || []; - - if (remoteSources.length === 0) { - this.quickpick.items = [{ - label: localize('none found', "No remote repositories found."), - alwaysShow: true - }]; - } else { - this.quickpick.items = remoteSources.map(remoteSource => ({ - label: remoteSource.name, - description: remoteSource.description || (typeof remoteSource.url === 'string' ? remoteSource.url : remoteSource.url[0]), - remoteSource - })); - } - } catch (err) { - this.quickpick.items = [{ label: localize('error', "$(error) Error: {0}", err.message), alwaysShow: true }]; - console.error(err); - } finally { - this.quickpick.busy = false; - } - } - - async pick(): Promise { - this.query(); - const result = await getQuickPickResult(this.quickpick); - return result?.remoteSource; - } -} - export class CommandCenter { private disposables: Disposable[]; @@ -527,51 +461,10 @@ export class CommandCenter { @command('git.clone') async clone(url?: string, parentPath?: string): Promise { if (!url) { - const quickpick = window.createQuickPick<(QuickPickItem & { provider?: RemoteSourceProvider, url?: string })>(); - quickpick.ignoreFocusOut = true; - - const providers = this.model.getRemoteProviders() - .map(provider => ({ label: (provider.icon ? `$(${provider.icon}) ` : '') + localize('clonefrom', "Clone from {0}", provider.name), alwaysShow: true, provider })); - - quickpick.placeholder = providers.length === 0 - ? localize('provide url', "Provide repository URL") - : localize('provide url or pick', "Provide repository URL or pick a repository source."); - - const updatePicks = (value?: string) => { - if (value) { - quickpick.items = [{ - label: localize('repourl', "Clone from URL"), - description: value, - alwaysShow: true, - url: value - }, - ...providers]; - } else { - quickpick.items = providers; - } - }; - - quickpick.onDidChangeValue(updatePicks); - updatePicks(); - - const result = await getQuickPickResult(quickpick); - - if (result) { - if (result.url) { - url = result.url; - } else if (result.provider) { - const quickpick = new RemoteSourceProviderQuickPick(result.provider); - const remote = await quickpick.pick(); - - if (remote) { - if (typeof remote.url === 'string') { - url = remote.url; - } else if (remote.url.length > 0) { - url = await window.showQuickPick(remote.url, { ignoreFocusOut: true, placeHolder: localize('pick url', "Choose a URL to clone from.") }); - } - } - } - } + url = await pickRemoteSource(this.model, { + providerLabel: provider => localize('clonefrom', "Clone from {0}", provider.name), + urlLabel: localize('repourl', "Clone from URL") + }); } if (!url) { @@ -2146,52 +2039,10 @@ export class CommandCenter { @command('git.addRemote', { repository: true }) async addRemote(repository: Repository): Promise { - const quickpick = window.createQuickPick<(QuickPickItem & { provider?: RemoteSourceProvider, url?: string })>(); - quickpick.ignoreFocusOut = true; - - const providers = this.model.getRemoteProviders() - .map(provider => ({ label: (provider.icon ? `$(${provider.icon}) ` : '') + localize('addfrom', "Add remote from {0}", provider.name), alwaysShow: true, provider })); - - quickpick.placeholder = providers.length === 0 - ? localize('provide url', "Provide repository URL") - : localize('provide url or pick', "Provide repository URL or pick a repository source."); - - const updatePicks = (value?: string) => { - if (value) { - quickpick.items = [{ - label: localize('addFrom', "Add remote from URL"), - description: value, - alwaysShow: true, - url: value - }, - ...providers]; - } else { - quickpick.items = providers; - } - }; - - quickpick.onDidChangeValue(updatePicks); - updatePicks(); - - const result = await getQuickPickResult(quickpick); - let url: string | undefined; - - if (result) { - if (result.url) { - url = result.url; - } else if (result.provider) { - const quickpick = new RemoteSourceProviderQuickPick(result.provider); - const remote = await quickpick.pick(); - - if (remote) { - if (typeof remote.url === 'string') { - url = remote.url; - } else if (remote.url.length > 0) { - url = await window.showQuickPick(remote.url, { ignoreFocusOut: true, placeHolder: localize('pick url', "Choose a URL to clone from.") }); - } - } - } - } + const url = await pickRemoteSource(this.model, { + providerLabel: provider => localize('addfrom', "Add remote from {0}", provider.name), + urlLabel: localize('addFrom', "Add remote from URL") + }); if (!url) { return; diff --git a/extensions/git/src/main.ts b/extensions/git/src/main.ts index d541220ec30..c1c78e629bf 100644 --- a/extensions/git/src/main.ts +++ b/extensions/git/src/main.ts @@ -127,7 +127,7 @@ async function warnAboutMissingGit(): Promise { } } -export async function _activate(context: ExtensionContext): Promise { +export async function _activate(context: ExtensionContext): Promise { const disposables: Disposable[] = []; context.subscriptions.push(new Disposable(() => Disposable.from(...disposables).dispose())); diff --git a/extensions/git/src/remoteSource.ts b/extensions/git/src/remoteSource.ts new file mode 100644 index 00000000000..b736f606e67 --- /dev/null +++ b/extensions/git/src/remoteSource.ts @@ -0,0 +1,133 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { QuickPickItem, window, QuickPick } from 'vscode'; +import * as nls from 'vscode-nls'; +import { RemoteSourceProvider, RemoteSource } from './api/git'; +import { Model } from './model'; +import { throttle, debounce } from './decorators'; + +const localize = nls.loadMessageBundle(); + +async function getQuickPickResult(quickpick: QuickPick): Promise { + const result = await new Promise(c => { + quickpick.onDidAccept(() => c(quickpick.selectedItems[0])); + quickpick.onDidHide(() => c(undefined)); + quickpick.show(); + }); + + quickpick.hide(); + return result; +} + +class RemoteSourceProviderQuickPick { + + private quickpick: QuickPick; + + constructor(private provider: RemoteSourceProvider) { + this.quickpick = window.createQuickPick(); + this.quickpick.ignoreFocusOut = true; + + if (provider.supportsQuery) { + this.quickpick.placeholder = localize('type to search', "Repository name (type to search)"); + this.quickpick.onDidChangeValue(this.onDidChangeValue, this); + } else { + this.quickpick.placeholder = localize('type to filter', "Repository name"); + } + } + + @debounce(300) + private onDidChangeValue(): void { + this.query(); + } + + @throttle + private async query(): Promise { + this.quickpick.busy = true; + + try { + const remoteSources = await this.provider.getRemoteSources(this.quickpick.value) || []; + + if (remoteSources.length === 0) { + this.quickpick.items = [{ + label: localize('none found', "No remote repositories found."), + alwaysShow: true + }]; + } else { + this.quickpick.items = remoteSources.map(remoteSource => ({ + label: remoteSource.name, + description: remoteSource.description || (typeof remoteSource.url === 'string' ? remoteSource.url : remoteSource.url[0]), + remoteSource + })); + } + } catch (err) { + this.quickpick.items = [{ label: localize('error', "$(error) Error: {0}", err.message), alwaysShow: true }]; + console.error(err); + } finally { + this.quickpick.busy = false; + } + } + + async pick(): Promise { + this.query(); + const result = await getQuickPickResult(this.quickpick); + return result?.remoteSource; + } +} + +export interface PickRemoteSourceOptions { + readonly providerLabel?: (provider: RemoteSourceProvider) => string; + readonly urlLabel?: string; +} + +export async function pickRemoteSource(model: Model, options: PickRemoteSourceOptions = {}): Promise { + const quickpick = window.createQuickPick<(QuickPickItem & { provider?: RemoteSourceProvider, url?: string })>(); + quickpick.ignoreFocusOut = true; + + const providers = model.getRemoteProviders() + .map(provider => ({ label: (provider.icon ? `$(${provider.icon}) ` : '') + (options.providerLabel ? options.providerLabel(provider) : provider.name), alwaysShow: true, provider })); + + quickpick.placeholder = providers.length === 0 + ? localize('provide url', "Provide repository URL") + : localize('provide url or pick', "Provide repository URL or pick a repository source."); + + const updatePicks = (value?: string) => { + if (value) { + quickpick.items = [{ + label: options.urlLabel ?? localize('url', "URL"), + description: value, + alwaysShow: true, + url: value + }, + ...providers]; + } else { + quickpick.items = providers; + } + }; + + quickpick.onDidChangeValue(updatePicks); + updatePicks(); + + const result = await getQuickPickResult(quickpick); + + if (result) { + if (result.url) { + return result.url; + } else if (result.provider) { + const quickpick = new RemoteSourceProviderQuickPick(result.provider); + const remote = await quickpick.pick(); + + if (remote) { + if (typeof remote.url === 'string') { + return remote.url; + } else if (remote.url.length > 0) { + return await window.showQuickPick(remote.url, { ignoreFocusOut: true, placeHolder: localize('pick url', "Choose a URL to clone from.") }); + } + } + } + } + + return undefined; +} -- GitLab