diff --git a/src/vs/code/electron-main/windows.ts b/src/vs/code/electron-main/windows.ts index b3a582ce9c5..af0f2f7e387 100644 --- a/src/vs/code/electron-main/windows.ts +++ b/src/vs/code/electron-main/windows.ts @@ -24,9 +24,9 @@ import { Event as CommonEvent, Emitter } from 'vs/base/common/event'; import product from 'vs/platform/product/node/product'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IWindowsMainService, IOpenConfiguration, IWindowsCountChangedEvent, ICodeWindow, IWindowState as ISingleWindowState, WindowMode } from 'vs/platform/windows/electron-main/windows'; -import { IHistoryMainService } from 'vs/platform/history/common/history'; +import { IHistoryMainService, IRecent } from 'vs/platform/history/common/history'; import { IProcessEnvironment, isMacintosh, isWindows } from 'vs/base/common/platform'; -import { IWorkspacesMainService, IWorkspaceIdentifier, WORKSPACE_FILTER, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspaces'; +import { IWorkspacesMainService, IWorkspaceIdentifier, WORKSPACE_FILTER, isSingleFolderWorkspaceIdentifier, hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspaces'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { mnemonicButtonLabel } from 'vs/base/common/labels'; import { Schemas } from 'vs/base/common/network'; @@ -37,6 +37,7 @@ import { exists } from 'vs/base/node/pfs'; import { getComparisonKey, isEqual, normalizePath, basename as resourcesBasename, originalFSPath, hasTrailingPathSeparator, removeTrailingPathSeparator } from 'vs/base/common/resources'; import { getRemoteAuthority } from 'vs/platform/remote/common/remoteHosts'; import { restoreWindowsState, WindowsStateStorageData, getWindowsStateStoreData } from 'vs/code/electron-main/windowsStateStorage'; +import { getWorkspaceIdentifier } from 'vs/platform/workspaces/electron-main/workspacesMainService'; const enum WindowError { UNRESPONSIVE = 1, @@ -114,6 +115,9 @@ interface IPathToOpen extends IPath { // indicator to create the file path in the Code instance createFilePath?: boolean; + + // optional label for the recent history + label?: string; } function isFolderPathToOpen(path: IPathToOpen): path is IFolderPathToOpen { @@ -130,6 +134,9 @@ interface IFolderPathToOpen { // the remote authority for the Code instance to open. Undefined if not remote. remoteAuthority?: string; + + // optional label for the recent history + label?: string; } function isWorkspacePathToOpen(path: IPathToOpen): path is IWorkspacePathToOpen { @@ -146,6 +153,9 @@ interface IWorkspacePathToOpen { // the remote authority for the Code instance to open. Undefined if not remote. remoteAuthority?: string; + + // optional label for the recent history + label?: string; } export class WindowsManager implements IWindowsMainService { @@ -479,23 +489,18 @@ export class WindowsManager implements IWindowsMainService { // Remember in recent document list (unless this opens for extension development) // Also do not add paths when files are opened for diffing, only if opened individually - if (!usedWindows.some(w => w.isExtensionDevelopmentHost) && !openConfig.diffMode) { - const recentlyOpenedWorkspaces: Array = []; - const recentlyOpenedFiles: URI[] = []; - - pathsToOpen.forEach(win => { - if (win.workspace) { - recentlyOpenedWorkspaces.push(win.workspace); - } else if (win.folderUri) { - recentlyOpenedWorkspaces.push(win.folderUri); - } else if (win.fileUri) { - recentlyOpenedFiles.push(win.fileUri); + if (!usedWindows.some(w => w.isExtensionDevelopmentHost) && !openConfig.diffMode && !this.environmentService.skipAddToRecentlyOpened) { + const recents: IRecent[] = []; + for (let pathToOpen of pathsToOpen) { + if (pathToOpen.workspace) { + recents.push({ label: pathToOpen.label, workspace: pathToOpen.workspace }); + } else if (pathToOpen.folderUri) { + recents.push({ label: pathToOpen.label, folderUri: pathToOpen.folderUri }); + } else if (pathToOpen.fileUri) { + recents.push({ label: pathToOpen.label, fileUri: pathToOpen.fileUri }); } - }); - - if (!this.environmentService.skipAddToRecentlyOpened) { - this.historyMainService.addRecentlyOpened(recentlyOpenedWorkspaces, recentlyOpenedFiles); } + this.historyMainService.addRecentlyOpened(recents); } // If we got started with --wait from the CLI, we need to signal to the outside when the window @@ -844,6 +849,7 @@ export class WindowsManager implements IWindowsMainService { const path = this.parseUri(pathToOpen.uri, pathToOpen.typeHint, parseOptions); if (path) { + path.label = pathToOpen.label; pathsToOpen.push(path); } else { @@ -1031,7 +1037,7 @@ export class WindowsManager implements IWindowsMainService { } if (hasWorkspaceFileExtension(uri.path) && !options.forceOpenWorkspaceAsFile) { return { - workspace: this.workspacesMainService.getWorkspaceIdentifier(uri), + workspace: getWorkspaceIdentifier(uri), remoteAuthority }; } @@ -1098,9 +1104,8 @@ export class WindowsManager implements IWindowsMainService { } } } catch (error) { - this.historyMainService.removeFromRecentlyOpened([candidate]); // since file does not seem to exist anymore, remove from recent - const fileUri = URI.file(candidate); + this.historyMainService.removeFromRecentlyOpened([fileUri]); // since file does not seem to exist anymore, remove from recent if (options && options.ignoreFileNotFound) { return { fileUri, createFilePath: true, remoteAuthority }; // assume this is a file that does not yet exist } @@ -1513,7 +1518,7 @@ export class WindowsManager implements IWindowsMainService { private doEnterWorkspace(win: ICodeWindow, result: IEnterWorkspaceResult): IEnterWorkspaceResult { // Mark as recently opened - this.historyMainService.addRecentlyOpened([result.workspace], []); + this.historyMainService.addRecentlyOpened([{ workspace: result.workspace }]); // Trigger Eevent to indicate load of workspace into window this._onWindowReady.fire(win); @@ -1968,7 +1973,7 @@ class WorkspacesManager { if (!isValid) { return null; // return early if the workspace is not valid } - const workspaceIdentifier = this.workspacesMainService.getWorkspaceIdentifier(path); + const workspaceIdentifier = getWorkspaceIdentifier(path); return this.doOpenWorkspace(window, workspaceIdentifier); }); @@ -1984,7 +1989,7 @@ class WorkspacesManager { } // Prevent overwriting a workspace that is currently opened in another window - if (findWindowOnWorkspace(this.windowsMainService.getWindows(), this.workspacesMainService.getWorkspaceIdentifier(path))) { + if (findWindowOnWorkspace(this.windowsMainService.getWindows(), getWorkspaceIdentifier(path))) { const options: Electron.MessageBoxOptions = { title: product.nameLong, type: 'info', diff --git a/src/vs/platform/history/common/history.ts b/src/vs/platform/history/common/history.ts index 9f040b786d2..2d7a8e08611 100644 --- a/src/vs/platform/history/common/history.ts +++ b/src/vs/platform/history/common/history.ts @@ -3,27 +3,57 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IPath } from 'vs/platform/windows/common/windows'; import { Event as CommonEvent } from 'vs/base/common/event'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { URI } from 'vs/base/common/uri'; +import { IPath } from 'vs/platform/windows/common/windows'; export const IHistoryMainService = createDecorator('historyMainService'); export interface IRecentlyOpened { - workspaces: Array; - files: URI[]; + workspaces: Array; + 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['workspace']; +} + +export function isRecentFolder(curr: IRecent): curr is IRecentFolder { + return !!curr['folderUri']; +} + +export function isRecentFile(curr: IRecent): curr is IRecentFile { + return !!curr['fileUri']; +} + + export interface IHistoryMainService { _serviceBrand: any; onRecentlyOpenedChange: CommonEvent; - addRecentlyOpened(workspaces: undefined | Array, files: URI[]): void; - getRecentlyOpened(currentWorkspace?: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier, currentFiles?: IPath[]): IRecentlyOpened; - removeFromRecentlyOpened(paths: Array): void; + addRecentlyOpened(recents: IRecent[]): void; + getRecentlyOpened(currentWorkspace?: IWorkspaceIdentifier, currentFolder?: ISingleFolderWorkspaceIdentifier, currentFiles?: IPath[]): IRecentlyOpened; + removeFromRecentlyOpened(paths: URI[]): void; clearRecentlyOpened(): void; updateWindowsJumpList(): void; diff --git a/src/vs/platform/history/electron-main/historyMainService.ts b/src/vs/platform/history/electron-main/historyMainService.ts index 156e85b9f8f..b2cdb76a419 100644 --- a/src/vs/platform/history/electron-main/historyMainService.ts +++ b/src/vs/platform/history/electron-main/historyMainService.ts @@ -11,12 +11,11 @@ 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, isLinux } from 'vs/base/common/platform'; -import { IWorkspaceIdentifier, IWorkspacesMainService, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; -import { IHistoryMainService, IRecentlyOpened } from 'vs/platform/history/common/history'; -import { isEqual } from 'vs/base/common/extpath'; +import { isWindows, isMacintosh } from 'vs/base/common/platform'; +import { IWorkspaceIdentifier, IWorkspacesMainService, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; +import { IHistoryMainService, IRecentlyOpened, isRecentWorkspace, isRecentFolder, IRecent, isRecentFile } from 'vs/platform/history/common/history'; import { RunOnceScheduler } from 'vs/base/common/async'; -import { getComparisonKey, isEqual as areResourcesEqual, dirname, originalFSPath } from 'vs/base/common/resources'; +import { isEqual as areResourcesEqual, dirname, originalFSPath } 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'; @@ -47,37 +46,26 @@ export class HistoryMainService implements IHistoryMainService { this.macOSRecentDocumentsUpdater = new RunOnceScheduler(() => this.updateMacOSRecentDocuments(), 800); } - addRecentlyOpened(workspaces: Array, files: URI[]): void { - if ((workspaces && workspaces.length > 0) || (files && files.length > 0)) { - const mru = this.getRecentlyOpened(); - - // Workspaces - if (Array.isArray(workspaces)) { - workspaces.forEach(workspace => { - const isUntitledWorkspace = !isSingleFolderWorkspaceIdentifier(workspace) && this.workspacesMainService.isUntitledWorkspace(workspace); - if (isUntitledWorkspace) { - return; // only store saved workspaces - } - - mru.workspaces.unshift(workspace); - mru.workspaces = arrays.distinct(mru.workspaces, workspace => this.distinctFn(workspace)); - - // We do not add to recent documents here because on Windows we do this from a custom - // JumpList and on macOS we fill the recent documents in one go from all our data later. - }); - } - - // Files - if (Array.isArray(files)) { - files.forEach((fileUri) => { - mru.files.unshift(fileUri); - mru.files = arrays.distinct(mru.files, file => this.distinctFn(file)); + addRecentlyOpened(newlyAdded: IRecent[]): void { + const mru = this.getRecentlyOpened(); + for (let curr of newlyAdded) { + if (isRecentWorkspace(curr)) { + if (!this.workspacesMainService.isUntitledWorkspace(curr.workspace) && indexOfWorkspace(mru.workspaces, curr.workspace) >= 0) { + mru.workspaces.unshift(curr); + } + } else if (isRecentFolder(curr)) { + if (indexOfFolder(mru.workspaces, curr.folderUri) >= 0) { + mru.workspaces.unshift(curr); + } + } else { + if (indexOfFile(mru.files, curr.fileUri) >= 0) { + mru.files.unshift(curr); // Add to recent documents (Windows only, macOS later) - if (isWindows && fileUri.scheme === Schemas.file) { - app.addRecentDocument(fileUri.fsPath); + if (isWindows && curr.fileUri.scheme === Schemas.file) { + app.addRecentDocument(curr.fileUri.fsPath); } - }); + } } // Make sure its bounded @@ -94,53 +82,23 @@ export class HistoryMainService implements IHistoryMainService { } } - removeFromRecentlyOpened(pathsToRemove: Array): void { + 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(); - let update = false; + const workspaces = mru.workspaces.filter(keep); + const files = mru.files.filter(keep); - pathsToRemove.forEach(pathToRemove => { - - // Remove workspace - let index = arrays.firstIndex(mru.workspaces, workspace => { - if (isWorkspaceIdentifier(pathToRemove)) { - return isWorkspaceIdentifier(workspace) && areResourcesEqual(pathToRemove.configPath, workspace.configPath, !isLinux /* ignorecase */); - } - if (isSingleFolderWorkspaceIdentifier(pathToRemove)) { - return isSingleFolderWorkspaceIdentifier(workspace) && areResourcesEqual(pathToRemove, workspace); - } - if (typeof pathToRemove === 'string') { - if (isSingleFolderWorkspaceIdentifier(workspace)) { - return workspace.scheme === Schemas.file && isEqual(pathToRemove, workspace.fsPath, !isLinux /* ignorecase */); - } - if (isWorkspaceIdentifier(workspace)) { - return workspace.configPath.scheme === Schemas.file && isEqual(pathToRemove, workspace.configPath.fsPath, !isLinux /* ignorecase */); - } - } - return false; - }); - if (index >= 0) { - mru.workspaces.splice(index, 1); - update = true; - } - - // Remove file - index = arrays.firstIndex(mru.files, file => { - if (pathToRemove instanceof URI) { - return areResourcesEqual(file, pathToRemove); - } else if (typeof pathToRemove === 'string') { - return isEqual(file.fsPath, pathToRemove, !isLinux /* ignorecase */); - } - return false; - }); - - if (index >= 0) { - mru.files.splice(index, 1); - update = true; - } - }); - - if (update) { - this.saveRecentlyOpened(mru); + 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 @@ -164,28 +122,19 @@ export class HistoryMainService implements IHistoryMainService { const mru = this.getRecentlyOpened(); // Fill in workspaces - let entries = 0; - for (let i = 0; i < mru.workspaces.length && entries < HistoryMainService.MAX_MACOS_DOCK_RECENT_FOLDERS; i++) { - const workspace = mru.workspaces[i]; - if (isSingleFolderWorkspaceIdentifier(workspace)) { - if (workspace.scheme === Schemas.file) { - app.addRecentDocument(originalFSPath(workspace)); - entries++; - } - } else { - if (workspace.configPath.scheme === Schemas.file) { - app.addRecentDocument(originalFSPath(workspace.configPath)); - entries++; - } + for (let i = 0, entries = 0; i < mru.workspaces.length && entries < HistoryMainService.MAX_MACOS_DOCK_RECENT_FOLDERS; i++) { + const loc = location(mru.workspaces[i]); + if (loc.scheme === Schemas.file) { + app.addRecentDocument(originalFSPath(loc)); + entries++; } } // Fill in files - entries = 0; - for (let i = 0; i < mru.files.length && entries < HistoryMainService.MAX_MACOS_DOCK_RECENT_FILES; i++) { - const file = mru.files[i]; - if (file.scheme === Schemas.file) { - app.addRecentDocument(originalFSPath(file)); + for (let i = 0, entries = 0; i < mru.files.length && entries < HistoryMainService.MAX_MACOS_DOCK_RECENT_FILES; i++) { + const loc = location(mru.files[i]); + if (loc.scheme === Schemas.file) { + app.addRecentDocument(originalFSPath(loc)); entries++; } } @@ -199,39 +148,33 @@ export class HistoryMainService implements IHistoryMainService { this._onRecentlyOpenedChange.fire(); } - getRecentlyOpened(currentWorkspace?: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier, currentFiles?: IPath[]): IRecentlyOpened { + getRecentlyOpened(currentWorkspace?: IWorkspaceIdentifier, currentFolder?: ISingleFolderWorkspaceIdentifier, currentFiles?: IPath[]): IRecentlyOpened { // Get from storage let { workspaces, files } = this.getRecentlyOpenedFromStorage(); // Add current workspace to beginning if set - if (currentWorkspace) { - workspaces.unshift(currentWorkspace); + if (currentWorkspace && !this.workspacesMainService.isUntitledWorkspace(currentWorkspace) && !workspaces.some(w => isRecentWorkspace(w) && w.workspace.id === currentWorkspace.id)) { + workspaces.unshift({ workspace: currentWorkspace }); + } + + if (currentFolder && !workspaces.some(w => isRecentFolder(w) && areResourcesEqual(w.folderUri, currentFolder))) { + workspaces.unshift({ folderUri: currentFolder }); } // Add currently files to open to the beginning if any if (currentFiles) { - files.unshift(...arrays.coalesce(currentFiles.map(f => f.fileUri))); + for (let currentFile of currentFiles) { + const fileUri = currentFile.fileUri; + if (fileUri && !files.some(f => areResourcesEqual(f.fileUri, fileUri))) { + files.unshift({ fileUri }); + } + } } - // Clear those dupes - workspaces = arrays.distinct(workspaces, workspace => this.distinctFn(workspace)); - files = arrays.distinct(files, file => this.distinctFn(file)); - - // Hide untitled workspaces - workspaces = workspaces.filter(workspace => isSingleFolderWorkspaceIdentifier(workspace) || !this.workspacesMainService.isUntitledWorkspace(workspace)); - return { workspaces, files }; } - private distinctFn(workspaceOrFile: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | URI): string { - if (workspaceOrFile instanceof URI) { - return getComparisonKey(workspaceOrFile); - } - - return workspaceOrFile.id; - } - private getRecentlyOpenedFromStorage(): IRecentlyOpened { const storedRecents = this.stateService.getItem(HistoryMainService.recentlyOpenedStorageKey); return restoreRecentlyOpened(storedRecents); @@ -272,7 +215,7 @@ export class HistoryMainService implements IHistoryMainService { // 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: Array = []; + let toRemove: URI[] = []; for (let item of app.getJumpListSettings().removedItems) { const args = item.args; if (args) { @@ -288,8 +231,9 @@ export class HistoryMainService implements IHistoryMainService { 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(workspace => { - const title = getSimpleWorkspaceLabel(workspace, this.environmentService.untitledWorkspacesHome); + 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)) { @@ -325,3 +269,25 @@ export class HistoryMainService implements IHistoryMainService { } } } + +function location(recent: IRecent): URI { + if (isRecentFolder(recent)) { + return recent.folderUri; + } + if (isRecentFile(recent)) { + return recent.fileUri; + } + return recent.workspace.configPath; +} + +function indexOfWorkspace(arr: Array, workspace: IWorkspaceIdentifier): number { + return arrays.firstIndex(arr, w => isRecentWorkspace(w) && w.workspace.id === workspace.id); +} + +function indexOfFolder(arr: Array, folderURI: ISingleFolderWorkspaceIdentifier): number { + return arrays.firstIndex(arr, f => isRecentFolder(f) && areResourcesEqual(f.folderUri, folderURI)); +} + +function indexOfFile(arr: Array, fileURI: URI): number { + return arrays.firstIndex(arr, f => isRecentFile(f) && areResourcesEqual(f.fileUri, fileURI)); +} \ No newline at end of file diff --git a/src/vs/platform/history/electron-main/historyStorage.ts b/src/vs/platform/history/electron-main/historyStorage.ts index 5f4649dfcd0..3649a237e52 100644 --- a/src/vs/platform/history/electron-main/historyStorage.ts +++ b/src/vs/platform/history/electron-main/historyStorage.ts @@ -3,12 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { UriComponents, URI } from 'vs/base/common/uri'; -import { IRecentlyOpened } from 'vs/platform/history/common/history'; -import { isSingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; +import { IRecentlyOpened, isRecentFolder } from 'vs/platform/history/common/history'; interface ISerializedRecentlyOpened { workspaces3: Array; // workspace or URI.toString() + workspaceLabels: Array; files2: string[]; // files as URI.toString() + fileLabels: Array; } interface ILegacySerializedRecentlyOpened { @@ -27,19 +28,21 @@ export function restoreRecentlyOpened(data: RecentlyOpenedStorageData | undefine if (data) { const storedRecents = data as ISerializedRecentlyOpened & ILegacySerializedRecentlyOpened; if (Array.isArray(storedRecents.workspaces3)) { - for (const workspace of storedRecents.workspaces3) { + for (let i = 0; i < storedRecents.workspaces3.length; i++) { + const workspace = storedRecents.workspaces3[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({ id: workspace.id, configPath: URI.parse(workspace.configURIPath) }); + result.workspaces.push({ label, workspace: { id: workspace.id, configPath: URI.parse(workspace.configURIPath) } }); } else if (typeof workspace === 'string') { - result.workspaces.push(URI.parse(workspace)); + result.workspaces.push({ label, folderUri: URI.parse(workspace) }); } } } else if (Array.isArray(storedRecents.workspaces2)) { for (const workspace of storedRecents.workspaces2) { if (typeof workspace === 'object' && typeof workspace.id === 'string' && typeof workspace.configPath === 'string') { - result.workspaces.push({ id: workspace.id, configPath: URI.file(workspace.configPath) }); + result.workspaces.push({ workspace: { id: workspace.id, configPath: URI.file(workspace.configPath) } }); } else if (typeof workspace === 'string') { - result.workspaces.push(URI.parse(workspace)); + result.workspaces.push({ folderUri: URI.parse(workspace) }); } } } else if (Array.isArray(storedRecents.workspaces)) { @@ -47,26 +50,28 @@ export function restoreRecentlyOpened(data: RecentlyOpenedStorageData | undefine // format of 1.25 and before for (const workspace of storedRecents.workspaces) { if (typeof workspace === 'string') { - result.workspaces.push(URI.file(workspace)); + result.workspaces.push({ folderUri: URI.file(workspace) }); } else if (typeof workspace === 'object' && typeof workspace['id'] === 'string' && typeof workspace['configPath'] === 'string') { - result.workspaces.push({ id: workspace['id'], configPath: URI.file(workspace['configPath']) }); + result.workspaces.push({ workspace: { id: workspace['id'], configPath: URI.file(workspace['configPath']) } }); } else if (workspace && typeof workspace['path'] === 'string' && typeof workspace['scheme'] === 'string') { // added by 1.26-insiders - result.workspaces.push(URI.revive(workspace)); + result.workspaces.push({ folderUri: URI.revive(workspace) }); } } } if (Array.isArray(storedRecents.files2)) { - for (const file of storedRecents.files2) { + for (let i = 0; i < storedRecents.files2.length; i++) { + const file = storedRecents.files2[i]; + const label: string | undefined = (Array.isArray(storedRecents.fileLabels) && storedRecents.fileLabels[i]) || undefined; if (typeof file === 'string') { - result.files.push(URI.parse(file)); + result.files.push({ label, fileUri: URI.parse(file) }); } } } else if (Array.isArray(storedRecents.files)) { for (const file of storedRecents.files) { if (typeof file === 'string') { - result.files.push(URI.file(file)); + result.files.push({ fileUri: URI.file(file) }); } } } @@ -75,19 +80,21 @@ export function restoreRecentlyOpened(data: RecentlyOpenedStorageData | undefine return result; } -export function toStoreData(recent: IRecentlyOpened): RecentlyOpenedStorageData { - const serialized: ISerializedRecentlyOpened = { workspaces3: [], files2: [] }; +export function toStoreData(recents: IRecentlyOpened): RecentlyOpenedStorageData { + const serialized: ISerializedRecentlyOpened = { workspaces3: [], files2: [], workspaceLabels: [], fileLabels: [] }; - for (const workspace of recent.workspaces) { - if (isSingleFolderWorkspaceIdentifier(workspace)) { - serialized.workspaces3.push(workspace.toString()); + for (const recent of recents.workspaces) { + if (isRecentFolder(recent)) { + serialized.workspaces3.push(recent.folderUri.toString()); } else { - serialized.workspaces3.push({ id: workspace.id, configURIPath: workspace.configPath.toString() }); + serialized.workspaces3.push({ id: recent.workspace.id, configURIPath: recent.workspace.configPath.toString() }); } + serialized.workspaceLabels.push(recent.label || null); } - for (const file of recent.files) { - serialized.files2.push(file.toString()); + for (const recent of recents.files) { + serialized.files2.push(recent.fileUri.toString()); + serialized.fileLabels.push(recent.label || null); } return serialized; diff --git a/src/vs/platform/history/test/electron-main/historyStorage.test.ts b/src/vs/platform/history/test/electron-main/historyStorage.test.ts index bf7f3acaaa4..aedc55b46c6 100644 --- a/src/vs/platform/history/test/electron-main/historyStorage.test.ts +++ b/src/vs/platform/history/test/electron-main/historyStorage.test.ts @@ -8,7 +8,7 @@ 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 } from 'vs/platform/history/common/history'; +import { IRecentlyOpened, isRecentFolder, IRecentFolder, IRecentWorkspace } from 'vs/platform/history/common/history'; import { toStoreData, restoreRecentlyOpened } from 'vs/platform/history/electron-main/historyStorage'; function toWorkspace(uri: URI): IWorkspaceIdentifier { @@ -30,18 +30,22 @@ function assertEqualWorkspace(w1: IWorkspaceIdentifier | undefined, w2: IWorkspa assertEqualURI(w1.configPath, w2.configPath, message); } -function assertEqualRecentlyOpened(expected: IRecentlyOpened, actual: IRecentlyOpened, message?: string) { - assert.equal(expected.files.length, actual.files.length, message); - for (let i = 0; i < expected.files.length; i++) { - assertEqualURI(expected.files[i], actual.files[i], 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(expected.workspaces.length, actual.workspaces.length, message); - for (let i = 0; i < expected.workspaces.length; i++) { - if (expected.workspaces[i] instanceof URI) { - assertEqualURI(expected.workspaces[i], actual.workspaces[i], message); + 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, (expectedRecent).folderUri, message); } else { - assertEqualWorkspace(expected.workspaces[i], actual.workspaces[i], message); + assertEqualWorkspace(actualRecent.workspace, (expectedRecent).workspace, message); } + assert.equal(actualRecent.label, expectedRecent.label); } } @@ -68,26 +72,31 @@ suite('History Storage', () => { }; assertRestoring(ro, 'empty'); ro = { - files: [testFileURI], + files: [{ fileUri: testFileURI }], workspaces: [] }; assertRestoring(ro, 'file'); ro = { files: [], - workspaces: [testFolderURI] + workspaces: [{ folderUri: testFolderURI }] }; assertRestoring(ro, 'folder'); ro = { files: [], - workspaces: [toWorkspace(testWSPath), testFolderURI] + workspaces: [{ workspace: toWorkspace(testWSPath) }, { folderUri: testFolderURI }] }; assertRestoring(ro, 'workspaces and folders'); ro = { - files: [testRemoteFileURI], - workspaces: [toWorkspace(testRemoteWSURI), testRemoteFolderURI] + 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', () => { @@ -111,15 +120,15 @@ suite('History Storage', () => { let actual = restoreRecentlyOpened(JSON.parse(v1_25_win)); let expected: IRecentlyOpened = { - files: [URI.file('C:\\workspaces\\test.code-workspace'), URI.file('C:\\workspaces\\testing\\test-ext\\.gitignore')], + files: [{ fileUri: URI.file('C:\\workspaces\\test.code-workspace') }, { fileUri: URI.file('C:\\workspaces\\testing\\test-ext\\.gitignore') }], workspaces: [ - { id: '2fa677dbdf5f771e775af84dea9feaea', configPath: URI.file('C:\\workspaces\\testing\\test.code-workspace') }, - URI.file('C:\\workspaces\\testing\\test-ext'), - { id: 'd87a0241f8abc86b95c4e5481ebcbf56', configPath: URI.file('C:\\workspaces\\test.code-workspace') } + { 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(expected, actual, 'v1_31_win'); + assertEqualRecentlyOpened(actual, expected, 'v1_31_win'); }); test('open 1_31', () => { @@ -139,15 +148,15 @@ suite('History Storage', () => { let actual = restoreRecentlyOpened(JSON.parse(v1_31_win)); let expected: IRecentlyOpened = { - files: [URI.parse('file:///c%3A/workspaces/vscode/.yarnrc')], + files: [{ fileUri: URI.parse('file:///c%3A/workspaces/vscode/.yarnrc') }], workspaces: [ - URI.parse('file:///c%3A/workspaces/testing/test-ext'), - URI.parse('file:///c%3A/WINDOWS/system32'), - { id: 'd87a0241f8abc86b95c4e5481ebcbf56', configPath: URI.file('c:\\workspaces\\test.code-workspace') } + { 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(expected, actual, 'v1_31_win'); + assertEqualRecentlyOpened(actual, expected, 'v1_31_win'); }); test('open 1_32', () => { @@ -166,15 +175,49 @@ suite('History Storage', () => { let windowsState = restoreRecentlyOpened(JSON.parse(v1_32)); let expected: IRecentlyOpened = { - files: [URI.parse('file:///home/user/.config/code-oss-dev/storage.json')], + files: [{ fileUri: URI.parse('file:///home/user/.config/code-oss-dev/storage.json') }], workspaces: [ - { id: '53b714b46ef1a2d4346568b4f591028c', configPath: URI.parse('file:///home/user/workspaces/testing/custom.code-workspace') }, - URI.parse('file:///home/user/workspaces/testing/folding') + { workspace: { id: '53b714b46ef1a2d4346568b4f591028c', configPath: URI.parse('file:///home/user/workspaces/testing/custom.code-workspace') } }, + { folderUri: URI.parse('file:///home/user/workspaces/testing/folding') } ] }; - assertEqualRecentlyOpened(expected, windowsState, 'v1_32'); + 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)); + 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'); }); + }); \ No newline at end of file diff --git a/src/vs/platform/windows/common/windows.ts b/src/vs/platform/windows/common/windows.ts index 1be5b5e4a63..73a44e03f07 100644 --- a/src/vs/platform/windows/common/windows.ts +++ b/src/vs/platform/windows/common/windows.ts @@ -9,7 +9,7 @@ import { ITelemetryData } from 'vs/platform/telemetry/common/telemetry'; import { IProcessEnvironment, isMacintosh, isLinux } from 'vs/base/common/platform'; import { ParsedArgs, IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; -import { IRecentlyOpened } from 'vs/platform/history/common/history'; +import { IRecentlyOpened, IRecent } from 'vs/platform/history/common/history'; import { ISerializableCommandAction } from 'vs/platform/actions/common/actions'; import { ExportData } from 'vs/base/common/performance'; import { LogLevel } from 'vs/platform/log/common/log'; @@ -117,8 +117,8 @@ export interface IWindowsService { enterWorkspace(windowId: number, path: URI): Promise; toggleFullScreen(windowId: number): Promise; setRepresentedFilename(windowId: number, fileName: string): Promise; - addRecentlyOpened(workspaces: URI[], folders: URI[], files: URI[]): Promise; - removeFromRecentlyOpened(paths: Array): Promise; + addRecentlyOpened(recents: IRecent[]): Promise; + removeFromRecentlyOpened(paths: URI[]): Promise; clearRecentlyOpened(): Promise; getRecentlyOpened(windowId: number): Promise; focusWindow(windowId: number): Promise; @@ -190,6 +190,7 @@ export type URIType = 'file' | 'folder'; export interface IURIToOpen { uri: URI; typeHint?: URIType; + label?: string; } export interface IWindowService { diff --git a/src/vs/platform/windows/electron-main/windowsService.ts b/src/vs/platform/windows/electron-main/windowsService.ts index d832fe25a4a..4c448bdcb75 100644 --- a/src/vs/platform/windows/electron-main/windowsService.ts +++ b/src/vs/platform/windows/electron-main/windowsService.ts @@ -16,8 +16,8 @@ import { Event } from 'vs/base/common/event'; import { IURLService, IURLHandler } from 'vs/platform/url/common/url'; import { ILifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain'; import { IWindowsMainService, ISharedProcess, ICodeWindow } from 'vs/platform/windows/electron-main/windows'; -import { IHistoryMainService, IRecentlyOpened } from 'vs/platform/history/common/history'; -import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, IWorkspacesMainService } from 'vs/platform/workspaces/common/workspaces'; +import { IHistoryMainService, IRecentlyOpened, IRecent } from 'vs/platform/history/common/history'; +import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { ISerializableCommandAction } from 'vs/platform/actions/common/actions'; import { Schemas } from 'vs/base/common/network'; import { mnemonicButtonLabel } from 'vs/base/common/labels'; @@ -50,8 +50,7 @@ export class WindowsService implements IWindowsService, IURLHandler, IDisposable @IURLService urlService: IURLService, @ILifecycleService private readonly lifecycleService: ILifecycleService, @IHistoryMainService private readonly historyService: IHistoryMainService, - @ILogService private readonly logService: ILogService, - @IWorkspacesMainService private readonly workspacesMainService: IWorkspacesMainService, + @ILogService private readonly logService: ILogService ) { urlService.registerHandler(this); @@ -157,14 +156,12 @@ export class WindowsService implements IWindowsService, IURLHandler, IDisposable return this.withWindow(windowId, codeWindow => codeWindow.setRepresentedFilename(fileName)); } - async addRecentlyOpened(workspaces: URI[], folders: URI[], files: URI[]): Promise { + async addRecentlyOpened(recents: IRecent[]): Promise { this.logService.trace('windowsService#addRecentlyOpened'); - - const workspaceIdentifiers = workspaces.map(w => this.workspacesMainService.getWorkspaceIdentifier(w)); - this.historyService.addRecentlyOpened([...workspaceIdentifiers, ...folders], files); + this.historyService.addRecentlyOpened(recents); } - async removeFromRecentlyOpened(paths: Array): Promise { + async removeFromRecentlyOpened(paths: URI[]): Promise { this.logService.trace('windowsService#removeFromRecentlyOpened'); this.historyService.removeFromRecentlyOpened(paths); @@ -179,7 +176,7 @@ export class WindowsService implements IWindowsService, IURLHandler, IDisposable async getRecentlyOpened(windowId: number): Promise { this.logService.trace('windowsService#getRecentlyOpened', windowId); - return this.withWindow(windowId, codeWindow => this.historyService.getRecentlyOpened(codeWindow.config.workspace || codeWindow.config.folderUri, codeWindow.config.filesToOpen), () => this.historyService.getRecentlyOpened())!; + return this.withWindow(windowId, codeWindow => this.historyService.getRecentlyOpened(codeWindow.config.workspace, codeWindow.config.folderUri, codeWindow.config.filesToOpen), () => this.historyService.getRecentlyOpened())!; } async newWindowTab(): Promise { diff --git a/src/vs/platform/windows/node/windowsIpc.ts b/src/vs/platform/windows/node/windowsIpc.ts index 81314da6c66..b06f9df6fce 100644 --- a/src/vs/platform/windows/node/windowsIpc.ts +++ b/src/vs/platform/windows/node/windowsIpc.ts @@ -7,7 +7,7 @@ import { Event } from 'vs/base/common/event'; import { IChannel, IServerChannel } from 'vs/base/parts/ipc/node/ipc'; import { IWindowsService, INativeOpenDialogOptions, IEnterWorkspaceResult, CrashReporterStartOptions, IMessageBoxResult, MessageBoxOptions, SaveDialogOptions, OpenDialogOptions, IDevToolsOptions, INewWindowOptions, IURIToOpen } from 'vs/platform/windows/common/windows'; import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, reviveWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; -import { IRecentlyOpened } from 'vs/platform/history/common/history'; +import { IRecentlyOpened, isRecentFile, isRecentFolder, IRecent, isRecentWorkspace } from 'vs/platform/history/common/history'; import { ISerializableCommandAction } from 'vs/platform/actions/common/actions'; import { URI } from 'vs/base/common/uri'; import { ParsedArgs } from 'vs/platform/environment/common/environment'; @@ -59,14 +59,17 @@ export class WindowsChannel implements IServerChannel { case 'enterWorkspace': return this.service.enterWorkspace(arg[0], URI.revive(arg[1])); case 'toggleFullScreen': return this.service.toggleFullScreen(arg); case 'setRepresentedFilename': return this.service.setRepresentedFilename(arg[0], arg[1]); - case 'addRecentlyOpened': return this.service.addRecentlyOpened(arg[0].map(URI.revive), arg[1].map(URI.revive), arg[2].map(URI.revive)); - case 'removeFromRecentlyOpened': { - let paths: Array = arg; - if (Array.isArray(paths)) { - paths = paths.map(path => isChanneledWorkspaceIdentifier(path) ? reviveWorkspaceIdentifier(path) : typeof path === 'string' ? path : URI.revive(path)); + case 'addRecentlyOpened': return this.service.addRecentlyOpened(arg.map((recent: IRecent) => { + if (isRecentFile(recent)) { + recent.fileUri = URI.revive(recent.fileUri); + } else if (isRecentFolder(recent)) { + recent.folderUri = URI.revive(recent.folderUri); + } else { + recent.workspace = reviveWorkspaceIdentifier(recent.workspace); } - return this.service.removeFromRecentlyOpened(paths); - } + return recent; + })); + case 'removeFromRecentlyOpened': return this.service.removeFromRecentlyOpened(arg.map(URI.revive)); case 'clearRecentlyOpened': return this.service.clearRecentlyOpened(); case 'newWindowTab': return this.service.newWindowTab(); case 'showPreviousWindowTab': return this.service.showPreviousWindowTab(); @@ -85,7 +88,7 @@ export class WindowsChannel implements IServerChannel { case 'minimizeWindow': return this.service.minimizeWindow(arg); case 'onWindowTitleDoubleClick': return this.service.onWindowTitleDoubleClick(arg); case 'setDocumentEdited': return this.service.setDocumentEdited(arg[0], arg[1]); - case 'openWindow': return this.service.openWindow(arg[0], arg[1] ? (arg[1]).map(r => ({ uri: URI.revive(r.uri), typeHint: r.typeHint })) : arg[1], arg[2]); + case 'openWindow': return this.service.openWindow(arg[0], arg[1] ? (arg[1]).map(r => { r.uri = URI.revive(r.uri); return r; }) : arg[1], arg[2]); case 'openNewWindow': return this.service.openNewWindow(arg); case 'showWindow': return this.service.showWindow(arg); case 'getWindows': return this.service.getWindows(); @@ -166,7 +169,8 @@ export class WindowsChannelClient implements IWindowsService { enterWorkspace(windowId: number, path: URI): Promise { return this.channel.call('enterWorkspace', [windowId, path]).then((result: IEnterWorkspaceResult) => { - return { backupPath: result.backupPath, workspace: reviveWorkspaceIdentifier(result.workspace) }; + result.workspace = reviveWorkspaceIdentifier(result.workspace); + return result; }); } @@ -178,11 +182,11 @@ export class WindowsChannelClient implements IWindowsService { return this.channel.call('setRepresentedFilename', [windowId, fileName]); } - addRecentlyOpened(workspaces: URI[], folders: URI[], files: URI[]): Promise { - return this.channel.call('addRecentlyOpened', [workspaces, folders, files]); + addRecentlyOpened(recent: IRecent[]): Promise { + return this.channel.call('addRecentlyOpened', recent); } - removeFromRecentlyOpened(paths: Array): Promise { + removeFromRecentlyOpened(paths: Array): Promise { return this.channel.call('removeFromRecentlyOpened', paths); } @@ -193,8 +197,8 @@ export class WindowsChannelClient implements IWindowsService { getRecentlyOpened(windowId: number): Promise { return this.channel.call('getRecentlyOpened', windowId) .then((recentlyOpened: IRecentlyOpened) => { - recentlyOpened.workspaces = recentlyOpened.workspaces.map(workspace => isChanneledWorkspaceIdentifier(workspace) ? reviveWorkspaceIdentifier(workspace) : URI.revive(workspace)); - recentlyOpened.files = recentlyOpened.files.map(URI.revive); + recentlyOpened.workspaces.forEach(recent => isRecentWorkspace(recent) ? recent.workspace = reviveWorkspaceIdentifier(recent.workspace) : recent.folderUri = URI.revive(recent.folderUri)); + recentlyOpened.files.forEach(recent => recent.fileUri = URI.revive(recent.fileUri)); return recentlyOpened; }); } @@ -336,8 +340,4 @@ export class WindowsChannelClient implements IWindowsService { resolveProxy(windowId: number, url: string): Promise { return Promise.resolve(this.channel.call('resolveProxy', [windowId, url])); } -} - -function isChanneledWorkspaceIdentifier(obj: any): obj is IWorkspaceIdentifier { - return obj && obj['configPath']; } \ No newline at end of file diff --git a/src/vs/platform/workspaces/common/workspaces.ts b/src/vs/platform/workspaces/common/workspaces.ts index 0f745aea0da..57961a4c5c5 100644 --- a/src/vs/platform/workspaces/common/workspaces.ts +++ b/src/vs/platform/workspaces/common/workspaces.ts @@ -106,8 +106,6 @@ export interface IWorkspacesMainService extends IWorkspacesService { deleteUntitledWorkspaceSync(workspace: IWorkspaceIdentifier): void; getUntitledWorkspacesSync(): IUntitledWorkspaceInfo[]; - - getWorkspaceIdentifier(workspacePath: URI): IWorkspaceIdentifier; } export interface IWorkspacesService { @@ -116,6 +114,8 @@ export interface IWorkspacesService { createUntitledWorkspace(folders?: IWorkspaceFolderCreationData[], remoteAuthority?: string): Promise; deleteUntitledWorkspace(workspace: IWorkspaceIdentifier): Promise; + + getWorkspaceIdentifier(workspacePath: URI): Promise; } export function isSingleFolderWorkspaceIdentifier(obj: any): obj is ISingleFolderWorkspaceIdentifier { diff --git a/src/vs/platform/workspaces/electron-main/workspacesMainService.ts b/src/vs/platform/workspaces/electron-main/workspacesMainService.ts index b257f0720c5..b4d83979407 100644 --- a/src/vs/platform/workspaces/electron-main/workspacesMainService.ts +++ b/src/vs/platform/workspaces/electron-main/workspacesMainService.ts @@ -68,7 +68,7 @@ export class WorkspacesMainService extends Disposable implements IWorkspacesMain private doResolveWorkspace(path: URI, contents: string): IResolvedWorkspace | null { try { const workspace = this.doParseStoredWorkspace(path, contents); - const workspaceIdentifier = this.getWorkspaceIdentifier(path); + const workspaceIdentifier = getWorkspaceIdentifier(path); return { id: workspaceIdentifier.id, configPath: workspaceIdentifier.configPath, @@ -143,25 +143,13 @@ export class WorkspacesMainService extends Disposable implements IWorkspacesMain } return { - workspace: this.getWorkspaceIdentifier(untitledWorkspaceConfigPath), + workspace: getWorkspaceIdentifier(untitledWorkspaceConfigPath), storedWorkspace: { folders: storedWorkspaceFolder, remoteAuthority } }; } - getWorkspaceId(configPath: URI): string { - let workspaceConfigPath = configPath.scheme === Schemas.file ? originalFSPath(configPath) : configPath.toString(); - if (!isLinux) { - workspaceConfigPath = workspaceConfigPath.toLowerCase(); // sanitize for platform file system - } - - return createHash('md5').update(workspaceConfigPath).digest('hex'); - } - - getWorkspaceIdentifier(configPath: URI): IWorkspaceIdentifier { - return { - configPath, - id: this.getWorkspaceId(configPath) - }; + getWorkspaceIdentifier(configPath: URI): Promise { + return Promise.resolve(getWorkspaceIdentifier(configPath)); } isUntitledWorkspace(workspace: IWorkspaceIdentifier): boolean { @@ -206,7 +194,7 @@ export class WorkspacesMainService extends Disposable implements IWorkspacesMain try { const untitledWorkspacePaths = readdirSync(this.untitledWorkspacesHome.fsPath).map(folder => joinPath(this.untitledWorkspacesHome, folder, UNTITLED_WORKSPACE_NAME)); for (const untitledWorkspacePath of untitledWorkspacePaths) { - const workspace = this.getWorkspaceIdentifier(untitledWorkspacePath); + const workspace = getWorkspaceIdentifier(untitledWorkspacePath); const resolvedWorkspace = this.resolveLocalWorkspaceSync(untitledWorkspacePath); if (!resolvedWorkspace) { this.doDeleteUntitledWorkspaceSync(workspace); @@ -222,3 +210,19 @@ export class WorkspacesMainService extends Disposable implements IWorkspacesMain return untitledWorkspaces; } } + +function getWorkspaceId(configPath: URI): string { + let workspaceConfigPath = configPath.scheme === Schemas.file ? originalFSPath(configPath) : configPath.toString(); + if (!isLinux) { + workspaceConfigPath = workspaceConfigPath.toLowerCase(); // sanitize for platform file system + } + + return createHash('md5').update(workspaceConfigPath).digest('hex'); +} + +export function getWorkspaceIdentifier(configPath: URI): IWorkspaceIdentifier { + return { + configPath, + id: getWorkspaceId(configPath) + }; +} diff --git a/src/vs/platform/workspaces/node/workspacesIpc.ts b/src/vs/platform/workspaces/node/workspacesIpc.ts index ba831722e79..405479b69ef 100644 --- a/src/vs/platform/workspaces/node/workspacesIpc.ts +++ b/src/vs/platform/workspaces/node/workspacesIpc.ts @@ -37,6 +37,9 @@ export class WorkspacesChannel implements IServerChannel { const w: IWorkspaceIdentifier = arg; return this.service.deleteUntitledWorkspace({ id: w.id, configPath: URI.revive(w.configPath) }); } + case 'getWorkspaceIdentifier': { + return this.service.getWorkspaceIdentifier(URI.revive(arg)); + } } throw new Error(`Call not found: ${command}`); @@ -56,4 +59,8 @@ export class WorkspacesChannelClient implements IWorkspacesService { deleteUntitledWorkspace(workspaceIdentifier: IWorkspaceIdentifier): Promise { return this.channel.call('deleteUntitledWorkspace', workspaceIdentifier); } + + getWorkspaceIdentifier(configPath: URI): Promise { + return this.channel.call('getWorkspaceIdentifier', configPath); + } } diff --git a/src/vs/workbench/api/node/apiCommands.ts b/src/vs/workbench/api/node/apiCommands.ts index c5177b90d08..fabd2cb70d1 100644 --- a/src/vs/workbench/api/node/apiCommands.ts +++ b/src/vs/workbench/api/node/apiCommands.ts @@ -11,7 +11,6 @@ import { ITextEditorOptions } from 'vs/platform/editor/common/editor'; import { EditorViewColumn } from 'vs/workbench/api/shared/editor'; import { EditorGroupLayout } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { IWindowsService } from 'vs/platform/windows/common/windows'; import { IDownloadService } from 'vs/platform/download/common/download'; @@ -34,10 +33,11 @@ function adjustHandler(handler: (executor: ICommandsExecutor, ...args: any[]) => export class OpenFolderAPICommand { public static ID = 'vscode.openFolder'; - public static execute(executor: ICommandsExecutor, uri?: URI, forceNewWindow?: boolean): Promise { + public static execute(executor: ICommandsExecutor, uri?: URI, forceNewWindow?: ConstrainBoolean): Promise { if (!uri) { return executor.executeCommand('_files.pickFolderAndOpen', forceNewWindow); } + uri = URI.revive(uri); return executor.executeCommand('_files.windowOpen', { urisToOpen: [{ uri }], forceNewWindow }); } } @@ -99,15 +99,19 @@ export class OpenAPICommand { } CommandsRegistry.registerCommand(OpenAPICommand.ID, adjustHandler(OpenAPICommand.execute)); -CommandsRegistry.registerCommand('_workbench.removeFromRecentlyOpened', function (accessor: ServicesAccessor, path: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | URI | string) { +CommandsRegistry.registerCommand('_workbench.removeFromRecentlyOpened', function (accessor: ServicesAccessor, uri: URI) { const windowsService = accessor.get(IWindowsService); - - return windowsService.removeFromRecentlyOpened([path]).then(() => undefined); + return windowsService.removeFromRecentlyOpened([uri]).then(() => undefined); }); export class RemoveFromRecentlyOpenedAPICommand { public static ID = 'vscode.removeFromRecentlyOpened'; - public static execute(executor: ICommandsExecutor, path: string): Promise { + public static execute(executor: ICommandsExecutor, path: string | URI): Promise { + if (typeof path === 'string') { + path = path.match(/^[^:/?#]+:\/\//) ? URI.parse(path) : URI.file(path); + } else { + path = URI.revive(path); // called from extension host + } return executor.executeCommand('_workbench.removeFromRecentlyOpened', path); } } diff --git a/src/vs/workbench/browser/dnd.ts b/src/vs/workbench/browser/dnd.ts index 30603937bd9..d48f730b7e3 100644 --- a/src/vs/workbench/browser/dnd.ts +++ b/src/vs/workbench/browser/dnd.ts @@ -28,6 +28,7 @@ import { IEditorService, IResourceEditor } from 'vs/workbench/services/editor/co import { Disposable } from 'vs/base/common/lifecycle'; import { addDisposableListener, EventType } from 'vs/base/browser/dom'; import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IRecentFile } from 'vs/platform/history/common/history'; export interface IDraggedResource { resource: URI; @@ -178,9 +179,9 @@ export class ResourcesDropHandler { } // Add external ones to recently open list unless dropped resource is a workspace - const filesToAddToHistory = untitledOrFileResources.filter(d => d.isExternal && d.resource.scheme === Schemas.file).map(d => d.resource); - if (filesToAddToHistory.length) { - this.windowsService.addRecentlyOpened([], [], filesToAddToHistory); + const recents: IRecentFile[] = untitledOrFileResources.filter(d => d.isExternal && d.resource.scheme === Schemas.file).map(d => ({ fileUri: d.resource })); + if (recents.length) { + this.windowsService.addRecentlyOpened(recents); } const editors: IResourceEditor[] = untitledOrFileResources.map(untitledOrFileResource => ({ diff --git a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts index 0399029f2e6..d3a09910947 100644 --- a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts +++ b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts @@ -17,8 +17,7 @@ import { isMacintosh, isLinux } from 'vs/base/common/platform'; import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; import { Event, Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; -import { IRecentlyOpened } from 'vs/platform/history/common/history'; -import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; +import { IRecentlyOpened, isRecentFolder, IRecent, isRecentWorkspace } from 'vs/platform/history/common/history'; import { RunOnceScheduler } from 'vs/base/common/async'; import { MENUBAR_SELECTION_FOREGROUND, MENUBAR_SELECTION_BACKGROUND, MENUBAR_SELECTION_BORDER, TITLE_BAR_ACTIVE_FOREGROUND, TITLE_BAR_INACTIVE_FOREGROUND } from 'vs/workbench/common/theme'; import { URI } from 'vs/base/common/uri'; @@ -348,26 +347,26 @@ export class MenubarControl extends Disposable { return label; } - private createOpenRecentMenuAction(workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | URI, isFile: boolean): IAction & { uri: URI } { + private createOpenRecentMenuAction(recent: IRecent, isFile: boolean): IAction & { uri: URI } { let label: string; let uri: URI; let commandId: string; let typeHint: URIType | undefined; - if (isSingleFolderWorkspaceIdentifier(workspace) && !isFile) { - label = this.labelService.getWorkspaceLabel(workspace, { verbose: true }); - uri = workspace; + if (isRecentFolder(recent)) { + uri = recent.folderUri; + label = recent.label || this.labelService.getWorkspaceLabel(uri, { verbose: true }); commandId = 'openRecentFolder'; typeHint = 'folder'; - } else if (isWorkspaceIdentifier(workspace)) { - label = this.labelService.getWorkspaceLabel(workspace, { verbose: true }); - uri = workspace.configPath; + } else if (isRecentWorkspace(recent)) { + uri = recent.workspace.configPath; + label = recent.label || this.labelService.getWorkspaceLabel(recent.workspace, { verbose: true }); commandId = 'openRecentWorkspace'; typeHint = 'file'; } else { - uri = workspace; - label = this.labelService.getUriLabel(uri); + uri = recent.fileUri; + label = recent.label || this.labelService.getUriLabel(uri); commandId = 'openRecentFile'; typeHint = 'file'; } @@ -377,7 +376,7 @@ export class MenubarControl extends Disposable { const ret: IAction = new Action(commandId, label, undefined, undefined, (event) => { const openInNewWindow = event && ((!isMacintosh && (event.ctrlKey || event.shiftKey)) || (isMacintosh && (event.metaKey || event.altKey))); - return this.windowService.openWindow([{ uri, typeHint }], { + return this.windowService.openWindow([{ uri, typeHint, label }], { forceNewWindow: openInNewWindow, forceOpenWorkspaceAsFile: isFile }); diff --git a/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts b/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts index 3564664a988..11580e7d614 100644 --- a/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts +++ b/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts @@ -28,12 +28,11 @@ import { IExtensionEnablementService, IExtensionManagementService, IExtensionGal import { used } from 'vs/workbench/contrib/welcome/page/browser/vs_code_welcome_page'; import { ILifecycleService, StartupKind } from 'vs/platform/lifecycle/common/lifecycle'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; -import { tildify, getBaseLabel } from 'vs/base/common/labels'; +import { tildify } from 'vs/base/common/labels'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { registerColor, focusBorder, textLinkForeground, textLinkActiveForeground, foreground, descriptionForeground, contrastBorder, activeContrastBorder } from 'vs/platform/theme/common/colorRegistry'; import { getExtraColor } from 'vs/workbench/contrib/welcome/walkThrough/common/walkThroughUtils'; import { IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions'; -import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { IEditorInputFactory, EditorInput } from 'vs/workbench/common/editor'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { TimeoutTimer } from 'vs/base/common/async'; @@ -42,6 +41,7 @@ import { ILabelService } from 'vs/platform/label/common/label'; import { IFileService } from 'vs/platform/files/common/files'; import { ExtensionType } from 'vs/platform/extensions/common/extensions'; import { joinPath } from 'vs/base/common/resources'; +import { IRecentlyOpened, isRecentWorkspace, IRecentWorkspace, IRecentFolder, isRecentFolder } from 'vs/platform/history/common/history'; used(); @@ -289,7 +289,7 @@ class WelcomePage { return this.editorService.openEditor(this.editorInput, { pinned: false }); } - private onReady(container: HTMLElement, recentlyOpened: Promise<{ files: URI[]; workspaces: Array; }>, installedExtensions: Promise): void { + private onReady(container: HTMLElement, recentlyOpened: Promise, installedExtensions: Promise): void { const enabled = isWelcomePageEnabled(this.configurationService, this.contextService); const showOnStartup = container.querySelector('#showOnStartup'); if (enabled) { @@ -301,7 +301,7 @@ class WelcomePage { recentlyOpened.then(({ workspaces }) => { // Filter out the current workspace - workspaces = workspaces.filter(workspace => !this.contextService.isCurrentWorkspace(workspace)); + workspaces = workspaces.filter(recent => !this.contextService.isCurrentWorkspace(isRecentWorkspace(recent) ? recent.workspace : recent.folderUri)); if (!workspaces.length) { const recent = container.querySelector('.welcomePage') as HTMLElement; recent.classList.add('emptyRecent'); @@ -339,22 +339,18 @@ class WelcomePage { })); } - private createListEntries(workspaces: (URI | IWorkspaceIdentifier)[]) { - return workspaces.map(workspace => { + private createListEntries(recents: (IRecentWorkspace | IRecentFolder)[]) { + return recents.map(recent => { let label: string; let resource: URI; let typeHint: URIType | undefined; - if (isSingleFolderWorkspaceIdentifier(workspace)) { - resource = workspace; - label = this.labelService.getWorkspaceLabel(workspace); + if (isRecentFolder(recent)) { + resource = recent.folderUri; + label = recent.label || this.labelService.getWorkspaceLabel(recent.folderUri); typeHint = 'folder'; - } else if (isWorkspaceIdentifier(workspace)) { - label = this.labelService.getWorkspaceLabel(workspace); - resource = workspace.configPath; - typeHint = 'file'; } else { - label = getBaseLabel(workspace); - resource = URI.file(workspace); + label = recent.label || this.labelService.getWorkspaceLabel(recent.workspace); + resource = recent.workspace.configPath; typeHint = 'file'; } @@ -392,7 +388,7 @@ class WelcomePage { id: 'openRecentFolder', from: telemetryFrom }); - this.windowService.openWindow([{ uri: resource, typeHint }], { forceNewWindow: e.ctrlKey || e.metaKey }); + this.windowService.openWindow([{ uri: resource, typeHint, label }], { forceNewWindow: e.ctrlKey || e.metaKey }); e.preventDefault(); e.stopPropagation(); }); diff --git a/src/vs/workbench/electron-browser/actions/windowActions.ts b/src/vs/workbench/electron-browser/actions/windowActions.ts index ea55154a0ad..f5b027024ba 100644 --- a/src/vs/workbench/electron-browser/actions/windowActions.ts +++ b/src/vs/workbench/electron-browser/actions/windowActions.ts @@ -15,7 +15,6 @@ import * as browser from 'vs/base/browser/browser'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { webFrame } from 'electron'; import { getBaseLabel } from 'vs/base/common/labels'; -import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { FileKind } from 'vs/platform/files/common/files'; import { ILabelService } from 'vs/platform/label/common/label'; import { dirname } from 'vs/base/common/resources'; @@ -27,6 +26,7 @@ import product from 'vs/platform/product/node/product'; import { ICommandHandler } from 'vs/platform/commands/common/commands'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IRecentFolder, IRecentFile, IRecentWorkspace, IRecent, isRecentFolder, isRecentWorkspace } from 'vs/platform/history/common/history'; export class CloseCurrentWindowAction extends Action { @@ -337,24 +337,28 @@ export abstract class BaseOpenRecentAction extends Action { .then(({ workspaces, files }) => this.openRecent(workspaces, files)); } - private openRecent(recentWorkspaces: Array, recentFiles: URI[]): void { + private openRecent(recentWorkspaces: Array, recentFiles: IRecentFile[]): void { - const toPick = (workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | URI, fileKind: FileKind, labelService: ILabelService, buttons: IQuickInputButton[] | undefined) => { - let resource: URI; - let label: string; - let description: string; - if (isSingleFolderWorkspaceIdentifier(workspace) && fileKind !== FileKind.FILE) { - resource = workspace; - label = labelService.getWorkspaceLabel(workspace); - description = labelService.getUriLabel(dirname(resource)!); - } else if (isWorkspaceIdentifier(workspace)) { - resource = workspace.configPath; - label = labelService.getWorkspaceLabel(workspace); - description = labelService.getUriLabel(dirname(resource)!); + const toPick = (recent: IRecent, labelService: ILabelService, buttons: IQuickInputButton[] | undefined) => { + let resource: URI | undefined; + let label: string | undefined; + let description: string | undefined; + let fileKind: FileKind | undefined; + if (isRecentFolder(recent)) { + resource = recent.folderUri; + label = recent.label || labelService.getWorkspaceLabel(recent.folderUri); + description = labelService.getUriLabel(dirname(resource)); + fileKind = FileKind.FOLDER; + } else if (isRecentWorkspace(recent)) { + resource = recent.workspace.configPath; + label = recent.label || labelService.getWorkspaceLabel(recent.workspace); + description = labelService.getUriLabel(dirname(resource)); + fileKind = FileKind.ROOT_FOLDER; } else { - resource = workspace; - label = getBaseLabel(workspace); - description = labelService.getUriLabel(dirname(resource)!); + resource = recent.fileUri; + label = recent.label || getBaseLabel(recent.fileUri); + description = labelService.getUriLabel(dirname(resource)); + fileKind = FileKind.FILE; } return { @@ -362,22 +366,22 @@ export abstract class BaseOpenRecentAction extends Action { label, description, buttons, - workspace, resource, fileKind, }; }; - const runPick = (uri: URI, isFile: boolean, keyMods: IKeyMods) => { + const runPick = (uri: URI, isFile: boolean, keyMods: IKeyMods, label: string) => { const forceNewWindow = keyMods.ctrlCmd; - return this.windowService.openWindow([{ uri, typeHint: isFile ? 'file' : 'folder' }], { forceNewWindow, forceOpenWorkspaceAsFile: isFile }); + return this.windowService.openWindow([{ uri, typeHint: isFile ? 'file' : 'folder', label }], { forceNewWindow, forceOpenWorkspaceAsFile: isFile }); }; - const workspacePicks = recentWorkspaces.map(workspace => toPick(workspace, isSingleFolderWorkspaceIdentifier(workspace) ? FileKind.FOLDER : FileKind.ROOT_FOLDER, this.labelService, !this.isQuickNavigate() ? [this.removeFromRecentlyOpened] : undefined)); - const filePicks = recentFiles.map(p => toPick(p, FileKind.FILE, this.labelService, !this.isQuickNavigate() ? [this.removeFromRecentlyOpened] : undefined)); + const workspacePicks = recentWorkspaces.map(workspace => toPick(workspace, this.labelService, !this.isQuickNavigate() ? [this.removeFromRecentlyOpened] : undefined)); + const filePicks = recentFiles.map(p => toPick(p, this.labelService, !this.isQuickNavigate() ? [this.removeFromRecentlyOpened] : undefined)); // focus second entry if the first recent workspace is the current workspace - let autoFocusSecondEntry: boolean = recentWorkspaces[0] && this.contextService.isCurrentWorkspace(recentWorkspaces[0]); + const firstEntry = recentWorkspaces[0]; + let autoFocusSecondEntry: boolean = firstEntry && this.contextService.isCurrentWorkspace(isRecentWorkspace(firstEntry) ? firstEntry.workspace : firstEntry.folderUri); let keyMods: IKeyMods; const workspaceSeparator: IQuickPickSeparator = { type: 'separator', label: nls.localize('workspaces', "workspaces") }; @@ -391,14 +395,13 @@ export abstract class BaseOpenRecentAction extends Action { onKeyMods: mods => keyMods = mods, quickNavigate: this.isQuickNavigate() ? { keybindings: this.keybindingService.lookupKeybindings(this.id) } : undefined, onDidTriggerItemButton: context => { - this.windowsService.removeFromRecentlyOpened([context.item.workspace]).then(() => context.removeItem()); + this.windowsService.removeFromRecentlyOpened([context.item.resource]).then(() => context.removeItem()); } - }) - .then((pick): Promise | void => { - if (pick) { - return runPick(pick.resource, pick.fileKind === FileKind.FILE, keyMods); - } - }); + }).then((pick): Promise | void => { + if (pick) { + return runPick(pick.resource, pick.fileKind === FileKind.FILE, keyMods, pick.label); + } + }); } } diff --git a/src/vs/workbench/services/dialogs/electron-browser/dialogService.ts b/src/vs/workbench/services/dialogs/electron-browser/dialogService.ts index bf501e161f9..23e0383425b 100644 --- a/src/vs/workbench/services/dialogs/electron-browser/dialogService.ts +++ b/src/vs/workbench/services/dialogs/electron-browser/dialogService.ts @@ -19,10 +19,11 @@ import { Schemas } from 'vs/base/common/network'; import * as resources from 'vs/base/common/resources'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { RemoteFileDialog } from 'vs/workbench/services/dialogs/electron-browser/remoteFileDialog'; -import { WORKSPACE_EXTENSION } from 'vs/platform/workspaces/common/workspaces'; +import { WORKSPACE_EXTENSION, hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspaces'; import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { ILabelService } from 'vs/platform/label/common/label'; interface IMassagedMessageBoxOptions { @@ -168,6 +169,7 @@ export class FileDialogService implements IFileDialogService { @IEnvironmentService private readonly environmentService: IEnvironmentService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IConfigurationService private readonly configurationService: IConfigurationService, + @ILabelService private readonly labelService: ILabelService ) { } defaultFilePath(schemeFilter = this.getSchemeFilterForWindow()): URI | undefined { @@ -361,12 +363,23 @@ export class FileDialogService implements IFileDialogService { private pickRemoteResourceAndOpen(options: IOpenDialogOptions, forceNewWindow: boolean, forceOpenWorkspaceAsFile: boolean) { return this.pickRemoteResource(options).then(urisToOpen => { if (urisToOpen) { + urisToOpen.forEach(u => u.label = this.getOpenLabel(u, forceOpenWorkspaceAsFile)); return this.windowService.openWindow(urisToOpen, { forceNewWindow, forceOpenWorkspaceAsFile }); } return undefined; }); } + private getOpenLabel(u: IURIToOpen, forceOpenWorkspaceAsFile: boolean): string { + if (u.typeHint === 'folder') { + return this.labelService.getWorkspaceLabel(u.uri); + } else if (!forceOpenWorkspaceAsFile && hasWorkspaceFileExtension(u.uri.path)) { + return this.labelService.getWorkspaceLabel({ id: '', configPath: u.uri }); + } else { + return this.labelService.getUriLabel(u.uri); + } + } + private pickRemoteResource(options: IOpenDialogOptions): Promise { const remoteFileDialog = this.instantiationService.createInstance(RemoteFileDialog); return remoteFileDialog.showOpenDialog(options); diff --git a/src/vs/workbench/services/workspace/node/workspaceEditingService.ts b/src/vs/workbench/services/workspace/node/workspaceEditingService.ts index c79cd34347b..8fbcbb3d732 100644 --- a/src/vs/workbench/services/workspace/node/workspaceEditingService.ts +++ b/src/vs/workbench/services/workspace/node/workspaceEditingService.ts @@ -30,6 +30,7 @@ import { IFileDialogService, IDialogService } from 'vs/platform/dialogs/common/d import { mnemonicButtonLabel } from 'vs/base/common/labels'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { ILabelService } from 'vs/platform/label/common/label'; export class WorkspaceEditingService implements IWorkspaceEditingService { @@ -51,7 +52,8 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { @IEnvironmentService private readonly environmentService: IEnvironmentService, @IFileDialogService private readonly fileDialogService: IFileDialogService, @IDialogService private readonly dialogService: IDialogService, - @ILifecycleService readonly lifecycleService: ILifecycleService + @ILifecycleService readonly lifecycleService: ILifecycleService, + @ILabelService readonly labelService: ILabelService ) { lifecycleService.onBeforeShutdown(async e => { @@ -116,9 +118,12 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { return this.pickNewWorkspacePath().then(newWorkspacePath => { if (newWorkspacePath) { return this.saveWorkspaceAs(workspaceIdentifier, newWorkspacePath).then(_ => { - this.windowsService.addRecentlyOpened([newWorkspacePath], [], []); - this.workspaceService.deleteUntitledWorkspace(workspaceIdentifier); - return false; + return this.workspaceService.getWorkspaceIdentifier(newWorkspacePath).then(newWorkspaceIdentifier => { + const label = this.labelService.getWorkspaceLabel(newWorkspaceIdentifier); + this.windowsService.addRecentlyOpened([{ label, workspace: newWorkspaceIdentifier }]); + this.workspaceService.deleteUntitledWorkspace(workspaceIdentifier); + return false; + }); }, () => false); } return true; // keep veto if no target was provided diff --git a/src/vs/workbench/test/workbenchTestServices.ts b/src/vs/workbench/test/workbenchTestServices.ts index ccd4fc6df40..aca614a11e2 100644 --- a/src/vs/workbench/test/workbenchTestServices.ts +++ b/src/vs/workbench/test/workbenchTestServices.ts @@ -44,7 +44,7 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment' import { IThemeService } from 'vs/platform/theme/common/themeService'; import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; -import { IRecentlyOpened } from 'vs/platform/history/common/history'; +import { IRecentlyOpened, IRecent } from 'vs/platform/history/common/history'; import { ITextResourceConfigurationService, ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; import { IPosition, Position as EditorPosition } from 'vs/editor/common/core/position'; import { IMenuService, MenuId, IMenu, ISerializableCommandAction } from 'vs/platform/actions/common/actions'; @@ -1240,7 +1240,7 @@ export class TestWindowsService implements IWindowsService { return Promise.resolve(); } - addRecentlyOpened(_workspaces: URI[], _folders: URI[], _files: URI[]): Promise { + addRecentlyOpened(_recents: IRecent[]): Promise { return Promise.resolve(); }