未验证 提交 4276d2c3 编写于 作者: M Martin Aeschlimann 提交者: GitHub

Merge pull request #70071 from Microsoft/aeschli/recentWithLabel

Store label with recent entry
......@@ -9,6 +9,7 @@ import { endsWith, ltrim, startsWithIgnoreCase, rtrim, startsWith } from 'vs/bas
import { Schemas } from 'vs/base/common/network';
import { isLinux, isWindows, isMacintosh } from 'vs/base/common/platform';
import { isEqual, basename } from 'vs/base/common/resources';
import { CharCode } from 'vs/base/common/charCode';
export interface IWorkspaceFolderProvider {
getWorkspaceFolder(resource: URI): { uri: URI, name?: string } | null;
......@@ -383,3 +384,16 @@ export function mnemonicButtonLabel(label: string): string {
export function unmnemonicLabel(label: string): string {
return label.replace(/&/g, '&&');
}
/**
* Splits a path in name and parent path, supporting both '/' and '\'
*/
export function splitName(fullPath: string): { name: string, parentPath: string } {
for (let i = fullPath.length - 1; i >= 1; i--) {
const code = fullPath.charCodeAt(i);
if (code === CharCode.Slash || code === CharCode.Backslash) {
return { parentPath: fullPath.substr(0, i), name: fullPath.substr(i + 1) };
}
}
return { parentPath: '', name: fullPath };
}
......@@ -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<IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier> = [];
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
......@@ -845,6 +850,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 {
......@@ -1032,7 +1038,7 @@ export class WindowsManager implements IWindowsMainService {
}
if (hasWorkspaceFileExtension(uri.path) && !options.forceOpenWorkspaceAsFile) {
return {
workspace: this.workspacesMainService.getWorkspaceIdentifier(uri),
workspace: getWorkspaceIdentifier(uri),
remoteAuthority
};
}
......@@ -1099,9 +1105,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
}
......@@ -1514,7 +1519,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);
......@@ -1969,7 +1974,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);
});
......@@ -1985,7 +1990,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',
......
......@@ -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<IHistoryMainService>('historyMainService');
export interface IRecentlyOpened {
workspaces: Array<IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier>;
files: URI[];
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['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<void>;
addRecentlyOpened(workspaces: undefined | Array<IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier>, files: URI[]): void;
getRecentlyOpened(currentWorkspace?: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier, currentFiles?: IPath[]): IRecentlyOpened;
removeFromRecentlyOpened(paths: Array<IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | URI | string>): void;
addRecentlyOpened(recents: IRecent[]): void;
getRecentlyOpened(currentWorkspace?: IWorkspaceIdentifier, currentFolder?: ISingleFolderWorkspaceIdentifier, currentFiles?: IPath[]): IRecentlyOpened;
removeFromRecentlyOpened(paths: URI[]): void;
clearRecentlyOpened(): void;
updateWindowsJumpList(): void;
......
......@@ -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, IRecentFolder, IRecentWorkspace, IRecentFile } 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<IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier>, 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) === -1) {
mru.workspaces.unshift(curr);
}
} else if (isRecentFolder(curr)) {
if (indexOfFolder(mru.workspaces, curr.folderUri) === -1) {
mru.workspaces.unshift(curr);
}
} else {
if (indexOfFile(mru.files, curr.fileUri) === -1) {
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<IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | URI | string>): void {
const mru = this.getRecentlyOpened();
let update = false;
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 */);
removeFromRecentlyOpened(toRemove: URI[]): void {
const keep = (recent: IRecent) => {
const uri = location(recent);
for (const r of toRemove) {
if (areResourcesEqual(r, uri)) {
return false;
}
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;
}
return 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;
}
});
const mru = this.getRecentlyOpened();
const workspaces = mru.workspaces.filter(keep);
const files = mru.files.filter(keep);
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,37 +148,48 @@ 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();
const workspaces: Array<IRecentFolder | IRecentWorkspace> = [];
const files: IRecentFile[] = [];
// Add current workspace to beginning if set
if (currentWorkspace) {
workspaces.unshift(currentWorkspace);
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) {
files.unshift(...arrays.coalesce(currentFiles.map(f => f.fileUri)));
for (let currentFile of currentFiles) {
const fileUri = currentFile.fileUri;
if (fileUri && indexOfFile(files, fileUri) === -1) {
files.push({ 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);
// 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);
}
}
return workspaceOrFile.id;
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);
}
}
return { workspaces, files };
}
private getRecentlyOpenedFromStorage(): IRecentlyOpened {
......@@ -272,7 +232,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<ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier> = [];
let toRemove: URI[] = [];
for (let item of app.getJumpListSettings().removedItems) {
const args = item.args;
if (args) {
......@@ -288,8 +248,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 +286,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: 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));
}
\ No newline at end of file
......@@ -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<ISerializedWorkspace | string>; // workspace or URI.toString()
files2: string[]; // files as URI.toString()
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 {
......@@ -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,33 @@ export function restoreRecentlyOpened(data: RecentlyOpenedStorageData | undefine
return result;
}
export function toStoreData(recent: IRecentlyOpened): RecentlyOpenedStorageData {
export function toStoreData(recents: IRecentlyOpened): RecentlyOpenedStorageData {
const serialized: ISerializedRecentlyOpened = { workspaces3: [], files2: [] };
for (const workspace of recent.workspaces) {
if (isSingleFolderWorkspaceIdentifier(workspace)) {
serialized.workspaces3.push(workspace.toString());
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: workspace.id, configURIPath: workspace.configPath.toString() });
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;
}
for (const file of recent.files) {
serialized.files2.push(file.toString());
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;
......
......@@ -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(<URI>expected.workspaces[i], <URI>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, (<IRecentFolder>expectedRecent).folderUri, message);
} else {
assertEqualWorkspace(<IWorkspaceIdentifier>expected.workspaces[i], <IWorkspaceIdentifier>actual.workspaces[i], message);
assertEqualWorkspace(actualRecent.workspace, (<IRecentWorkspace>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: [
{ 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));
let expected: IRecentlyOpened = {
files: [{ label: 'def', 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') } },
{ label: 'abc', folderUri: URI.parse('file:///home/user/workspaces/testing/folding') }
]
};
assertEqualRecentlyOpened(expected, windowsState, 'v1_32');
assertEqualRecentlyOpened(windowsState, expected, 'v1_33');
});
});
\ No newline at end of file
......@@ -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<IEnterWorkspaceResult | undefined>;
toggleFullScreen(windowId: number): Promise<void>;
setRepresentedFilename(windowId: number, fileName: string): Promise<void>;
addRecentlyOpened(workspaces: URI[], folders: URI[], files: URI[]): Promise<void>;
removeFromRecentlyOpened(paths: Array<IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | URI | string>): Promise<void>;
addRecentlyOpened(recents: IRecent[]): Promise<void>;
removeFromRecentlyOpened(paths: URI[]): Promise<void>;
clearRecentlyOpened(): Promise<void>;
getRecentlyOpened(windowId: number): Promise<IRecentlyOpened>;
focusWindow(windowId: number): Promise<void>;
......@@ -190,6 +190,7 @@ export type URIType = 'file' | 'folder';
export interface IURIToOpen {
uri: URI;
typeHint?: URIType;
label?: string;
}
export interface IWindowService {
......
......@@ -10,6 +10,8 @@ import { ISerializableCommandAction } from 'vs/platform/actions/common/actions';
import { ParsedArgs } from 'vs/platform/environment/common/environment';
import { URI } from 'vs/base/common/uri';
import { Disposable } from 'vs/base/common/lifecycle';
import { hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspaces';
import { ILabelService } from 'vs/platform/label/common/label';
export class WindowService extends Disposable implements IWindowService {
......@@ -25,7 +27,8 @@ export class WindowService extends Disposable implements IWindowService {
constructor(
private configuration: IWindowConfiguration,
@IWindowsService private readonly windowsService: IWindowsService
@IWindowsService private readonly windowsService: IWindowsService,
@ILabelService private readonly labelService: ILabelService
) {
super();
......@@ -96,6 +99,9 @@ export class WindowService extends Disposable implements IWindowService {
}
openWindow(uris: IURIToOpen[], options?: IOpenSettings): Promise<void> {
if (!!this.configuration.remoteAuthority) {
uris.forEach(u => u.label = u.label || this.getRecentLabel(u, !!(options && options.forceOpenWorkspaceAsFile)));
}
return this.windowsService.openWindow(this.windowId, uris, options);
}
......@@ -170,4 +176,15 @@ export class WindowService extends Disposable implements IWindowService {
resolveProxy(url: string): Promise<string | undefined> {
return this.windowsService.resolveProxy(this.windowId, url);
}
private getRecentLabel(u: IURIToOpen, forceOpenWorkspaceAsFile: boolean): string {
if (u.typeHint === 'folder') {
return this.labelService.getWorkspaceLabel(u.uri, { verbose: true });
} else if (!forceOpenWorkspaceAsFile && hasWorkspaceFileExtension(u.uri.path)) {
return this.labelService.getWorkspaceLabel({ id: '', configPath: u.uri }, { verbose: true });
} else {
return this.labelService.getUriLabel(u.uri);
}
}
}
......@@ -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<void> {
async addRecentlyOpened(recents: IRecent[]): Promise<void> {
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<IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | URI | string>): Promise<void> {
async removeFromRecentlyOpened(paths: URI[]): Promise<void> {
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<IRecentlyOpened> {
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<void> {
......
......@@ -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<IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | URI | string> = 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] ? (<IURIToOpen[]>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] ? (<IURIToOpen[]>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<IEnterWorkspaceResult> {
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<void> {
return this.channel.call('addRecentlyOpened', [workspaces, folders, files]);
addRecentlyOpened(recent: IRecent[]): Promise<void> {
return this.channel.call('addRecentlyOpened', recent);
}
removeFromRecentlyOpened(paths: Array<IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | URI>): Promise<void> {
removeFromRecentlyOpened(paths: Array<URI>): Promise<void> {
return this.channel.call('removeFromRecentlyOpened', paths);
}
......@@ -193,8 +197,8 @@ export class WindowsChannelClient implements IWindowsService {
getRecentlyOpened(windowId: number): Promise<IRecentlyOpened> {
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<string | undefined> {
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
......@@ -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<IWorkspaceIdentifier>;
deleteUntitledWorkspace(workspace: IWorkspaceIdentifier): Promise<void>;
getWorkspaceIdentifier(workspacePath: URI): Promise<IWorkspaceIdentifier>;
}
export function isSingleFolderWorkspaceIdentifier(obj: any): obj is ISingleFolderWorkspaceIdentifier {
......
......@@ -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<IWorkspaceIdentifier> {
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)
};
}
......@@ -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<void> {
return this.channel.call('deleteUntitledWorkspace', workspaceIdentifier);
}
getWorkspaceIdentifier(configPath: URI): Promise<IWorkspaceIdentifier> {
return this.channel.call('getWorkspaceIdentifier', configPath);
}
}
......@@ -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, IOpenSettings } from 'vs/platform/windows/common/windows';
import { IDownloadService } from 'vs/platform/download/common/download';
......@@ -52,6 +51,7 @@ export class OpenFolderAPICommand {
if (arg.noRecentEntry) {
options.args = { _: [], 'skip-add-to-recently-opened': true };
}
uri = URI.revive(uri);
return executor.executeCommand('_files.windowOpen', [{ uri }], options);
}
}
......@@ -106,15 +106,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<any> {
public static execute(executor: ICommandsExecutor, path: string | URI): Promise<any> {
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);
}
}
......
......@@ -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 => ({
......
......@@ -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,33 +347,31 @@ 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';
}
label = unmnemonicLabel(label);
const ret: IAction = new Action(commandId, label, undefined, undefined, (event) => {
const ret: IAction = new Action(commandId, unmnemonicLabel(label), undefined, undefined, (event) => {
const openInNewWindow = event && ((!isMacintosh && (event.ctrlKey || event.shiftKey)) || (isMacintosh && (event.metaKey || event.altKey)));
return this.windowService.openWindow([{ uri, typeHint }], {
......
......@@ -6,7 +6,6 @@
import 'vs/css!./welcomePage';
import { URI } from 'vs/base/common/uri';
import * as strings from 'vs/base/common/strings';
import * as path from 'vs/base/common/path';
import { ICommandService } from 'vs/platform/commands/common/commands';
import * as arrays from 'vs/base/common/arrays';
import { WalkThroughInput } from 'vs/workbench/contrib/welcome/walkThrough/common/walkThroughInput';
......@@ -20,7 +19,6 @@ import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configur
import { localize } from 'vs/nls';
import { Action } from 'vs/base/common/actions';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { Schemas } from 'vs/base/common/network';
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
import { getInstalledExtensions, IExtensionStatus, onExtensionChanged, isKeymapExtension } from 'vs/workbench/contrib/extensions/common/extensionsUtils';
......@@ -28,12 +26,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 { splitName } 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 +39,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();
......@@ -256,7 +254,6 @@ class WelcomePage {
@IWindowService private readonly windowService: IWindowService,
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IEnvironmentService private readonly environmentService: IEnvironmentService,
@ILabelService private readonly labelService: ILabelService,
@INotificationService private readonly notificationService: INotificationService,
@IExtensionEnablementService private readonly extensionEnablementService: IExtensionEnablementService,
......@@ -289,7 +286,7 @@ class WelcomePage {
return this.editorService.openEditor(this.editorInput, { pinned: false });
}
private onReady(container: HTMLElement, recentlyOpened: Promise<{ files: URI[]; workspaces: Array<IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier>; }>, installedExtensions: Promise<IExtensionStatus[]>): void {
private onReady(container: HTMLElement, recentlyOpened: Promise<IRecentlyOpened>, installedExtensions: Promise<IExtensionStatus[]>): void {
const enabled = isWelcomePageEnabled(this.configurationService, this.contextService);
const showOnStartup = <HTMLInputElement>container.querySelector('#showOnStartup');
if (enabled) {
......@@ -301,7 +298,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,47 +336,29 @@ class WelcomePage {
}));
}
private createListEntries(workspaces: (URI | IWorkspaceIdentifier)[]) {
return workspaces.map(workspace => {
let label: string;
private createListEntries(recents: (IRecentWorkspace | IRecentFolder)[]) {
return recents.map(recent => {
let fullPath: string;
let resource: URI;
let typeHint: URIType | undefined;
if (isSingleFolderWorkspaceIdentifier(workspace)) {
resource = workspace;
label = this.labelService.getWorkspaceLabel(workspace);
if (isRecentFolder(recent)) {
resource = recent.folderUri;
fullPath = recent.label || this.labelService.getWorkspaceLabel(recent.folderUri, { verbose: true });
typeHint = 'folder';
} else if (isWorkspaceIdentifier(workspace)) {
label = this.labelService.getWorkspaceLabel(workspace);
resource = workspace.configPath;
typeHint = 'file';
} else {
label = getBaseLabel(workspace);
resource = URI.file(workspace);
fullPath = recent.label || this.labelService.getWorkspaceLabel(recent.workspace, { verbose: true });
resource = recent.workspace.configPath;
typeHint = 'file';
}
const li = document.createElement('li');
const { name, parentPath } = splitName(fullPath);
const li = document.createElement('li');
const a = document.createElement('a');
let name = label;
let parentFolderPath: string | undefined;
if (resource.scheme === Schemas.file) {
let parentFolder = path.dirname(resource.fsPath);
if (!name && parentFolder) {
const tmp = name;
name = parentFolder;
parentFolder = tmp;
}
parentFolderPath = tildify(parentFolder, this.environmentService.userHome);
} else {
parentFolderPath = this.labelService.getUriLabel(resource);
}
a.innerText = name;
a.title = label;
a.setAttribute('aria-label', localize('welcomePage.openFolderWithPath', "Open folder {0} with path {1}", name, parentFolderPath));
a.title = fullPath;
a.setAttribute('aria-label', localize('welcomePage.openFolderWithPath', "Open folder {0} with path {1}", name, parentPath));
a.href = 'javascript:void(0)';
a.addEventListener('click', e => {
/* __GDPR__
......@@ -401,8 +380,8 @@ class WelcomePage {
const span = document.createElement('span');
span.classList.add('path');
span.classList.add('detail');
span.innerText = parentFolderPath;
span.title = label;
span.innerText = parentPath;
span.title = fullPath;
li.appendChild(span);
return li;
......
......@@ -14,11 +14,8 @@ import { isMacintosh } from 'vs/base/common/platform';
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';
import { IModelService } from 'vs/editor/common/services/modelService';
import { IModeService } from 'vs/editor/common/services/modeService';
import { IQuickInputService, IQuickPickItem, IQuickInputButton, IQuickPickSeparator, IKeyMods } from 'vs/platform/quickinput/common/quickInput';
......@@ -27,6 +24,8 @@ 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';
import { splitName } from 'vs/base/common/labels';
export class CloseCurrentWindowAction extends Action {
......@@ -337,32 +336,31 @@ export abstract class BaseOpenRecentAction extends Action {
.then(({ workspaces, files }) => this.openRecent(workspaces, files));
}
private openRecent(recentWorkspaces: Array<IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier>, recentFiles: URI[]): 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)!);
private openRecent(recentWorkspaces: Array<IRecentWorkspace | IRecentFolder>, recentFiles: IRecentFile[]): void {
const toPick = (recent: IRecent, labelService: ILabelService, buttons: IQuickInputButton[] | undefined) => {
let resource: URI | undefined;
let fullLabel: string | undefined;
let fileKind: FileKind | undefined;
if (isRecentFolder(recent)) {
resource = recent.folderUri;
fullLabel = recent.label || labelService.getWorkspaceLabel(recent.folderUri, { verbose: true });
fileKind = FileKind.FOLDER;
} else if (isRecentWorkspace(recent)) {
resource = recent.workspace.configPath;
fullLabel = recent.label || labelService.getWorkspaceLabel(recent.workspace, { verbose: true });
fileKind = FileKind.ROOT_FOLDER;
} else {
resource = workspace;
label = getBaseLabel(workspace);
description = labelService.getUriLabel(dirname(resource)!);
resource = recent.fileUri;
fullLabel = recent.label || labelService.getUriLabel(recent.fileUri);
fileKind = FileKind.FILE;
}
const { name, parentPath } = splitName(fullLabel);
return {
iconClasses: getIconClasses(this.modelService, this.modeService, resource, fileKind),
label,
description,
label: name,
description: parentPath,
buttons,
workspace,
resource,
fileKind,
};
......@@ -373,11 +371,12 @@ export abstract class BaseOpenRecentAction extends Action {
return this.windowService.openWindow([{ uri, typeHint: isFile ? 'file' : 'folder' }], { 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 +390,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> | void => {
if (pick) {
return runPick(pick.resource, pick.fileKind === FileKind.FILE, keyMods);
}
});
}).then((pick): Promise<void> | void => {
if (pick) {
return runPick(pick.resource, pick.fileKind === FileKind.FILE, keyMods);
}
});
}
}
......
......@@ -172,7 +172,7 @@ export class FileDialogService implements IFileDialogService {
@IHistoryService private readonly historyService: IHistoryService,
@IEnvironmentService private readonly environmentService: IEnvironmentService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IConfigurationService private readonly configurationService: IConfigurationService
) { }
defaultFilePath(schemeFilter = this.getSchemeFilterForWindow()): URI | undefined {
......
......@@ -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, { verbose: true });
this.windowsService.addRecentlyOpened([{ label, workspace: newWorkspaceIdentifier }]);
this.workspaceService.deleteUntitledWorkspace(workspaceIdentifier);
return false;
});
}, () => false);
}
return true; // keep veto if no target was provided
......
......@@ -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';
......@@ -1356,7 +1356,7 @@ export class TestWindowsService implements IWindowsService {
return Promise.resolve();
}
addRecentlyOpened(_workspaces: URI[], _folders: URI[], _files: URI[]): Promise<void> {
addRecentlyOpened(_recents: IRecent[]): Promise<void> {
return Promise.resolve();
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册