Merge from vscode e0762af258c0b20320ed03f3871a41967acc4421 (#7404)

* Merge from vscode e0762af258c0b20320ed03f3871a41967acc4421

* readd svgs
This commit is contained in:
Anthony Dresser
2019-09-27 11:13:19 -07:00
committed by GitHub
parent 6385443a4c
commit 07109617b5
348 changed files with 4219 additions and 4307 deletions

View File

@@ -17,7 +17,6 @@ import { normalizeDriveLetter } from 'vs/base/common/labels';
import { toSlashes } from 'vs/base/common/extpath';
import { FormattingOptions } from 'vs/base/common/jsonFormatter';
import { getRemoteAuthority } from 'vs/platform/remote/common/remoteHosts';
import { IEnterWorkspaceResult } from 'vs/platform/windows/common/windows';
export const WORKSPACE_EXTENSION = 'code-workspace';
export const WORKSPACE_FILTER = [{ name: localize('codeWorkspace', "Code Workspace"), extensions: [WORKSPACE_EXTENSION] }];
@@ -92,6 +91,11 @@ export interface IUntitledWorkspaceInfo {
remoteAuthority?: string;
}
export interface IEnterWorkspaceResult {
workspace: IWorkspaceIdentifier;
backupPath?: string;
}
export const IWorkspacesService = createDecorator<IWorkspacesService>('workspacesService');
export interface IWorkspacesService {

View File

@@ -0,0 +1,41 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { URI } from 'vs/base/common/uri';
export interface IRecentlyOpened {
workspaces: Array<IRecentWorkspace | IRecentFolder>;
files: IRecentFile[];
}
export type IRecent = IRecentWorkspace | IRecentFolder | IRecentFile;
export interface IRecentWorkspace {
workspace: IWorkspaceIdentifier;
label?: string;
}
export interface IRecentFolder {
folderUri: ISingleFolderWorkspaceIdentifier;
label?: string;
}
export interface IRecentFile {
fileUri: URI;
label?: string;
}
export function isRecentWorkspace(curr: IRecent): curr is IRecentWorkspace {
return curr.hasOwnProperty('workspace');
}
export function isRecentFolder(curr: IRecent): curr is IRecentFolder {
return curr.hasOwnProperty('folderUri');
}
export function isRecentFile(curr: IRecent): curr is IRecentFile {
return curr.hasOwnProperty('fileUri');
}

View File

@@ -0,0 +1,129 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { UriComponents, URI } from 'vs/base/common/uri';
import { IRecentlyOpened, isRecentFolder } from 'vs/platform/workspaces/common/workspacesHistory';
import { ILogService } from 'vs/platform/log/common/log';
interface ISerializedRecentlyOpened {
workspaces3: Array<ISerializedWorkspace | string>; // workspace or URI.toString() // added in 1.32
workspaceLabels?: Array<string | null>; // added in 1.33
files2: string[]; // files as URI.toString() // added in 1.32
fileLabels?: Array<string | null>; // added in 1.33
}
interface ILegacySerializedRecentlyOpened {
workspaces2: Array<ILegacySerializedWorkspace | string>; // legacy, configPath as file path
workspaces: Array<ILegacySerializedWorkspace | string | UriComponents>; // legacy (UriComponents was also supported for a few insider builds)
files: string[]; // files as paths
}
interface ISerializedWorkspace { id: string; configURIPath: string; }
interface ILegacySerializedWorkspace { id: string; configPath: string; }
function isLegacySerializedWorkspace(curr: any): curr is ILegacySerializedWorkspace {
return typeof curr === 'object' && typeof curr['id'] === 'string' && typeof curr['configPath'] === 'string';
}
function isUriComponents(curr: any): curr is UriComponents {
return curr && typeof curr['path'] === 'string' && typeof curr['scheme'] === 'string';
}
export type RecentlyOpenedStorageData = object;
export function restoreRecentlyOpened(data: RecentlyOpenedStorageData | undefined, logService: ILogService): IRecentlyOpened {
const result: IRecentlyOpened = { workspaces: [], files: [] };
if (data) {
const restoreGracefully = function <T>(entries: T[], func: (entry: T, index: number) => void) {
for (let i = 0; i < entries.length; i++) {
try {
func(entries[i], i);
} catch (e) {
logService.warn(`Error restoring recent entry ${JSON.stringify(entries[i])}: ${e.toString()}. Skip entry.`);
}
}
};
const storedRecents = data as ISerializedRecentlyOpened & ILegacySerializedRecentlyOpened;
if (Array.isArray(storedRecents.workspaces3)) {
restoreGracefully(storedRecents.workspaces3, (workspace, i) => {
const label: string | undefined = (Array.isArray(storedRecents.workspaceLabels) && storedRecents.workspaceLabels[i]) || undefined;
if (typeof workspace === 'object' && typeof workspace.id === 'string' && typeof workspace.configURIPath === 'string') {
result.workspaces.push({ label, workspace: { id: workspace.id, configPath: URI.parse(workspace.configURIPath) } });
} else if (typeof workspace === 'string') {
result.workspaces.push({ label, folderUri: URI.parse(workspace) });
}
});
} else if (Array.isArray(storedRecents.workspaces2)) {
restoreGracefully(storedRecents.workspaces2, workspace => {
if (typeof workspace === 'object' && typeof workspace.id === 'string' && typeof workspace.configPath === 'string') {
result.workspaces.push({ workspace: { id: workspace.id, configPath: URI.file(workspace.configPath) } });
} else if (typeof workspace === 'string') {
result.workspaces.push({ folderUri: URI.parse(workspace) });
}
});
} else if (Array.isArray(storedRecents.workspaces)) {
// TODO@martin legacy support can be removed at some point (6 month?)
// format of 1.25 and before
restoreGracefully(storedRecents.workspaces, workspace => {
if (typeof workspace === 'string') {
result.workspaces.push({ folderUri: URI.file(workspace) });
} else if (isLegacySerializedWorkspace(workspace)) {
result.workspaces.push({ workspace: { id: workspace.id, configPath: URI.file(workspace.configPath) } });
} else if (isUriComponents(workspace)) {
// added by 1.26-insiders
result.workspaces.push({ folderUri: URI.revive(<UriComponents>workspace) });
}
});
}
if (Array.isArray(storedRecents.files2)) {
restoreGracefully(storedRecents.files2, (file, i) => {
const label: string | undefined = (Array.isArray(storedRecents.fileLabels) && storedRecents.fileLabels[i]) || undefined;
if (typeof file === 'string') {
result.files.push({ label, fileUri: URI.parse(file) });
}
});
} else if (Array.isArray(storedRecents.files)) {
restoreGracefully(storedRecents.files, file => {
if (typeof file === 'string') {
result.files.push({ fileUri: URI.file(file) });
}
});
}
}
return result;
}
export function toStoreData(recents: IRecentlyOpened): RecentlyOpenedStorageData {
const serialized: ISerializedRecentlyOpened = { workspaces3: [], files2: [] };
let hasLabel = false;
const workspaceLabels: (string | null)[] = [];
for (const recent of recents.workspaces) {
if (isRecentFolder(recent)) {
serialized.workspaces3.push(recent.folderUri.toString());
} else {
serialized.workspaces3.push({ id: recent.workspace.id, configURIPath: recent.workspace.configPath.toString() });
}
workspaceLabels.push(recent.label || null);
hasLabel = hasLabel || !!recent.label;
}
if (hasLabel) {
serialized.workspaceLabels = workspaceLabels;
}
hasLabel = false;
const fileLabels: (string | null)[] = [];
for (const recent of recents.files) {
serialized.files2.push(recent.fileUri.toString());
fileLabels.push(recent.label || null);
hasLabel = hasLabel || !!recent.label;
}
if (hasLabel) {
serialized.fileLabels = fileLabels;
}
return serialized;
}

View File

@@ -0,0 +1,407 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import * as arrays from 'vs/base/common/arrays';
import { IStateService } from 'vs/platform/state/node/state';
import { app, JumpListCategory } from 'electron';
import { ILogService } from 'vs/platform/log/common/log';
import { getBaseLabel, getPathLabel } from 'vs/base/common/labels';
import { IPath } from 'vs/platform/windows/common/windows';
import { Event as CommonEvent, Emitter } from 'vs/base/common/event';
import { isWindows, isMacintosh } from 'vs/base/common/platform';
import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { IWorkspacesMainService } from 'vs/platform/workspaces/electron-main/workspacesMainService';
import { IRecentlyOpened, isRecentWorkspace, isRecentFolder, IRecent, isRecentFile, IRecentFolder, IRecentWorkspace, IRecentFile } from 'vs/platform/workspaces/common/workspacesHistory';
import { ThrottledDelayer } from 'vs/base/common/async';
import { isEqual as areResourcesEqual, dirname, originalFSPath, basename } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
import { Schemas } from 'vs/base/common/network';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { getSimpleWorkspaceLabel } from 'vs/platform/label/common/label';
import { toStoreData, restoreRecentlyOpened, RecentlyOpenedStorageData } from 'vs/platform/workspaces/common/workspacesHistoryStorage';
import { exists } from 'vs/base/node/pfs';
import { ILifecycleMainService, LifecycleMainPhase } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
export const IWorkspacesHistoryMainService = createDecorator<IWorkspacesHistoryMainService>('workspacesHistoryMainService');
export interface IWorkspacesHistoryMainService {
_serviceBrand: undefined;
onRecentlyOpenedChange: CommonEvent<void>;
addRecentlyOpened(recents: IRecent[]): void;
getRecentlyOpened(currentWorkspace?: IWorkspaceIdentifier, currentFolder?: ISingleFolderWorkspaceIdentifier, currentFiles?: IPath[]): IRecentlyOpened;
removeFromRecentlyOpened(paths: URI[]): void;
clearRecentlyOpened(): void;
updateWindowsJumpList(): void;
}
export class WorkspacesHistoryMainService implements IWorkspacesHistoryMainService {
private static readonly MAX_TOTAL_RECENT_ENTRIES = 100;
private static readonly MAX_MACOS_DOCK_RECENT_WORKSPACES = 7; // prefer more workspaces...
private static readonly MAX_MACOS_DOCK_RECENT_ENTRIES_TOTAL = 10; // ...compared to files
// Exclude some very common files from the dock/taskbar
private static readonly COMMON_FILES_FILTER = [
'COMMIT_EDITMSG',
'MERGE_MSG'
];
private static readonly recentlyOpenedStorageKey = 'openedPathsList';
_serviceBrand: undefined;
private readonly _onRecentlyOpenedChange = new Emitter<void>();
readonly onRecentlyOpenedChange: CommonEvent<void> = this._onRecentlyOpenedChange.event;
private macOSRecentDocumentsUpdater: ThrottledDelayer<void>;
constructor(
@IStateService private readonly stateService: IStateService,
@ILogService private readonly logService: ILogService,
@IWorkspacesMainService private readonly workspacesMainService: IWorkspacesMainService,
@IEnvironmentService private readonly environmentService: IEnvironmentService,
@ILifecycleMainService lifecycleMainService: ILifecycleMainService
) {
this.macOSRecentDocumentsUpdater = new ThrottledDelayer<void>(800);
lifecycleMainService.when(LifecycleMainPhase.AfterWindowOpen).then(() => this.handleWindowsJumpList());
}
private handleWindowsJumpList(): void {
if (!isWindows) {
return; // only on windows
}
this.updateWindowsJumpList();
this.onRecentlyOpenedChange(() => this.updateWindowsJumpList());
}
addRecentlyOpened(newlyAdded: IRecent[]): void {
const workspaces: Array<IRecentFolder | IRecentWorkspace> = [];
const files: IRecentFile[] = [];
for (let curr of newlyAdded) {
// Workspace
if (isRecentWorkspace(curr)) {
if (!this.workspacesMainService.isUntitledWorkspace(curr.workspace) && indexOfWorkspace(workspaces, curr.workspace) === -1) {
workspaces.push(curr);
}
}
// Folder
else if (isRecentFolder(curr)) {
if (indexOfFolder(workspaces, curr.folderUri) === -1) {
workspaces.push(curr);
}
}
// File
else {
const alreadyExistsInHistory = indexOfFile(files, curr.fileUri) >= 0;
const shouldBeFiltered = curr.fileUri.scheme === Schemas.file && WorkspacesHistoryMainService.COMMON_FILES_FILTER.indexOf(basename(curr.fileUri)) >= 0;
if (!alreadyExistsInHistory && !shouldBeFiltered) {
files.push(curr);
// Add to recent documents (Windows only, macOS later)
if (isWindows && curr.fileUri.scheme === Schemas.file) {
app.addRecentDocument(curr.fileUri.fsPath);
}
}
}
}
this.addEntriesFromStorage(workspaces, files);
if (workspaces.length > WorkspacesHistoryMainService.MAX_TOTAL_RECENT_ENTRIES) {
workspaces.length = WorkspacesHistoryMainService.MAX_TOTAL_RECENT_ENTRIES;
}
if (files.length > WorkspacesHistoryMainService.MAX_TOTAL_RECENT_ENTRIES) {
files.length = WorkspacesHistoryMainService.MAX_TOTAL_RECENT_ENTRIES;
}
this.saveRecentlyOpened({ workspaces, files });
this._onRecentlyOpenedChange.fire();
// Schedule update to recent documents on macOS dock
if (isMacintosh) {
this.macOSRecentDocumentsUpdater.trigger(() => this.updateMacOSRecentDocuments());
}
}
removeFromRecentlyOpened(toRemove: URI[]): void {
const keep = (recent: IRecent) => {
const uri = location(recent);
for (const r of toRemove) {
if (areResourcesEqual(r, uri)) {
return false;
}
}
return true;
};
const mru = this.getRecentlyOpened();
const workspaces = mru.workspaces.filter(keep);
const files = mru.files.filter(keep);
if (workspaces.length !== mru.workspaces.length || files.length !== mru.files.length) {
this.saveRecentlyOpened({ files, workspaces });
this._onRecentlyOpenedChange.fire();
// Schedule update to recent documents on macOS dock
if (isMacintosh) {
this.macOSRecentDocumentsUpdater.trigger(() => this.updateMacOSRecentDocuments());
}
}
}
private async updateMacOSRecentDocuments(): Promise<void> {
if (!isMacintosh) {
return;
}
// We clear all documents first to ensure an up-to-date view on the set. Since entries
// can get deleted on disk, this ensures that the list is always valid
app.clearRecentDocuments();
const mru = this.getRecentlyOpened();
// Collect max-N recent workspaces that are known to exist
const workspaceEntries: string[] = [];
let entries = 0;
for (let i = 0; i < mru.workspaces.length && entries < WorkspacesHistoryMainService.MAX_MACOS_DOCK_RECENT_WORKSPACES; i++) {
const loc = location(mru.workspaces[i]);
if (loc.scheme === Schemas.file) {
const workspacePath = originalFSPath(loc);
if (await exists(workspacePath)) {
workspaceEntries.push(workspacePath);
entries++;
}
}
}
// Collect max-N recent files that are known to exist
const fileEntries: string[] = [];
for (let i = 0; i < mru.files.length && entries < WorkspacesHistoryMainService.MAX_MACOS_DOCK_RECENT_ENTRIES_TOTAL; i++) {
const loc = location(mru.files[i]);
if (loc.scheme === Schemas.file) {
const filePath = originalFSPath(loc);
if (
WorkspacesHistoryMainService.COMMON_FILES_FILTER.indexOf(basename(loc)) !== -1 || // skip some well known file entries
workspaceEntries.indexOf(filePath) !== -1 // prefer a workspace entry over a file entry (e.g. for .code-workspace)
) {
continue;
}
if (await exists(filePath)) {
fileEntries.push(filePath);
entries++;
}
}
}
// The apple guidelines (https://developer.apple.com/design/human-interface-guidelines/macos/menus/menu-anatomy/)
// explain that most recent entries should appear close to the interaction by the user (e.g. close to the
// mouse click). Most native macOS applications that add recent documents to the dock, show the most recent document
// to the bottom (because the dock menu is not appearing from top to bottom, but from the bottom to the top). As such
// we fill in the entries in reverse order so that the most recent shows up at the bottom of the menu.
//
// On top of that, the maximum number of documents can be configured by the user (defaults to 10). To ensure that
// we are not failing to show the most recent entries, we start by adding files first (in reverse order of recency)
// and then add folders (in reverse order of recency). Given that strategy, we can ensure that the most recent
// N folders are always appearing, even if the limit is low (https://github.com/microsoft/vscode/issues/74788)
fileEntries.reverse().forEach(fileEntry => app.addRecentDocument(fileEntry));
workspaceEntries.reverse().forEach(workspaceEntry => app.addRecentDocument(workspaceEntry));
}
clearRecentlyOpened(): void {
this.saveRecentlyOpened({ workspaces: [], files: [] });
app.clearRecentDocuments();
// Event
this._onRecentlyOpenedChange.fire();
}
getRecentlyOpened(currentWorkspace?: IWorkspaceIdentifier, currentFolder?: ISingleFolderWorkspaceIdentifier, currentFiles?: IPath[]): IRecentlyOpened {
const workspaces: Array<IRecentFolder | IRecentWorkspace> = [];
const files: IRecentFile[] = [];
// Add current workspace to beginning if set
if (currentWorkspace && !this.workspacesMainService.isUntitledWorkspace(currentWorkspace)) {
workspaces.push({ workspace: currentWorkspace });
}
if (currentFolder) {
workspaces.push({ folderUri: currentFolder });
}
// Add currently files to open to the beginning if any
if (currentFiles) {
for (let currentFile of currentFiles) {
const fileUri = currentFile.fileUri;
if (fileUri && indexOfFile(files, fileUri) === -1) {
files.push({ fileUri });
}
}
}
this.addEntriesFromStorage(workspaces, files);
return { workspaces, files };
}
private addEntriesFromStorage(workspaces: Array<IRecentFolder | IRecentWorkspace>, files: IRecentFile[]) {
// Get from storage
let recents = this.getRecentlyOpenedFromStorage();
for (let recent of recents.workspaces) {
let index = isRecentFolder(recent) ? indexOfFolder(workspaces, recent.folderUri) : indexOfWorkspace(workspaces, recent.workspace);
if (index >= 0) {
workspaces[index].label = workspaces[index].label || recent.label;
} else {
workspaces.push(recent);
}
}
for (let recent of recents.files) {
let index = indexOfFile(files, recent.fileUri);
if (index >= 0) {
files[index].label = files[index].label || recent.label;
} else {
files.push(recent);
}
}
}
private getRecentlyOpenedFromStorage(): IRecentlyOpened {
const storedRecents = this.stateService.getItem<RecentlyOpenedStorageData>(WorkspacesHistoryMainService.recentlyOpenedStorageKey);
return restoreRecentlyOpened(storedRecents, this.logService);
}
private saveRecentlyOpened(recent: IRecentlyOpened): void {
const serialized = toStoreData(recent);
this.stateService.setItem(WorkspacesHistoryMainService.recentlyOpenedStorageKey, serialized);
}
updateWindowsJumpList(): void {
if (!isWindows) {
return; // only on windows
}
const jumpList: JumpListCategory[] = [];
// Tasks
jumpList.push({
type: 'tasks',
items: [
{
type: 'task',
title: nls.localize('newWindow', "New Window"),
description: nls.localize('newWindowDesc', "Opens a new window"),
program: process.execPath,
args: '-n', // force new window
iconPath: process.execPath,
iconIndex: 0
}
]
});
// Recent Workspaces
if (this.getRecentlyOpened().workspaces.length > 0) {
// The user might have meanwhile removed items from the jump list and we have to respect that
// so we need to update our list of recent paths with the choice of the user to not add them again
// Also: Windows will not show our custom category at all if there is any entry which was removed
// by the user! See https://github.com/Microsoft/vscode/issues/15052
let toRemove: URI[] = [];
for (let item of app.getJumpListSettings().removedItems) {
const args = item.args;
if (args) {
const match = /^--(folder|file)-uri\s+"([^"]+)"$/.exec(args);
if (match) {
toRemove.push(URI.parse(match[2]));
}
}
}
this.removeFromRecentlyOpened(toRemove);
// Add entries
jumpList.push({
type: 'custom',
name: nls.localize('recentFolders', "Recent Workspaces"),
items: arrays.coalesce(this.getRecentlyOpened().workspaces.slice(0, 7 /* limit number of entries here */).map(recent => {
const workspace = isRecentWorkspace(recent) ? recent.workspace : recent.folderUri;
const title = recent.label || getSimpleWorkspaceLabel(workspace, this.environmentService.untitledWorkspacesHome);
let description;
let args;
if (isSingleFolderWorkspaceIdentifier(workspace)) {
description = nls.localize('folderDesc', "{0} {1}", getBaseLabel(workspace), getPathLabel(dirname(workspace), this.environmentService));
args = `--folder-uri "${workspace.toString()}"`;
} else {
description = nls.localize('workspaceDesc', "{0} {1}", getBaseLabel(workspace.configPath), getPathLabel(dirname(workspace.configPath), this.environmentService));
args = `--file-uri "${workspace.configPath.toString()}"`;
}
return {
type: 'task',
title,
description,
program: process.execPath,
args,
iconPath: 'explorer.exe', // simulate folder icon
iconIndex: 0
};
}))
});
}
// Recent
jumpList.push({
type: 'recent' // this enables to show files in the "recent" category
});
try {
app.setJumpList(jumpList);
} catch (error) {
this.logService.warn('#setJumpList', error); // since setJumpList is relatively new API, make sure to guard for errors
}
}
}
function location(recent: IRecent): URI {
if (isRecentFolder(recent)) {
return recent.folderUri;
}
if (isRecentFile(recent)) {
return recent.fileUri;
}
return recent.workspace.configPath;
}
function indexOfWorkspace(arr: IRecent[], workspace: IWorkspaceIdentifier): number {
return arrays.firstIndex(arr, w => isRecentWorkspace(w) && w.workspace.id === workspace.id);
}
function indexOfFolder(arr: IRecent[], folderURI: ISingleFolderWorkspaceIdentifier): number {
return arrays.firstIndex(arr, f => isRecentFolder(f) && areResourcesEqual(f.folderUri, folderURI));
}
function indexOfFile(arr: IRecentFile[], fileURI: URI): number {
return arrays.firstIndex(arr, f => areResourcesEqual(f.fileUri, fileURI));
}

