UNPKG

sussudio

Version:

An unofficial VS Code Internal API

204 lines (203 loc) 9.53 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __param = (this && this.__param) || function (paramIndex, decorator) { return function (target, key) { decorator(target, key, paramIndex); } }; import { BrowserWindow, dialog } from 'electron'; import { Queue } from "../../../base/common/async.mjs"; import { hash } from "../../../base/common/hash.mjs"; import { mnemonicButtonLabel } from "../../../base/common/labels.mjs"; import { Disposable, dispose, toDisposable } from "../../../base/common/lifecycle.mjs"; import { normalizeNFC } from "../../../base/common/normalization.mjs"; import { isMacintosh } from "../../../base/common/platform.mjs"; import { withNullAsUndefined } from "../../../base/common/types.mjs"; import { Promises } from "../../../base/node/pfs.mjs"; import { localize } from "../../../nls.mjs"; import { createDecorator } from "../../instantiation/common/instantiation.mjs"; import { ILogService } from "../../log/common/log.mjs"; import { WORKSPACE_FILTER } from "../../workspace/common/workspace.mjs"; export const IDialogMainService = createDecorator('dialogMainService'); let DialogMainService = class DialogMainService { logService; windowFileDialogLocks = new Map(); windowDialogQueues = new Map(); noWindowDialogueQueue = new Queue(); constructor(logService) { this.logService = logService; } pickFileFolder(options, window) { return this.doPick({ ...options, pickFolders: true, pickFiles: true, title: localize('open', "Open") }, window); } pickFolder(options, window) { return this.doPick({ ...options, pickFolders: true, title: localize('openFolder', "Open Folder") }, window); } pickFile(options, window) { return this.doPick({ ...options, pickFiles: true, title: localize('openFile', "Open File") }, window); } pickWorkspace(options, window) { const title = localize('openWorkspaceTitle', "Open Workspace from File"); const buttonLabel = mnemonicButtonLabel(localize({ key: 'openWorkspace', comment: ['&& denotes a mnemonic'] }, "&&Open")); const filters = WORKSPACE_FILTER; return this.doPick({ ...options, pickFiles: true, title, filters, buttonLabel }, window); } async doPick(options, window) { // Ensure dialog options const dialogOptions = { title: options.title, buttonLabel: options.buttonLabel, filters: options.filters, defaultPath: options.defaultPath }; // Ensure properties if (typeof options.pickFiles === 'boolean' || typeof options.pickFolders === 'boolean') { dialogOptions.properties = undefined; // let it override based on the booleans if (options.pickFiles && options.pickFolders) { dialogOptions.properties = ['multiSelections', 'openDirectory', 'openFile', 'createDirectory']; } } if (!dialogOptions.properties) { dialogOptions.properties = ['multiSelections', options.pickFolders ? 'openDirectory' : 'openFile', 'createDirectory']; } if (isMacintosh) { dialogOptions.properties.push('treatPackageAsDirectory'); // always drill into .app files } // Show Dialog const result = await this.showOpenDialog(dialogOptions, withNullAsUndefined(window || BrowserWindow.getFocusedWindow())); if (result && result.filePaths && result.filePaths.length > 0) { return result.filePaths; } return undefined; } getWindowDialogQueue(window) { // Queue message box requests per window so that one can show // after the other. if (window) { let windowDialogQueue = this.windowDialogQueues.get(window.id); if (!windowDialogQueue) { windowDialogQueue = new Queue(); this.windowDialogQueues.set(window.id, windowDialogQueue); } return windowDialogQueue; } else { return this.noWindowDialogueQueue; } } showMessageBox(options, window) { return this.getWindowDialogQueue(window).queue(async () => { if (window) { return dialog.showMessageBox(window, options); } return dialog.showMessageBox(options); }); } async showSaveDialog(options, window) { // Prevent duplicates of the same dialog queueing at the same time const fileDialogLock = this.acquireFileDialogLock(options, window); if (!fileDialogLock) { this.logService.error('[DialogMainService]: file save dialog is already or will be showing for the window with the same configuration'); return { canceled: true }; } try { return await this.getWindowDialogQueue(window).queue(async () => { let result; if (window) { result = await dialog.showSaveDialog(window, options); } else { result = await dialog.showSaveDialog(options); } result.filePath = this.normalizePath(result.filePath); return result; }); } finally { dispose(fileDialogLock); } } normalizePath(path) { if (path && isMacintosh) { path = normalizeNFC(path); // macOS only: normalize paths to NFC form } return path; } normalizePaths(paths) { return paths.map(path => this.normalizePath(path)); } async showOpenDialog(options, window) { // Ensure the path exists (if provided) if (options.defaultPath) { const pathExists = await Promises.exists(options.defaultPath); if (!pathExists) { options.defaultPath = undefined; } } // Prevent duplicates of the same dialog queueing at the same time const fileDialogLock = this.acquireFileDialogLock(options, window); if (!fileDialogLock) { this.logService.error('[DialogMainService]: file open dialog is already or will be showing for the window with the same configuration'); return { canceled: true, filePaths: [] }; } try { return await this.getWindowDialogQueue(window).queue(async () => { let result; if (window) { result = await dialog.showOpenDialog(window, options); } else { result = await dialog.showOpenDialog(options); } result.filePaths = this.normalizePaths(result.filePaths); return result; }); } finally { dispose(fileDialogLock); } } acquireFileDialogLock(options, window) { // If no window is provided, allow as many dialogs as // needed since we consider them not modal per window if (!window) { return Disposable.None; } // If a window is provided, only allow a single dialog // at the same time because dialogs are modal and we // do not want to open one dialog after the other // (https://github.com/microsoft/vscode/issues/114432) // we figure this out by `hashing` the configuration // options for the dialog to prevent duplicates this.logService.trace('[DialogMainService]: request to acquire file dialog lock', options); let windowFileDialogLocks = this.windowFileDialogLocks.get(window.id); if (!windowFileDialogLocks) { windowFileDialogLocks = new Set(); this.windowFileDialogLocks.set(window.id, windowFileDialogLocks); } const optionsHash = hash(options); if (windowFileDialogLocks.has(optionsHash)) { return undefined; // prevent duplicates, return } this.logService.trace('[DialogMainService]: new file dialog lock created', options); windowFileDialogLocks.add(optionsHash); return toDisposable(() => { this.logService.trace('[DialogMainService]: file dialog lock disposed', options); windowFileDialogLocks?.delete(optionsHash); // If the window has no more dialog locks, delete it from the set of locks if (windowFileDialogLocks?.size === 0) { this.windowFileDialogLocks.delete(window.id); } }); } }; DialogMainService = __decorate([ __param(0, ILogService) ], DialogMainService); export { DialogMainService };