Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/files/browser/explorerViewlet.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 './media/explorerviewlet.css';
7
import { localize, localize2 } from '../../../../nls.js';
8
import { mark } from '../../../../base/common/performance.js';
9
import { VIEWLET_ID, VIEW_ID, IFilesConfiguration, ExplorerViewletVisibleContext } from '../common/files.js';
10
import { IViewletViewOptions } from '../../../browser/parts/views/viewsViewlet.js';
11
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
12
import { ExplorerView } from './views/explorerView.js';
13
import { EmptyView } from './views/emptyView.js';
14
import { OpenEditorsView } from './views/openEditorsView.js';
15
import { IStorageService } from '../../../../platform/storage/common/storage.js';
16
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
17
import { IExtensionService } from '../../../services/extensions/common/extensions.js';
18
import { IWorkspaceContextService, WorkbenchState } from '../../../../platform/workspace/common/workspace.js';
19
import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';
20
import { IContextKeyService, IContextKey, ContextKeyExpr } from '../../../../platform/contextkey/common/contextkey.js';
21
import { IThemeService } from '../../../../platform/theme/common/themeService.js';
22
import { IViewsRegistry, IViewDescriptor, Extensions, ViewContainer, IViewContainersRegistry, ViewContainerLocation, IViewDescriptorService, ViewContentGroups } from '../../../common/views.js';
23
import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js';
24
import { Disposable } from '../../../../base/common/lifecycle.js';
25
import { IWorkbenchContribution } from '../../../common/contributions.js';
26
import { IWorkbenchLayoutService } from '../../../services/layout/browser/layoutService.js';
27
import { ViewPaneContainer } from '../../../browser/parts/views/viewPaneContainer.js';
28
import { ViewPane } from '../../../browser/parts/views/viewPane.js';
29
import { KeyChord, KeyMod, KeyCode } from '../../../../base/common/keyCodes.js';
30
import { Registry } from '../../../../platform/registry/common/platform.js';
31
import { IProgressService, ProgressLocation } from '../../../../platform/progress/common/progress.js';
32
import { SyncDescriptor } from '../../../../platform/instantiation/common/descriptors.js';
33
import { WorkbenchStateContext, RemoteNameContext, OpenFolderWorkspaceSupportContext } from '../../../common/contextkeys.js';
34
import { IsWebContext } from '../../../../platform/contextkey/common/contextkeys.js';
35
import { AddRootFolderAction, OpenFolderAction, OpenFolderViaWorkspaceAction } from '../../../browser/actions/workspaceActions.js';
36
import { OpenRecentAction } from '../../../browser/actions/windowActions.js';
37
import { Codicon } from '../../../../base/common/codicons.js';
38
import { registerIcon } from '../../../../platform/theme/common/iconRegistry.js';
39
import { isMouseEvent } from '../../../../base/browser/dom.js';
40
import { ILogService } from '../../../../platform/log/common/log.js';
41
42
const explorerViewIcon = registerIcon('explorer-view-icon', Codicon.files, localize('explorerViewIcon', 'View icon of the explorer view.'));
43
const openEditorsViewIcon = registerIcon('open-editors-view-icon', Codicon.book, localize('openEditorsIcon', 'View icon of the open editors view.'));
44
45
export class ExplorerViewletViewsContribution extends Disposable implements IWorkbenchContribution {
46
47
static readonly ID = 'workbench.contrib.explorerViewletViews';
48
49
constructor(
50
@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService,
51
@IProgressService progressService: IProgressService
52
) {
53
super();
54
55
progressService.withProgress({ location: ProgressLocation.Explorer }, () => workspaceContextService.getCompleteWorkspace()).finally(() => {
56
this.registerViews();
57
58
this._register(workspaceContextService.onDidChangeWorkbenchState(() => this.registerViews()));
59
this._register(workspaceContextService.onDidChangeWorkspaceFolders(() => this.registerViews()));
60
});
61
}
62
63
private registerViews(): void {
64
mark('code/willRegisterExplorerViews');
65
66
const viewDescriptors = viewsRegistry.getViews(VIEW_CONTAINER);
67
68
const viewDescriptorsToRegister: IViewDescriptor[] = [];
69
const viewDescriptorsToDeregister: IViewDescriptor[] = [];
70
71
const openEditorsViewDescriptor = this.createOpenEditorsViewDescriptor();
72
if (!viewDescriptors.some(v => v.id === openEditorsViewDescriptor.id)) {
73
viewDescriptorsToRegister.push(openEditorsViewDescriptor);
74
}
75
76
const explorerViewDescriptor = this.createExplorerViewDescriptor();
77
const registeredExplorerViewDescriptor = viewDescriptors.find(v => v.id === explorerViewDescriptor.id);
78
const emptyViewDescriptor = this.createEmptyViewDescriptor();
79
const registeredEmptyViewDescriptor = viewDescriptors.find(v => v.id === emptyViewDescriptor.id);
80
81
if (this.workspaceContextService.getWorkbenchState() === WorkbenchState.EMPTY || this.workspaceContextService.getWorkspace().folders.length === 0) {
82
if (registeredExplorerViewDescriptor) {
83
viewDescriptorsToDeregister.push(registeredExplorerViewDescriptor);
84
}
85
if (!registeredEmptyViewDescriptor) {
86
viewDescriptorsToRegister.push(emptyViewDescriptor);
87
}
88
} else {
89
if (registeredEmptyViewDescriptor) {
90
viewDescriptorsToDeregister.push(registeredEmptyViewDescriptor);
91
}
92
if (!registeredExplorerViewDescriptor) {
93
viewDescriptorsToRegister.push(explorerViewDescriptor);
94
}
95
}
96
97
if (viewDescriptorsToDeregister.length) {
98
viewsRegistry.deregisterViews(viewDescriptorsToDeregister, VIEW_CONTAINER);
99
}
100
if (viewDescriptorsToRegister.length) {
101
viewsRegistry.registerViews(viewDescriptorsToRegister, VIEW_CONTAINER);
102
}
103
104
mark('code/didRegisterExplorerViews');
105
}
106
107
private createOpenEditorsViewDescriptor(): IViewDescriptor {
108
return {
109
id: OpenEditorsView.ID,
110
name: OpenEditorsView.NAME,
111
ctorDescriptor: new SyncDescriptor(OpenEditorsView),
112
containerIcon: openEditorsViewIcon,
113
order: 0,
114
canToggleVisibility: true,
115
canMoveView: true,
116
collapsed: false,
117
hideByDefault: true,
118
focusCommand: {
119
id: 'workbench.files.action.focusOpenEditorsView',
120
keybindings: { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyCode.KeyE) }
121
}
122
};
123
}
124
125
private createEmptyViewDescriptor(): IViewDescriptor {
126
return {
127
id: EmptyView.ID,
128
name: EmptyView.NAME,
129
containerIcon: explorerViewIcon,
130
ctorDescriptor: new SyncDescriptor(EmptyView),
131
order: 1,
132
canToggleVisibility: true,
133
focusCommand: {
134
id: 'workbench.explorer.fileView.focus'
135
}
136
};
137
}
138
139
private createExplorerViewDescriptor(): IViewDescriptor {
140
return {
141
id: VIEW_ID,
142
name: localize2('folders', "Folders"),
143
containerIcon: explorerViewIcon,
144
ctorDescriptor: new SyncDescriptor(ExplorerView),
145
order: 1,
146
canMoveView: true,
147
canToggleVisibility: false,
148
focusCommand: {
149
id: 'workbench.explorer.fileView.focus'
150
}
151
};
152
}
153
}
154
155
export class ExplorerViewPaneContainer extends ViewPaneContainer {
156
157
private viewletVisibleContextKey: IContextKey<boolean>;
158
159
constructor(
160
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
161
@ITelemetryService telemetryService: ITelemetryService,
162
@IWorkspaceContextService contextService: IWorkspaceContextService,
163
@IStorageService storageService: IStorageService,
164
@IConfigurationService configurationService: IConfigurationService,
165
@IInstantiationService instantiationService: IInstantiationService,
166
@IContextKeyService contextKeyService: IContextKeyService,
167
@IThemeService themeService: IThemeService,
168
@IContextMenuService contextMenuService: IContextMenuService,
169
@IExtensionService extensionService: IExtensionService,
170
@IViewDescriptorService viewDescriptorService: IViewDescriptorService,
171
@ILogService logService: ILogService,
172
) {
173
174
super(VIEWLET_ID, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService, logService);
175
176
this.viewletVisibleContextKey = ExplorerViewletVisibleContext.bindTo(contextKeyService);
177
this._register(this.contextService.onDidChangeWorkspaceName(e => this.updateTitleArea()));
178
}
179
180
override create(parent: HTMLElement): void {
181
super.create(parent);
182
parent.classList.add('explorer-viewlet');
183
}
184
185
protected override createView(viewDescriptor: IViewDescriptor, options: IViewletViewOptions): ViewPane {
186
if (viewDescriptor.id === VIEW_ID) {
187
return this.instantiationService.createInstance(ExplorerView, {
188
...options, delegate: {
189
willOpenElement: e => {
190
if (!isMouseEvent(e)) {
191
return; // only delay when user clicks
192
}
193
194
const openEditorsView = this.getOpenEditorsView();
195
if (openEditorsView) {
196
let delay = 0;
197
198
const config = this.configurationService.getValue<IFilesConfiguration>();
199
if (!!config.workbench?.editor?.enablePreview) {
200
// delay open editors view when preview is enabled
201
// to accomodate for the user doing a double click
202
// to pin the editor.
203
// without this delay a double click would be not
204
// possible because the next element would move
205
// under the mouse after the first click.
206
delay = 250;
207
}
208
209
openEditorsView.setStructuralRefreshDelay(delay);
210
}
211
},
212
didOpenElement: e => {
213
if (!isMouseEvent(e)) {
214
return; // only delay when user clicks
215
}
216
217
const openEditorsView = this.getOpenEditorsView();
218
openEditorsView?.setStructuralRefreshDelay(0);
219
}
220
}
221
});
222
}
223
return super.createView(viewDescriptor, options);
224
}
225
226
getExplorerView(): ExplorerView {
227
return <ExplorerView>this.getView(VIEW_ID);
228
}
229
230
getOpenEditorsView(): OpenEditorsView {
231
return <OpenEditorsView>this.getView(OpenEditorsView.ID);
232
}
233
234
override setVisible(visible: boolean): void {
235
this.viewletVisibleContextKey.set(visible);
236
super.setVisible(visible);
237
}
238
239
override focus(): void {
240
const explorerView = this.getView(VIEW_ID);
241
if (explorerView && this.panes.every(p => !p.isExpanded())) {
242
explorerView.setExpanded(true);
243
}
244
if (explorerView?.isExpanded()) {
245
explorerView.focus();
246
} else {
247
super.focus();
248
}
249
}
250
}
251
252
const viewContainerRegistry = Registry.as<IViewContainersRegistry>(Extensions.ViewContainersRegistry);
253
254
/**
255
* Explorer viewlet container.
256
*/
257
export const VIEW_CONTAINER: ViewContainer = viewContainerRegistry.registerViewContainer({
258
id: VIEWLET_ID,
259
title: localize2('explore', "Explorer"),
260
ctorDescriptor: new SyncDescriptor(ExplorerViewPaneContainer),
261
storageId: 'workbench.explorer.views.state',
262
icon: explorerViewIcon,
263
alwaysUseContainerInfo: true,
264
hideIfEmpty: true,
265
order: 0,
266
openCommandActionDescriptor: {
267
id: VIEWLET_ID,
268
title: localize2('explore', "Explorer"),
269
mnemonicTitle: localize({ key: 'miViewExplorer', comment: ['&& denotes a mnemonic'] }, "&&Explorer"),
270
keybindings: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyE },
271
order: 0
272
},
273
}, ViewContainerLocation.Sidebar, { isDefault: true });
274
275
const openFolder = localize('openFolder', "Open Folder");
276
const addAFolder = localize('addAFolder', "add a folder");
277
const openRecent = localize('openRecent', "Open Recent");
278
279
const addRootFolderButton = `[${openFolder}](command:${AddRootFolderAction.ID})`;
280
const addAFolderButton = `[${addAFolder}](command:${AddRootFolderAction.ID})`;
281
const openFolderButton = `[${openFolder}](command:${OpenFolderAction.ID})`;
282
const openFolderViaWorkspaceButton = `[${openFolder}](command:${OpenFolderViaWorkspaceAction.ID})`;
283
const openRecentButton = `[${openRecent}](command:${OpenRecentAction.ID})`;
284
285
const viewsRegistry = Registry.as<IViewsRegistry>(Extensions.ViewsRegistry);
286
viewsRegistry.registerViewWelcomeContent(EmptyView.ID, {
287
content: localize({ key: 'noWorkspaceHelp', comment: ['Please do not translate the word "command", it is part of our internal syntax which must not change'] },
288
"You have not yet added a folder to the workspace.\n{0}", addRootFolderButton),
289
when: ContextKeyExpr.and(
290
// inside a .code-workspace
291
WorkbenchStateContext.isEqualTo('workspace'),
292
// unless we cannot enter or open workspaces (e.g. web serverless)
293
OpenFolderWorkspaceSupportContext
294
),
295
group: ViewContentGroups.Open,
296
order: 1
297
});
298
299
viewsRegistry.registerViewWelcomeContent(EmptyView.ID, {
300
content: localize({ key: 'noFolderHelpWeb', comment: ['Please do not translate the word "command", it is part of our internal syntax which must not change'] },
301
"You have not yet opened a folder.\n{0}\n{1}", openFolderViaWorkspaceButton, openRecentButton),
302
when: ContextKeyExpr.and(
303
// inside a .code-workspace
304
WorkbenchStateContext.isEqualTo('workspace'),
305
// we cannot enter workspaces (e.g. web serverless)
306
OpenFolderWorkspaceSupportContext.toNegated()
307
),
308
group: ViewContentGroups.Open,
309
order: 1
310
});
311
312
viewsRegistry.registerViewWelcomeContent(EmptyView.ID, {
313
content: localize({ key: 'remoteNoFolderHelp', comment: ['Please do not translate the word "command", it is part of our internal syntax which must not change'] },
314
"Connected to remote.\n{0}", openFolderButton),
315
when: ContextKeyExpr.and(
316
// not inside a .code-workspace
317
WorkbenchStateContext.notEqualsTo('workspace'),
318
// connected to a remote
319
RemoteNameContext.notEqualsTo(''),
320
// but not in web
321
IsWebContext.toNegated()),
322
group: ViewContentGroups.Open,
323
order: 1
324
});
325
326
viewsRegistry.registerViewWelcomeContent(EmptyView.ID, {
327
content: localize({ key: 'noFolderButEditorsHelp', comment: ['Please do not translate the word "command", it is part of our internal syntax which must not change'] },
328
"You have not yet opened a folder.\n{0}\nOpening a folder will close all currently open editors. To keep them open, {1} instead.", openFolderButton, addAFolderButton),
329
when: ContextKeyExpr.and(
330
// editors are opened
331
ContextKeyExpr.has('editorIsOpen'),
332
ContextKeyExpr.or(
333
// not inside a .code-workspace and local
334
ContextKeyExpr.and(WorkbenchStateContext.notEqualsTo('workspace'), RemoteNameContext.isEqualTo('')),
335
// not inside a .code-workspace and web
336
ContextKeyExpr.and(WorkbenchStateContext.notEqualsTo('workspace'), IsWebContext)
337
)
338
),
339
group: ViewContentGroups.Open,
340
order: 1
341
});
342
343
viewsRegistry.registerViewWelcomeContent(EmptyView.ID, {
344
content: localize({ key: 'noFolderHelp', comment: ['Please do not translate the word "command", it is part of our internal syntax which must not change'] },
345
"You have not yet opened a folder.\n{0}", openFolderButton),
346
when: ContextKeyExpr.and(
347
// no editor is open
348
ContextKeyExpr.has('editorIsOpen')?.negate(),
349
ContextKeyExpr.or(
350
// not inside a .code-workspace and local
351
ContextKeyExpr.and(WorkbenchStateContext.notEqualsTo('workspace'), RemoteNameContext.isEqualTo('')),
352
// not inside a .code-workspace and web
353
ContextKeyExpr.and(WorkbenchStateContext.notEqualsTo('workspace'), IsWebContext)
354
)
355
),
356
group: ViewContentGroups.Open,
357
order: 1
358
});
359
360