View File

@@ -0,0 +1,224 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import * as os from 'os';
import * as path from 'vs/base/common/path';
import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { URI } from 'vs/base/common/uri';
import { IRecentlyOpened, isRecentFolder, IRecentFolder, IRecentWorkspace } from 'vs/platform/workspaces/common/workspacesHistory';
import { toStoreData, restoreRecentlyOpened } from 'vs/platform/workspaces/common/workspacesHistoryStorage';
import { NullLogService } from 'vs/platform/log/common/log';
function toWorkspace(uri: URI): IWorkspaceIdentifier {
return {
id: '1234',
configPath: uri
};
}
function assertEqualURI(u1: URI | undefined, u2: URI | undefined, message?: string): void {
assert.equal(u1 && u1.toString(), u2 && u2.toString(), message);
}
function assertEqualWorkspace(w1: IWorkspaceIdentifier | undefined, w2: IWorkspaceIdentifier | undefined, message?: string): void {
if (!w1 || !w2) {
assert.equal(w1, w2, message);
return;
}
assert.equal(w1.id, w2.id, message);
assertEqualURI(w1.configPath, w2.configPath, message);
}
function assertEqualRecentlyOpened(actual: IRecentlyOpened, expected: IRecentlyOpened, message?: string) {
assert.equal(actual.files.length, expected.files.length, message);
for (let i = 0; i < actual.files.length; i++) {
assertEqualURI(actual.files[i].fileUri, expected.files[i].fileUri, message);
assert.equal(actual.files[i].label, expected.files[i].label);
}
assert.equal(actual.workspaces.length, expected.workspaces.length, message);
for (let i = 0; i < actual.workspaces.length; i++) {
let expectedRecent = expected.workspaces[i];
let actualRecent = actual.workspaces[i];
if (isRecentFolder(actualRecent)) {
assertEqualURI(actualRecent.folderUri, (<IRecentFolder>expectedRecent).folderUri, message);
} else {
assertEqualWorkspace(actualRecent.workspace, (<IRecentWorkspace>expectedRecent).workspace, message);
}
assert.equal(actualRecent.label, expectedRecent.label);
}
}
function assertRestoring(state: IRecentlyOpened, message?: string) {
const stored = toStoreData(state);
const restored = restoreRecentlyOpened(stored, new NullLogService());
assertEqualRecentlyOpened(state, restored, message);
}
const testWSPath = URI.file(path.join(os.tmpdir(), 'windowStateTest', 'test.code-workspace'));
const testFileURI = URI.file(path.join(os.tmpdir(), 'windowStateTest', 'testFile.txt'));
const testFolderURI = URI.file(path.join(os.tmpdir(), 'windowStateTest', 'testFolder'));
const testRemoteFolderURI = URI.parse('foo://bar/c/e');
const testRemoteFileURI = URI.parse('foo://bar/c/d.txt');
const testRemoteWSURI = URI.parse('foo://bar/c/test.code-workspace');
suite('History Storage', () => {
test('storing and restoring', () => {
let ro: IRecentlyOpened;
ro = {
files: [],
workspaces: []
};
assertRestoring(ro, 'empty');
ro = {
files: [{ fileUri: testFileURI }],
workspaces: []
};
assertRestoring(ro, 'file');
ro = {
files: [],
workspaces: [{ folderUri: testFolderURI }]
};
assertRestoring(ro, 'folder');
ro = {
files: [],
workspaces: [{ workspace: toWorkspace(testWSPath) }, { folderUri: testFolderURI }]
};
assertRestoring(ro, 'workspaces and folders');
ro = {
files: [{ fileUri: testRemoteFileURI }],
workspaces: [{ workspace: toWorkspace(testRemoteWSURI) }, { folderUri: testRemoteFolderURI }]
};
assertRestoring(ro, 'remote workspaces and folders');
ro = {
files: [{ label: 'abc', fileUri: testFileURI }],
workspaces: [{ label: 'def', workspace: toWorkspace(testWSPath) }, { folderUri: testRemoteFolderURI }]
};
assertRestoring(ro, 'labels');
});
test('open 1_25', () => {
const v1_25_win = `{
"workspaces": [
{
"id": "2fa677dbdf5f771e775af84dea9feaea",
"configPath": "C:\\\\workspaces\\\\testing\\\\test.code-workspace"
},
"C:\\\\workspaces\\\\testing\\\\test-ext",
{
"id": "d87a0241f8abc86b95c4e5481ebcbf56",
"configPath": "C:\\\\workspaces\\\\test.code-workspace"
}
],
"files": [
"C:\\\\workspaces\\\\test.code-workspace",
"C:\\\\workspaces\\\\testing\\\\test-ext\\\\.gitignore"
]
}`;
let actual = restoreRecentlyOpened(JSON.parse(v1_25_win), new NullLogService());
let expected: IRecentlyOpened = {
files: [{ fileUri: URI.file('C:\\workspaces\\test.code-workspace') }, { fileUri: URI.file('C:\\workspaces\\testing\\test-ext\\.gitignore') }],
workspaces: [
{ workspace: { id: '2fa677dbdf5f771e775af84dea9feaea', configPath: URI.file('C:\\workspaces\\testing\\test.code-workspace') } },
{ folderUri: URI.file('C:\\workspaces\\testing\\test-ext') },
{ workspace: { id: 'd87a0241f8abc86b95c4e5481ebcbf56', configPath: URI.file('C:\\workspaces\\test.code-workspace') } }
]
};
assertEqualRecentlyOpened(actual, expected, 'v1_31_win');
});
test('open 1_31', () => {
const v1_31_win = `{
"workspaces2": [
"file:///c%3A/workspaces/testing/test-ext",
"file:///c%3A/WINDOWS/system32",
{
"id": "d87a0241f8abc86b95c4e5481ebcbf56",
"configPath": "c:\\\\workspaces\\\\test.code-workspace"
}
],
"files2": [
"file:///c%3A/workspaces/vscode/.yarnrc"
]
}`;
let actual = restoreRecentlyOpened(JSON.parse(v1_31_win), new NullLogService());
let expected: IRecentlyOpened = {
files: [{ fileUri: URI.parse('file:///c%3A/workspaces/vscode/.yarnrc') }],
workspaces: [
{ folderUri: URI.parse('file:///c%3A/workspaces/testing/test-ext') },
{ folderUri: URI.parse('file:///c%3A/WINDOWS/system32') },
{ workspace: { id: 'd87a0241f8abc86b95c4e5481ebcbf56', configPath: URI.file('c:\\workspaces\\test.code-workspace') } }
]
};
assertEqualRecentlyOpened(actual, expected, 'v1_31_win');
});
test('open 1_32', () => {
const v1_32 = `{
"workspaces3": [
{
"id": "53b714b46ef1a2d4346568b4f591028c",
"configURIPath": "file:///home/user/workspaces/testing/custom.code-workspace"
},
"file:///home/user/workspaces/testing/folding"
],
"files2": [
"file:///home/user/.config/code-oss-dev/storage.json"
]
}`;
let windowsState = restoreRecentlyOpened(JSON.parse(v1_32), new NullLogService());
let expected: IRecentlyOpened = {
files: [{ fileUri: URI.parse('file:///home/user/.config/code-oss-dev/storage.json') }],
workspaces: [
{ workspace: { id: '53b714b46ef1a2d4346568b4f591028c', configPath: URI.parse('file:///home/user/workspaces/testing/custom.code-workspace') } },
{ folderUri: URI.parse('file:///home/user/workspaces/testing/folding') }
]
};
assertEqualRecentlyOpened(windowsState, expected, 'v1_32');
});
test('open 1_33', () => {
const v1_33 = `{
"workspaces3": [
{
"id": "53b714b46ef1a2d4346568b4f591028c",
"configURIPath": "file:///home/user/workspaces/testing/custom.code-workspace"
},
"file:///home/user/workspaces/testing/folding"
],
"files2": [
"file:///home/user/.config/code-oss-dev/storage.json"
],
"workspaceLabels": [
null,
"abc"
],
"fileLabels": [
"def"
]
}`;
let windowsState = restoreRecentlyOpened(JSON.parse(v1_33), new NullLogService());
let expected: IRecentlyOpened = {
files: [{ label: 'def', fileUri: URI.parse('file:///home/user/.config/code-oss-dev/storage.json') }],
workspaces: [
{ workspace: { id: '53b714b46ef1a2d4346568b4f591028c', configPath: URI.parse('file:///home/user/workspaces/testing/custom.code-workspace') } },
{ label: 'abc', folderUri: URI.parse('file:///home/user/workspaces/testing/folding') }
]
};
assertEqualRecentlyOpened(windowsState, expected, 'v1_33');
});
});