diff --git a/src/vs/base/common/labels.ts b/src/vs/base/common/labels.ts index aea4b726f2fe7aa0eb283668f2a687205ce44c06..84925bcc745c0ea82fa2ed8c51f2c9b151446e37 100644 --- a/src/vs/base/common/labels.ts +++ b/src/vs/base/common/labels.ts @@ -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 }; +} diff --git a/src/vs/platform/history/electron-main/historyMainService.ts b/src/vs/platform/history/electron-main/historyMainService.ts index b2cdb76a41943b88ea40f698a96e166675584e35..450bb41b582de946a93187c9143960cbdd17a929 100644 --- a/src/vs/platform/history/electron-main/historyMainService.ts +++ b/src/vs/platform/history/electron-main/historyMainService.ts @@ -13,7 +13,7 @@ import { IPath } from 'vs/platform/windows/common/windows'; import { Event as CommonEvent, Emitter } from 'vs/base/common/event'; import { isWindows, isMacintosh } from 'vs/base/common/platform'; import { IWorkspaceIdentifier, IWorkspacesMainService, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; -import { IHistoryMainService, IRecentlyOpened, isRecentWorkspace, isRecentFolder, IRecent, isRecentFile } from 'vs/platform/history/common/history'; +import { IHistoryMainService, IRecentlyOpened, isRecentWorkspace, isRecentFolder, IRecent, isRecentFile, IRecentFolder, IRecentWorkspace, IRecentFile } from 'vs/platform/history/common/history'; import { RunOnceScheduler } from 'vs/base/common/async'; import { isEqual as areResourcesEqual, dirname, originalFSPath } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; @@ -51,15 +51,15 @@ export class HistoryMainService implements IHistoryMainService { for (let curr of newlyAdded) { if (isRecentWorkspace(curr)) { - if (!this.workspacesMainService.isUntitledWorkspace(curr.workspace) && indexOfWorkspace(mru.workspaces, curr.workspace) >= 0) { + 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) >= 0) { + if (indexOfFolder(mru.workspaces, curr.folderUri) === -1) { mru.workspaces.unshift(curr); } } else { - if (indexOfFile(mru.files, curr.fileUri) >= 0) { + if (indexOfFile(mru.files, curr.fileUri) === -1) { mru.files.unshift(curr); // Add to recent documents (Windows only, macOS later) if (isWindows && curr.fileUri.scheme === Schemas.file) { @@ -150,28 +150,45 @@ export class HistoryMainService implements IHistoryMainService { getRecentlyOpened(currentWorkspace?: IWorkspaceIdentifier, currentFolder?: ISingleFolderWorkspaceIdentifier, currentFiles?: IPath[]): IRecentlyOpened { - // Get from storage - let { workspaces, files } = this.getRecentlyOpenedFromStorage(); + const workspaces: Array = []; + const files: IRecentFile[] = []; // Add current workspace to beginning if set - if (currentWorkspace && !this.workspacesMainService.isUntitledWorkspace(currentWorkspace) && !workspaces.some(w => isRecentWorkspace(w) && w.workspace.id === currentWorkspace.id)) { - workspaces.unshift({ workspace: currentWorkspace }); + if (currentWorkspace && !this.workspacesMainService.isUntitledWorkspace(currentWorkspace)) { + workspaces.push({ workspace: currentWorkspace }); } - - if (currentFolder && !workspaces.some(w => isRecentFolder(w) && areResourcesEqual(w.folderUri, currentFolder))) { - workspaces.unshift({ folderUri: currentFolder }); + if (currentFolder) { + workspaces.push({ folderUri: currentFolder }); } // Add currently files to open to the beginning if any if (currentFiles) { for (let currentFile of currentFiles) { const fileUri = currentFile.fileUri; - if (fileUri && !files.some(f => areResourcesEqual(f.fileUri, fileUri))) { - files.unshift({ fileUri }); + if (fileUri && indexOfFile(files, fileUri) === -1) { + files.push({ fileUri }); } } } + // Get from storage + let recents = this.getRecentlyOpenedFromStorage(); + for (let recent of recents.workspaces) { + let index = isRecentFolder(recent) ? indexOfFolder(workspaces, recent.folderUri) : indexOfWorkspace(workspaces, recent.workspace); + if (index >= 0) { + workspaces[index].label = workspaces[index].label || recent.label; + } else { + workspaces.push(recent); + } + } + for (let recent of recents.files) { + let index = indexOfFile(files, recent.fileUri); + if (index >= 0) { + files[index].label = files[index].label || recent.label; + } else { + files.push(recent); + } + } return { workspaces, files }; } @@ -280,14 +297,14 @@ function location(recent: IRecent): URI { return recent.workspace.configPath; } -function indexOfWorkspace(arr: Array, workspace: IWorkspaceIdentifier): number { +function indexOfWorkspace(arr: IRecent[], workspace: IWorkspaceIdentifier): number { return arrays.firstIndex(arr, w => isRecentWorkspace(w) && w.workspace.id === workspace.id); } -function indexOfFolder(arr: Array, folderURI: ISingleFolderWorkspaceIdentifier): number { +function indexOfFolder(arr: IRecent[], 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)); +function indexOfFile(arr: IRecentFile[], fileURI: URI): number { + return arrays.firstIndex(arr, 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 3649a237e52226286b4ebfa77ea8fc2a504d3a9c..2cbc82c7ec42609e01991e2de7f1acd8e4f63a6a 100644 --- a/src/vs/platform/history/electron-main/historyStorage.ts +++ b/src/vs/platform/history/electron-main/historyStorage.ts @@ -6,10 +6,10 @@ import { UriComponents, URI } from 'vs/base/common/uri'; 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; + workspaces3: Array; // workspace or URI.toString() // added in 1.32 + workspaceLabels: Array; // added in 1.33 + files2: string[]; // files as URI.toString() // added in 1.32 + fileLabels: Array; // added in 1.33 } interface ILegacySerializedRecentlyOpened { diff --git a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts index d3a09910947ac3123cabc43758726bb06d0586e4..b4fca9b4b0d48133a490d287cdfc66c982d9d79a 100644 --- a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts +++ b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts @@ -371,9 +371,7 @@ export class MenubarControl extends Disposable { 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, label }], { diff --git a/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts b/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts index 11580e7d614918079090f9ed587df2c5b6c09604..8ff0c7a7c4a3be8b210472a1f2b612079abca261 100644 --- a/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts +++ b/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts @@ -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,7 +26,7 @@ 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 } 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'; @@ -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, @@ -341,41 +338,27 @@ class WelcomePage { private createListEntries(recents: (IRecentWorkspace | IRecentFolder)[]) { return recents.map(recent => { - let label: string; + let fullPath: string; let resource: URI; let typeHint: URIType | undefined; if (isRecentFolder(recent)) { resource = recent.folderUri; - label = recent.label || this.labelService.getWorkspaceLabel(recent.folderUri); + fullPath = recent.label || this.labelService.getWorkspaceLabel(recent.folderUri, { verbose: true }); typeHint = 'folder'; } else { - label = recent.label || this.labelService.getWorkspaceLabel(recent.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__ @@ -388,7 +371,7 @@ class WelcomePage { id: 'openRecentFolder', from: telemetryFrom }); - this.windowService.openWindow([{ uri: resource, typeHint, label }], { forceNewWindow: e.ctrlKey || e.metaKey }); + this.windowService.openWindow([{ uri: resource, typeHint }], { forceNewWindow: e.ctrlKey || e.metaKey }); e.preventDefault(); e.stopPropagation(); }); @@ -397,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; diff --git a/src/vs/workbench/electron-browser/actions/windowActions.ts b/src/vs/workbench/electron-browser/actions/windowActions.ts index f5b027024ba407eec32206c283964558512a1784..239b8288fb275f12485118848efbc61775a7c4cd 100644 --- a/src/vs/workbench/electron-browser/actions/windowActions.ts +++ b/src/vs/workbench/electron-browser/actions/windowActions.ts @@ -14,10 +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 { 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 +25,7 @@ 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 { @@ -341,39 +340,35 @@ export abstract class BaseOpenRecentAction extends Action { const toPick = (recent: IRecent, labelService: ILabelService, buttons: IQuickInputButton[] | undefined) => { let resource: URI | undefined; - let label: string | undefined; - let description: string | undefined; + let fullLabel: string | undefined; let fileKind: FileKind | undefined; if (isRecentFolder(recent)) { resource = recent.folderUri; - label = recent.label || labelService.getWorkspaceLabel(recent.folderUri); - description = labelService.getUriLabel(dirname(resource)); + fullLabel = recent.label || labelService.getWorkspaceLabel(recent.folderUri, { verbose: true }); fileKind = FileKind.FOLDER; } else if (isRecentWorkspace(recent)) { resource = recent.workspace.configPath; - label = recent.label || labelService.getWorkspaceLabel(recent.workspace); - description = labelService.getUriLabel(dirname(resource)); + fullLabel = recent.label || labelService.getWorkspaceLabel(recent.workspace, { verbose: true }); fileKind = FileKind.ROOT_FOLDER; } else { resource = recent.fileUri; - label = recent.label || getBaseLabel(recent.fileUri); - description = labelService.getUriLabel(dirname(resource)); + 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, resource, fileKind, }; }; - const runPick = (uri: URI, isFile: boolean, keyMods: IKeyMods, label: string) => { + const runPick = (uri: URI, isFile: boolean, keyMods: IKeyMods) => { const forceNewWindow = keyMods.ctrlCmd; - return this.windowService.openWindow([{ uri, typeHint: isFile ? 'file' : 'folder', label }], { forceNewWindow, forceOpenWorkspaceAsFile: isFile }); + return this.windowService.openWindow([{ uri, typeHint: isFile ? 'file' : 'folder' }], { forceNewWindow, forceOpenWorkspaceAsFile: isFile }); }; const workspacePicks = recentWorkspaces.map(workspace => toPick(workspace, this.labelService, !this.isQuickNavigate() ? [this.removeFromRecentlyOpened] : undefined)); @@ -399,7 +394,7 @@ export abstract class BaseOpenRecentAction extends Action { } }).then((pick): Promise | void => { if (pick) { - return runPick(pick.resource, pick.fileKind === FileKind.FILE, keyMods, pick.label); + return runPick(pick.resource, pick.fileKind === FileKind.FILE, keyMods); } }); } diff --git a/src/vs/workbench/services/workspace/node/workspaceEditingService.ts b/src/vs/workbench/services/workspace/node/workspaceEditingService.ts index 8fbcbb3d732ef619ad62d541d9d29ac8140907c7..bb495c6ea73347375bab77470f201f118adaa660 100644 --- a/src/vs/workbench/services/workspace/node/workspaceEditingService.ts +++ b/src/vs/workbench/services/workspace/node/workspaceEditingService.ts @@ -119,7 +119,7 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { if (newWorkspacePath) { return this.saveWorkspaceAs(workspaceIdentifier, newWorkspacePath).then(_ => { return this.workspaceService.getWorkspaceIdentifier(newWorkspacePath).then(newWorkspaceIdentifier => { - const label = this.labelService.getWorkspaceLabel(newWorkspaceIdentifier); + const label = this.labelService.getWorkspaceLabel(newWorkspaceIdentifier, { verbose: true }); this.windowsService.addRecentlyOpened([{ label, workspace: newWorkspaceIdentifier }]); this.workspaceService.deleteUntitledWorkspace(workspaceIdentifier); return false;