diff --git a/src/vs/editor/standalone/browser/simpleServices.ts b/src/vs/editor/standalone/browser/simpleServices.ts index e58fb45a8c8..c8247a83871 100644 --- a/src/vs/editor/standalone/browser/simpleServices.ts +++ b/src/vs/editor/standalone/browser/simpleServices.ts @@ -36,7 +36,7 @@ import { KeybindingResolver } from 'vs/platform/keybinding/common/keybindingReso import { IKeybindingItem, KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem'; import { USLayoutResolvedKeybinding } from 'vs/platform/keybinding/common/usLayoutResolvedKeybinding'; -import { ILabelService, LabelRules } from 'vs/platform/label/common/label'; +import { ILabelService, ResourceLabelFormatter } from 'vs/platform/label/common/label'; import { INotification, INotificationHandle, INotificationService, IPromptChoice, IPromptOptions, NoOpNotification } from 'vs/platform/notification/common/notification'; import { IProgressRunner, IProgressService } from 'vs/platform/progress/common/progress'; import { ITelemetryInfo, ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -625,7 +625,7 @@ export class SimpleUriLabelService implements ILabelService { return ''; } - public registerFormatter(selector: string, formatter: LabelRules): IDisposable { + public registerFormatter(formatter: ResourceLabelFormatter): IDisposable { throw new Error('Not implemented'); } diff --git a/src/vs/platform/label/common/label.ts b/src/vs/platform/label/common/label.ts index 8e81be2bf4e..5846fd3796e 100644 --- a/src/vs/platform/label/common/label.ts +++ b/src/vs/platform/label/common/label.ts @@ -25,21 +25,23 @@ export interface ILabelService { getUriLabel(resource: URI, options?: { relative?: boolean, noPrefix?: boolean }): string; getWorkspaceLabel(workspace: (IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IWorkspace), options?: { verbose: boolean }): string; getHostLabel(): string; - registerFormatter(selector: string, formatter: LabelRules): IDisposable; + registerFormatter(formatter: ResourceLabelFormatter): IDisposable; onDidRegisterFormatter: Event; } -export interface LabelRules { - uri: { - label: string; // myLabel:/${path} - separator: '/' | '\\' | ''; - tildify?: boolean; - normalizeDriveLetter?: boolean; - authorityPrefix?: string; - }; - workspace?: { - suffix: string; - }; +export interface ResourceLabelFormatter { + scheme: string; + authority?: string; + formatting: ResourceLabelFormatting; +} + +export interface ResourceLabelFormatting { + label: string; // myLabel:/${path} + separator: '/' | '\\' | ''; + tildify?: boolean; + normalizeDriveLetter?: boolean; + workspaceSuffix?: string; + authorityPrefix?: string; } const LABEL_SERVICE_ID = 'label'; diff --git a/src/vs/workbench/api/electron-browser/mainThreadFileSystem.ts b/src/vs/workbench/api/electron-browser/mainThreadFileSystem.ts index 277b711619d..3ad9bff063c 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadFileSystem.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadFileSystem.ts @@ -9,7 +9,7 @@ import { URI } from 'vs/base/common/uri'; import { FileWriteOptions, FileSystemProviderCapabilities, IFileChange, IFileService, IFileSystemProvider, IStat, IWatchOptions, FileType, FileOverwriteOptions, FileDeleteOptions } from 'vs/platform/files/common/files'; import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers'; import { ExtHostContext, ExtHostFileSystemShape, IExtHostContext, IFileChangeDto, MainContext, MainThreadFileSystemShape } from '../node/extHost.protocol'; -import { LabelRules, ILabelService } from 'vs/platform/label/common/label'; +import { ResourceLabelFormatter, ILabelService } from 'vs/platform/label/common/label'; @extHostNamedCustomer(MainContext.MainThreadFileSystem) export class MainThreadFileSystem implements MainThreadFileSystemShape { @@ -39,8 +39,8 @@ export class MainThreadFileSystem implements MainThreadFileSystemShape { this._fileProvider.delete(handle); } - $setUriFormatter(selector: string, formatter: LabelRules): void { - this._labelService.registerFormatter(selector, formatter); + $setUriFormatter(formatter: ResourceLabelFormatter): void { + this._labelService.registerFormatter(formatter); } $onFileSystemChange(handle: number, changes: IFileChangeDto[]): void { diff --git a/src/vs/workbench/api/node/extHost.protocol.ts b/src/vs/workbench/api/node/extHost.protocol.ts index 7ecd69fcb54..fdaa10614ad 100644 --- a/src/vs/workbench/api/node/extHost.protocol.ts +++ b/src/vs/workbench/api/node/extHost.protocol.ts @@ -21,7 +21,7 @@ import { ICommandHandlerDescription } from 'vs/platform/commands/common/commands import { ConfigurationTarget, IConfigurationData, IConfigurationModel } from 'vs/platform/configuration/common/configuration'; import { ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { FileChangeType, FileDeleteOptions, FileOverwriteOptions, FileSystemProviderCapabilities, FileType, FileWriteOptions, IStat, IWatchOptions } from 'vs/platform/files/common/files'; -import { LabelRules } from 'vs/platform/label/common/label'; +import { ResourceLabelFormatter } from 'vs/platform/label/common/label'; import { LogLevel } from 'vs/platform/log/common/log'; import { IMarkerData } from 'vs/platform/markers/common/markers'; import { IPickOptions, IQuickInputButton, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; @@ -512,7 +512,7 @@ export interface IFileChangeDto { export interface MainThreadFileSystemShape extends IDisposable { $registerFileSystemProvider(handle: number, scheme: string, capabilities: FileSystemProviderCapabilities): void; $unregisterProvider(handle: number): void; - $setUriFormatter(scheme: string, formatter: LabelRules): void; + $setUriFormatter(formatter: ResourceLabelFormatter): void; $onFileSystemChange(handle: number, resource: IFileChangeDto[]): void; } diff --git a/src/vs/workbench/api/node/extHostFileSystem.ts b/src/vs/workbench/api/node/extHostFileSystem.ts index 6a605ba9158..2fd8bd78205 100644 --- a/src/vs/workbench/api/node/extHostFileSystem.ts +++ b/src/vs/workbench/api/node/extHostFileSystem.ts @@ -12,7 +12,7 @@ import { FileChangeType, DocumentLink } from 'vs/workbench/api/node/extHostTypes import * as typeConverter from 'vs/workbench/api/node/extHostTypeConverters'; import { ExtHostLanguageFeatures } from 'vs/workbench/api/node/extHostLanguageFeatures'; import { Schemas } from 'vs/base/common/network'; -import { LabelRules } from 'vs/platform/label/common/label'; +import { ResourceLabelFormatter } from 'vs/platform/label/common/label'; import { State, StateMachine, LinkComputer } from 'vs/editor/common/modes/linkComputer'; import { commonPrefixLength } from 'vs/base/common/strings'; import { CharCode } from 'vs/base/common/charCode'; @@ -205,8 +205,8 @@ export class ExtHostFileSystem implements ExtHostFileSystemShape { }); } - setUriFormatter(scheme: string, formatter: LabelRules): void { - this._proxy.$setUriFormatter(scheme, formatter); + setUriFormatter(formatter: ResourceLabelFormatter): void { + this._proxy.$setUriFormatter(formatter); } private static _asIStat(stat: vscode.FileStat): files.IStat { diff --git a/src/vs/workbench/parts/files/electron-browser/files.contribution.ts b/src/vs/workbench/parts/files/electron-browser/files.contribution.ts index b325009c33e..a0bfe75ae60 100644 --- a/src/vs/workbench/parts/files/electron-browser/files.contribution.ts +++ b/src/vs/workbench/parts/files/electron-browser/files.contribution.ts @@ -57,16 +57,15 @@ export class OpenExplorerViewletAction extends ShowViewletAction { class FileUriLabelContribution implements IWorkbenchContribution { constructor(@ILabelService labelService: ILabelService) { - labelService.registerFormatter('file://', { - uri: { + labelService.registerFormatter({ + scheme: 'file', + formatting: { label: '${authority}${path}', separator: nativeSep, tildify: !platform.isWindows, normalizeDriveLetter: platform.isWindows, - authorityPrefix: nativeSep + nativeSep - }, - workspace: { - suffix: '' + authorityPrefix: nativeSep + nativeSep, + workspaceSuffix: '' } }); } diff --git a/src/vs/workbench/services/label/common/labelService.ts b/src/vs/workbench/services/label/common/labelService.ts index 56d994214dc..2e7fc21a2a6 100644 --- a/src/vs/workbench/services/label/common/labelService.ts +++ b/src/vs/workbench/services/label/common/labelService.ts @@ -12,65 +12,56 @@ import { IWorkspaceContextService, IWorkspace } from 'vs/platform/workspace/comm import { isEqual, basenameOrAuthority, basename as resourceBasename } from 'vs/base/common/resources'; import { isLinux, isWindows } from 'vs/base/common/platform'; import { tildify, getPathLabel } from 'vs/base/common/labels'; -import { ltrim, startsWith } from 'vs/base/common/strings'; +import { ltrim } from 'vs/base/common/strings'; import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, WORKSPACE_EXTENSION, toWorkspaceIdentifier, isWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { isParent } from 'vs/platform/files/common/files'; import { basename, dirname, join } from 'vs/base/common/paths'; import { Schemas } from 'vs/base/common/network'; import { IWindowService } from 'vs/platform/windows/common/windows'; import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; -import { ILabelService, LabelRules } from 'vs/platform/label/common/label'; +import { ILabelService, ResourceLabelFormatter, ResourceLabelFormatting } from 'vs/platform/label/common/label'; import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry'; +import { match } from 'vs/base/common/glob'; -interface LabelRulesWithPrefix extends LabelRules { - prefix: string; -} - -export const labelsExtPoint = ExtensionsRegistry.registerExtensionPoint({ - extensionPoint: 'labels', +export const labelsExtPoint = ExtensionsRegistry.registerExtensionPoint({ + extensionPoint: 'resourceLabelFormatters', jsonSchema: { - description: localize('vscode.extension.contributes.labels', 'Contributes label formatting rules.'), + description: localize('vscode.extension.contributes.resourceLabelFormatters', 'Contributes resource label formatting rules.'), type: 'array', items: { type: 'object', - required: ['prefix'], + required: ['scheme', 'formatting'], properties: { - prefix: { + scheme: { type: 'string', - description: localize('vscode.extension.contributes.labels.prefix', 'URI prefix on which to match the rules on. For example file:///myFolder'), + description: localize('vscode.extension.contributes.resourceLabelFormatters.scheme', 'URI scheme on which to match the formatter on. For example "file". Simple glob patterns are supported.'), }, - uri: { - description: localize('vscode.extension.contributes.labels.uri', "Rules for formatting uri resource labels."), + authority: { + type: 'string', + description: localize('vscode.extension.contributes.resourceLabelFormatters.authority', 'URI authority on which to match the formatter on. Simple glob patterns are supported.'), + }, + formatting: { + description: localize('vscode.extension.contributes.resourceLabelFormatters.formatting', "Rules for formatting uri resource labels."), type: 'object', properties: { label: { type: 'string', - description: localize('vscode.extension.contributes.labels.uri.label', "Label rules to display. For example: myLabel:/${path}. ${path}, ${scheme} and ${authority} are supported as variables.") + description: localize('vscode.extension.contributes.resourceLabelFormatters.label', "Label rules to display. For example: myLabel:/${path}. ${path}, ${scheme} and ${authority} are supported as variables.") }, separator: { type: 'string', - description: localize('vscode.extension.contributes.labels.uri.separator', "Separator to be used in the uri label display. '/' or '\' as an example.") + description: localize('vscode.extension.contributes.resourceLabelFormatters.separator', "Separator to be used in the uri label display. '/' or '\' as an example.") }, tildify: { type: 'boolean', - description: localize('vscode.extension.contributes.labels.uri.tildify', "Controls if the start of the uri label should be tildified when possible.") + description: localize('vscode.extension.contributes.resourceLabelFormatters.tildify', "Controls if the start of the uri label should be tildified when possible.") }, - normalizeDriveLetter: { - type: 'boolean', - description: localize('vscode.extension.contributes.labels.uri.normalizeDriveLetter', "Controls if the drive letters should be upper cased.") - } - } - }, - workspace: { - description: localize('vscode.extension.contributes.labels.workspace', "Rules for formatting workspace labels."), - type: 'object', - properties: { - suffix: { + workspaceSuffix: { type: 'string', - description: localize('vscode.extension.contributes.labels.workspace.suffix', "Suffix appended to the workspace label.") + description: localize('vscode.extension.contributes.resourceLabelFormatters.formatting.workspaceSuffix', "Suffix appended to the workspace label.") } } - }, + } } } } @@ -86,7 +77,7 @@ function hasDriveLetter(path: string): boolean { export class LabelService implements ILabelService { _serviceBrand: any; - private readonly formatters: { [prefix: string]: LabelRules } = Object.create(null); + private formatters: ResourceLabelFormatter[] = []; private readonly _onDidRegisterFormatter = new Emitter(); constructor( @@ -95,7 +86,7 @@ export class LabelService implements ILabelService { @IWindowService private readonly windowService: IWindowService ) { labelsExtPoint.setHandler(extensions => { - extensions.forEach(extension => extension.value.forEach(rule => this.formatters[rule.prefix] = rule)); + extensions.forEach(extension => extension.value.forEach(formatter => this.formatters.push(formatter))); this._onDidRegisterFormatter.fire(); }); } @@ -104,23 +95,28 @@ export class LabelService implements ILabelService { return this._onDidRegisterFormatter.event; } - findFormatter(resource: URI): LabelRules | undefined { - const path = `${resource.scheme}://${resource.authority}`; - let bestPrefix = ''; - for (let prefix in this.formatters) { - if (startsWith(path, prefix) && prefix.length > bestPrefix.length) { - bestPrefix = prefix; + findFormatting(resource: URI): ResourceLabelFormatting | undefined { + let bestResult: ResourceLabelFormatter; + + this.formatters.forEach(formatter => { + if (formatter.scheme === resource.scheme) { + if (!formatter.authority && !bestResult) { + bestResult = formatter; + return; + } + + if (match(resource.authority, formatter.authority) && (!bestResult.authority || formatter.authority.length > bestResult.authority.length)) { + bestResult = formatter; + } } - } - if (bestPrefix.length) { - return this.formatters[bestPrefix]; - } - return undefined; + }); + + return bestResult.formatting; } getUriLabel(resource: URI, options: { relative?: boolean, noPrefix?: boolean } = {}): string { - const formatter = this.findFormatter(resource); - if (!formatter) { + const formatting = this.findFormatting(resource); + if (!formatting) { return getPathLabel(resource.path, this.environmentService, options.relative ? this.contextService : undefined); } @@ -131,8 +127,8 @@ export class LabelService implements ILabelService { if (isEqual(baseResource.uri, resource, !isLinux)) { relativeLabel = ''; // no label if resources are identical } else { - const baseResourceLabel = this.formatUri(baseResource.uri, formatter, options.noPrefix); - relativeLabel = ltrim(this.formatUri(resource, formatter, options.noPrefix).substring(baseResourceLabel.length), formatter.uri.separator); + const baseResourceLabel = this.formatUri(baseResource.uri, formatting, options.noPrefix); + relativeLabel = ltrim(this.formatUri(resource, formatting, options.noPrefix).substring(baseResourceLabel.length), formatting.separator); } const hasMultipleRoots = this.contextService.getWorkspace().folders.length > 1; @@ -145,7 +141,7 @@ export class LabelService implements ILabelService { } } - return this.formatUri(resource, formatter, options.noPrefix); + return this.formatUri(resource, formatting, options.noPrefix); } getWorkspaceLabel(workspace: (IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IWorkspace), options?: { verbose: boolean }): string { @@ -166,8 +162,8 @@ export class LabelService implements ILabelService { return label; } - const formatter = this.findFormatter(workspace); - const suffix = formatter && formatter.workspace && (typeof formatter.workspace.suffix === 'string') ? formatter.workspace.suffix : workspace.scheme; + const formatting = this.findFormatting(workspace); + const suffix = formatting && (typeof formatting.workspaceSuffix === 'string') ? formatting.workspaceSuffix : workspace.scheme; return suffix ? `${label} (${suffix})` : label; } @@ -190,26 +186,26 @@ export class LabelService implements ILabelService { if (this.windowService) { const authority = this.windowService.getConfiguration().remoteAuthority; if (authority) { - const formatter = this.findFormatter(URI.from({ scheme: REMOTE_HOST_SCHEME, authority })); - if (formatter && formatter.workspace) { - return formatter.workspace.suffix; + const formatter = this.findFormatting(URI.from({ scheme: REMOTE_HOST_SCHEME, authority })); + if (formatter && formatter.workspaceSuffix) { + return formatter.workspaceSuffix; } } } return ''; } - registerFormatter(selector: string, formatter: LabelRules): IDisposable { - this.formatters[selector] = formatter; + registerFormatter(formatter: ResourceLabelFormatter): IDisposable { + this.formatters.push(formatter); this._onDidRegisterFormatter.fire(); return { - dispose: () => delete this.formatters[selector] + dispose: () => this.formatters = this.formatters.filter(f => f !== formatter) }; } - private formatUri(resource: URI, formatter: LabelRules, forceNoTildify?: boolean): string { - let label = formatter.uri.label.replace(labelMatchingRegexp, match => { + private formatUri(resource: URI, formatting: ResourceLabelFormatting, forceNoTildify?: boolean): string { + let label = formatting.label.replace(labelMatchingRegexp, match => { switch (match) { case '${scheme}': return resource.scheme; case '${authority}': return resource.authority; @@ -219,17 +215,17 @@ export class LabelService implements ILabelService { }); // convert \c:\something => C:\something - if (formatter.uri.normalizeDriveLetter && hasDriveLetter(label)) { + if (formatting.normalizeDriveLetter && hasDriveLetter(label)) { label = label.charAt(1).toUpperCase() + label.substr(2); } - if (formatter.uri.tildify && !forceNoTildify) { + if (formatting.tildify && !forceNoTildify) { label = tildify(label, this.environmentService.userHome); } - if (formatter.uri.authorityPrefix && resource.authority) { - label = formatter.uri.authorityPrefix + label; + if (formatting.authorityPrefix && resource.authority) { + label = formatting.authorityPrefix + label; } - return label.replace(sepRegexp, formatter.uri.separator); + return label.replace(sepRegexp, formatting.separator); } } diff --git a/src/vs/workbench/services/label/test/label.test.ts b/src/vs/workbench/services/label/test/label.test.ts index f983ba30f34..39a59ecf86c 100644 --- a/src/vs/workbench/services/label/test/label.test.ts +++ b/src/vs/workbench/services/label/test/label.test.ts @@ -20,8 +20,9 @@ suite('URI Label', () => { }); test('file scheme', function () { - labelService.registerFormatter('file://', { - uri: { + labelService.registerFormatter({ + scheme: 'file://', + formatting: { label: '${path}', separator: nativeSep, tildify: !isWindows, @@ -38,8 +39,9 @@ suite('URI Label', () => { }); test('custom scheme', function () { - labelService.registerFormatter('vscode://', { - uri: { + labelService.registerFormatter({ + scheme: 'vscode://', + formatting: { label: 'LABEL/${path}/${authority}/END', separator: '/', tildify: true,