Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/git-base/src/remoteSource.ts
5223 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { QuickPickItem, window, QuickPick, QuickPickItemKind, l10n, Disposable } from 'vscode';
7
import { RemoteSourceProvider, RemoteSource, PickRemoteSourceOptions, PickRemoteSourceResult, RemoteSourceAction } from './api/git-base';
8
import { Model } from './model';
9
import { throttle, debounce } from './decorators';
10
11
async function getQuickPickResult<T extends QuickPickItem>(quickpick: QuickPick<T>): Promise<T | undefined> {
12
const listeners: Disposable[] = [];
13
const result = await new Promise<T | undefined>(c => {
14
listeners.push(
15
quickpick.onDidAccept(() => c(quickpick.selectedItems[0])),
16
quickpick.onDidHide(() => c(undefined)),
17
);
18
quickpick.show();
19
});
20
21
quickpick.hide();
22
listeners.forEach(l => l.dispose());
23
return result;
24
}
25
26
class RemoteSourceProviderQuickPick implements Disposable {
27
28
private disposables: Disposable[] = [];
29
private isDisposed: boolean = false;
30
31
private quickpick: QuickPick<QuickPickItem & { remoteSource?: RemoteSource }> | undefined;
32
33
constructor(private provider: RemoteSourceProvider) { }
34
35
dispose() {
36
this.disposables.forEach(d => d.dispose());
37
this.disposables = [];
38
this.quickpick = undefined;
39
this.isDisposed = true;
40
}
41
42
private ensureQuickPick() {
43
if (!this.quickpick) {
44
this.quickpick = window.createQuickPick();
45
this.disposables.push(this.quickpick);
46
this.quickpick.ignoreFocusOut = true;
47
this.disposables.push(this.quickpick.onDidHide(() => this.dispose()));
48
if (this.provider.supportsQuery) {
49
this.quickpick.placeholder = this.provider.placeholder ?? l10n.t('Repository name (type to search)');
50
this.disposables.push(this.quickpick.onDidChangeValue(this.onDidChangeValue, this));
51
} else {
52
this.quickpick.placeholder = this.provider.placeholder ?? l10n.t('Repository name');
53
}
54
}
55
}
56
57
@debounce(300)
58
private onDidChangeValue(): void {
59
this.query();
60
}
61
62
@throttle
63
private async query(): Promise<void> {
64
try {
65
if (this.isDisposed) {
66
return;
67
}
68
this.ensureQuickPick();
69
this.quickpick!.busy = true;
70
this.quickpick!.show();
71
72
const remoteSources = await this.provider.getRemoteSources(this.quickpick?.value) || [];
73
// The user may have cancelled the picker in the meantime
74
if (this.isDisposed) {
75
return;
76
}
77
78
if (remoteSources.length === 0) {
79
this.quickpick!.items = [{
80
label: l10n.t('No remote repositories found.'),
81
alwaysShow: true
82
}];
83
} else {
84
this.quickpick!.items = remoteSources.map(remoteSource => ({
85
label: remoteSource.icon ? `$(${remoteSource.icon}) ${remoteSource.name}` : remoteSource.name,
86
description: remoteSource.description || (typeof remoteSource.url === 'string' ? remoteSource.url : remoteSource.url[0]),
87
detail: remoteSource.detail,
88
remoteSource,
89
alwaysShow: true
90
}));
91
}
92
} catch (err) {
93
this.quickpick!.items = [{ label: l10n.t('{0} Error: {1}', '$(error)', err.message), alwaysShow: true }];
94
console.error(err);
95
} finally {
96
if (!this.isDisposed) {
97
this.quickpick!.busy = false;
98
}
99
}
100
}
101
102
async pick(): Promise<RemoteSource | undefined> {
103
await this.query();
104
if (this.isDisposed) {
105
return;
106
}
107
const result = await getQuickPickResult(this.quickpick!);
108
return result?.remoteSource;
109
}
110
}
111
112
export async function getRemoteSourceActions(model: Model, url: string): Promise<RemoteSourceAction[]> {
113
const providers = model.getRemoteProviders();
114
115
const remoteSourceActions = [];
116
for (const provider of providers) {
117
const providerActions = await provider.getRemoteSourceActions?.(url);
118
if (providerActions?.length) {
119
remoteSourceActions.push(...providerActions);
120
}
121
}
122
123
return remoteSourceActions;
124
}
125
126
export async function pickRemoteSource(model: Model, options: PickRemoteSourceOptions): Promise<string | PickRemoteSourceResult | undefined>;
127
export async function pickRemoteSource(model: Model, options: PickRemoteSourceOptions & { branch?: false | undefined }): Promise<string | undefined>;
128
export async function pickRemoteSource(model: Model, options: PickRemoteSourceOptions & { branch: true }): Promise<PickRemoteSourceResult | undefined>;
129
export async function pickRemoteSource(model: Model, options: PickRemoteSourceOptions = {}): Promise<string | PickRemoteSourceResult | undefined> {
130
const quickpick = window.createQuickPick<(QuickPickItem & { provider?: RemoteSourceProvider; url?: string })>();
131
quickpick.title = options.title;
132
133
if (options.providerName) {
134
const provider = model.getRemoteProviders()
135
.filter(provider => provider.name === options.providerName)[0];
136
137
if (provider) {
138
return await pickProviderSource(provider, options);
139
}
140
}
141
142
const remoteProviders = model.getRemoteProviders()
143
.map(provider => ({ label: (provider.icon ? `$(${provider.icon}) ` : '') + (options.providerLabel ? options.providerLabel(provider) : provider.name), alwaysShow: true, provider }));
144
145
const recentSources: (QuickPickItem & { url?: string; timestamp: number })[] = [];
146
if (options.showRecentSources) {
147
for (const { provider } of remoteProviders) {
148
const sources = (await provider.getRecentRemoteSources?.() ?? []).map((item) => {
149
return {
150
...item,
151
label: (item.icon ? `$(${item.icon}) ` : '') + item.name,
152
url: typeof item.url === 'string' ? item.url : item.url[0],
153
};
154
});
155
recentSources.push(...sources);
156
}
157
}
158
159
const items = [
160
{ kind: QuickPickItemKind.Separator, label: l10n.t('remote sources') },
161
...remoteProviders,
162
{ kind: QuickPickItemKind.Separator, label: l10n.t('recently opened') },
163
...recentSources.sort((a, b) => b.timestamp - a.timestamp)
164
];
165
166
quickpick.placeholder = options.placeholder ?? (remoteProviders.length === 0
167
? l10n.t('Provide repository URL')
168
: l10n.t('Provide repository URL or pick a repository source.'));
169
170
const updatePicks = (value?: string) => {
171
if (value) {
172
const label = (typeof options.urlLabel === 'string' ? options.urlLabel : options.urlLabel?.(value)) ?? l10n.t('URL');
173
quickpick.items = [{
174
label: label,
175
description: value,
176
alwaysShow: true,
177
url: value
178
},
179
...items
180
];
181
} else {
182
quickpick.items = items;
183
}
184
};
185
186
quickpick.onDidChangeValue(updatePicks);
187
updatePicks();
188
189
const result = await getQuickPickResult(quickpick);
190
191
if (result) {
192
if (result.url) {
193
return result.url;
194
} else if (result.provider) {
195
return await pickProviderSource(result.provider, options);
196
}
197
}
198
199
return undefined;
200
}
201
202
async function pickProviderSource(provider: RemoteSourceProvider, options: PickRemoteSourceOptions = {}): Promise<string | PickRemoteSourceResult | undefined> {
203
const quickpick = new RemoteSourceProviderQuickPick(provider);
204
const remote = await quickpick.pick();
205
quickpick.dispose();
206
207
let url: string | undefined;
208
209
if (remote) {
210
if (typeof remote.url === 'string') {
211
url = remote.url;
212
} else if (remote.url.length > 0) {
213
url = await window.showQuickPick(remote.url, { ignoreFocusOut: true, placeHolder: l10n.t('Choose a URL to clone from.') });
214
}
215
}
216
217
if (!url || !options.branch) {
218
return url;
219
}
220
221
if (!provider.getBranches) {
222
return { url };
223
}
224
225
const branches = await provider.getBranches(url);
226
227
if (!branches) {
228
return { url };
229
}
230
231
const branch = await window.showQuickPick(branches, {
232
placeHolder: l10n.t('Branch name')
233
});
234
235
if (!branch) {
236
return { url };
237
}
238
239
return { url, branch };
240
}
241
242