mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
Merge from master
This commit is contained in:
210
src/vs/platform/label/common/label.ts
Normal file
210
src/vs/platform/label/common/label.ts
Normal file
@@ -0,0 +1,210 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { IWorkspaceContextService, IWorkspace } from 'vs/platform/workspace/common/workspace';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { isEqual, basenameOrAuthority } 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 { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, WORKSPACE_EXTENSION, toWorkspaceIdentifier, isWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { localize } from 'vs/nls';
|
||||
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';
|
||||
|
||||
export interface RegisterFormatterEvent {
|
||||
selector: string;
|
||||
formatter: LabelRules;
|
||||
}
|
||||
|
||||
export interface ILabelService {
|
||||
_serviceBrand: any;
|
||||
/**
|
||||
* Gets the human readable label for a uri.
|
||||
* If relative is passed returns a label relative to the workspace root that the uri belongs to.
|
||||
* If noPrefix is passed does not tildify the label and also does not prepand the root name for relative labels in a multi root scenario.
|
||||
*/
|
||||
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;
|
||||
onDidRegisterFormatter: Event<RegisterFormatterEvent>;
|
||||
}
|
||||
|
||||
export interface LabelRules {
|
||||
uri: {
|
||||
label: string; // myLabel:/${path}
|
||||
separator: '/' | '\\' | '';
|
||||
tildify?: boolean;
|
||||
normalizeDriveLetter?: boolean;
|
||||
authorityPrefix?: string;
|
||||
};
|
||||
workspace?: {
|
||||
suffix: string;
|
||||
};
|
||||
}
|
||||
|
||||
const LABEL_SERVICE_ID = 'label';
|
||||
const sepRegexp = /\//g;
|
||||
const labelMatchingRegexp = /\$\{scheme\}|\$\{authority\}|\$\{path\}/g;
|
||||
|
||||
function hasDriveLetter(path: string): boolean {
|
||||
return !!(isWindows && path && path[2] === ':');
|
||||
}
|
||||
|
||||
export class LabelService implements ILabelService {
|
||||
_serviceBrand: any;
|
||||
|
||||
private readonly formatters: { [prefix: string]: LabelRules } = Object.create(null);
|
||||
private readonly _onDidRegisterFormatter = new Emitter<RegisterFormatterEvent>();
|
||||
|
||||
constructor(
|
||||
@IEnvironmentService private environmentService: IEnvironmentService,
|
||||
@IWorkspaceContextService private contextService: IWorkspaceContextService,
|
||||
@IWindowService private windowService: IWindowService
|
||||
) { }
|
||||
|
||||
get onDidRegisterFormatter(): Event<RegisterFormatterEvent> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
if (bestPrefix.length) {
|
||||
return this.formatters[bestPrefix];
|
||||
}
|
||||
return void 0;
|
||||
}
|
||||
|
||||
getUriLabel(resource: URI, options: { relative?: boolean, noPrefix?: boolean } = {}): string {
|
||||
const formatter = this.findFormatter(resource);
|
||||
if (!formatter) {
|
||||
return getPathLabel(resource.path, this.environmentService, options.relative ? this.contextService : undefined);
|
||||
}
|
||||
|
||||
if (options.relative) {
|
||||
const baseResource = this.contextService && this.contextService.getWorkspaceFolder(resource);
|
||||
if (baseResource) {
|
||||
let relativeLabel: string;
|
||||
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 hasMultipleRoots = this.contextService.getWorkspace().folders.length > 1;
|
||||
if (hasMultipleRoots && !options.noPrefix) {
|
||||
const rootName = (baseResource && baseResource.name) ? baseResource.name : basenameOrAuthority(baseResource.uri);
|
||||
relativeLabel = relativeLabel ? (rootName + ' • ' + relativeLabel) : rootName; // always show root basename if there are multiple
|
||||
}
|
||||
|
||||
return relativeLabel;
|
||||
}
|
||||
}
|
||||
|
||||
return this.formatUri(resource, formatter, options.noPrefix);
|
||||
}
|
||||
|
||||
getWorkspaceLabel(workspace: (IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IWorkspace), options?: { verbose: boolean }): string {
|
||||
if (!isWorkspaceIdentifier(workspace) && !isSingleFolderWorkspaceIdentifier(workspace)) {
|
||||
const identifier = toWorkspaceIdentifier(workspace);
|
||||
if (!identifier) {
|
||||
return '';
|
||||
}
|
||||
|
||||
workspace = identifier;
|
||||
}
|
||||
|
||||
// Workspace: Single Folder
|
||||
if (isSingleFolderWorkspaceIdentifier(workspace)) {
|
||||
// Folder on disk
|
||||
const formatter = this.findFormatter(workspace);
|
||||
const label = options && options.verbose ? this.getUriLabel(workspace) : basenameOrAuthority(workspace);
|
||||
if (workspace.scheme === Schemas.file) {
|
||||
return label;
|
||||
}
|
||||
|
||||
const suffix = formatter && formatter.workspace && (typeof formatter.workspace.suffix === 'string') ? formatter.workspace.suffix : workspace.scheme;
|
||||
return suffix ? `${label} (${suffix})` : label;
|
||||
}
|
||||
|
||||
// Workspace: Untitled
|
||||
if (isParent(workspace.configPath, this.environmentService.workspacesHome, !isLinux /* ignore case */)) {
|
||||
return localize('untitledWorkspace', "Untitled (Workspace)");
|
||||
}
|
||||
|
||||
// Workspace: Saved
|
||||
const filename = basename(workspace.configPath);
|
||||
const workspaceName = filename.substr(0, filename.length - WORKSPACE_EXTENSION.length - 1);
|
||||
if (options && options.verbose) {
|
||||
return localize('workspaceNameVerbose', "{0} (Workspace)", this.getUriLabel(URI.file(join(dirname(workspace.configPath), workspaceName))));
|
||||
}
|
||||
|
||||
return localize('workspaceName', "{0} (Workspace)", workspaceName);
|
||||
}
|
||||
|
||||
getHostLabel(): string {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
registerFormatter(selector: string, formatter: LabelRules): IDisposable {
|
||||
this.formatters[selector] = formatter;
|
||||
this._onDidRegisterFormatter.fire({ selector, formatter });
|
||||
|
||||
return {
|
||||
dispose: () => delete this.formatters[selector]
|
||||
};
|
||||
}
|
||||
|
||||
private formatUri(resource: URI, formatter: LabelRules, forceNoTildify?: boolean): string {
|
||||
let label = formatter.uri.label.replace(labelMatchingRegexp, match => {
|
||||
switch (match) {
|
||||
case '${scheme}': return resource.scheme;
|
||||
case '${authority}': return resource.authority;
|
||||
case '${path}': return resource.path;
|
||||
default: return '';
|
||||
}
|
||||
});
|
||||
|
||||
// convert \c:\something => C:\something
|
||||
if (formatter.uri.normalizeDriveLetter && hasDriveLetter(label)) {
|
||||
label = label.charAt(1).toUpperCase() + label.substr(2);
|
||||
}
|
||||
|
||||
if (formatter.uri.tildify && !forceNoTildify) {
|
||||
label = tildify(label, this.environmentService.userHome);
|
||||
}
|
||||
if (formatter.uri.authorityPrefix && resource.authority) {
|
||||
label = formatter.uri.authorityPrefix + label;
|
||||
}
|
||||
|
||||
return label.replace(sepRegexp, formatter.uri.separator);
|
||||
}
|
||||
}
|
||||
|
||||
export const ILabelService = createDecorator<ILabelService>(LABEL_SERVICE_ID);
|
||||
27
src/vs/platform/label/electron-browser/label.contribution.ts
Normal file
27
src/vs/platform/label/electron-browser/label.contribution.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
// TODO@Isidor bad layering
|
||||
// tslint:disable-next-line:import-patterns
|
||||
import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions';
|
||||
import { ILabelService } from 'vs/platform/label/common/label';
|
||||
import { ipcRenderer as ipc } from 'electron';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
|
||||
/**
|
||||
* Uri display registration needs to be shared from renderer to main.
|
||||
* Since there will be another instance of the uri display service running on main.
|
||||
*/
|
||||
class LabelRegistrationContribution implements IWorkbenchContribution {
|
||||
|
||||
constructor(@ILabelService labelService: ILabelService) {
|
||||
labelService.onDidRegisterFormatter(data => {
|
||||
ipc.send('vscode:labelRegisterFormatter', data);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(LabelRegistrationContribution, LifecyclePhase.Starting);
|
||||
53
src/vs/platform/label/test/label.test.ts
Normal file
53
src/vs/platform/label/test/label.test.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { LabelService } from 'vs/platform/label/common/label';
|
||||
import { TestEnvironmentService, TestContextService, TestWindowService } from 'vs/workbench/test/workbenchTestServices';
|
||||
import { TestWorkspace } from 'vs/platform/workspace/test/common/testWorkspace';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { nativeSep } from 'vs/base/common/paths';
|
||||
import { isWindows } from 'vs/base/common/platform';
|
||||
|
||||
suite('URI Label', () => {
|
||||
|
||||
let labelService: LabelService;
|
||||
|
||||
setup(() => {
|
||||
labelService = new LabelService(TestEnvironmentService, new TestContextService(), new TestWindowService());
|
||||
});
|
||||
|
||||
test('file scheme', function () {
|
||||
labelService.registerFormatter('file://', {
|
||||
uri: {
|
||||
label: '${path}',
|
||||
separator: nativeSep,
|
||||
tildify: !isWindows,
|
||||
normalizeDriveLetter: isWindows
|
||||
}
|
||||
});
|
||||
|
||||
const uri1 = TestWorkspace.folders[0].uri.with({ path: TestWorkspace.folders[0].uri.path.concat('/a/b/c/d') });
|
||||
assert.equal(labelService.getUriLabel(uri1, { relative: true }), isWindows ? 'a\\b\\c\\d' : 'a/b/c/d');
|
||||
assert.equal(labelService.getUriLabel(uri1, { relative: false }), isWindows ? 'C:\\testWorkspace\\a\\b\\c\\d' : '/testWorkspace/a/b/c/d');
|
||||
|
||||
const uri2 = URI.file('c:\\1/2/3');
|
||||
assert.equal(labelService.getUriLabel(uri2, { relative: false }), isWindows ? 'C:\\1\\2\\3' : '/c:\\1/2/3');
|
||||
});
|
||||
|
||||
test('custom scheme', function () {
|
||||
labelService.registerFormatter('vscode://', {
|
||||
uri: {
|
||||
label: 'LABEL/${path}/${authority}/END',
|
||||
separator: '/',
|
||||
tildify: true,
|
||||
normalizeDriveLetter: true
|
||||
}
|
||||
});
|
||||
|
||||
const uri1 = URI.parse('vscode://microsoft.com/1/2/3/4/5');
|
||||
assert.equal(labelService.getUriLabel(uri1, { relative: false }), 'LABEL//1/2/3/4/5/microsoft.com/END');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user