sussudio
Version:
An unofficial VS Code Internal API
270 lines (269 loc) • 13.9 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* 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 } from 'electron';
import { Emitter } from "../../../base/common/event.mjs";
import { parse } from "../../../base/common/json.mjs";
import { mnemonicButtonLabel } from "../../../base/common/labels.mjs";
import { Disposable } from "../../../base/common/lifecycle.mjs";
import { Schemas } from "../../../base/common/network.mjs";
import { dirname, join } from "../../../base/common/path.mjs";
import { basename, extUriBiasedIgnorePathCase, joinPath, originalFSPath } from "../../../base/common/resources.mjs";
import { withNullAsUndefined } from "../../../base/common/types.mjs";
import { Promises } from "../../../base/node/pfs.mjs";
import { localize } from "../../../nls.mjs";
import { IBackupMainService } from "../../backup/electron-main/backup.mjs";
import { IDialogMainService } from "../../dialogs/electron-main/dialogMainService.mjs";
import { IEnvironmentMainService } from "../../environment/electron-main/environmentMainService.mjs";
import { createDecorator } from "../../instantiation/common/instantiation.mjs";
import { ILogService } from "../../log/common/log.mjs";
import { IProductService } from "../../product/common/productService.mjs";
import { IUserDataProfilesMainService } from "../../userDataProfile/electron-main/userDataProfile.mjs";
import { findWindowOnWorkspaceOrFolder } from "../../windows/electron-main/windowsFinder.mjs";
import { isWorkspaceIdentifier, hasWorkspaceFileExtension, UNTITLED_WORKSPACE_NAME, isUntitledWorkspace } from "../../workspace/common/workspace.mjs";
import { getStoredWorkspaceFolder, isStoredWorkspaceFolder, toWorkspaceFolders } from "../common/workspaces.mjs";
import { getWorkspaceIdentifier } from "../node/workspaces.mjs";
export const IWorkspacesManagementMainService = createDecorator('workspacesManagementMainService');
let WorkspacesManagementMainService = class WorkspacesManagementMainService extends Disposable {
environmentMainService;
logService;
userDataProfilesMainService;
backupMainService;
dialogMainService;
productService;
_onDidDeleteUntitledWorkspace = this._register(new Emitter());
onDidDeleteUntitledWorkspace = this._onDidDeleteUntitledWorkspace.event;
_onDidEnterWorkspace = this._register(new Emitter());
onDidEnterWorkspace = this._onDidEnterWorkspace.event;
untitledWorkspacesHome = this.environmentMainService.untitledWorkspacesHome; // local URI that contains all untitled workspaces
untitledWorkspaces = [];
constructor(environmentMainService, logService, userDataProfilesMainService, backupMainService, dialogMainService, productService) {
super();
this.environmentMainService = environmentMainService;
this.logService = logService;
this.userDataProfilesMainService = userDataProfilesMainService;
this.backupMainService = backupMainService;
this.dialogMainService = dialogMainService;
this.productService = productService;
}
async initialize() {
// Reset
this.untitledWorkspaces = [];
// Resolve untitled workspaces
try {
const untitledWorkspacePaths = (await Promises.readdir(this.untitledWorkspacesHome.fsPath)).map(folder => joinPath(this.untitledWorkspacesHome, folder, UNTITLED_WORKSPACE_NAME));
for (const untitledWorkspacePath of untitledWorkspacePaths) {
const workspace = getWorkspaceIdentifier(untitledWorkspacePath);
const resolvedWorkspace = await this.resolveLocalWorkspace(untitledWorkspacePath);
if (!resolvedWorkspace) {
await this.deleteUntitledWorkspace(workspace);
}
else {
this.untitledWorkspaces.push({ workspace, remoteAuthority: resolvedWorkspace.remoteAuthority });
}
}
}
catch (error) {
if (error.code !== 'ENOENT') {
this.logService.warn(`Unable to read folders in ${this.untitledWorkspacesHome} (${error}).`);
}
}
}
resolveLocalWorkspace(uri) {
return this.doResolveLocalWorkspace(uri, path => Promises.readFile(path, 'utf8'));
}
doResolveLocalWorkspace(uri, contentsFn) {
if (!this.isWorkspacePath(uri)) {
return undefined; // does not look like a valid workspace config file
}
if (uri.scheme !== Schemas.file) {
return undefined;
}
try {
const contents = contentsFn(uri.fsPath);
if (contents instanceof Promise) {
return contents.then(value => this.doResolveWorkspace(uri, value), error => undefined /* invalid workspace */);
}
else {
return this.doResolveWorkspace(uri, contents);
}
}
catch {
return undefined; // invalid workspace
}
}
isWorkspacePath(uri) {
return isUntitledWorkspace(uri, this.environmentMainService) || hasWorkspaceFileExtension(uri);
}
doResolveWorkspace(path, contents) {
try {
const workspace = this.doParseStoredWorkspace(path, contents);
const workspaceIdentifier = getWorkspaceIdentifier(path);
return {
id: workspaceIdentifier.id,
configPath: workspaceIdentifier.configPath,
folders: toWorkspaceFolders(workspace.folders, workspaceIdentifier.configPath, extUriBiasedIgnorePathCase),
remoteAuthority: workspace.remoteAuthority,
transient: workspace.transient
};
}
catch (error) {
this.logService.warn(error.toString());
}
return undefined;
}
doParseStoredWorkspace(path, contents) {
// Parse workspace file
const storedWorkspace = parse(contents); // use fault tolerant parser
// Filter out folders which do not have a path or uri set
if (storedWorkspace && Array.isArray(storedWorkspace.folders)) {
storedWorkspace.folders = storedWorkspace.folders.filter(folder => isStoredWorkspaceFolder(folder));
}
else {
throw new Error(`${path.toString(true)} looks like an invalid workspace file.`);
}
return storedWorkspace;
}
async createUntitledWorkspace(folders, remoteAuthority) {
const { workspace, storedWorkspace } = this.newUntitledWorkspace(folders, remoteAuthority);
const configPath = workspace.configPath.fsPath;
await Promises.mkdir(dirname(configPath), { recursive: true });
await Promises.writeFile(configPath, JSON.stringify(storedWorkspace, null, '\t'));
this.untitledWorkspaces.push({ workspace, remoteAuthority });
return workspace;
}
newUntitledWorkspace(folders = [], remoteAuthority) {
const randomId = (Date.now() + Math.round(Math.random() * 1000)).toString();
const untitledWorkspaceConfigFolder = joinPath(this.untitledWorkspacesHome, randomId);
const untitledWorkspaceConfigPath = joinPath(untitledWorkspaceConfigFolder, UNTITLED_WORKSPACE_NAME);
const storedWorkspaceFolder = [];
for (const folder of folders) {
storedWorkspaceFolder.push(getStoredWorkspaceFolder(folder.uri, true, folder.name, untitledWorkspaceConfigFolder, extUriBiasedIgnorePathCase));
}
return {
workspace: getWorkspaceIdentifier(untitledWorkspaceConfigPath),
storedWorkspace: { folders: storedWorkspaceFolder, remoteAuthority }
};
}
async getWorkspaceIdentifier(configPath) {
return getWorkspaceIdentifier(configPath);
}
isUntitledWorkspace(workspace) {
return isUntitledWorkspace(workspace.configPath, this.environmentMainService);
}
async deleteUntitledWorkspace(workspace) {
if (!this.isUntitledWorkspace(workspace)) {
return; // only supported for untitled workspaces
}
// Delete from disk
await this.doDeleteUntitledWorkspace(workspace);
// unset workspace from profiles
if (this.userDataProfilesMainService.isEnabled()) {
this.userDataProfilesMainService.unsetWorkspace(workspace);
}
// Event
this._onDidDeleteUntitledWorkspace.fire(workspace);
}
async doDeleteUntitledWorkspace(workspace) {
const configPath = originalFSPath(workspace.configPath);
try {
// Delete Workspace
await Promises.rm(dirname(configPath));
// Mark Workspace Storage to be deleted
const workspaceStoragePath = join(this.environmentMainService.workspaceStorageHome.fsPath, workspace.id);
if (await Promises.exists(workspaceStoragePath)) {
await Promises.writeFile(join(workspaceStoragePath, 'obsolete'), '');
}
// Remove from list
this.untitledWorkspaces = this.untitledWorkspaces.filter(untitledWorkspace => untitledWorkspace.workspace.id !== workspace.id);
}
catch (error) {
this.logService.warn(`Unable to delete untitled workspace ${configPath} (${error}).`);
}
}
getUntitledWorkspaces() {
return this.untitledWorkspaces;
}
async enterWorkspace(window, windows, path) {
if (!window || !window.win || !window.isReady) {
return undefined; // return early if the window is not ready or disposed
}
const isValid = await this.isValidTargetWorkspacePath(window, windows, path);
if (!isValid) {
return undefined; // return early if the workspace is not valid
}
const result = await this.doEnterWorkspace(window, getWorkspaceIdentifier(path));
if (!result) {
return undefined;
}
// Emit as event
this._onDidEnterWorkspace.fire({ window, workspace: result.workspace });
return result;
}
async isValidTargetWorkspacePath(window, windows, workspacePath) {
if (!workspacePath) {
return true;
}
if (isWorkspaceIdentifier(window.openedWorkspace) && extUriBiasedIgnorePathCase.isEqual(window.openedWorkspace.configPath, workspacePath)) {
return false; // window is already opened on a workspace with that path
}
// Prevent overwriting a workspace that is currently opened in another window
if (findWindowOnWorkspaceOrFolder(windows, workspacePath)) {
const options = {
title: this.productService.nameLong,
type: 'info',
buttons: [mnemonicButtonLabel(localize({ key: 'ok', comment: ['&& denotes a mnemonic'] }, "&&OK"))],
message: localize('workspaceOpenedMessage', "Unable to save workspace '{0}'", basename(workspacePath)),
detail: localize('workspaceOpenedDetail', "The workspace is already opened in another window. Please close that window first and then try again."),
noLink: true,
defaultId: 0
};
await this.dialogMainService.showMessageBox(options, withNullAsUndefined(BrowserWindow.getFocusedWindow()));
return false;
}
return true; // OK
}
async doEnterWorkspace(window, workspace) {
if (!window.config) {
return undefined;
}
window.focus();
// Register window for backups and migrate current backups over
let backupPath;
if (!window.config.extensionDevelopmentPath) {
if (window.config.backupPath) {
backupPath = await this.backupMainService.registerWorkspaceBackup({ workspace, remoteAuthority: window.remoteAuthority }, window.config.backupPath);
}
else {
backupPath = this.backupMainService.registerWorkspaceBackup({ workspace, remoteAuthority: window.remoteAuthority });
}
}
// if the window was opened on an untitled workspace, delete it.
if (isWorkspaceIdentifier(window.openedWorkspace) && this.isUntitledWorkspace(window.openedWorkspace)) {
await this.deleteUntitledWorkspace(window.openedWorkspace);
}
// Update window configuration properly based on transition to workspace
window.config.workspace = workspace;
window.config.backupPath = backupPath;
return { workspace, backupPath };
}
};
WorkspacesManagementMainService = __decorate([
__param(0, IEnvironmentMainService),
__param(1, ILogService),
__param(2, IUserDataProfilesMainService),
__param(3, IBackupMainService),
__param(4, IDialogMainService),
__param(5, IProductService)
], WorkspacesManagementMainService);
export { WorkspacesManagementMainService };