Path: blob/main/src/vs/workbench/contrib/files/browser/explorerViewlet.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import './media/explorerviewlet.css';6import { localize, localize2 } from '../../../../nls.js';7import { mark } from '../../../../base/common/performance.js';8import { VIEWLET_ID, VIEW_ID, IFilesConfiguration, ExplorerViewletVisibleContext } from '../common/files.js';9import { IViewletViewOptions } from '../../../browser/parts/views/viewsViewlet.js';10import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';11import { ExplorerView } from './views/explorerView.js';12import { EmptyView } from './views/emptyView.js';13import { OpenEditorsView } from './views/openEditorsView.js';14import { IStorageService } from '../../../../platform/storage/common/storage.js';15import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';16import { IExtensionService } from '../../../services/extensions/common/extensions.js';17import { IWorkspaceContextService, WorkbenchState } from '../../../../platform/workspace/common/workspace.js';18import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';19import { IContextKeyService, IContextKey, ContextKeyExpr } from '../../../../platform/contextkey/common/contextkey.js';20import { IThemeService } from '../../../../platform/theme/common/themeService.js';21import { IViewsRegistry, IViewDescriptor, Extensions, ViewContainer, IViewContainersRegistry, ViewContainerLocation, IViewDescriptorService, ViewContentGroups } from '../../../common/views.js';22import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js';23import { Disposable } from '../../../../base/common/lifecycle.js';24import { IWorkbenchContribution } from '../../../common/contributions.js';25import { IWorkbenchLayoutService } from '../../../services/layout/browser/layoutService.js';26import { ViewPaneContainer } from '../../../browser/parts/views/viewPaneContainer.js';27import { ViewPane } from '../../../browser/parts/views/viewPane.js';28import { KeyChord, KeyMod, KeyCode } from '../../../../base/common/keyCodes.js';29import { Registry } from '../../../../platform/registry/common/platform.js';30import { IProgressService, ProgressLocation } from '../../../../platform/progress/common/progress.js';31import { SyncDescriptor } from '../../../../platform/instantiation/common/descriptors.js';32import { WorkbenchStateContext, RemoteNameContext, OpenFolderWorkspaceSupportContext } from '../../../common/contextkeys.js';33import { IsWebContext } from '../../../../platform/contextkey/common/contextkeys.js';34import { AddRootFolderAction, OpenFolderAction, OpenFolderViaWorkspaceAction } from '../../../browser/actions/workspaceActions.js';35import { OpenRecentAction } from '../../../browser/actions/windowActions.js';36import { Codicon } from '../../../../base/common/codicons.js';37import { registerIcon } from '../../../../platform/theme/common/iconRegistry.js';38import { isMouseEvent } from '../../../../base/browser/dom.js';39import { ILogService } from '../../../../platform/log/common/log.js';4041const explorerViewIcon = registerIcon('explorer-view-icon', Codicon.files, localize('explorerViewIcon', 'View icon of the explorer view.'));42const openEditorsViewIcon = registerIcon('open-editors-view-icon', Codicon.book, localize('openEditorsIcon', 'View icon of the open editors view.'));4344export class ExplorerViewletViewsContribution extends Disposable implements IWorkbenchContribution {4546static readonly ID = 'workbench.contrib.explorerViewletViews';4748constructor(49@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService,50@IProgressService progressService: IProgressService51) {52super();5354progressService.withProgress({ location: ProgressLocation.Explorer }, () => workspaceContextService.getCompleteWorkspace()).finally(() => {55this.registerViews();5657this._register(workspaceContextService.onDidChangeWorkbenchState(() => this.registerViews()));58this._register(workspaceContextService.onDidChangeWorkspaceFolders(() => this.registerViews()));59});60}6162private registerViews(): void {63mark('code/willRegisterExplorerViews');6465const viewDescriptors = viewsRegistry.getViews(VIEW_CONTAINER);6667const viewDescriptorsToRegister: IViewDescriptor[] = [];68const viewDescriptorsToDeregister: IViewDescriptor[] = [];6970const openEditorsViewDescriptor = this.createOpenEditorsViewDescriptor();71if (!viewDescriptors.some(v => v.id === openEditorsViewDescriptor.id)) {72viewDescriptorsToRegister.push(openEditorsViewDescriptor);73}7475const explorerViewDescriptor = this.createExplorerViewDescriptor();76const registeredExplorerViewDescriptor = viewDescriptors.find(v => v.id === explorerViewDescriptor.id);77const emptyViewDescriptor = this.createEmptyViewDescriptor();78const registeredEmptyViewDescriptor = viewDescriptors.find(v => v.id === emptyViewDescriptor.id);7980if (this.workspaceContextService.getWorkbenchState() === WorkbenchState.EMPTY || this.workspaceContextService.getWorkspace().folders.length === 0) {81if (registeredExplorerViewDescriptor) {82viewDescriptorsToDeregister.push(registeredExplorerViewDescriptor);83}84if (!registeredEmptyViewDescriptor) {85viewDescriptorsToRegister.push(emptyViewDescriptor);86}87} else {88if (registeredEmptyViewDescriptor) {89viewDescriptorsToDeregister.push(registeredEmptyViewDescriptor);90}91if (!registeredExplorerViewDescriptor) {92viewDescriptorsToRegister.push(explorerViewDescriptor);93}94}9596if (viewDescriptorsToDeregister.length) {97viewsRegistry.deregisterViews(viewDescriptorsToDeregister, VIEW_CONTAINER);98}99if (viewDescriptorsToRegister.length) {100viewsRegistry.registerViews(viewDescriptorsToRegister, VIEW_CONTAINER);101}102103mark('code/didRegisterExplorerViews');104}105106private createOpenEditorsViewDescriptor(): IViewDescriptor {107return {108id: OpenEditorsView.ID,109name: OpenEditorsView.NAME,110ctorDescriptor: new SyncDescriptor(OpenEditorsView),111containerIcon: openEditorsViewIcon,112order: 0,113canToggleVisibility: true,114canMoveView: true,115collapsed: false,116hideByDefault: true,117focusCommand: {118id: 'workbench.files.action.focusOpenEditorsView',119keybindings: { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyCode.KeyE) }120}121};122}123124private createEmptyViewDescriptor(): IViewDescriptor {125return {126id: EmptyView.ID,127name: EmptyView.NAME,128containerIcon: explorerViewIcon,129ctorDescriptor: new SyncDescriptor(EmptyView),130order: 1,131canToggleVisibility: true,132focusCommand: {133id: 'workbench.explorer.fileView.focus'134}135};136}137138private createExplorerViewDescriptor(): IViewDescriptor {139return {140id: VIEW_ID,141name: localize2('folders', "Folders"),142containerIcon: explorerViewIcon,143ctorDescriptor: new SyncDescriptor(ExplorerView),144order: 1,145canMoveView: true,146canToggleVisibility: false,147focusCommand: {148id: 'workbench.explorer.fileView.focus'149}150};151}152}153154export class ExplorerViewPaneContainer extends ViewPaneContainer {155156private viewletVisibleContextKey: IContextKey<boolean>;157158constructor(159@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,160@ITelemetryService telemetryService: ITelemetryService,161@IWorkspaceContextService contextService: IWorkspaceContextService,162@IStorageService storageService: IStorageService,163@IConfigurationService configurationService: IConfigurationService,164@IInstantiationService instantiationService: IInstantiationService,165@IContextKeyService contextKeyService: IContextKeyService,166@IThemeService themeService: IThemeService,167@IContextMenuService contextMenuService: IContextMenuService,168@IExtensionService extensionService: IExtensionService,169@IViewDescriptorService viewDescriptorService: IViewDescriptorService,170@ILogService logService: ILogService,171) {172173super(VIEWLET_ID, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService, logService);174175this.viewletVisibleContextKey = ExplorerViewletVisibleContext.bindTo(contextKeyService);176this._register(this.contextService.onDidChangeWorkspaceName(e => this.updateTitleArea()));177}178179override create(parent: HTMLElement): void {180super.create(parent);181parent.classList.add('explorer-viewlet');182}183184protected override createView(viewDescriptor: IViewDescriptor, options: IViewletViewOptions): ViewPane {185if (viewDescriptor.id === VIEW_ID) {186return this.instantiationService.createInstance(ExplorerView, {187...options, delegate: {188willOpenElement: e => {189if (!isMouseEvent(e)) {190return; // only delay when user clicks191}192193const openEditorsView = this.getOpenEditorsView();194if (openEditorsView) {195let delay = 0;196197const config = this.configurationService.getValue<IFilesConfiguration>();198if (!!config.workbench?.editor?.enablePreview) {199// delay open editors view when preview is enabled200// to accomodate for the user doing a double click201// to pin the editor.202// without this delay a double click would be not203// possible because the next element would move204// under the mouse after the first click.205delay = 250;206}207208openEditorsView.setStructuralRefreshDelay(delay);209}210},211didOpenElement: e => {212if (!isMouseEvent(e)) {213return; // only delay when user clicks214}215216const openEditorsView = this.getOpenEditorsView();217openEditorsView?.setStructuralRefreshDelay(0);218}219}220});221}222return super.createView(viewDescriptor, options);223}224225getExplorerView(): ExplorerView {226return <ExplorerView>this.getView(VIEW_ID);227}228229getOpenEditorsView(): OpenEditorsView {230return <OpenEditorsView>this.getView(OpenEditorsView.ID);231}232233override setVisible(visible: boolean): void {234this.viewletVisibleContextKey.set(visible);235super.setVisible(visible);236}237238override focus(): void {239const explorerView = this.getView(VIEW_ID);240if (explorerView && this.panes.every(p => !p.isExpanded())) {241explorerView.setExpanded(true);242}243if (explorerView?.isExpanded()) {244explorerView.focus();245} else {246super.focus();247}248}249}250251const viewContainerRegistry = Registry.as<IViewContainersRegistry>(Extensions.ViewContainersRegistry);252253/**254* Explorer viewlet container.255*/256export const VIEW_CONTAINER: ViewContainer = viewContainerRegistry.registerViewContainer({257id: VIEWLET_ID,258title: localize2('explore', "Explorer"),259ctorDescriptor: new SyncDescriptor(ExplorerViewPaneContainer),260storageId: 'workbench.explorer.views.state',261icon: explorerViewIcon,262alwaysUseContainerInfo: true,263hideIfEmpty: true,264order: 0,265openCommandActionDescriptor: {266id: VIEWLET_ID,267title: localize2('explore', "Explorer"),268mnemonicTitle: localize({ key: 'miViewExplorer', comment: ['&& denotes a mnemonic'] }, "&&Explorer"),269keybindings: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyE },270order: 0271},272}, ViewContainerLocation.Sidebar, { isDefault: true });273274const openFolder = localize('openFolder', "Open Folder");275const addAFolder = localize('addAFolder', "add a folder");276const openRecent = localize('openRecent', "Open Recent");277278const addRootFolderButton = `[${openFolder}](command:${AddRootFolderAction.ID})`;279const addAFolderButton = `[${addAFolder}](command:${AddRootFolderAction.ID})`;280const openFolderButton = `[${openFolder}](command:${OpenFolderAction.ID})`;281const openFolderViaWorkspaceButton = `[${openFolder}](command:${OpenFolderViaWorkspaceAction.ID})`;282const openRecentButton = `[${openRecent}](command:${OpenRecentAction.ID})`;283284const viewsRegistry = Registry.as<IViewsRegistry>(Extensions.ViewsRegistry);285viewsRegistry.registerViewWelcomeContent(EmptyView.ID, {286content: localize({ key: 'noWorkspaceHelp', comment: ['Please do not translate the word "command", it is part of our internal syntax which must not change'] },287"You have not yet added a folder to the workspace.\n{0}", addRootFolderButton),288when: ContextKeyExpr.and(289// inside a .code-workspace290WorkbenchStateContext.isEqualTo('workspace'),291// unless we cannot enter or open workspaces (e.g. web serverless)292OpenFolderWorkspaceSupportContext293),294group: ViewContentGroups.Open,295order: 1296});297298viewsRegistry.registerViewWelcomeContent(EmptyView.ID, {299content: localize({ key: 'noFolderHelpWeb', comment: ['Please do not translate the word "command", it is part of our internal syntax which must not change'] },300"You have not yet opened a folder.\n{0}\n{1}", openFolderViaWorkspaceButton, openRecentButton),301when: ContextKeyExpr.and(302// inside a .code-workspace303WorkbenchStateContext.isEqualTo('workspace'),304// we cannot enter workspaces (e.g. web serverless)305OpenFolderWorkspaceSupportContext.toNegated()306),307group: ViewContentGroups.Open,308order: 1309});310311viewsRegistry.registerViewWelcomeContent(EmptyView.ID, {312content: localize({ key: 'remoteNoFolderHelp', comment: ['Please do not translate the word "command", it is part of our internal syntax which must not change'] },313"Connected to remote.\n{0}", openFolderButton),314when: ContextKeyExpr.and(315// not inside a .code-workspace316WorkbenchStateContext.notEqualsTo('workspace'),317// connected to a remote318RemoteNameContext.notEqualsTo(''),319// but not in web320IsWebContext.toNegated()),321group: ViewContentGroups.Open,322order: 1323});324325viewsRegistry.registerViewWelcomeContent(EmptyView.ID, {326content: localize({ key: 'noFolderButEditorsHelp', comment: ['Please do not translate the word "command", it is part of our internal syntax which must not change'] },327"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),328when: ContextKeyExpr.and(329// editors are opened330ContextKeyExpr.has('editorIsOpen'),331ContextKeyExpr.or(332// not inside a .code-workspace and local333ContextKeyExpr.and(WorkbenchStateContext.notEqualsTo('workspace'), RemoteNameContext.isEqualTo('')),334// not inside a .code-workspace and web335ContextKeyExpr.and(WorkbenchStateContext.notEqualsTo('workspace'), IsWebContext)336)337),338group: ViewContentGroups.Open,339order: 1340});341342viewsRegistry.registerViewWelcomeContent(EmptyView.ID, {343content: localize({ key: 'noFolderHelp', comment: ['Please do not translate the word "command", it is part of our internal syntax which must not change'] },344"You have not yet opened a folder.\n{0}", openFolderButton),345when: ContextKeyExpr.and(346// no editor is open347ContextKeyExpr.has('editorIsOpen')?.negate(),348ContextKeyExpr.or(349// not inside a .code-workspace and local350ContextKeyExpr.and(WorkbenchStateContext.notEqualsTo('workspace'), RemoteNameContext.isEqualTo('')),351// not inside a .code-workspace and web352ContextKeyExpr.and(WorkbenchStateContext.notEqualsTo('workspace'), IsWebContext)353)354),355group: ViewContentGroups.Open,356order: 1357});358359360