Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEdit.contribution.ts
3296 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 { Registry } from '../../../../../platform/registry/common/platform.js';
7
import { WorkbenchPhase, registerWorkbenchContribution2 } from '../../../../common/contributions.js';
8
import { IBulkEditService, ResourceEdit } from '../../../../../editor/browser/services/bulkEditService.js';
9
import { BulkEditPane } from './bulkEditPane.js';
10
import { IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainerLocation, IViewsRegistry } from '../../../../common/views.js';
11
import { IViewsService } from '../../../../services/views/common/viewsService.js';
12
import { FocusedViewContext } from '../../../../common/contextkeys.js';
13
import { localize, localize2 } from '../../../../../nls.js';
14
import { ViewPaneContainer } from '../../../../browser/parts/views/viewPaneContainer.js';
15
import { RawContextKey, IContextKeyService, IContextKey, ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js';
16
import { IEditorGroupsService } from '../../../../services/editor/common/editorGroupsService.js';
17
import { KeybindingWeight } from '../../../../../platform/keybinding/common/keybindingsRegistry.js';
18
import { KeyMod, KeyCode } from '../../../../../base/common/keyCodes.js';
19
import { WorkbenchListFocusContextKey } from '../../../../../platform/list/browser/listService.js';
20
import { SyncDescriptor } from '../../../../../platform/instantiation/common/descriptors.js';
21
import { MenuId, registerAction2, Action2 } from '../../../../../platform/actions/common/actions.js';
22
import { EditorResourceAccessor, SideBySideEditor } from '../../../../common/editor.js';
23
import { EditorInput } from '../../../../common/editor/editorInput.js';
24
import type { ServicesAccessor } from '../../../../../platform/instantiation/common/instantiation.js';
25
import { CancellationTokenSource } from '../../../../../base/common/cancellation.js';
26
import { IDialogService } from '../../../../../platform/dialogs/common/dialogs.js';
27
import Severity from '../../../../../base/common/severity.js';
28
import { Codicon } from '../../../../../base/common/codicons.js';
29
import { registerIcon } from '../../../../../platform/theme/common/iconRegistry.js';
30
import { IPaneCompositePartService } from '../../../../services/panecomposite/browser/panecomposite.js';
31
32
async function getBulkEditPane(viewsService: IViewsService): Promise<BulkEditPane | undefined> {
33
const view = await viewsService.openView(BulkEditPane.ID, true);
34
if (view instanceof BulkEditPane) {
35
return view;
36
}
37
return undefined;
38
}
39
40
class UXState {
41
42
private readonly _activePanel: string | undefined;
43
44
constructor(
45
@IPaneCompositePartService private readonly _paneCompositeService: IPaneCompositePartService,
46
@IEditorGroupsService private readonly _editorGroupsService: IEditorGroupsService,
47
) {
48
this._activePanel = _paneCompositeService.getActivePaneComposite(ViewContainerLocation.Panel)?.getId();
49
}
50
51
async restore(panels: boolean, editors: boolean): Promise<void> {
52
53
// (1) restore previous panel
54
if (panels) {
55
if (typeof this._activePanel === 'string') {
56
await this._paneCompositeService.openPaneComposite(this._activePanel, ViewContainerLocation.Panel);
57
} else {
58
this._paneCompositeService.hideActivePaneComposite(ViewContainerLocation.Panel);
59
}
60
}
61
62
// (2) close preview editors
63
if (editors) {
64
for (const group of this._editorGroupsService.groups) {
65
const previewEditors: EditorInput[] = [];
66
for (const input of group.editors) {
67
68
const resource = EditorResourceAccessor.getCanonicalUri(input, { supportSideBySide: SideBySideEditor.PRIMARY });
69
if (resource?.scheme === BulkEditPane.Schema) {
70
previewEditors.push(input);
71
}
72
}
73
74
if (previewEditors.length) {
75
group.closeEditors(previewEditors, { preserveFocus: true });
76
}
77
}
78
}
79
}
80
}
81
82
class PreviewSession {
83
constructor(
84
readonly uxState: UXState,
85
readonly cts: CancellationTokenSource = new CancellationTokenSource(),
86
) { }
87
}
88
89
class BulkEditPreviewContribution {
90
91
static readonly ID = 'workbench.contrib.bulkEditPreview';
92
93
static readonly ctxEnabled = new RawContextKey('refactorPreview.enabled', false);
94
95
private readonly _ctxEnabled: IContextKey<boolean>;
96
97
private _activeSession: PreviewSession | undefined;
98
99
constructor(
100
@IPaneCompositePartService private readonly _paneCompositeService: IPaneCompositePartService,
101
@IViewsService private readonly _viewsService: IViewsService,
102
@IEditorGroupsService private readonly _editorGroupsService: IEditorGroupsService,
103
@IDialogService private readonly _dialogService: IDialogService,
104
@IBulkEditService bulkEditService: IBulkEditService,
105
@IContextKeyService contextKeyService: IContextKeyService,
106
) {
107
bulkEditService.setPreviewHandler(edits => this._previewEdit(edits));
108
this._ctxEnabled = BulkEditPreviewContribution.ctxEnabled.bindTo(contextKeyService);
109
}
110
111
private async _previewEdit(edits: ResourceEdit[]): Promise<ResourceEdit[]> {
112
this._ctxEnabled.set(true);
113
114
const uxState = this._activeSession?.uxState ?? new UXState(this._paneCompositeService, this._editorGroupsService);
115
const view = await getBulkEditPane(this._viewsService);
116
if (!view) {
117
this._ctxEnabled.set(false);
118
return edits;
119
}
120
121
// check for active preview session and let the user decide
122
if (view.hasInput()) {
123
const { confirmed } = await this._dialogService.confirm({
124
type: Severity.Info,
125
message: localize('overlap', "Another refactoring is being previewed."),
126
detail: localize('detail', "Press 'Continue' to discard the previous refactoring and continue with the current refactoring."),
127
primaryButton: localize({ key: 'continue', comment: ['&& denotes a mnemonic'] }, "&&Continue")
128
});
129
130
if (!confirmed) {
131
return [];
132
}
133
}
134
135
// session
136
let session: PreviewSession;
137
if (this._activeSession) {
138
await this._activeSession.uxState.restore(false, true);
139
this._activeSession.cts.dispose(true);
140
session = new PreviewSession(uxState);
141
} else {
142
session = new PreviewSession(uxState);
143
}
144
this._activeSession = session;
145
146
// the actual work...
147
try {
148
149
return await view.setInput(edits, session.cts.token) ?? [];
150
151
} finally {
152
// restore UX state
153
if (this._activeSession === session) {
154
await this._activeSession.uxState.restore(true, true);
155
this._activeSession.cts.dispose();
156
this._ctxEnabled.set(false);
157
this._activeSession = undefined;
158
}
159
}
160
}
161
}
162
163
164
// CMD: accept
165
registerAction2(class ApplyAction extends Action2 {
166
167
constructor() {
168
super({
169
id: 'refactorPreview.apply',
170
title: localize2('apply', "Apply Refactoring"),
171
category: localize2('cat', "Refactor Preview"),
172
icon: Codicon.check,
173
precondition: ContextKeyExpr.and(BulkEditPreviewContribution.ctxEnabled, BulkEditPane.ctxHasCheckedChanges),
174
menu: [{
175
id: MenuId.BulkEditContext,
176
order: 1
177
}],
178
keybinding: {
179
weight: KeybindingWeight.EditorContrib - 10,
180
when: ContextKeyExpr.and(BulkEditPreviewContribution.ctxEnabled, FocusedViewContext.isEqualTo(BulkEditPane.ID)),
181
primary: KeyMod.CtrlCmd + KeyCode.Enter,
182
}
183
});
184
}
185
186
async run(accessor: ServicesAccessor): Promise<void> {
187
const viewsService = accessor.get(IViewsService);
188
const view = await getBulkEditPane(viewsService);
189
view?.accept();
190
}
191
});
192
193
// CMD: discard
194
registerAction2(class DiscardAction extends Action2 {
195
196
constructor() {
197
super({
198
id: 'refactorPreview.discard',
199
title: localize2('Discard', "Discard Refactoring"),
200
category: localize2('cat', "Refactor Preview"),
201
icon: Codicon.clearAll,
202
precondition: BulkEditPreviewContribution.ctxEnabled,
203
menu: [{
204
id: MenuId.BulkEditContext,
205
order: 2
206
}]
207
});
208
}
209
210
async run(accessor: ServicesAccessor): Promise<void> {
211
const viewsService = accessor.get(IViewsService);
212
const view = await getBulkEditPane(viewsService);
213
view?.discard();
214
}
215
});
216
217
218
// CMD: toggle change
219
registerAction2(class ToggleAction extends Action2 {
220
221
constructor() {
222
super({
223
id: 'refactorPreview.toggleCheckedState',
224
title: localize2('toogleSelection', "Toggle Change"),
225
category: localize2('cat', "Refactor Preview"),
226
precondition: BulkEditPreviewContribution.ctxEnabled,
227
keybinding: {
228
weight: KeybindingWeight.WorkbenchContrib,
229
when: WorkbenchListFocusContextKey,
230
primary: KeyCode.Space,
231
},
232
menu: {
233
id: MenuId.BulkEditContext,
234
group: 'navigation'
235
}
236
});
237
}
238
239
async run(accessor: ServicesAccessor): Promise<void> {
240
const viewsService = accessor.get(IViewsService);
241
const view = await getBulkEditPane(viewsService);
242
view?.toggleChecked();
243
}
244
});
245
246
247
// CMD: toggle category
248
registerAction2(class GroupByFile extends Action2 {
249
250
constructor() {
251
super({
252
id: 'refactorPreview.groupByFile',
253
title: localize2('groupByFile', "Group Changes By File"),
254
category: localize2('cat', "Refactor Preview"),
255
icon: Codicon.ungroupByRefType,
256
precondition: ContextKeyExpr.and(BulkEditPane.ctxHasCategories, BulkEditPane.ctxGroupByFile.negate(), BulkEditPreviewContribution.ctxEnabled),
257
menu: [{
258
id: MenuId.BulkEditTitle,
259
when: ContextKeyExpr.and(BulkEditPane.ctxHasCategories, BulkEditPane.ctxGroupByFile.negate()),
260
group: 'navigation',
261
order: 3,
262
}]
263
});
264
}
265
266
async run(accessor: ServicesAccessor): Promise<void> {
267
const viewsService = accessor.get(IViewsService);
268
const view = await getBulkEditPane(viewsService);
269
view?.groupByFile();
270
}
271
});
272
273
registerAction2(class GroupByType extends Action2 {
274
275
constructor() {
276
super({
277
id: 'refactorPreview.groupByType',
278
title: localize2('groupByType', "Group Changes By Type"),
279
category: localize2('cat', "Refactor Preview"),
280
icon: Codicon.groupByRefType,
281
precondition: ContextKeyExpr.and(BulkEditPane.ctxHasCategories, BulkEditPane.ctxGroupByFile, BulkEditPreviewContribution.ctxEnabled),
282
menu: [{
283
id: MenuId.BulkEditTitle,
284
when: ContextKeyExpr.and(BulkEditPane.ctxHasCategories, BulkEditPane.ctxGroupByFile),
285
group: 'navigation',
286
order: 3
287
}]
288
});
289
}
290
291
async run(accessor: ServicesAccessor): Promise<void> {
292
const viewsService = accessor.get(IViewsService);
293
const view = await getBulkEditPane(viewsService);
294
view?.groupByType();
295
}
296
});
297
298
registerAction2(class ToggleGrouping extends Action2 {
299
300
constructor() {
301
super({
302
id: 'refactorPreview.toggleGrouping',
303
title: localize2('groupByType', "Group Changes By Type"),
304
category: localize2('cat', "Refactor Preview"),
305
icon: Codicon.listTree,
306
toggled: BulkEditPane.ctxGroupByFile.negate(),
307
precondition: ContextKeyExpr.and(BulkEditPane.ctxHasCategories, BulkEditPreviewContribution.ctxEnabled),
308
menu: [{
309
id: MenuId.BulkEditContext,
310
order: 3
311
}]
312
});
313
}
314
315
async run(accessor: ServicesAccessor): Promise<void> {
316
const viewsService = accessor.get(IViewsService);
317
const view = await getBulkEditPane(viewsService);
318
view?.toggleGrouping();
319
}
320
});
321
322
registerWorkbenchContribution2(
323
BulkEditPreviewContribution.ID, BulkEditPreviewContribution, WorkbenchPhase.BlockRestore
324
);
325
326
const refactorPreviewViewIcon = registerIcon('refactor-preview-view-icon', Codicon.lightbulb, localize('refactorPreviewViewIcon', 'View icon of the refactor preview view.'));
327
328
const container = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({
329
id: BulkEditPane.ID,
330
title: localize2('panel', "Refactor Preview"),
331
hideIfEmpty: true,
332
ctorDescriptor: new SyncDescriptor(
333
ViewPaneContainer,
334
[BulkEditPane.ID, { mergeViewWithContainerWhenSingleView: true }]
335
),
336
icon: refactorPreviewViewIcon,
337
storageId: BulkEditPane.ID
338
}, ViewContainerLocation.Panel);
339
340
Registry.as<IViewsRegistry>(ViewContainerExtensions.ViewsRegistry).registerViews([{
341
id: BulkEditPane.ID,
342
name: localize2('panel', "Refactor Preview"),
343
when: BulkEditPreviewContribution.ctxEnabled,
344
ctorDescriptor: new SyncDescriptor(BulkEditPane),
345
containerIcon: refactorPreviewViewIcon,
346
}], container);
347
348