Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/git/src/statusbar.ts
5228 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 { Disposable, Command, EventEmitter, Event, workspace, Uri, l10n } from 'vscode';
7
import { Repository } from './repository';
8
import { anyEvent, dispose, filterEvent } from './util';
9
import { Branch, RefType, RemoteSourcePublisher } from './api/git';
10
import { IRemoteSourcePublisherRegistry } from './remotePublisher';
11
import { CheckoutOperation, CheckoutTrackingOperation, OperationKind } from './operation';
12
13
interface CheckoutStatusBarState {
14
readonly isCheckoutRunning: boolean;
15
readonly isCommitRunning: boolean;
16
readonly isSyncRunning: boolean;
17
}
18
19
class CheckoutStatusBar {
20
21
private _onDidChange = new EventEmitter<void>();
22
get onDidChange(): Event<void> { return this._onDidChange.event; }
23
private disposables: Disposable[] = [];
24
25
private _state: CheckoutStatusBarState;
26
private get state() { return this._state; }
27
private set state(state: CheckoutStatusBarState) {
28
this._state = state;
29
this._onDidChange.fire();
30
}
31
32
constructor(private repository: Repository) {
33
this._state = {
34
isCheckoutRunning: false,
35
isCommitRunning: false,
36
isSyncRunning: false
37
};
38
39
repository.onDidChangeOperations(this.onDidChangeOperations, this, this.disposables);
40
repository.onDidRunGitStatus(this._onDidChange.fire, this._onDidChange, this.disposables);
41
repository.onDidChangeBranchProtection(this._onDidChange.fire, this._onDidChange, this.disposables);
42
}
43
44
get command(): Command | undefined {
45
const operationData = [
46
...this.repository.operations.getOperations(OperationKind.Checkout) as CheckoutOperation[],
47
...this.repository.operations.getOperations(OperationKind.CheckoutTracking) as CheckoutTrackingOperation[]
48
];
49
50
const rebasing = !!this.repository.rebaseCommit;
51
const label = operationData[0]?.refLabel ?? `${this.repository.headLabel}${rebasing ? ` (${l10n.t('Rebasing')})` : ''}`;
52
const command = (this.state.isCheckoutRunning || this.state.isCommitRunning || this.state.isSyncRunning) ? '' : 'git.checkout';
53
54
return {
55
command,
56
tooltip: `${label}, ${this.getTooltip()}`,
57
title: `${this.getIcon()} ${label}`,
58
arguments: [this.repository.sourceControl]
59
};
60
}
61
62
private getIcon(): string {
63
if (!this.repository.HEAD) {
64
return '';
65
}
66
67
// Checkout
68
if (this.state.isCheckoutRunning) {
69
return '$(loading~spin)';
70
}
71
72
// Branch
73
if (this.repository.HEAD.type === RefType.Head && this.repository.HEAD.name) {
74
switch (true) {
75
case this.repository.isBranchProtected():
76
return '$(lock)';
77
case this.repository.mergeInProgress || !!this.repository.rebaseCommit:
78
return '$(git-branch-conflicts)';
79
case this.repository.indexGroup.resourceStates.length > 0:
80
return '$(git-branch-staged-changes)';
81
case this.repository.workingTreeGroup.resourceStates.length + this.repository.untrackedGroup.resourceStates.length > 0:
82
return '$(git-branch-changes)';
83
default:
84
return '$(git-branch)';
85
}
86
}
87
88
// Tag
89
if (this.repository.HEAD.type === RefType.Tag) {
90
return '$(tag)';
91
}
92
93
// Commit
94
return '$(git-commit)';
95
}
96
97
private getTooltip(): string {
98
if (this.state.isCheckoutRunning) {
99
return l10n.t('Checking Out Branch/Tag...');
100
}
101
102
if (this.state.isCommitRunning) {
103
return l10n.t('Committing Changes...');
104
105
}
106
107
if (this.state.isSyncRunning) {
108
return l10n.t('Synchronizing Changes...');
109
}
110
111
return l10n.t('Checkout Branch/Tag...');
112
}
113
114
private onDidChangeOperations(): void {
115
const isCommitRunning = this.repository.operations.isRunning(OperationKind.Commit);
116
const isCheckoutRunning = this.repository.operations.isRunning(OperationKind.Checkout) ||
117
this.repository.operations.isRunning(OperationKind.CheckoutTracking);
118
const isSyncRunning = this.repository.operations.isRunning(OperationKind.Sync) ||
119
this.repository.operations.isRunning(OperationKind.Push) ||
120
this.repository.operations.isRunning(OperationKind.Pull);
121
122
this.state = { ...this.state, isCheckoutRunning, isCommitRunning, isSyncRunning };
123
}
124
125
dispose(): void {
126
this.disposables.forEach(d => d.dispose());
127
}
128
}
129
130
interface SyncStatusBarState {
131
readonly enabled: boolean;
132
readonly isCheckoutRunning: boolean;
133
readonly isCommitRunning: boolean;
134
readonly isSyncRunning: boolean;
135
readonly hasRemotes: boolean;
136
readonly HEAD: Branch | undefined;
137
readonly remoteSourcePublishers: RemoteSourcePublisher[];
138
}
139
140
class SyncStatusBar {
141
142
private _onDidChange = new EventEmitter<void>();
143
get onDidChange(): Event<void> { return this._onDidChange.event; }
144
private disposables: Disposable[] = [];
145
146
private _state: SyncStatusBarState;
147
private get state() { return this._state; }
148
private set state(state: SyncStatusBarState) {
149
this._state = state;
150
this._onDidChange.fire();
151
}
152
153
constructor(private repository: Repository, private remoteSourcePublisherRegistry: IRemoteSourcePublisherRegistry) {
154
this._state = {
155
enabled: true,
156
isCheckoutRunning: false,
157
isCommitRunning: false,
158
isSyncRunning: false,
159
hasRemotes: false,
160
HEAD: undefined,
161
remoteSourcePublishers: remoteSourcePublisherRegistry.getRemoteSourcePublishers()
162
};
163
164
repository.onDidRunGitStatus(this.onDidRunGitStatus, this, this.disposables);
165
repository.onDidChangeOperations(this.onDidChangeOperations, this, this.disposables);
166
167
anyEvent(remoteSourcePublisherRegistry.onDidAddRemoteSourcePublisher, remoteSourcePublisherRegistry.onDidRemoveRemoteSourcePublisher)
168
(this.onDidChangeRemoteSourcePublishers, this, this.disposables);
169
170
const onEnablementChange = filterEvent(workspace.onDidChangeConfiguration, e => e.affectsConfiguration('git.enableStatusBarSync'));
171
onEnablementChange(this.updateEnablement, this, this.disposables);
172
this.updateEnablement();
173
}
174
175
private updateEnablement(): void {
176
const config = workspace.getConfiguration('git', Uri.file(this.repository.root));
177
const enabled = config.get<boolean>('enableStatusBarSync', true);
178
179
this.state = { ... this.state, enabled };
180
}
181
182
private onDidChangeOperations(): void {
183
const isCommitRunning = this.repository.operations.isRunning(OperationKind.Commit);
184
const isCheckoutRunning = this.repository.operations.isRunning(OperationKind.Checkout) ||
185
this.repository.operations.isRunning(OperationKind.CheckoutTracking);
186
const isSyncRunning = this.repository.operations.isRunning(OperationKind.Sync) ||
187
this.repository.operations.isRunning(OperationKind.Push) ||
188
this.repository.operations.isRunning(OperationKind.Pull);
189
190
this.state = { ...this.state, isCheckoutRunning, isCommitRunning, isSyncRunning };
191
}
192
193
private onDidRunGitStatus(): void {
194
this.state = {
195
...this.state,
196
hasRemotes: this.repository.remotes.length > 0,
197
HEAD: this.repository.HEAD
198
};
199
}
200
201
private onDidChangeRemoteSourcePublishers(): void {
202
this.state = {
203
...this.state,
204
remoteSourcePublishers: this.remoteSourcePublisherRegistry.getRemoteSourcePublishers()
205
};
206
}
207
208
get command(): Command | undefined {
209
if (!this.state.enabled) {
210
return;
211
}
212
213
if (!this.state.hasRemotes) {
214
if (this.state.remoteSourcePublishers.length === 0) {
215
return;
216
}
217
218
const command = (this.state.isCheckoutRunning || this.state.isCommitRunning) ? '' : 'git.publish';
219
const tooltip =
220
this.state.isCheckoutRunning ? l10n.t('Checking Out Changes...') :
221
this.state.isCommitRunning ? l10n.t('Committing Changes...') :
222
this.state.remoteSourcePublishers.length === 1
223
? l10n.t('Publish to {0}', this.state.remoteSourcePublishers[0].name)
224
: l10n.t('Publish to...');
225
226
return {
227
command,
228
title: `$(cloud-upload)`,
229
tooltip,
230
arguments: [this.repository.sourceControl]
231
};
232
}
233
234
const HEAD = this.state.HEAD;
235
let icon = '$(sync)';
236
let text = '';
237
let command = '';
238
let tooltip = '';
239
240
if (HEAD && HEAD.name && HEAD.commit) {
241
if (HEAD.upstream) {
242
if (HEAD.ahead || HEAD.behind) {
243
text += this.repository.syncLabel;
244
}
245
246
command = 'git.sync';
247
tooltip = this.repository.syncTooltip;
248
} else {
249
icon = '$(cloud-upload)';
250
command = 'git.publish';
251
tooltip = l10n.t('Publish Branch');
252
}
253
} else {
254
command = '';
255
tooltip = '';
256
}
257
258
if (this.state.isCheckoutRunning) {
259
command = '';
260
tooltip = l10n.t('Checking Out Changes...');
261
}
262
263
if (this.state.isCommitRunning) {
264
command = '';
265
tooltip = l10n.t('Committing Changes...');
266
}
267
268
if (this.state.isSyncRunning) {
269
icon = '$(sync~spin)';
270
command = '';
271
tooltip = l10n.t('Synchronizing Changes...');
272
}
273
274
return {
275
command,
276
title: [icon, text].join(' ').trim(),
277
tooltip,
278
arguments: [this.repository.sourceControl]
279
};
280
}
281
282
dispose(): void {
283
this.disposables.forEach(d => d.dispose());
284
}
285
}
286
287
export class StatusBarCommands {
288
289
readonly onDidChange: Event<void>;
290
291
private syncStatusBar: SyncStatusBar;
292
private checkoutStatusBar: CheckoutStatusBar;
293
private disposables: Disposable[] = [];
294
295
constructor(private readonly repository: Repository, remoteSourcePublisherRegistry: IRemoteSourcePublisherRegistry) {
296
this.syncStatusBar = new SyncStatusBar(repository, remoteSourcePublisherRegistry);
297
this.checkoutStatusBar = new CheckoutStatusBar(repository);
298
this.onDidChange = anyEvent(this.syncStatusBar.onDidChange, this.checkoutStatusBar.onDidChange);
299
}
300
301
get commands(): Command[] {
302
if (this.repository.isHidden) {
303
return [];
304
}
305
306
return [this.checkoutStatusBar.command, this.syncStatusBar.command]
307
.filter((c): c is Command => !!c);
308
}
309
310
dispose(): void {
311
this.syncStatusBar.dispose();
312
this.checkoutStatusBar.dispose();
313
this.disposables = dispose(this.disposables);
314
}
315
}
316
317