UNPKG

@theia/workspace

Version:
573 lines • 27 kB
"use strict"; // ***************************************************************************** // Copyright (C) 2017 TypeFox and others. // // This program and the accompanying materials are made available under the // terms of the Eclipse Public License v. 2.0 which is available at // http://www.eclipse.org/legal/epl-2.0. // // This Source Code may also be made available under the following Secondary // Licenses when the conditions for such availability set forth in the Eclipse // Public License v. 2.0 are satisfied: GNU General Public License, version 2 // with the GNU Classpath Exception which is available at // https://www.gnu.org/software/classpath/license.html. // // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 // ***************************************************************************** Object.defineProperty(exports, "__esModule", { value: true }); exports.WorkspaceFrontendContribution = exports.FILE_WORKSPACE = exports.WorkspaceStates = void 0; const tslib_1 = require("tslib"); const inversify_1 = require("@theia/core/shared/inversify"); const common_1 = require("@theia/core/lib/common"); const core_1 = require("@theia/core"); const browser_1 = require("@theia/core/lib/browser"); const browser_2 = require("@theia/filesystem/lib/browser"); const context_key_service_1 = require("@theia/core/lib/browser/context-key-service"); const workspace_service_1 = require("./workspace-service"); const common_2 = require("../common"); const workspace_commands_1 = require("./workspace-commands"); const workspace_trust_service_1 = require("./workspace-trust-service"); const quick_open_workspace_1 = require("./quick-open-workspace"); const file_service_1 = require("@theia/filesystem/lib/browser/file-service"); const encoding_registry_1 = require("@theia/core/lib/browser/encoding-registry"); const encodings_1 = require("@theia/core/lib/common/encodings"); const disposable_1 = require("@theia/core/lib/common/disposable"); const preference_configurations_1 = require("@theia/core/lib/common/preferences/preference-configurations"); const nls_1 = require("@theia/core/lib/common/nls"); const buffer_1 = require("@theia/core/lib/common/buffer"); const untitled_workspace_exit_dialog_1 = require("./untitled-workspace-exit-dialog"); const filesystem_saveable_service_1 = require("@theia/filesystem/lib/browser/filesystem-saveable-service"); const frontend_application_state_1 = require("@theia/core/lib/common/frontend-application-state"); var WorkspaceStates; (function (WorkspaceStates) { /** * The state is `empty` when no workspace is opened. */ WorkspaceStates["empty"] = "empty"; /** * The state is `workspace` when a workspace is opened. */ WorkspaceStates["workspace"] = "workspace"; /** * The state is `folder` when a folder is opened. (1 folder) */ WorkspaceStates["folder"] = "folder"; })(WorkspaceStates || (exports.WorkspaceStates = WorkspaceStates = {})); ; /** Create the workspace section after open {@link CommonMenus.FILE_OPEN}. */ exports.FILE_WORKSPACE = [...browser_1.CommonMenus.FILE, '2_workspace']; let WorkspaceFrontendContribution = class WorkspaceFrontendContribution { constructor() { this.toDisposeOnUpdateEncodingOverrides = new disposable_1.DisposableCollection(); } configure() { const workspaceExtensions = this.workspaceFileService.getWorkspaceFileExtensions(); for (const extension of workspaceExtensions) { this.encodingRegistry.registerOverride({ encoding: encodings_1.UTF8, extension }); } this.updateEncodingOverrides(); const workspaceFolderCountKey = this.contextKeyService.createKey('workspaceFolderCount', 0); const updateWorkspaceFolderCountKey = () => workspaceFolderCountKey.set(this.workspaceService.tryGetRoots().length); updateWorkspaceFolderCountKey(); const workspaceStateKey = this.contextKeyService.createKey('workspaceState', 'empty'); const updateWorkspaceStateKey = () => workspaceStateKey.set(this.updateWorkspaceStateKey()); updateWorkspaceStateKey(); const workbenchStateKey = this.contextKeyService.createKey('workbenchState', 'empty'); const updateWorkbenchStateKey = () => workbenchStateKey.set(this.updateWorkbenchStateKey()); updateWorkbenchStateKey(); this.updateStyles(); this.workspaceService.onWorkspaceChanged(() => { this.updateEncodingOverrides(); updateWorkspaceFolderCountKey(); updateWorkspaceStateKey(); updateWorkbenchStateKey(); this.updateStyles(); }); } updateEncodingOverrides() { this.toDisposeOnUpdateEncodingOverrides.dispose(); for (const root of this.workspaceService.tryGetRoots()) { for (const configPath of this.preferenceConfigurations.getPaths()) { const parent = root.resource.resolve(configPath); this.toDisposeOnUpdateEncodingOverrides.push(this.encodingRegistry.registerOverride({ encoding: encodings_1.UTF8, parent })); } } } updateStyles() { document.body.classList.remove('theia-no-open-workspace'); // Display the 'no workspace opened' theme color when no folders are opened (single-root). if (!this.workspaceService.isMultiRootWorkspaceOpened && !this.workspaceService.tryGetRoots().length) { document.body.classList.add('theia-no-open-workspace'); } } registerCommands(commands) { // Not visible/enabled on Windows/Linux in electron. commands.registerCommand(workspace_commands_1.WorkspaceCommands.OPEN, { isEnabled: () => core_1.isOSX || !this.isElectron(), isVisible: () => core_1.isOSX || !this.isElectron(), execute: () => this.doOpen() }); // Visible/enabled only on Windows/Linux in electron. commands.registerCommand(workspace_commands_1.WorkspaceCommands.OPEN_FILE, { isEnabled: () => true, execute: () => this.doOpenFile() }); // Visible/enabled only on Windows/Linux in electron. commands.registerCommand(workspace_commands_1.WorkspaceCommands.OPEN_FOLDER, { isEnabled: () => true, execute: () => this.doOpenFolder() }); commands.registerCommand(workspace_commands_1.WorkspaceCommands.OPEN_WORKSPACE, { isEnabled: () => true, execute: () => this.doOpenWorkspace() }); commands.registerCommand(workspace_commands_1.WorkspaceCommands.CLOSE, { isEnabled: () => this.workspaceService.opened, execute: () => this.closeWorkspace() }); commands.registerCommand(workspace_commands_1.WorkspaceCommands.OPEN_RECENT_WORKSPACE, { execute: () => this.quickOpenWorkspace.select() }); commands.registerCommand(workspace_commands_1.WorkspaceCommands.SAVE_WORKSPACE_AS, { isVisible: () => this.workspaceService.opened, isEnabled: () => this.workspaceService.opened, execute: () => this.saveWorkspaceAs() }); commands.registerCommand(workspace_commands_1.WorkspaceCommands.OPEN_WORKSPACE_FILE, { isEnabled: () => this.workspaceService.saved, execute: () => { if (this.workspaceService.saved && this.workspaceService.workspace) { (0, browser_1.open)(this.openerService, this.workspaceService.workspace.resource); } } }); commands.registerCommand(workspace_commands_1.WorkspaceCommands.MANAGE_WORKSPACE_TRUST, { execute: () => this.manageWorkspaceTrust() }); } registerMenus(menus) { if (core_1.isOSX || !this.isElectron()) { menus.registerMenuAction(browser_1.CommonMenus.FILE_OPEN, { commandId: workspace_commands_1.WorkspaceCommands.OPEN.id, order: 'a00' }); } if (!core_1.isOSX && this.isElectron()) { menus.registerMenuAction(browser_1.CommonMenus.FILE_OPEN, { commandId: workspace_commands_1.WorkspaceCommands.OPEN_FILE.id, label: `${workspace_commands_1.WorkspaceCommands.OPEN_FILE.dialogLabel}...`, order: 'a01' }); menus.registerMenuAction(browser_1.CommonMenus.FILE_OPEN, { commandId: workspace_commands_1.WorkspaceCommands.OPEN_FOLDER.id, label: `${workspace_commands_1.WorkspaceCommands.OPEN_FOLDER.dialogLabel}...`, order: 'a02' }); } menus.registerMenuAction(browser_1.CommonMenus.FILE_OPEN, { commandId: workspace_commands_1.WorkspaceCommands.OPEN_WORKSPACE.id, order: 'a10' }); menus.registerMenuAction(browser_1.CommonMenus.FILE_OPEN, { commandId: workspace_commands_1.WorkspaceCommands.OPEN_RECENT_WORKSPACE.id, order: 'a20' }); menus.registerMenuAction(exports.FILE_WORKSPACE, { commandId: workspace_commands_1.WorkspaceCommands.ADD_FOLDER.id, order: 'a10' }); menus.registerMenuAction(exports.FILE_WORKSPACE, { commandId: workspace_commands_1.WorkspaceCommands.SAVE_WORKSPACE_AS.id, order: 'a20' }); menus.registerMenuAction(browser_1.CommonMenus.FILE_CLOSE, { commandId: workspace_commands_1.WorkspaceCommands.CLOSE.id }); menus.registerMenuAction(browser_1.CommonMenus.FILE_SAVE, { commandId: workspace_commands_1.WorkspaceCommands.SAVE_AS.id, }); menus.registerMenuAction(browser_1.SHELL_TABBAR_CONTEXT_COPY, { commandId: workspace_commands_1.WorkspaceCommands.COPY_RELATIVE_FILE_PATH.id, label: workspace_commands_1.WorkspaceCommands.COPY_RELATIVE_FILE_PATH.label, }); } registerKeybindings(keybindings) { keybindings.registerKeybinding({ command: core_1.isOSX || !this.isElectron() ? workspace_commands_1.WorkspaceCommands.OPEN.id : workspace_commands_1.WorkspaceCommands.OPEN_FILE.id, keybinding: this.isElectron() ? 'ctrlcmd+o' : 'ctrlcmd+alt+o', }); if (!core_1.isOSX && this.isElectron()) { keybindings.registerKeybinding({ command: workspace_commands_1.WorkspaceCommands.OPEN_FOLDER.id, keybinding: 'ctrl+k ctrl+o', }); } keybindings.registerKeybinding({ command: workspace_commands_1.WorkspaceCommands.OPEN_WORKSPACE.id, keybinding: 'ctrlcmd+alt+w', }); keybindings.registerKeybinding({ command: workspace_commands_1.WorkspaceCommands.OPEN_RECENT_WORKSPACE.id, keybinding: 'ctrlcmd+alt+r', }); keybindings.registerKeybinding({ command: workspace_commands_1.WorkspaceCommands.SAVE_AS.id, keybinding: 'ctrlcmd+shift+s', }); keybindings.registerKeybinding({ command: workspace_commands_1.WorkspaceCommands.COPY_RELATIVE_FILE_PATH.id, keybinding: common_1.isWindows ? 'ctrl+k ctrl+shift+c' : 'ctrlcmd+shift+alt+c', when: '!editorFocus' }); } /** * This is the generic `Open` method. Opens files and directories too. Resolves to the opened URI. * Except when you are on either Windows or Linux `AND` running in electron. If so, it opens a file. */ async doOpen() { if (!core_1.isOSX && this.isElectron()) { return this.doOpenFile(); } const [rootStat] = await this.workspaceService.roots; let selectedUris = await this.fileDialogService.showOpenDialog({ title: workspace_commands_1.WorkspaceCommands.OPEN.dialogLabel, canSelectFolders: true, canSelectFiles: true, canSelectMany: true }, rootStat); if (selectedUris) { if (!Array.isArray(selectedUris)) { selectedUris = [selectedUris]; } const folders = []; // Only open files then open all folders in a new workspace, as done with Electron see doOpenFolder. for (const uri of selectedUris) { const destination = await this.fileService.resolve(uri); if (destination.isDirectory) { if (this.getCurrentWorkspaceUri()?.toString() !== uri.toString()) { folders.push(uri); } } else { await (0, browser_1.open)(this.openerService, uri); } } if (folders.length > 0) { const openableURI = await this.getOpenableWorkspaceUri(folders); if (openableURI && (!this.workspaceService.workspace || !openableURI.isEqual(this.workspaceService.workspace.resource))) { this.workspaceService.open(openableURI); } } return selectedUris; } return undefined; } /** * Opens a set of files after prompting the `Open File` dialog. Resolves to `undefined`, if * - the workspace root is not set, * - the file to open does not exist, or * - it was not a file, but a directory. * * Otherwise, resolves to the set of URIs of the files. */ async doOpenFile() { const props = { title: workspace_commands_1.WorkspaceCommands.OPEN_FILE.dialogLabel, canSelectFolders: false, canSelectFiles: true, canSelectMany: true }; const [rootStat] = await this.workspaceService.roots; let selectedFilesUris = await this.fileDialogService.showOpenDialog(props, rootStat); if (selectedFilesUris) { if (!Array.isArray(selectedFilesUris)) { selectedFilesUris = [selectedFilesUris]; } const result = []; for (const uri of selectedFilesUris) { const destination = await this.fileService.resolve(uri); if (destination.isFile) { await (0, browser_1.open)(this.openerService, uri); result.push(uri); } } return result; } return undefined; } /** * Opens one or more folders after prompting the `Open Folder` dialog. Resolves to `undefined`, if * - the user's selection is empty or contains only files. * - the new workspace is equal to the old workspace. * * Otherwise, resolves to the URI of the new workspace: * - a single folder if a single folder was selected. * - a new, untitled workspace file if multiple folders were selected. */ async doOpenFolder() { const props = { title: workspace_commands_1.WorkspaceCommands.OPEN_FOLDER.dialogLabel, canSelectFolders: true, canSelectFiles: false, canSelectMany: true, }; const [rootStat] = await this.workspaceService.roots; const targetFolders = await this.fileDialogService.showOpenDialog(props, rootStat); if (targetFolders) { const openableUri = await this.getOpenableWorkspaceUri(targetFolders); if (openableUri) { if (!this.workspaceService.workspace || !openableUri.isEqual(this.workspaceService.workspace.resource)) { this.workspaceService.open(openableUri); return openableUri; } } ; } return undefined; } async getOpenableWorkspaceUri(uris) { if (Array.isArray(uris)) { if (uris.length < 2) { return uris[0]; } else { const foldersToOpen = (await Promise.all(uris.map(uri => this.fileService.resolve(uri)))) .filter(fileStat => !!fileStat?.isDirectory); if (foldersToOpen.length === 1) { return foldersToOpen[0].resource; } else { return this.createMultiRootWorkspace(foldersToOpen); } } } else { return uris; } } async createMultiRootWorkspace(roots) { const untitledWorkspace = await this.workspaceService.getUntitledWorkspace(); const folders = Array.from(new Set(roots.map(stat => stat.resource.path.toString())), path => ({ path })); const workspaceStat = await this.fileService.createFile(untitledWorkspace, buffer_1.BinaryBuffer.fromString(JSON.stringify({ folders }, null, 4)), // eslint-disable-line no-null/no-null { overwrite: true }); return workspaceStat.resource; } /** * Opens a workspace after raising the `Open Workspace` dialog. Resolves to the URI of the recently opened workspace, * if it was successful. Otherwise, resolves to `undefined`. */ async doOpenWorkspace() { const props = { title: workspace_commands_1.WorkspaceCommands.OPEN_WORKSPACE.dialogLabel, canSelectFiles: true, canSelectFolders: false, filters: this.getWorkspaceDialogFileFilters() }; const [rootStat] = await this.workspaceService.roots; const workspaceFileUri = await this.fileDialogService.showOpenDialog(props, rootStat); if (workspaceFileUri && this.getCurrentWorkspaceUri()?.toString() !== workspaceFileUri.toString()) { if (await this.fileService.exists(workspaceFileUri)) { this.workspaceService.open(workspaceFileUri); return workspaceFileUri; } } return undefined; } async closeWorkspace() { await this.workspaceService.close(); } /** * @returns whether the file was successfully saved. */ async saveWorkspaceAs() { let exist = false; let overwrite = false; let selected; do { selected = await this.fileDialogService.showSaveDialog({ title: workspace_commands_1.WorkspaceCommands.SAVE_WORKSPACE_AS.label, filters: this.getWorkspaceDialogFileFilters() }); if (selected) { const displayName = selected.displayName; const extensions = this.workspaceFileService.getWorkspaceFileExtensions(true); if (!extensions.some(ext => displayName.endsWith(ext))) { const defaultExtension = extensions[this.workspaceFileService.defaultFileTypeIndex]; selected = selected.parent.resolve(`${displayName}${defaultExtension}`); } exist = await this.fileService.exists(selected); if (exist) { overwrite = await this.saveService.confirmOverwrite(selected); } } } while (selected && exist && !overwrite); if (selected) { try { await this.workspaceService.save(selected); return true; } catch { this.messageService.error(nls_1.nls.localizeByDefault("Unable to save workspace '{0}'", selected.path.fsPath())); } } return false; } canBeSavedAs(widget) { return this.saveService.canSaveAs(widget); } async saveAs(widget) { await this.saveService.saveAs(widget); } updateWorkspaceStateKey() { return this.doUpdateState(); } updateWorkbenchStateKey() { return this.doUpdateState(); } doUpdateState() { if (this.workspaceService.opened) { return this.workspaceService.isMultiRootWorkspaceOpened ? 'workspace' : 'folder'; } return 'empty'; } getWorkspaceDialogFileFilters() { const filters = {}; for (const fileType of this.workspaceFileService.getWorkspaceFileTypes()) { filters[`${nls_1.nls.localizeByDefault('{0} workspace', fileType.name)} (*.${fileType.extension})`] = [fileType.extension]; } return filters; } isElectron() { return core_1.environment.electron.is(); } /** * Get the current workspace URI. * * @returns the current workspace URI. */ getCurrentWorkspaceUri() { return this.workspaceService.workspace?.resource; } async manageWorkspaceTrust() { const currentTrust = await this.workspaceTrustService.getWorkspaceTrust(); const trust = nls_1.nls.localizeByDefault('Trust'); const dontTrust = nls_1.nls.localizeByDefault("Don't Trust"); const currentSuffix = `(${nls_1.nls.localizeByDefault('Current')})`; const items = [ { label: trust, description: currentTrust ? currentSuffix : undefined }, { label: dontTrust, description: !currentTrust ? currentSuffix : undefined } ]; const selected = await this.quickInputService.showQuickPick(items, { title: nls_1.nls.localizeByDefault('Manage Workspace Trust'), placeholder: nls_1.nls.localize('theia/workspace/manageTrustPlaceholder', 'Select trust state for this workspace') }); if (selected) { const newTrust = selected.label === trust; if (newTrust !== currentTrust) { await this.workspaceTrustService.setWorkspaceTrust(newTrust); } } } onWillStop() { const { workspace } = this.workspaceService; if (workspace && this.workspaceService.isUntitledWorkspace(workspace.resource)) { return { prepare: async (reason) => reason === frontend_application_state_1.StopReason.Reload && this.workspaceService.isSafeToReload(workspace.resource), action: async (alreadyConfirmedSafe) => { if (alreadyConfirmedSafe) { return true; } const shouldSaveFile = await new untitled_workspace_exit_dialog_1.UntitledWorkspaceExitDialog({ title: nls_1.nls.localizeByDefault('Do you want to save your workspace configuration as a file?') }).open(); if (shouldSaveFile === "Don't Save") { return true; } else if (shouldSaveFile === 'Save') { return this.saveWorkspaceAs(); } return false; // If cancel, prevent exit. }, reason: 'Untitled workspace.', // Since deleting the workspace would hobble any future functionality, run this late. priority: 100, }; } } }; exports.WorkspaceFrontendContribution = WorkspaceFrontendContribution; tslib_1.__decorate([ (0, inversify_1.inject)(common_1.MessageService), tslib_1.__metadata("design:type", common_1.MessageService) ], WorkspaceFrontendContribution.prototype, "messageService", void 0); tslib_1.__decorate([ (0, inversify_1.inject)(file_service_1.FileService), tslib_1.__metadata("design:type", file_service_1.FileService) ], WorkspaceFrontendContribution.prototype, "fileService", void 0); tslib_1.__decorate([ (0, inversify_1.inject)(browser_1.OpenerService), tslib_1.__metadata("design:type", Object) ], WorkspaceFrontendContribution.prototype, "openerService", void 0); tslib_1.__decorate([ (0, inversify_1.inject)(workspace_service_1.WorkspaceService), tslib_1.__metadata("design:type", workspace_service_1.WorkspaceService) ], WorkspaceFrontendContribution.prototype, "workspaceService", void 0); tslib_1.__decorate([ (0, inversify_1.inject)(quick_open_workspace_1.QuickOpenWorkspace), tslib_1.__metadata("design:type", quick_open_workspace_1.QuickOpenWorkspace) ], WorkspaceFrontendContribution.prototype, "quickOpenWorkspace", void 0); tslib_1.__decorate([ (0, inversify_1.inject)(browser_2.FileDialogService), tslib_1.__metadata("design:type", Object) ], WorkspaceFrontendContribution.prototype, "fileDialogService", void 0); tslib_1.__decorate([ (0, inversify_1.inject)(context_key_service_1.ContextKeyService), tslib_1.__metadata("design:type", Object) ], WorkspaceFrontendContribution.prototype, "contextKeyService", void 0); tslib_1.__decorate([ (0, inversify_1.inject)(encoding_registry_1.EncodingRegistry), tslib_1.__metadata("design:type", encoding_registry_1.EncodingRegistry) ], WorkspaceFrontendContribution.prototype, "encodingRegistry", void 0); tslib_1.__decorate([ (0, inversify_1.inject)(preference_configurations_1.PreferenceConfigurations), tslib_1.__metadata("design:type", preference_configurations_1.PreferenceConfigurations) ], WorkspaceFrontendContribution.prototype, "preferenceConfigurations", void 0); tslib_1.__decorate([ (0, inversify_1.inject)(filesystem_saveable_service_1.FilesystemSaveableService), tslib_1.__metadata("design:type", filesystem_saveable_service_1.FilesystemSaveableService) ], WorkspaceFrontendContribution.prototype, "saveService", void 0); tslib_1.__decorate([ (0, inversify_1.inject)(common_2.WorkspaceFileService), tslib_1.__metadata("design:type", common_2.WorkspaceFileService) ], WorkspaceFrontendContribution.prototype, "workspaceFileService", void 0); tslib_1.__decorate([ (0, inversify_1.inject)(browser_1.QuickInputService), tslib_1.__metadata("design:type", Object) ], WorkspaceFrontendContribution.prototype, "quickInputService", void 0); tslib_1.__decorate([ (0, inversify_1.inject)(workspace_trust_service_1.WorkspaceTrustService), tslib_1.__metadata("design:type", workspace_trust_service_1.WorkspaceTrustService) ], WorkspaceFrontendContribution.prototype, "workspaceTrustService", void 0); exports.WorkspaceFrontendContribution = WorkspaceFrontendContribution = tslib_1.__decorate([ (0, inversify_1.injectable)() ], WorkspaceFrontendContribution); (function (WorkspaceFrontendContribution) { /** * File filter for all Theia and VS Code workspace file types. * * @deprecated Since 1.39.0 Use `WorkspaceFrontendContribution#getWorkspaceDialogFileFilters` instead. */ WorkspaceFrontendContribution.DEFAULT_FILE_FILTER = { 'Theia Workspace (*.theia-workspace)': [common_2.THEIA_EXT], 'VS Code Workspace (*.code-workspace)': [common_2.VSCODE_EXT] }; })(WorkspaceFrontendContribution || (exports.WorkspaceFrontendContribution = WorkspaceFrontendContribution = {})); //# sourceMappingURL=workspace-frontend-contribution.js.map