Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enrich Git extension's remote source provider API #147613

Merged
merged 15 commits into from
Apr 22, 2022
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion extensions/git-base/src/api/git-base.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,12 @@ export interface GitBaseExtension {

export interface PickRemoteSourceOptions {
readonly providerLabel?: (provider: RemoteSourceProvider) => string;
readonly urlLabel?: string;
readonly urlLabel?: string | ((url: string) => string);
readonly providerName?: string;
readonly title?: string;
readonly placeholder?: string;
readonly branch?: boolean; // then result is PickRemoteSourceResult
readonly showRecentSources?: boolean;
}

export interface PickRemoteSourceResult {
Expand All @@ -44,17 +47,29 @@ export interface PickRemoteSourceResult {
export interface RemoteSource {
readonly name: string;
readonly description?: string;
readonly detail?: string;
/**
* Codicon name
*/
readonly icon?: string;
readonly url: string | string[];
}

export interface RecentRemoteSource extends RemoteSource {
readonly timestamp: number;
}

export interface RemoteSourceProvider {
readonly name: string;
/**
* Codicon name
*/
readonly icon?: string;
readonly label?: string;
readonly placeholder?: string;
readonly supportsQuery?: boolean;

getBranches?(url: string): ProviderResult<string[]>;
getRecentRemoteSources?(query?: string): ProviderResult<RecentRemoteSource[]>;
getRemoteSources(query?: string): ProviderResult<RemoteSource[]>;
}
81 changes: 55 additions & 26 deletions extensions/git-base/src/remoteSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { QuickPickItem, window, QuickPick } from 'vscode';
import { QuickPickItem, window, QuickPick, QuickPickItemKind } from 'vscode';
import * as nls from 'vscode-nls';
import { RemoteSourceProvider, RemoteSource, PickRemoteSourceOptions, PickRemoteSourceResult } from './api/git-base';
import { Model } from './model';
Expand All @@ -24,17 +24,20 @@ async function getQuickPickResult<T extends QuickPickItem>(quickpick: QuickPick<

class RemoteSourceProviderQuickPick {

private quickpick: QuickPick<QuickPickItem & { remoteSource?: RemoteSource }>;
private quickpick: QuickPick<QuickPickItem & { remoteSource?: RemoteSource }> | undefined;

constructor(private provider: RemoteSourceProvider) {
this.quickpick = window.createQuickPick();
this.quickpick.ignoreFocusOut = true;
constructor(private provider: RemoteSourceProvider) { }

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");
private ensureQuickPick() {
if (!this.quickpick) {
this.quickpick = window.createQuickPick();
this.quickpick.ignoreFocusOut = true;
if (this.provider.supportsQuery) {
this.quickpick.placeholder = this.provider.placeholder ?? localize('type to search', "Repository name (type to search)");
this.quickpick.onDidChangeValue(this.onDidChangeValue, this);
} else {
this.quickpick.placeholder = this.provider.placeholder ?? localize('type to filter', "Repository name");
}
}
}

Expand All @@ -45,35 +48,37 @@ class RemoteSourceProviderQuickPick {

@throttle
private async query(): Promise<void> {
this.quickpick.busy = true;

try {
const remoteSources = await this.provider.getRemoteSources(this.quickpick.value) || [];
const remoteSources = await this.provider.getRemoteSources() || [];

this.ensureQuickPick();
this.quickpick!.show();

if (remoteSources.length === 0) {
this.quickpick.items = [{
this.quickpick!.items = [{
label: localize('none found', "No remote repositories found."),
alwaysShow: true
}];
} else {
this.quickpick.items = remoteSources.map(remoteSource => ({
label: remoteSource.name,
this.quickpick!.items = remoteSources.map(remoteSource => ({
label: remoteSource.icon ? `$(${remoteSource.icon}) ${remoteSource.name}` : remoteSource.name,
description: remoteSource.description || (typeof remoteSource.url === 'string' ? remoteSource.url : remoteSource.url[0]),
detail: remoteSource.detail,
remoteSource,
alwaysShow: true
}));
}
} catch (err) {
this.quickpick.items = [{ label: localize('error', "$(error) Error: {0}", err.message), alwaysShow: true }];
this.quickpick!.items = [{ label: localize('error', "$(error) Error: {0}", err.message), alwaysShow: true }];
console.error(err);
} finally {
this.quickpick.busy = false;
this.quickpick!.busy = false;
}
}

async pick(): Promise<RemoteSource | undefined> {
this.query();
const result = await getQuickPickResult(this.quickpick);
await this.query();
const result = await getQuickPickResult(this.quickpick!);
return result?.remoteSource;
}
}
Expand All @@ -83,6 +88,7 @@ export async function pickRemoteSource(model: Model, options: PickRemoteSourceOp
export async function pickRemoteSource(model: Model, options: PickRemoteSourceOptions = {}): Promise<string | PickRemoteSourceResult | undefined> {
const quickpick = window.createQuickPick<(QuickPickItem & { provider?: RemoteSourceProvider; url?: string })>();
quickpick.ignoreFocusOut = true;
quickpick.title = options.title;

if (options.providerName) {
const provider = model.getRemoteProviders()
Expand All @@ -93,24 +99,47 @@ export async function pickRemoteSource(model: Model, options: PickRemoteSourceOp
}
}

const providers = model.getRemoteProviders()
const remoteProviders = model.getRemoteProviders()
.map(provider => ({ label: (provider.icon ? `$(${provider.icon}) ` : '') + (options.providerLabel ? options.providerLabel(provider) : provider.name), alwaysShow: true, provider }));

quickpick.placeholder = providers.length === 0
const recentSources: (QuickPickItem & { url?: string; timestamp: number })[] = [];
if (options.showRecentSources) {
for (const { provider } of remoteProviders) {
const sources = (await provider.getRecentRemoteSources?.() ?? []).map((item) => {
return {
...item,
label: (item.icon ? `$(${item.icon}) ` : '') + item.name,
url: typeof item.url === 'string' ? item.url : item.url[0],
};
});
recentSources.push(...sources);
}
}

const items = [
{ kind: QuickPickItemKind.Separator, label: localize('remote sources', 'remote sources') },
...remoteProviders,
{ kind: QuickPickItemKind.Separator, label: localize('recently opened', 'recently opened') },
...recentSources.sort((a, b) => b.timestamp - a.timestamp)
];

quickpick.placeholder = options.placeholder ?? (remoteProviders.length === 0
? localize('provide url', "Provide repository URL")
: localize('provide url or pick', "Provide repository URL or pick a repository source.");
: localize('provide url or pick', "Provide repository URL or pick a repository source."));

const updatePicks = (value?: string) => {
if (value) {
const label = (typeof options.urlLabel === 'string' ? options.urlLabel : options.urlLabel?.(value)) ?? localize('url', "URL");
quickpick.items = [{
label: options.urlLabel ?? localize('url', "URL"),
label: label,
description: value,
alwaysShow: true,
url: value
},
...providers];
...items
];
} else {
quickpick.items = providers;
quickpick.items = items;
}
};

Expand Down
6 changes: 5 additions & 1 deletion extensions/github/src/remoteSourceProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ function asRemoteSource(raw: any): RemoteSource {
const protocol = workspace.getConfiguration('github').get<'https' | 'ssh'>('gitProtocol');
return {
name: `$(github) ${raw.full_name}`,
description: raw.description || undefined,
description: `${raw.stargazers_count > 0 ? `$(star-full) ${raw.stargazers_count}` : ''
}`,
detail: raw.description || undefined,
url: protocol === 'https' ? raw.clone_url : raw.ssh_url
};
}
Expand Down Expand Up @@ -75,6 +77,8 @@ export class GithubRemoteSourceProvider implements RemoteSourceProvider {
return [];
}

query += ` fork:true`;

const raw = await octokit.search.repos({ q: query, sort: 'stars' });
return raw.data.items.map(asRemoteSource);
}
Expand Down
17 changes: 16 additions & 1 deletion extensions/github/src/typings/git-base.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,12 @@ export interface GitBaseExtension {

export interface PickRemoteSourceOptions {
readonly providerLabel?: (provider: RemoteSourceProvider) => string;
readonly urlLabel?: string;
readonly urlLabel?: string | ((url: string) => string);
readonly providerName?: string;
readonly title?: string;
readonly placeholder?: string;
readonly branch?: boolean; // then result is PickRemoteSourceResult
readonly showRecentSources?: boolean;
}

export interface PickRemoteSourceResult {
Expand All @@ -44,17 +47,29 @@ export interface PickRemoteSourceResult {
export interface RemoteSource {
readonly name: string;
readonly description?: string;
readonly detail?: string;
/**
* Codicon name
*/
readonly icon?: string;
readonly url: string | string[];
}

export interface RecentRemoteSource extends RemoteSource {
readonly timestamp: number;
}

export interface RemoteSourceProvider {
readonly name: string;
/**
* Codicon name
*/
readonly icon?: string;
readonly label?: string;
readonly placeholder?: string;
readonly supportsQuery?: boolean;

getBranches?(url: string): ProviderResult<string[]>;
getRecentRemoteSources?(query?: string): ProviderResult<RecentRemoteSource[]>;
getRemoteSources(query?: string): ProviderResult<RemoteSource[]>;
}