UNPKG

@sussudio/platform

Version:

Internal APIs for VS Code's service injection the base services.

258 lines (257 loc) 9.86 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { isUNC, toSlashes } from '@sussudio/base/common/extpath.mjs'; import * as json from '@sussudio/base/common/json.mjs'; import * as jsonEdit from '@sussudio/base/common/jsonEdit.mjs'; import { normalizeDriveLetter } from '@sussudio/base/common/labels.mjs'; import { Schemas } from '@sussudio/base/common/network.mjs'; import { isAbsolute, posix } from '@sussudio/base/common/path.mjs'; import { isLinux, isMacintosh, isWindows } from '@sussudio/base/common/platform.mjs'; import { isEqualAuthority } from '@sussudio/base/common/resources.mjs'; import { URI } from '@sussudio/base/common/uri.mjs'; import { createDecorator } from '../../instantiation/common/instantiation.mjs'; import { getRemoteAuthority } from '../../remote/common/remoteHosts.mjs'; import { WorkspaceFolder } from '../../workspace/common/workspace.mjs'; export const IWorkspacesService = createDecorator('workspacesService'); export function isRecentWorkspace(curr) { return curr.hasOwnProperty('workspace'); } export function isRecentFolder(curr) { return curr.hasOwnProperty('folderUri'); } export function isRecentFile(curr) { return curr.hasOwnProperty('fileUri'); } //#endregion //#region Workspace File Utilities export function isStoredWorkspaceFolder(obj) { return isRawFileWorkspaceFolder(obj) || isRawUriWorkspaceFolder(obj); } function isRawFileWorkspaceFolder(obj) { const candidate = obj; return typeof candidate?.path === 'string' && (!candidate.name || typeof candidate.name === 'string'); } function isRawUriWorkspaceFolder(obj) { const candidate = obj; return typeof candidate?.uri === 'string' && (!candidate.name || typeof candidate.name === 'string'); } /** * Given a folder URI and the workspace config folder, computes the `IStoredWorkspaceFolder` * using a relative or absolute path or a uri. * Undefined is returned if the `folderURI` and the `targetConfigFolderURI` don't have the * same schema or authority. * * @param folderURI a workspace folder * @param forceAbsolute if set, keep the path absolute * @param folderName a workspace name * @param targetConfigFolderURI the folder where the workspace is living in */ export function getStoredWorkspaceFolder(folderURI, forceAbsolute, folderName, targetConfigFolderURI, extUri) { // Scheme mismatch: use full absolute URI as `uri` if (folderURI.scheme !== targetConfigFolderURI.scheme) { return { name: folderName, uri: folderURI.toString(true) }; } // Always prefer a relative path if possible unless // prevented to make the workspace file shareable // with other users let folderPath = !forceAbsolute ? extUri.relativePath(targetConfigFolderURI, folderURI) : undefined; if (folderPath !== undefined) { if (folderPath.length === 0) { folderPath = '.'; } else { if (isWindows) { folderPath = massagePathForWindows(folderPath); } } } // We could not resolve a relative path else { // Local file: use `fsPath` if (folderURI.scheme === Schemas.file) { folderPath = folderURI.fsPath; if (isWindows) { folderPath = massagePathForWindows(folderPath); } } // Different authority: use full absolute URI else if (!extUri.isEqualAuthority(folderURI.authority, targetConfigFolderURI.authority)) { return { name: folderName, uri: folderURI.toString(true) }; } // Non-local file: use `path` of URI else { folderPath = folderURI.path; } } return { name: folderName, path: folderPath }; } function massagePathForWindows(folderPath) { // Drive letter should be upper case folderPath = normalizeDriveLetter(folderPath); // Always prefer slash over backslash unless // we deal with UNC paths where backslash is // mandatory. if (!isUNC(folderPath)) { folderPath = toSlashes(folderPath); } return folderPath; } export function toWorkspaceFolders(configuredFolders, workspaceConfigFile, extUri) { const result = []; const seen = new Set(); const relativeTo = extUri.dirname(workspaceConfigFile); for (const configuredFolder of configuredFolders) { let uri = undefined; if (isRawFileWorkspaceFolder(configuredFolder)) { if (configuredFolder.path) { uri = extUri.resolvePath(relativeTo, configuredFolder.path); } } else if (isRawUriWorkspaceFolder(configuredFolder)) { try { uri = URI.parse(configuredFolder.uri); if (uri.path[0] !== posix.sep) { uri = uri.with({ path: posix.sep + uri.path }); // this makes sure all workspace folder are absolute } } catch (e) { console.warn(e); // ignore } } if (uri) { // remove duplicates const comparisonKey = extUri.getComparisonKey(uri); if (!seen.has(comparisonKey)) { seen.add(comparisonKey); const name = configuredFolder.name || extUri.basenameOrAuthority(uri); result.push(new WorkspaceFolder({ uri, name, index: result.length }, configuredFolder)); } } } return result; } /** * Rewrites the content of a workspace file to be saved at a new location. * Throws an exception if file is not a valid workspace file */ export function rewriteWorkspaceFileForNewLocation( rawWorkspaceContents, configPathURI, isFromUntitledWorkspace, targetConfigPathURI, extUri, ) { const storedWorkspace = doParseStoredWorkspace(configPathURI, rawWorkspaceContents); const sourceConfigFolder = extUri.dirname(configPathURI); const targetConfigFolder = extUri.dirname(targetConfigPathURI); const rewrittenFolders = []; for (const folder of storedWorkspace.folders) { const folderURI = isRawFileWorkspaceFolder(folder) ? extUri.resolvePath(sourceConfigFolder, folder.path) : URI.parse(folder.uri); let absolute; if (isFromUntitledWorkspace) { absolute = false; // if it was an untitled workspace, try to make paths relative } else { absolute = !isRawFileWorkspaceFolder(folder) || isAbsolute(folder.path); // for existing workspaces, preserve whether a path was absolute or relative } rewrittenFolders.push(getStoredWorkspaceFolder(folderURI, absolute, folder.name, targetConfigFolder, extUri)); } // Preserve as much of the existing workspace as possible by using jsonEdit // and only changing the folders portion. const formattingOptions = { insertSpaces: false, tabSize: 4, eol: isLinux || isMacintosh ? '\n' : '\r\n' }; const edits = jsonEdit.setProperty(rawWorkspaceContents, ['folders'], rewrittenFolders, formattingOptions); let newContent = jsonEdit.applyEdits(rawWorkspaceContents, edits); if (isEqualAuthority(storedWorkspace.remoteAuthority, getRemoteAuthority(targetConfigPathURI))) { // unsaved remote workspaces have the remoteAuthority set. Remove it when no longer nexessary. newContent = jsonEdit.applyEdits( newContent, jsonEdit.removeProperty(newContent, ['remoteAuthority'], formattingOptions), ); } return newContent; } function doParseStoredWorkspace(path, contents) { // Parse workspace file const storedWorkspace = json.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} looks like an invalid workspace file.`); } return storedWorkspace; } function isSerializedRecentWorkspace(data) { return ( data.workspace && typeof data.workspace === 'object' && typeof data.workspace.id === 'string' && typeof data.workspace.configPath === 'string' ); } function isSerializedRecentFolder(data) { return typeof data.folderUri === 'string'; } function isSerializedRecentFile(data) { return typeof data.fileUri === 'string'; } export function restoreRecentlyOpened(data, logService) { const result = { workspaces: [], files: [] }; if (data) { const restoreGracefully = function (entries, onEntry) { for (let i = 0; i < entries.length; i++) { try { onEntry(entries[i], i); } catch (e) { logService.warn(`Error restoring recent entry ${JSON.stringify(entries[i])}: ${e.toString()}. Skip entry.`); } } }; const storedRecents = data; if (Array.isArray(storedRecents.entries)) { restoreGracefully(storedRecents.entries, (entry) => { const label = entry.label; const remoteAuthority = entry.remoteAuthority; if (isSerializedRecentWorkspace(entry)) { result.workspaces.push({ label, remoteAuthority, workspace: { id: entry.workspace.id, configPath: URI.parse(entry.workspace.configPath) }, }); } else if (isSerializedRecentFolder(entry)) { result.workspaces.push({ label, remoteAuthority, folderUri: URI.parse(entry.folderUri) }); } else if (isSerializedRecentFile(entry)) { result.files.push({ label, remoteAuthority, fileUri: URI.parse(entry.fileUri) }); } }); } } return result; } export function toStoreData(recents) { const serialized = { entries: [] }; for (const recent of recents.workspaces) { if (isRecentFolder(recent)) { serialized.entries.push({ folderUri: recent.folderUri.toString(), label: recent.label, remoteAuthority: recent.remoteAuthority, }); } else { serialized.entries.push({ workspace: { id: recent.workspace.id, configPath: recent.workspace.configPath.toString() }, label: recent.label, remoteAuthority: recent.remoteAuthority, }); } } for (const recent of recents.files) { serialized.entries.push({ fileUri: recent.fileUri.toString(), label: recent.label, remoteAuthority: recent.remoteAuthority, }); } return serialized; } //#endregion