diff --git a/src/vs/base/browser/ui/iconLabel/iconLabel.ts b/src/vs/base/browser/ui/iconLabel/iconLabel.ts index 945dbd9c26bd4798ce37e1f9f75ba90bd4197950..26b4d09bcd38bacb7b6ac80d1f258ced496d6e86 100644 --- a/src/vs/base/browser/ui/iconLabel/iconLabel.ts +++ b/src/vs/base/browser/ui/iconLabel/iconLabel.ts @@ -11,7 +11,7 @@ import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlighte import { IMatch } from 'vs/base/common/filters'; import uri from 'vs/base/common/uri'; import paths = require('vs/base/common/paths'); -import { IWorkspaceFolderProvider, getPathLabel, IUserHomeProvider } from 'vs/base/common/labels'; +import { IWorkspaceFolderProvider, getPathLabel, IUserHomeProvider, getBaseLabel } from 'vs/base/common/labels'; import { IDisposable, combinedDisposable } from 'vs/base/common/lifecycle'; export interface IIconLabelCreationOptions { @@ -163,6 +163,6 @@ export class FileLabel extends IconLabel { public setFile(file: uri, provider: IWorkspaceFolderProvider, userHome: IUserHomeProvider): void { const parent = paths.dirname(file.fsPath); - this.setValue(paths.basename(file.fsPath), parent && parent !== '.' ? getPathLabel(parent, provider, userHome) : '', { title: file.fsPath }); + this.setValue(getBaseLabel(file), parent && parent !== '.' ? getPathLabel(parent, provider, userHome) : '', { title: file.fsPath }); } } diff --git a/src/vs/base/common/labels.ts b/src/vs/base/common/labels.ts index a5ec95f8337721f3e533fd3f08d4979a22f97515..3cbc39b324cfdc1cc47b10d4570a9e1bd0eee1b5 100644 --- a/src/vs/base/common/labels.ts +++ b/src/vs/base/common/labels.ts @@ -6,7 +6,7 @@ import URI from 'vs/base/common/uri'; import platform = require('vs/base/common/platform'); -import { nativeSep, normalize, isEqualOrParent, isEqual, basename, join } from 'vs/base/common/paths'; +import { nativeSep, normalize, isEqualOrParent, isEqual, basename as pathsBasename, join } from 'vs/base/common/paths'; import { endsWith, ltrim } from 'vs/base/common/strings'; export interface IWorkspaceFolderProvider { @@ -46,7 +46,7 @@ export function getPathLabel(resource: URI | string, rootProvider?: IWorkspaceFo } if (hasMultipleRoots) { - const rootName = basename(baseResource.uri.fsPath); + const rootName = pathsBasename(baseResource.uri.fsPath); pathLabel = pathLabel ? join(rootName, pathLabel) : rootName; // always show root basename if there are multiple } @@ -67,6 +67,26 @@ export function getPathLabel(resource: URI | string, rootProvider?: IWorkspaceFo return res; } +export function getBaseLabel(resource: URI | string): string { + if (!resource) { + return null; + } + + if (typeof resource === 'string') { + resource = URI.file(resource); + } + + let base = pathsBasename(resource.fsPath); + + // Windows: basename('C:\') returns empty string, so make sure to always + // return the drive letter at least in that case. + if (!base) { + base = normalize(normalizeDriveLetter(resource.fsPath), true); + } + + return base; +} + function hasDriveLetter(path: string): boolean { return platform.isWindows && path && path[1] === ':'; } diff --git a/src/vs/base/common/mime.ts b/src/vs/base/common/mime.ts index 12cfd795c4138e1dd4ae1995d30cf646fdb31f9f..467e832224973ec7fd6aef854b2b2809767695f9 100644 --- a/src/vs/base/common/mime.ts +++ b/src/vs/base/common/mime.ts @@ -112,23 +112,23 @@ export function guessMimeTypes(path: string, firstLine?: string): string[] { } path = path.toLowerCase(); - let filename = paths.basename(path); + const filename = paths.basename(path); // 1.) User configured mappings have highest priority - let configuredMime = guessMimeTypeByPath(path, filename, userRegisteredAssociations); + const configuredMime = guessMimeTypeByPath(path, filename, userRegisteredAssociations); if (configuredMime) { return [configuredMime, MIME_TEXT]; } // 2.) Registered mappings have middle priority - let registeredMime = guessMimeTypeByPath(path, filename, nonUserRegisteredAssociations); + const registeredMime = guessMimeTypeByPath(path, filename, nonUserRegisteredAssociations); if (registeredMime) { return [registeredMime, MIME_TEXT]; } // 3.) Firstline has lowest priority if (firstLine) { - let firstlineMime = guessMimeTypeByFirstline(firstLine); + const firstlineMime = guessMimeTypeByFirstline(firstLine); if (firstlineMime) { return [firstlineMime, MIME_TEXT]; } @@ -145,7 +145,7 @@ function guessMimeTypeByPath(path: string, filename: string, associations: IText // We want to prioritize associations based on the order they are registered so that the last registered // association wins over all other. This is for https://github.com/Microsoft/vscode/issues/20074 for (let i = associations.length - 1; i >= 0; i--) { - let association = associations[i]; + const association = associations[i]; // First exact name match if (filename === association.filenameLowercase) { @@ -156,7 +156,7 @@ function guessMimeTypeByPath(path: string, filename: string, associations: IText // Longest pattern match if (association.filepattern) { if (!patternMatch || association.filepattern.length > patternMatch.filepattern.length) { - let target = association.filepatternOnPath ? path : filename; // match on full path if pattern contains path separator + const target = association.filepatternOnPath ? path : filename; // match on full path if pattern contains path separator if (match(association.filepatternLowercase, target)) { patternMatch = association; } @@ -198,12 +198,12 @@ function guessMimeTypeByFirstline(firstLine: string): string { if (firstLine.length > 0) { for (let i = 0; i < registeredAssociations.length; ++i) { - let association = registeredAssociations[i]; + const association = registeredAssociations[i]; if (!association.firstline) { continue; } - let matches = firstLine.match(association.firstline); + const matches = firstLine.match(association.firstline); if (matches && matches.length > 0) { return association.mime; } @@ -227,7 +227,7 @@ export function isUnspecific(mime: string[] | string): boolean { export function suggestFilename(langId: string, prefix: string): string { for (let i = 0; i < registeredAssociations.length; i++) { - let association = registeredAssociations[i]; + const association = registeredAssociations[i]; if (association.userConfigured) { continue; // only support registered ones } diff --git a/src/vs/base/node/extfs.ts b/src/vs/base/node/extfs.ts index e50ad6b102c68ad37858043684ef9aae3e11f561..77b60dad3172bf32b235b84f8678f5a2bac03595 100644 --- a/src/vs/base/node/extfs.ts +++ b/src/vs/base/node/extfs.ts @@ -378,7 +378,7 @@ export function realcaseSync(path: string): string { return path; } - const name = paths.basename(path).toLowerCase(); + const name = (paths.basename(path) /* can be '' for windows drive letters */ || path).toLowerCase(); try { const entries = readdirSync(dir); const found = entries.filter(e => e.toLowerCase() === name); // use a case insensitive search diff --git a/src/vs/base/test/common/labels.test.ts b/src/vs/base/test/common/labels.test.ts index 650b4ee86a6f484154b6deef9436c511938e972b..9659955759ab1f611fd0acea0da25bc40ff4fed1 100644 --- a/src/vs/base/test/common/labels.test.ts +++ b/src/vs/base/test/common/labels.test.ts @@ -8,6 +8,7 @@ import * as assert from 'assert'; import labels = require('vs/base/common/labels'); import platform = require('vs/base/common/platform'); +import { getBaseLabel } from 'vs/base/common/labels'; suite('Labels', () => { test('shorten - windows', () => { @@ -143,4 +144,27 @@ suite('Labels', () => { assert.strictEqual(labels.template(t, { dirty: '', activeEditorShort: 'somefile.txt', rootName: 'monaco', appName: 'Visual Studio Code', separator: { label: ' - ' } }), 'somefile.txt - monaco - Visual Studio Code'); assert.strictEqual(labels.template(t, { dirty: '* ', activeEditorShort: 'somefile.txt', rootName: 'monaco', appName: 'Visual Studio Code', separator: { label: ' - ' } }), '* somefile.txt - monaco - Visual Studio Code'); }); + + test('getBaseLabel - unix', () => { + if (platform.isWindows) { + assert.ok(true); + return; + } + + assert.equal(getBaseLabel('/some/folder/file.txt'), 'file.txt'); + assert.equal(getBaseLabel('/some/folder'), 'folder'); + assert.equal(getBaseLabel('/'), '/'); + }); + + test('getBaseLabel - windows', () => { + if (!platform.isWindows) { + assert.ok(true); + return; + } + + assert.equal(getBaseLabel('c:'), 'C:\\'); + assert.equal(getBaseLabel('c:\\'), 'C:\\'); + assert.equal(getBaseLabel('c:\\some\\folder\\file.txt'), 'file.txt'); + assert.equal(getBaseLabel('c:\\some\\folder'), 'folder'); + }); }); \ No newline at end of file diff --git a/src/vs/code/node/cliProcessMain.ts b/src/vs/code/node/cliProcessMain.ts index 07bd80a27bb0dc741b5ca6db559b31bf40445bdd..288dba2a9afd2eb44e0a24946df9f69ad375c306 100644 --- a/src/vs/code/node/cliProcessMain.ts +++ b/src/vs/code/node/cliProcessMain.ts @@ -33,6 +33,7 @@ import { AppInsightsAppender } from 'vs/platform/telemetry/node/appInsightsAppen import { mkdirp } from 'vs/base/node/pfs'; import { IChoiceService } from 'vs/platform/message/common/message'; import { ChoiceCliService } from 'vs/platform/message/node/messageCli'; +import { getBaseLabel } from 'vs/base/common/labels'; const notFound = (id: string) => localize('notFound', "Extension '{0}' not found.", id); const notInstalled = (id: string) => localize('notInstalled', "Extension '{0}' is not installed.", id); @@ -95,7 +96,7 @@ class Main { const extension = path.isAbsolute(id) ? id : path.join(process.cwd(), id); return this.extensionManagementService.install(extension).then(() => { - console.log(localize('successVsixInstall', "Extension '{0}' was successfully installed!", path.basename(extension))); + console.log(localize('successVsixInstall', "Extension '{0}' was successfully installed!", getBaseLabel(extension))); }); }); diff --git a/src/vs/platform/history/electron-main/historyMainService.ts b/src/vs/platform/history/electron-main/historyMainService.ts index 0815d3461bd53cb7f8db624a46b2f6161a5fd41b..40fdc0b08027bcec75cf7d1de2020da753c1c90f 100644 --- a/src/vs/platform/history/electron-main/historyMainService.ts +++ b/src/vs/platform/history/electron-main/historyMainService.ts @@ -12,7 +12,7 @@ import { trim } from 'vs/base/common/strings'; import { IStorageService } from 'vs/platform/storage/node/storage'; import { app } from 'electron'; import { ILogService } from 'vs/platform/log/common/log'; -import { getPathLabel } from 'vs/base/common/labels'; +import { getPathLabel, getBaseLabel } from 'vs/base/common/labels'; import { IPath } from 'vs/platform/windows/common/windows'; import CommonEvent, { Emitter } from 'vs/base/common/event'; import { isWindows, isMacintosh, isLinux } from 'vs/base/common/platform'; @@ -257,8 +257,8 @@ export class HistoryMainService implements IHistoryMainService { type: 'custom', name: nls.localize('recentFolders', "Recent Workspaces"), items: this.getRecentlyOpened().workspaces.slice(0, 7 /* limit number of entries here */).map(workspace => { - const title = isSingleFolderWorkspaceIdentifier(workspace) ? path.basename(workspace) : getWorkspaceLabel(workspace, this.environmentService); - const description = isSingleFolderWorkspaceIdentifier(workspace) ? nls.localize('folderDesc', "{0} {1}", path.basename(workspace), getPathLabel(path.dirname(workspace))) : nls.localize('codeWorkspace', "Code Workspace"); + const title = isSingleFolderWorkspaceIdentifier(workspace) ? getBaseLabel(workspace) : getWorkspaceLabel(workspace, this.environmentService); + const description = isSingleFolderWorkspaceIdentifier(workspace) ? nls.localize('folderDesc', "{0} {1}", getBaseLabel(workspace), getPathLabel(path.dirname(workspace))) : nls.localize('codeWorkspace', "Code Workspace"); return { type: 'task', diff --git a/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts b/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts index bc3517a4f7ef11e51ba447aa46c2e457d23324b7..7f3f848b0fd581f7c8cba9d8c798aebf679eb82d 100644 --- a/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts +++ b/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts @@ -25,7 +25,6 @@ import { QuickOpenEntry, QuickOpenModel, QuickOpenEntryGroup, compareEntries, Qu import { QuickOpenWidget, HideReason } from 'vs/base/parts/quickopen/browser/quickOpenWidget'; import { ContributableActionProvider } from 'vs/workbench/browser/actions'; import labels = require('vs/base/common/labels'); -import paths = require('vs/base/common/paths'); import { ITextFileService, AutoSaveMode } from 'vs/workbench/services/textfile/common/textfiles'; import { Registry } from 'vs/platform/registry/common/platform'; import { IResourceInput, IEditorInput } from 'vs/platform/editor/common/editor'; @@ -57,6 +56,7 @@ import { ITree, IActionProvider } from 'vs/base/parts/tree/browser/tree'; import { BaseActionItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { FileKind, IFileService } from 'vs/platform/files/common/files'; import { scoreItem, ScorerCache, compareItemsByScore, prepareQuery } from 'vs/base/parts/quickopen/common/quickOpenScorer'; +import { getBaseLabel } from 'vs/base/common/labels'; const HELP_PREFIX = '?'; @@ -1272,7 +1272,7 @@ export class EditorHistoryEntry extends EditorQuickOpenEntry { } else { const resourceInput = input as IResourceInput; this.resource = resourceInput.resource; - this.label = paths.basename(resourceInput.resource.fsPath); + this.label = getBaseLabel(resourceInput.resource); this.description = labels.getPathLabel(resources.dirname(this.resource), contextService, environmentService); this.dirty = this.resource && this.textFileService.isDirty(this.resource); diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index 14162ffd5eebe5ab9e7b452f15c8d340ab4c89c2..f7f6d7ee3d30baef695bee66089f5dc57cda9b5b 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -307,9 +307,11 @@ export class TitlebarPart extends Part implements ITitleService { const path = segments.slice(0, pathOffset).join(paths.sep); - let label = paths.basename(path); + let label: string; if (!isFile) { - label = paths.basename(paths.dirname(path)); + label = labels.getBaseLabel(paths.dirname(path)); + } else { + label = labels.getBaseLabel(path); } actions.push(new ShowItemInFolderAction(path, label || paths.sep, this.windowsService)); diff --git a/src/vs/workbench/electron-browser/actions.ts b/src/vs/workbench/electron-browser/actions.ts index 70f30c4faf7fcdf8145b5989179ad53cda7e9e4e..7f2ba451b941a88030c4bc54b2824bc8c7c753e4 100644 --- a/src/vs/workbench/electron-browser/actions.ts +++ b/src/vs/workbench/electron-browser/actions.ts @@ -38,7 +38,7 @@ import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import * as os from 'os'; import { webFrame } from 'electron'; -import { getPathLabel } from 'vs/base/common/labels'; +import { getPathLabel, getBaseLabel } from 'vs/base/common/labels'; import { IViewlet } from 'vs/workbench/common/viewlet'; import { IPanel } from 'vs/workbench/common/panel'; import { IWorkspaceIdentifier, getWorkspaceLabel, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; @@ -724,7 +724,7 @@ export abstract class BaseOpenRecentAction extends Action { let description: string; if (isSingleFolderWorkspaceIdentifier(workspace)) { path = workspace; - label = paths.basename(path); + label = getBaseLabel(path); description = getPathLabel(paths.dirname(path), null, environmentService); } else { path = workspace.configPath; diff --git a/src/vs/workbench/parts/files/common/explorerModel.ts b/src/vs/workbench/parts/files/common/explorerModel.ts index eef97559c91a706ad8ab14708b8918c8665653e1..8e1a539b42d98126707d218c90ef7dac46ecc78c 100644 --- a/src/vs/workbench/parts/files/common/explorerModel.ts +++ b/src/vs/workbench/parts/files/common/explorerModel.ts @@ -14,6 +14,7 @@ import { IEditorInput } from 'vs/platform/editor/common/editor'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IEditorGroup, toResource } from 'vs/workbench/common/editor'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { getPathLabel } from 'vs/base/common/labels'; export class Model { @@ -78,7 +79,7 @@ export class FileStat implements IFileStat { public isDirectoryResolved: boolean; - constructor(resource: URI, public root: FileStat, isDirectory?: boolean, hasChildren?: boolean, name: string = paths.basename(resource.fsPath), mtime?: number, etag?: string) { + constructor(resource: URI, public root: FileStat, isDirectory?: boolean, hasChildren?: boolean, name: string = getPathLabel(resource), mtime?: number, etag?: string) { this.resource = resource; this.name = name; this.isDirectory = !!isDirectory; diff --git a/src/vs/workbench/parts/search/common/searchModel.ts b/src/vs/workbench/parts/search/common/searchModel.ts index 2fe02b67166f3ed0695eb342a45b7b9a1427cf25..cd9a3adc2ababa316793f7232982796361f1271c 100644 --- a/src/vs/workbench/parts/search/common/searchModel.ts +++ b/src/vs/workbench/parts/search/common/searchModel.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import paths = require('vs/base/common/paths'); import objects = require('vs/base/common/objects'); import strings = require('vs/base/common/strings'); import errors = require('vs/base/common/errors'); @@ -25,6 +24,7 @@ import { IProgressRunner } from 'vs/platform/progress/common/progress'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModelWithDecorations'; import { overviewRulerFindMatchForeground } from 'vs/platform/theme/common/colorRegistry'; import { themeColorFromId } from 'vs/platform/theme/common/themeService'; +import { getBaseLabel } from 'vs/base/common/labels'; export class Match { @@ -302,7 +302,7 @@ export class FileMatch extends Disposable { } public name(): string { - return paths.basename(this.resource().fsPath); + return getBaseLabel(this.resource()); } public add(match: Match, trigger?: boolean) { @@ -379,7 +379,7 @@ export class FolderMatch extends Disposable { } public name(): string { - return paths.basename(this.resource().fsPath); + return getBaseLabel(this.resource()); } public parent(): SearchResult { diff --git a/src/vs/workbench/parts/welcome/page/electron-browser/welcomePage.ts b/src/vs/workbench/parts/welcome/page/electron-browser/welcomePage.ts index f1d893f88ead09e81da365d1972fcd6eebc7422a..3ce78b87c09ef69881a7b1bf7f1cf3db9476c1af 100644 --- a/src/vs/workbench/parts/welcome/page/electron-browser/welcomePage.ts +++ b/src/vs/workbench/parts/welcome/page/electron-browser/welcomePage.ts @@ -30,7 +30,7 @@ import { IExtensionEnablementService, IExtensionManagementService, IExtensionGal import { used } from 'vs/workbench/parts/welcome/page/electron-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 { tildify, getBaseLabel } 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/parts/welcome/walkThrough/node/walkThroughUtils'; @@ -286,7 +286,7 @@ class WelcomePage { let parent: string; let wsPath: string; if (isSingleFolderWorkspaceIdentifier(workspace)) { - label = path.basename(workspace); + label = getBaseLabel(workspace); parent = path.dirname(workspace); wsPath = workspace; } else { diff --git a/src/vs/workbench/services/configuration/node/configurationService.ts b/src/vs/workbench/services/configuration/node/configurationService.ts index b918b72fa1709a3c7a5cbd7c51bc0a64f1271baa..89f541dacf6f949f04dbe2b3a78dd17da4d2559f 100644 --- a/src/vs/workbench/services/configuration/node/configurationService.ts +++ b/src/vs/workbench/services/configuration/node/configurationService.ts @@ -40,6 +40,7 @@ import { Schemas } from 'vs/base/common/network'; import { massageFolderPathForWorkspace } from 'vs/platform/workspaces/node/workspaces'; import { distinct } from 'vs/base/common/arrays'; import { UserConfiguration } from 'vs/platform/configuration/node/configuration'; +import { getBaseLabel } from 'vs/base/common/labels'; export class WorkspaceService extends Disposable implements IWorkspaceConfigurationService, IWorkspaceContextService { @@ -335,7 +336,7 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat const ctime = isLinux ? workspaceStat.ino : workspaceStat.birthtime.getTime(); // On Linux, birthtime is ctime, so we cannot use it! We use the ino instead! const id = createHash('md5').update(folderPath.fsPath).update(ctime ? String(ctime) : '').digest('hex'); const folder = URI.file(folderPath.fsPath); - return new Workspace(id, paths.basename(folderPath.fsPath), toWorkspaceFolders([{ path: folder.fsPath }]), null, ctime); + return new Workspace(id, getBaseLabel(folder), toWorkspaceFolders([{ path: folder.fsPath }]), null, ctime); }); } diff --git a/src/vs/workbench/services/files/node/fileService.ts b/src/vs/workbench/services/files/node/fileService.ts index dcd29519524a4b06c35faf35f56293af1f37619b..efd4248eaddbd33aaf317a44c7ae64117badf886 100644 --- a/src/vs/workbench/services/files/node/fileService.ts +++ b/src/vs/workbench/services/files/node/fileService.ts @@ -39,6 +39,7 @@ import { ITextResourceConfigurationService } from 'vs/editor/common/services/res import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; +import { getBaseLabel } from 'vs/base/common/labels'; export interface IEncodingOverride { resource: uri; @@ -1007,7 +1008,7 @@ export class StatResolver { this.resource = resource; this.isDirectory = isDirectory; this.mtime = mtime; - this.name = paths.basename(resource.fsPath); + this.name = getBaseLabel(resource); this.etag = etag(size, mtime); this.size = size;