sussudio
Version:
An unofficial VS Code Internal API
235 lines (234 loc) • 10.8 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* 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 "../../../base/common/extpath.mjs";
import * as json from "../../../base/common/json.mjs";
import * as jsonEdit from "../../../base/common/jsonEdit.mjs";
import { normalizeDriveLetter } from "../../../base/common/labels.mjs";
import { Schemas } from "../../../base/common/network.mjs";
import { isAbsolute, posix } from "../../../base/common/path.mjs";
import { isLinux, isMacintosh, isWindows } from "../../../base/common/platform.mjs";
import { isEqualAuthority } from "../../../base/common/resources.mjs";
import { URI } from "../../../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