Path: blob/main/extensions/git-base/src/remoteSource.ts
3314 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { QuickPickItem, window, QuickPick, QuickPickItemKind, l10n, Disposable } from 'vscode';6import { RemoteSourceProvider, RemoteSource, PickRemoteSourceOptions, PickRemoteSourceResult, RemoteSourceAction } from './api/git-base';7import { Model } from './model';8import { throttle, debounce } from './decorators';910async function getQuickPickResult<T extends QuickPickItem>(quickpick: QuickPick<T>): Promise<T | undefined> {11const listeners: Disposable[] = [];12const result = await new Promise<T | undefined>(c => {13listeners.push(14quickpick.onDidAccept(() => c(quickpick.selectedItems[0])),15quickpick.onDidHide(() => c(undefined)),16);17quickpick.show();18});1920quickpick.hide();21listeners.forEach(l => l.dispose());22return result;23}2425class RemoteSourceProviderQuickPick implements Disposable {2627private disposables: Disposable[] = [];28private isDisposed: boolean = false;2930private quickpick: QuickPick<QuickPickItem & { remoteSource?: RemoteSource }> | undefined;3132constructor(private provider: RemoteSourceProvider) { }3334dispose() {35this.disposables.forEach(d => d.dispose());36this.disposables = [];37this.quickpick = undefined;38this.isDisposed = true;39}4041private ensureQuickPick() {42if (!this.quickpick) {43this.quickpick = window.createQuickPick();44this.disposables.push(this.quickpick);45this.quickpick.ignoreFocusOut = true;46this.disposables.push(this.quickpick.onDidHide(() => this.dispose()));47if (this.provider.supportsQuery) {48this.quickpick.placeholder = this.provider.placeholder ?? l10n.t('Repository name (type to search)');49this.disposables.push(this.quickpick.onDidChangeValue(this.onDidChangeValue, this));50} else {51this.quickpick.placeholder = this.provider.placeholder ?? l10n.t('Repository name');52}53}54}5556@debounce(300)57private onDidChangeValue(): void {58this.query();59}6061@throttle62private async query(): Promise<void> {63try {64if (this.isDisposed) {65return;66}67this.ensureQuickPick();68this.quickpick!.busy = true;69this.quickpick!.show();7071const remoteSources = await this.provider.getRemoteSources(this.quickpick?.value) || [];72// The user may have cancelled the picker in the meantime73if (this.isDisposed) {74return;75}7677if (remoteSources.length === 0) {78this.quickpick!.items = [{79label: l10n.t('No remote repositories found.'),80alwaysShow: true81}];82} else {83this.quickpick!.items = remoteSources.map(remoteSource => ({84label: remoteSource.icon ? `$(${remoteSource.icon}) ${remoteSource.name}` : remoteSource.name,85description: remoteSource.description || (typeof remoteSource.url === 'string' ? remoteSource.url : remoteSource.url[0]),86detail: remoteSource.detail,87remoteSource,88alwaysShow: true89}));90}91} catch (err) {92this.quickpick!.items = [{ label: l10n.t('{0} Error: {1}', '$(error)', err.message), alwaysShow: true }];93console.error(err);94} finally {95if (!this.isDisposed) {96this.quickpick!.busy = false;97}98}99}100101async pick(): Promise<RemoteSource | undefined> {102await this.query();103if (this.isDisposed) {104return;105}106const result = await getQuickPickResult(this.quickpick!);107return result?.remoteSource;108}109}110111export async function getRemoteSourceActions(model: Model, url: string): Promise<RemoteSourceAction[]> {112const providers = model.getRemoteProviders();113114const remoteSourceActions = [];115for (const provider of providers) {116const providerActions = await provider.getRemoteSourceActions?.(url);117if (providerActions?.length) {118remoteSourceActions.push(...providerActions);119}120}121122return remoteSourceActions;123}124125export async function pickRemoteSource(model: Model, options: PickRemoteSourceOptions & { branch?: false | undefined }): Promise<string | undefined>;126export async function pickRemoteSource(model: Model, options: PickRemoteSourceOptions & { branch: true }): Promise<PickRemoteSourceResult | undefined>;127export async function pickRemoteSource(model: Model, options: PickRemoteSourceOptions = {}): Promise<string | PickRemoteSourceResult | undefined> {128const quickpick = window.createQuickPick<(QuickPickItem & { provider?: RemoteSourceProvider; url?: string })>();129quickpick.title = options.title;130131if (options.providerName) {132const provider = model.getRemoteProviders()133.filter(provider => provider.name === options.providerName)[0];134135if (provider) {136return await pickProviderSource(provider, options);137}138}139140const remoteProviders = model.getRemoteProviders()141.map(provider => ({ label: (provider.icon ? `$(${provider.icon}) ` : '') + (options.providerLabel ? options.providerLabel(provider) : provider.name), alwaysShow: true, provider }));142143const recentSources: (QuickPickItem & { url?: string; timestamp: number })[] = [];144if (options.showRecentSources) {145for (const { provider } of remoteProviders) {146const sources = (await provider.getRecentRemoteSources?.() ?? []).map((item) => {147return {148...item,149label: (item.icon ? `$(${item.icon}) ` : '') + item.name,150url: typeof item.url === 'string' ? item.url : item.url[0],151};152});153recentSources.push(...sources);154}155}156157const items = [158{ kind: QuickPickItemKind.Separator, label: l10n.t('remote sources') },159...remoteProviders,160{ kind: QuickPickItemKind.Separator, label: l10n.t('recently opened') },161...recentSources.sort((a, b) => b.timestamp - a.timestamp)162];163164quickpick.placeholder = options.placeholder ?? (remoteProviders.length === 0165? l10n.t('Provide repository URL')166: l10n.t('Provide repository URL or pick a repository source.'));167168const updatePicks = (value?: string) => {169if (value) {170const label = (typeof options.urlLabel === 'string' ? options.urlLabel : options.urlLabel?.(value)) ?? l10n.t('URL');171quickpick.items = [{172label: label,173description: value,174alwaysShow: true,175url: value176},177...items178];179} else {180quickpick.items = items;181}182};183184quickpick.onDidChangeValue(updatePicks);185updatePicks();186187const result = await getQuickPickResult(quickpick);188189if (result) {190if (result.url) {191return result.url;192} else if (result.provider) {193return await pickProviderSource(result.provider, options);194}195}196197return undefined;198}199200async function pickProviderSource(provider: RemoteSourceProvider, options: PickRemoteSourceOptions = {}): Promise<string | PickRemoteSourceResult | undefined> {201const quickpick = new RemoteSourceProviderQuickPick(provider);202const remote = await quickpick.pick();203quickpick.dispose();204205let url: string | undefined;206207if (remote) {208if (typeof remote.url === 'string') {209url = remote.url;210} else if (remote.url.length > 0) {211url = await window.showQuickPick(remote.url, { ignoreFocusOut: true, placeHolder: l10n.t('Choose a URL to clone from.') });212}213}214215if (!url || !options.branch) {216return url;217}218219if (!provider.getBranches) {220return { url };221}222223const branches = await provider.getBranches(url);224225if (!branches) {226return { url };227}228229const branch = await window.showQuickPick(branches, {230placeHolder: l10n.t('Branch name')231});232233if (!branch) {234return { url };235}236237return { url, branch };238}239240241