Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/git/src/statusbar.ts
3314 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
return this.repository.isBranchProtected() ? '$(lock)' : '$(git-branch)';
75
}
76
77
// Tag
78
if (this.repository.HEAD.type === RefType.Tag) {
79
return '$(tag)';
80
}
81
82
// Commit
83
return '$(git-commit)';
84
}
85
86
private getTooltip(): string {
87
if (this.state.isCheckoutRunning) {
88
return l10n.t('Checking Out Branch/Tag...');
89
}
90
91
if (this.state.isCommitRunning) {
92
return l10n.t('Committing Changes...');
93
94
}
95
96
if (this.state.isSyncRunning) {
97
return l10n.t('Synchronizing Changes...');
98
}
99
100
return l10n.t('Checkout Branch/Tag...');
101
}
102
103
private onDidChangeOperations(): void {
104
const isCommitRunning = this.repository.operations.isRunning(OperationKind.Commit);
105
const isCheckoutRunning = this.repository.operations.isRunning(OperationKind.Checkout) ||
106
this.repository.operations.isRunning(OperationKind.CheckoutTracking);
107
const isSyncRunning = this.repository.operations.isRunning(OperationKind.Sync) ||
108
this.repository.operations.isRunning(OperationKind.Push) ||
109
this.repository.operations.isRunning(OperationKind.Pull);
110
111
this.state = { ...this.state, isCheckoutRunning, isCommitRunning, isSyncRunning };
112
}
113
114
dispose(): void {
115
this.disposables.forEach(d => d.dispose());
116
}
117
}
118
119
interface SyncStatusBarState {
120
readonly enabled: boolean;
121
readonly isCheckoutRunning: boolean;
122
readonly isCommitRunning: boolean;
123
readonly isSyncRunning: boolean;
124
readonly hasRemotes: boolean;
125
readonly HEAD: Branch | undefined;
126
readonly remoteSourcePublishers: RemoteSourcePublisher[];
127
}
128
129
class SyncStatusBar {
130
131
private _onDidChange = new EventEmitter<void>();
132
get onDidChange(): Event<void> { return this._onDidChange.event; }
133
private disposables: Disposable[] = [];
134
135
private _state: SyncStatusBarState;
136
private get state() { return this._state; }
137
private set state(state: SyncStatusBarState) {
138
this._state = state;
139
this._onDidChange.fire();
140
}
141
142
constructor(private repository: Repository, private remoteSourcePublisherRegistry: IRemoteSourcePublisherRegistry) {
143
this._state = {
144
enabled: true,
145
isCheckoutRunning: false,
146
isCommitRunning: false,
147
isSyncRunning: false,
148
hasRemotes: false,
149
HEAD: undefined,
150
remoteSourcePublishers: remoteSourcePublisherRegistry.getRemoteSourcePublishers()
151
};
152
153
repository.onDidRunGitStatus(this.onDidRunGitStatus, this, this.disposables);
154
repository.onDidChangeOperations(this.onDidChangeOperations, this, this.disposables);
155
156
anyEvent(remoteSourcePublisherRegistry.onDidAddRemoteSourcePublisher, remoteSourcePublisherRegistry.onDidRemoveRemoteSourcePublisher)
157
(this.onDidChangeRemoteSourcePublishers, this, this.disposables);
158
159
const onEnablementChange = filterEvent(workspace.onDidChangeConfiguration, e => e.affectsConfiguration('git.enableStatusBarSync'));
160
onEnablementChange(this.updateEnablement, this, this.disposables);
161
this.updateEnablement();
162
}
163
164
private updateEnablement(): void {
165
const config = workspace.getConfiguration('git', Uri.file(this.repository.root));
166
const enabled = config.get<boolean>('enableStatusBarSync', true);
167
168
this.state = { ... this.state, enabled };
169
}
170
171
private onDidChangeOperations(): void {
172
const isCommitRunning = this.repository.operations.isRunning(OperationKind.Commit);
173
const isCheckoutRunning = this.repository.operations.isRunning(OperationKind.Checkout) ||
174
this.repository.operations.isRunning(OperationKind.CheckoutTracking);
175
const isSyncRunning = this.repository.operations.isRunning(OperationKind.Sync) ||
176
this.repository.operations.isRunning(OperationKind.Push) ||
177
this.repository.operations.isRunning(OperationKind.Pull);
178
179
this.state = { ...this.state, isCheckoutRunning, isCommitRunning, isSyncRunning };
180
}
181
182
private onDidRunGitStatus(): void {
183
this.state = {
184
...this.state,
185
hasRemotes: this.repository.remotes.length > 0,
186
HEAD: this.repository.HEAD
187
};
188
}
189
190
private onDidChangeRemoteSourcePublishers(): void {
191
this.state = {
192
...this.state,
193
remoteSourcePublishers: this.remoteSourcePublisherRegistry.getRemoteSourcePublishers()
194
};
195
}
196
197
get command(): Command | undefined {
198
if (!this.state.enabled) {
199
return;
200
}
201
202
if (!this.state.hasRemotes) {
203
if (this.state.remoteSourcePublishers.length === 0) {
204
return;
205
}
206
207
const command = (this.state.isCheckoutRunning || this.state.isCommitRunning) ? '' : 'git.publish';
208
const tooltip =
209
this.state.isCheckoutRunning ? l10n.t('Checking Out Changes...') :
210
this.state.isCommitRunning ? l10n.t('Committing Changes...') :
211
this.state.remoteSourcePublishers.length === 1
212
? l10n.t('Publish to {0}', this.state.remoteSourcePublishers[0].name)
213
: l10n.t('Publish to...');
214
215
return {
216
command,
217
title: `$(cloud-upload)`,
218
tooltip,
219
arguments: [this.repository.sourceControl]
220
};
221
}
222
223
const HEAD = this.state.HEAD;
224
let icon = '$(sync)';
225
let text = '';
226
let command = '';
227
let tooltip = '';
228
229
if (HEAD && HEAD.name && HEAD.commit) {
230
if (HEAD.upstream) {
231
if (HEAD.ahead || HEAD.behind) {
232
text += this.repository.syncLabel;
233
}
234
235
command = 'git.sync';
236
tooltip = this.repository.syncTooltip;
237
} else {
238
icon = '$(cloud-upload)';
239
command = 'git.publish';
240
tooltip = l10n.t('Publish Branch');
241
}
242
} else {
243
command = '';
244
tooltip = '';
245
}
246
247
if (this.state.isCheckoutRunning) {
248
command = '';
249
tooltip = l10n.t('Checking Out Changes...');
250
}
251
252
if (this.state.isCommitRunning) {
253
command = '';
254
tooltip = l10n.t('Committing Changes...');
255
}
256
257
if (this.state.isSyncRunning) {
258
icon = '$(sync~spin)';
259
command = '';
260
tooltip = l10n.t('Synchronizing Changes...');
261
}
262
263
return {
264
command,
265
title: [icon, text].join(' ').trim(),
266
tooltip,
267
arguments: [this.repository.sourceControl]
268
};
269
}
270
271
dispose(): void {
272
this.disposables.forEach(d => d.dispose());
273
}
274
}
275
276
export class StatusBarCommands {
277
278
readonly onDidChange: Event<void>;
279
280
private syncStatusBar: SyncStatusBar;
281
private checkoutStatusBar: CheckoutStatusBar;
282
private disposables: Disposable[] = [];
283
284
constructor(repository: Repository, remoteSourcePublisherRegistry: IRemoteSourcePublisherRegistry) {
285
this.syncStatusBar = new SyncStatusBar(repository, remoteSourcePublisherRegistry);
286
this.checkoutStatusBar = new CheckoutStatusBar(repository);
287
this.onDidChange = anyEvent(this.syncStatusBar.onDidChange, this.checkoutStatusBar.onDidChange);
288
}
289
290
get commands(): Command[] {
291
return [this.checkoutStatusBar.command, this.syncStatusBar.command]
292
.filter((c): c is Command => !!c);
293
}
294
295
dispose(): void {
296
this.syncStatusBar.dispose();
297
this.checkoutStatusBar.dispose();
298
this.disposables = dispose(this.disposables);
299
}
300
}
301
302