mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-14 03:58:33 -05:00
SQL Operations Studio Public Preview 1 (0.23) release source code
This commit is contained in:
64
src/vs/workbench/api/electron-browser/extHostCustomers.ts
Normal file
64
src/vs/workbench/api/electron-browser/extHostCustomers.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { ProxyIdentifier } from 'vs/workbench/services/thread/common/threadService';
|
||||
import { IConstructorSignature1 } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IExtHostContext } from 'vs/workbench/api/node/extHost.protocol';
|
||||
|
||||
export type IExtHostNamedCustomer<T extends IDisposable> = [ProxyIdentifier<T>, IExtHostCustomerCtor<T>];
|
||||
|
||||
export type IExtHostCustomerCtor<T extends IDisposable> = IConstructorSignature1<IExtHostContext, T>;
|
||||
|
||||
export function extHostNamedCustomer<T extends IDisposable>(id: ProxyIdentifier<T>) {
|
||||
return function (ctor: IExtHostCustomerCtor<T>): void {
|
||||
ExtHostCustomersRegistryImpl.INSTANCE.registerNamedCustomer(id, ctor);
|
||||
};
|
||||
}
|
||||
|
||||
export function extHostCustomer<T extends IDisposable>(ctor: IExtHostCustomerCtor<T>): void {
|
||||
ExtHostCustomersRegistryImpl.INSTANCE.registerCustomer(ctor);
|
||||
}
|
||||
|
||||
export namespace ExtHostCustomersRegistry {
|
||||
|
||||
export function getNamedCustomers(): IExtHostNamedCustomer<IDisposable>[] {
|
||||
return ExtHostCustomersRegistryImpl.INSTANCE.getNamedCustomers();
|
||||
}
|
||||
|
||||
export function getCustomers(): IExtHostCustomerCtor<IDisposable>[] {
|
||||
return ExtHostCustomersRegistryImpl.INSTANCE.getCustomers();
|
||||
}
|
||||
}
|
||||
|
||||
class ExtHostCustomersRegistryImpl {
|
||||
|
||||
public static INSTANCE = new ExtHostCustomersRegistryImpl();
|
||||
|
||||
private _namedCustomers: IExtHostNamedCustomer<any>[];
|
||||
private _customers: IExtHostCustomerCtor<any>[];
|
||||
|
||||
constructor() {
|
||||
this._namedCustomers = [];
|
||||
this._customers = [];
|
||||
}
|
||||
|
||||
public registerNamedCustomer<T extends IDisposable>(id: ProxyIdentifier<T>, ctor: IExtHostCustomerCtor<T>): void {
|
||||
const entry: IExtHostNamedCustomer<T> = [id, ctor];
|
||||
this._namedCustomers.push(entry);
|
||||
}
|
||||
public getNamedCustomers(): IExtHostNamedCustomer<any>[] {
|
||||
return this._namedCustomers;
|
||||
}
|
||||
|
||||
public registerCustomer<T extends IDisposable>(ctor: IExtHostCustomerCtor<T>): void {
|
||||
this._customers.push(ctor);
|
||||
}
|
||||
public getCustomers(): IExtHostCustomerCtor<any>[] {
|
||||
return this._customers;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
||||
// --- other interested parties
|
||||
import { JSONValidationExtensionPoint } from 'vs/platform/jsonschemas/common/jsonValidationExtensionPoint';
|
||||
import { ColorExtensionPoint } from 'vs/platform/theme/common/colorExtensionPoint';
|
||||
import { LanguageConfigurationFileHandler } from 'vs/workbench/parts/codeEditor/electron-browser/languageConfiguration/languageConfigurationExtensionPoint';
|
||||
|
||||
// --- mainThread participants
|
||||
import './mainThreadCommands';
|
||||
import './mainThreadConfiguration';
|
||||
import './mainThreadCredentials';
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
// disable the debug service
|
||||
// import './mainThreadDebugService';
|
||||
|
||||
import './mainThreadDiagnostics';
|
||||
import './mainThreadDialogs';
|
||||
import './mainThreadDocumentContentProviders';
|
||||
import './mainThreadDocuments';
|
||||
import './mainThreadDocumentsAndEditors';
|
||||
import './mainThreadEditor';
|
||||
import './mainThreadEditors';
|
||||
import './mainThreadErrors';
|
||||
import './mainThreadExtensionService';
|
||||
import './mainThreadFileSystemEventService';
|
||||
import './mainThreadHeapService';
|
||||
import './mainThreadLanguageFeatures';
|
||||
import './mainThreadLanguages';
|
||||
import './mainThreadMessageService';
|
||||
import './mainThreadOutputService';
|
||||
import './mainThreadProgress';
|
||||
import './mainThreadQuickOpen';
|
||||
import './mainThreadSCM';
|
||||
import './mainThreadSaveParticipant';
|
||||
import './mainThreadStatusBar';
|
||||
import './mainThreadStorage';
|
||||
import './mainThreadTask';
|
||||
import './mainThreadTelemetry';
|
||||
import './mainThreadTerminalService';
|
||||
import './mainThreadTreeViews';
|
||||
import './mainThreadWindow';
|
||||
import './mainThreadWorkspace';
|
||||
|
||||
export class ExtensionPoints implements IWorkbenchContribution {
|
||||
|
||||
constructor(
|
||||
@IInstantiationService private instantiationService: IInstantiationService
|
||||
) {
|
||||
// Classes that handle extension points...
|
||||
this.instantiationService.createInstance(JSONValidationExtensionPoint);
|
||||
this.instantiationService.createInstance(ColorExtensionPoint);
|
||||
this.instantiationService.createInstance(LanguageConfigurationFileHandler);
|
||||
}
|
||||
|
||||
public getId(): string {
|
||||
return 'vs.api.extensionPoints';
|
||||
}
|
||||
}
|
||||
|
||||
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(
|
||||
ExtensionPoints
|
||||
);
|
||||
102
src/vs/workbench/api/electron-browser/mainThreadCommands.ts
Normal file
102
src/vs/workbench/api/electron-browser/mainThreadCommands.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { ICommandService, CommandsRegistry, ICommandHandlerDescription } from 'vs/platform/commands/common/commands';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { ExtHostContext, MainThreadCommandsShape, ExtHostCommandsShape, MainContext, IExtHostContext } from '../node/extHost.protocol';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadCommands)
|
||||
export class MainThreadCommands implements MainThreadCommandsShape {
|
||||
|
||||
private readonly _disposables = new Map<string, IDisposable>();
|
||||
private readonly _generateCommandsDocumentationRegistration: IDisposable;
|
||||
private readonly _proxy: ExtHostCommandsShape;
|
||||
|
||||
constructor(
|
||||
extHostContext: IExtHostContext,
|
||||
@ICommandService private readonly _commandService: ICommandService,
|
||||
) {
|
||||
if (extHostContext) {
|
||||
this._proxy = extHostContext.get(ExtHostContext.ExtHostCommands);
|
||||
}
|
||||
|
||||
this._generateCommandsDocumentationRegistration = CommandsRegistry.registerCommand('_generateCommandsDocumentation', () => this._generateCommandsDocumentation());
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this._disposables.forEach(value => value.dispose());
|
||||
this._disposables.clear();
|
||||
|
||||
this._generateCommandsDocumentationRegistration.dispose();
|
||||
}
|
||||
|
||||
private _generateCommandsDocumentation(): TPromise<void> {
|
||||
return this._proxy.$getContributedCommandHandlerDescriptions().then(result => {
|
||||
// add local commands
|
||||
const commands = CommandsRegistry.getCommands();
|
||||
for (let id in commands) {
|
||||
let { description } = commands[id];
|
||||
if (description) {
|
||||
result[id] = description;
|
||||
}
|
||||
}
|
||||
|
||||
// print all as markdown
|
||||
const all: string[] = [];
|
||||
for (let id in result) {
|
||||
all.push('`' + id + '` - ' + _generateMarkdown(result[id]));
|
||||
}
|
||||
console.log(all.join('\n'));
|
||||
});
|
||||
}
|
||||
|
||||
$registerCommand(id: string): TPromise<any> {
|
||||
this._disposables.set(
|
||||
id,
|
||||
CommandsRegistry.registerCommand(id, (accessor, ...args) => this._proxy.$executeContributedCommand(id, ...args))
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
$unregisterCommand(id: string): TPromise<any> {
|
||||
if (this._disposables.has(id)) {
|
||||
this._disposables.get(id).dispose();
|
||||
this._disposables.delete(id);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
$executeCommand<T>(id: string, args: any[]): Thenable<T> {
|
||||
return this._commandService.executeCommand<T>(id, ...args);
|
||||
}
|
||||
|
||||
$getCommands(): Thenable<string[]> {
|
||||
return TPromise.as(Object.keys(CommandsRegistry.getCommands()));
|
||||
}
|
||||
}
|
||||
|
||||
// --- command doc
|
||||
|
||||
function _generateMarkdown(description: string | ICommandHandlerDescription): string {
|
||||
if (typeof description === 'string') {
|
||||
return description;
|
||||
} else {
|
||||
let parts = [description.description];
|
||||
parts.push('\n\n');
|
||||
if (description.args) {
|
||||
for (let arg of description.args) {
|
||||
parts.push(`* _${arg.name}_ ${arg.description || ''}\n`);
|
||||
}
|
||||
}
|
||||
if (description.returns) {
|
||||
parts.push(`* _(returns)_ ${description.returns}`);
|
||||
}
|
||||
parts.push('\n\n');
|
||||
return parts.join('');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
|
||||
import { IConfigurationEditingService, ConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditing';
|
||||
import { MainThreadConfigurationShape, MainContext, ExtHostContext, IExtHostContext } from '../node/extHost.protocol';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadConfiguration)
|
||||
export class MainThreadConfiguration implements MainThreadConfigurationShape {
|
||||
|
||||
private readonly _configurationListener: IDisposable;
|
||||
|
||||
constructor(
|
||||
extHostContext: IExtHostContext,
|
||||
@IConfigurationEditingService private readonly _configurationEditingService: IConfigurationEditingService,
|
||||
@IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService,
|
||||
@IWorkspaceConfigurationService configurationService: IWorkspaceConfigurationService
|
||||
) {
|
||||
const proxy = extHostContext.get(ExtHostContext.ExtHostConfiguration);
|
||||
|
||||
this._configurationListener = configurationService.onDidUpdateConfiguration(() => {
|
||||
proxy.$acceptConfigurationChanged(configurationService.getConfigurationData());
|
||||
});
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._configurationListener.dispose();
|
||||
}
|
||||
|
||||
$updateConfigurationOption(target: ConfigurationTarget, key: string, value: any, resource: URI): TPromise<void> {
|
||||
return this.writeConfiguration(target, key, value, resource);
|
||||
}
|
||||
|
||||
$removeConfigurationOption(target: ConfigurationTarget, key: string, resource: URI): TPromise<void> {
|
||||
return this.writeConfiguration(target, key, undefined, resource);
|
||||
}
|
||||
|
||||
private writeConfiguration(target: ConfigurationTarget, key: string, value: any, resource: URI): TPromise<void> {
|
||||
target = target !== null && target !== undefined ? target : this.deriveConfigurationTarget(key, resource);
|
||||
return this._configurationEditingService.writeConfiguration(target, { key, value }, { donotNotifyError: true, scopes: { resource } });
|
||||
}
|
||||
|
||||
private deriveConfigurationTarget(key: string, resource: URI): ConfigurationTarget {
|
||||
if (resource && this._workspaceContextService.hasMultiFolderWorkspace()) {
|
||||
const configurationProperties = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).getConfigurationProperties();
|
||||
if (configurationProperties[key] && configurationProperties[key].scope === ConfigurationScope.RESOURCE) {
|
||||
return ConfigurationTarget.FOLDER;
|
||||
}
|
||||
}
|
||||
return ConfigurationTarget.WORKSPACE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { ExtHostContext, MainThreadCredentialsShape, ExtHostCredentialsShape, MainContext, IExtHostContext } from '../node/extHost.protocol';
|
||||
import { ICredentialsService } from 'vs/platform/credentials/common/credentials';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadCredentials)
|
||||
export class MainThreadCredentials implements MainThreadCredentialsShape {
|
||||
|
||||
private _proxy: ExtHostCredentialsShape;
|
||||
|
||||
constructor(
|
||||
extHostContext: IExtHostContext,
|
||||
@ICredentialsService private _credentialsService: ICredentialsService
|
||||
) {
|
||||
this._proxy = extHostContext.get(ExtHostContext.ExtHostCredentials);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
}
|
||||
|
||||
$readSecret(service: string, account: string): Thenable<string | undefined> {
|
||||
return this._credentialsService.readSecret(service, account);
|
||||
}
|
||||
|
||||
$writeSecret(service: string, account: string, secret: string): Thenable<void> {
|
||||
return this._credentialsService.writeSecret(service, account, secret);
|
||||
}
|
||||
$deleteSecret(service: string, account: string): Thenable<boolean> {
|
||||
return this._credentialsService.deleteSecret(service, account);
|
||||
}
|
||||
}
|
||||
112
src/vs/workbench/api/electron-browser/mainThreadDebugService.ts
Normal file
112
src/vs/workbench/api/electron-browser/mainThreadDebugService.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
/*
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { IDebugService, IConfig, IDebugConfigurationProvider } from 'vs/workbench/parts/debug/common/debug';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { ExtHostContext, ExtHostDebugServiceShape, MainThreadDebugServiceShape, DebugSessionUUID, MainContext, IExtHostContext } from '../node/extHost.protocol';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadDebugService)
|
||||
export class MainThreadDebugService implements MainThreadDebugServiceShape {
|
||||
|
||||
private _proxy: ExtHostDebugServiceShape;
|
||||
private _toDispose: IDisposable[];
|
||||
|
||||
constructor(
|
||||
extHostContext: IExtHostContext,
|
||||
@IDebugService private debugService: IDebugService
|
||||
) {
|
||||
this._proxy = extHostContext.get(ExtHostContext.ExtHostDebugService);
|
||||
this._toDispose = [];
|
||||
this._toDispose.push(debugService.onDidNewProcess(proc => this._proxy.$acceptDebugSessionStarted(<DebugSessionUUID>proc.getId(), proc.configuration.type, proc.getName(false))));
|
||||
this._toDispose.push(debugService.onDidEndProcess(proc => this._proxy.$acceptDebugSessionTerminated(<DebugSessionUUID>proc.getId(), proc.configuration.type, proc.getName(false))));
|
||||
this._toDispose.push(debugService.getViewModel().onDidFocusProcess(proc => {
|
||||
if (proc) {
|
||||
this._proxy.$acceptDebugSessionActiveChanged(<DebugSessionUUID>proc.getId(), proc.configuration.type, proc.getName(false));
|
||||
} else {
|
||||
this._proxy.$acceptDebugSessionActiveChanged(undefined);
|
||||
}
|
||||
}));
|
||||
this._toDispose.push(debugService.onDidCustomEvent(event => {
|
||||
if (event && event.sessionId) {
|
||||
const process = this.debugService.findProcessByUUID(event.sessionId);
|
||||
this._proxy.$acceptDebugSessionCustomEvent(event.sessionId, process.configuration.type, process.configuration.name, event);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._toDispose = dispose(this._toDispose);
|
||||
}
|
||||
|
||||
public $registerDebugConfigurationProvider(debugType: string, hasProvide: boolean, hasResolve: boolean, handle: number): TPromise<void> {
|
||||
|
||||
const provider = <IDebugConfigurationProvider>{
|
||||
type: debugType
|
||||
};
|
||||
if (hasProvide) {
|
||||
provider.provideDebugConfigurations = (folder: URI | undefined) => {
|
||||
return this._proxy.$provideDebugConfigurations(handle, folder);
|
||||
};
|
||||
}
|
||||
if (hasResolve) {
|
||||
provider.resolveDebugConfiguration = (folder: URI | undefined, debugConfiguration: any) => {
|
||||
return this._proxy.$resolveDebugConfiguration(handle, folder, debugConfiguration);
|
||||
};
|
||||
}
|
||||
this.debugService.getConfigurationManager().registerDebugConfigurationProvider(handle, provider);
|
||||
|
||||
return TPromise.as<void>(undefined);
|
||||
}
|
||||
|
||||
public $unregisterDebugConfigurationProvider(handle: number): TPromise<any> {
|
||||
this.debugService.getConfigurationManager().unregisterDebugConfigurationProvider(handle);
|
||||
return TPromise.as<void>(undefined);
|
||||
}
|
||||
|
||||
public $startDebugging(folderUri: URI | undefined, nameOrConfiguration: string | IConfig): TPromise<boolean> {
|
||||
return this.debugService.startDebugging(folderUri, nameOrConfiguration).then(x => {
|
||||
return true;
|
||||
}, err => {
|
||||
return TPromise.wrapError(err && err.message ? err.message : 'cannot start debugging');
|
||||
});
|
||||
}
|
||||
|
||||
public $startDebugSession(folderUri: URI | undefined, configuration: IConfig): TPromise<DebugSessionUUID> {
|
||||
if (configuration.request !== 'launch' && configuration.request !== 'attach') {
|
||||
return TPromise.wrapError(new Error(`only 'launch' or 'attach' allowed for 'request' attribute`));
|
||||
}
|
||||
return this.debugService.createProcess(folderUri, configuration).then(process => {
|
||||
if (process) {
|
||||
return <DebugSessionUUID>process.getId();
|
||||
}
|
||||
return TPromise.wrapError<DebugSessionUUID>(new Error('cannot create debug session'));
|
||||
}, err => {
|
||||
return TPromise.wrapError(err && err.message ? err.message : 'cannot start debug session');
|
||||
});
|
||||
}
|
||||
|
||||
public $customDebugAdapterRequest(sessionId: DebugSessionUUID, request: string, args: any): TPromise<any> {
|
||||
const process = this.debugService.findProcessByUUID(sessionId);
|
||||
if (process) {
|
||||
return process.session.custom(request, args).then(response => {
|
||||
if (response.success) {
|
||||
return response.body;
|
||||
} else {
|
||||
return TPromise.wrapError(new Error(response.message));
|
||||
}
|
||||
});
|
||||
}
|
||||
return TPromise.wrapError(new Error('debug session not found'));
|
||||
}
|
||||
}
|
||||
// {{SQL CARBON EDIT}}
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { IMarkerService, IMarkerData } from 'vs/platform/markers/common/markers';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { MainThreadDiagnosticsShape, MainContext, IExtHostContext } from '../node/extHost.protocol';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadDiagnostics)
|
||||
export class MainThreadDiagnostics implements MainThreadDiagnosticsShape {
|
||||
|
||||
private readonly _activeOwners = new Set<string>();
|
||||
private readonly _markerService: IMarkerService;
|
||||
|
||||
constructor(
|
||||
extHostContext: IExtHostContext,
|
||||
@IMarkerService markerService: IMarkerService
|
||||
) {
|
||||
this._markerService = markerService;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._activeOwners.forEach(owner => this._markerService.changeAll(owner, undefined));
|
||||
}
|
||||
|
||||
$changeMany(owner: string, entries: [URI, IMarkerData[]][]): TPromise<any> {
|
||||
for (let entry of entries) {
|
||||
let [uri, markers] = entry;
|
||||
this._markerService.changeOne(owner, uri, markers);
|
||||
}
|
||||
this._activeOwners.add(owner);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
$clear(owner: string): TPromise<any> {
|
||||
this._markerService.changeAll(owner, undefined);
|
||||
this._activeOwners.delete(owner);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
63
src/vs/workbench/api/electron-browser/mainThreadDialogs.ts
Normal file
63
src/vs/workbench/api/electron-browser/mainThreadDialogs.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { isFalsyOrEmpty } from 'vs/base/common/arrays';
|
||||
import { MainThreadDiaglogsShape, MainContext, IExtHostContext, MainThreadDialogOptions } from '../node/extHost.protocol';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
|
||||
import { IWindowService } from 'vs/platform/windows/common/windows';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadDialogs)
|
||||
export class MainThreadDialogs implements MainThreadDiaglogsShape {
|
||||
|
||||
constructor(
|
||||
context: IExtHostContext,
|
||||
@IWindowService private readonly _windowService: IWindowService
|
||||
) {
|
||||
//
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
//
|
||||
}
|
||||
|
||||
$showOpenDialog(options: MainThreadDialogOptions): TPromise<string[]> {
|
||||
// TODO@joh what about remote dev setup?
|
||||
if (options.uri && options.uri.scheme !== 'file') {
|
||||
return TPromise.wrapError(new Error('bad path'));
|
||||
}
|
||||
return new TPromise<string[]>(resolve => {
|
||||
this._windowService.showOpenDialog(MainThreadDialogs._convertOptions(options), filenames => {
|
||||
resolve(isFalsyOrEmpty(filenames) ? undefined : filenames);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private static _convertOptions(options: MainThreadDialogOptions): Electron.OpenDialogOptions {
|
||||
const result: Electron.OpenDialogOptions = {
|
||||
properties: ['createDirectory']
|
||||
};
|
||||
if (options.openLabel) {
|
||||
result.buttonLabel = options.openLabel;
|
||||
}
|
||||
if (options.uri) {
|
||||
result.defaultPath = options.uri.fsPath;
|
||||
}
|
||||
if (!options.openFiles && !options.openFolders) {
|
||||
options.openFiles = true;
|
||||
}
|
||||
if (options.openFiles) {
|
||||
result.properties.push('openFile');
|
||||
}
|
||||
if (options.openFolders) {
|
||||
result.properties.push('openDirectory');
|
||||
}
|
||||
if (options.openMany) {
|
||||
result.properties.push('multiSelections');
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IModel } from 'vs/editor/common/editorCommon';
|
||||
import { ICodeEditorService } from 'vs/editor/common/services/codeEditorService';
|
||||
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
|
||||
import { MainThreadDocumentContentProvidersShape, ExtHostContext, ExtHostDocumentContentProvidersShape, MainContext, IExtHostContext } from '../node/extHost.protocol';
|
||||
import { ITextSource } from 'vs/editor/common/model/textSource';
|
||||
import { ITextModelService } from 'vs/editor/common/services/resolverService';
|
||||
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadDocumentContentProviders)
|
||||
export class MainThreadDocumentContentProviders implements MainThreadDocumentContentProvidersShape {
|
||||
|
||||
private _resourceContentProvider: { [handle: number]: IDisposable } = Object.create(null);
|
||||
private readonly _proxy: ExtHostDocumentContentProvidersShape;
|
||||
|
||||
constructor(
|
||||
extHostContext: IExtHostContext,
|
||||
@ITextModelService private readonly _textModelResolverService: ITextModelService,
|
||||
@IModeService private readonly _modeService: IModeService,
|
||||
@IModelService private readonly _modelService: IModelService,
|
||||
@ICodeEditorService codeEditorService: ICodeEditorService,
|
||||
@IEditorGroupService editorGroupService: IEditorGroupService
|
||||
) {
|
||||
this._proxy = extHostContext.get(ExtHostContext.ExtHostDocumentContentProviders);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
for (let handle in this._resourceContentProvider) {
|
||||
this._resourceContentProvider[handle].dispose();
|
||||
}
|
||||
}
|
||||
|
||||
$registerTextContentProvider(handle: number, scheme: string): void {
|
||||
this._resourceContentProvider[handle] = this._textModelResolverService.registerTextModelContentProvider(scheme, {
|
||||
provideTextContent: (uri: URI): TPromise<IModel> => {
|
||||
return this._proxy.$provideTextDocumentContent(handle, uri).then(value => {
|
||||
if (typeof value === 'string') {
|
||||
const firstLineText = value.substr(0, 1 + value.search(/\r?\n/));
|
||||
const mode = this._modeService.getOrCreateModeByFilenameOrFirstLine(uri.fsPath, firstLineText);
|
||||
return this._modelService.createModel(value, mode, uri);
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$unregisterTextContentProvider(handle: number): void {
|
||||
const registration = this._resourceContentProvider[handle];
|
||||
if (registration) {
|
||||
registration.dispose();
|
||||
delete this._resourceContentProvider[handle];
|
||||
}
|
||||
}
|
||||
|
||||
$onVirtualDocumentChange(uri: URI, value: ITextSource): void {
|
||||
const model = this._modelService.getModel(uri);
|
||||
if (!model) {
|
||||
return;
|
||||
}
|
||||
|
||||
const raw: ITextSource = {
|
||||
lines: value.lines,
|
||||
length: value.length,
|
||||
BOM: value.BOM,
|
||||
EOL: value.EOL,
|
||||
containsRTL: value.containsRTL,
|
||||
isBasicASCII: value.isBasicASCII,
|
||||
};
|
||||
|
||||
if (!model.equals(raw)) {
|
||||
model.setValueFromTextSource(raw);
|
||||
}
|
||||
}
|
||||
}
|
||||
237
src/vs/workbench/api/electron-browser/mainThreadDocuments.ts
Normal file
237
src/vs/workbench/api/electron-browser/mainThreadDocuments.ts
Normal file
@@ -0,0 +1,237 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { toErrorMessage } from 'vs/base/common/errorMessage';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { IDisposable, dispose, IReference } from 'vs/base/common/lifecycle';
|
||||
import { TextFileModelChangeEvent, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
|
||||
import { ExtHostContext, MainThreadDocumentsShape, ExtHostDocumentsShape, IExtHostContext } from '../node/extHost.protocol';
|
||||
import { ITextModelService } from 'vs/editor/common/services/resolverService';
|
||||
import { MainThreadDocumentsAndEditors } from './mainThreadDocumentsAndEditors';
|
||||
import * as editorCommon from 'vs/editor/common/editorCommon';
|
||||
import { ITextEditorModel } from 'vs/workbench/common/editor';
|
||||
|
||||
export class BoundModelReferenceCollection {
|
||||
|
||||
private _data = new Array<{ length: number, dispose(): void }>();
|
||||
private _length = 0;
|
||||
|
||||
constructor(
|
||||
private _maxAge: number = 1000 * 60 * 3,
|
||||
private _maxLength: number = 1024 * 1024 * 80
|
||||
) {
|
||||
//
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._data = dispose(this._data);
|
||||
}
|
||||
|
||||
add(ref: IReference<ITextEditorModel>): void {
|
||||
let length = ref.object.textEditorModel.getValueLength();
|
||||
let handle: number;
|
||||
let entry: { length: number, dispose(): void };
|
||||
const dispose = () => {
|
||||
let idx = this._data.indexOf(entry);
|
||||
if (idx >= 0) {
|
||||
this._length -= length;
|
||||
ref.dispose();
|
||||
clearTimeout(handle);
|
||||
this._data.splice(idx, 1);
|
||||
}
|
||||
};
|
||||
handle = setTimeout(dispose, this._maxAge);
|
||||
entry = { length, dispose };
|
||||
|
||||
this._data.push(entry);
|
||||
this._length += length;
|
||||
this._cleanup();
|
||||
}
|
||||
|
||||
private _cleanup(): void {
|
||||
while (this._length > this._maxLength) {
|
||||
this._data[0].dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class MainThreadDocuments implements MainThreadDocumentsShape {
|
||||
|
||||
private _modelService: IModelService;
|
||||
private _modeService: IModeService;
|
||||
private _textModelResolverService: ITextModelService;
|
||||
private _textFileService: ITextFileService;
|
||||
private _fileService: IFileService;
|
||||
private _untitledEditorService: IUntitledEditorService;
|
||||
|
||||
private _toDispose: IDisposable[];
|
||||
private _modelToDisposeMap: { [modelUrl: string]: IDisposable; };
|
||||
private _proxy: ExtHostDocumentsShape;
|
||||
private _modelIsSynced: { [modelId: string]: boolean; };
|
||||
private _modelReferenceCollection = new BoundModelReferenceCollection();
|
||||
|
||||
constructor(
|
||||
documentsAndEditors: MainThreadDocumentsAndEditors,
|
||||
extHostContext: IExtHostContext,
|
||||
@IModelService modelService: IModelService,
|
||||
@IModeService modeService: IModeService,
|
||||
@ITextFileService textFileService: ITextFileService,
|
||||
@IFileService fileService: IFileService,
|
||||
@ITextModelService textModelResolverService: ITextModelService,
|
||||
@IUntitledEditorService untitledEditorService: IUntitledEditorService,
|
||||
) {
|
||||
this._modelService = modelService;
|
||||
this._modeService = modeService;
|
||||
this._textModelResolverService = textModelResolverService;
|
||||
this._textFileService = textFileService;
|
||||
this._fileService = fileService;
|
||||
this._untitledEditorService = untitledEditorService;
|
||||
|
||||
this._proxy = extHostContext.get(ExtHostContext.ExtHostDocuments);
|
||||
this._modelIsSynced = {};
|
||||
|
||||
this._toDispose = [];
|
||||
this._toDispose.push(documentsAndEditors.onDocumentAdd(models => models.forEach(this._onModelAdded, this)));
|
||||
this._toDispose.push(documentsAndEditors.onDocumentRemove(urls => urls.forEach(this._onModelRemoved, this)));
|
||||
this._toDispose.push(this._modelReferenceCollection);
|
||||
this._toDispose.push(modelService.onModelModeChanged(this._onModelModeChanged, this));
|
||||
|
||||
this._toDispose.push(textFileService.models.onModelSaved(e => {
|
||||
if (this._shouldHandleFileEvent(e)) {
|
||||
this._proxy.$acceptModelSaved(e.resource.toString());
|
||||
}
|
||||
}));
|
||||
this._toDispose.push(textFileService.models.onModelReverted(e => {
|
||||
if (this._shouldHandleFileEvent(e)) {
|
||||
this._proxy.$acceptDirtyStateChanged(e.resource.toString(), false);
|
||||
}
|
||||
}));
|
||||
this._toDispose.push(textFileService.models.onModelDirty(e => {
|
||||
if (this._shouldHandleFileEvent(e)) {
|
||||
this._proxy.$acceptDirtyStateChanged(e.resource.toString(), true);
|
||||
}
|
||||
}));
|
||||
|
||||
this._modelToDisposeMap = Object.create(null);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
Object.keys(this._modelToDisposeMap).forEach((modelUrl) => {
|
||||
this._modelToDisposeMap[modelUrl].dispose();
|
||||
});
|
||||
this._modelToDisposeMap = Object.create(null);
|
||||
this._toDispose = dispose(this._toDispose);
|
||||
}
|
||||
|
||||
private _shouldHandleFileEvent(e: TextFileModelChangeEvent): boolean {
|
||||
const model = this._modelService.getModel(e.resource);
|
||||
return model && !model.isTooLargeForHavingARichMode();
|
||||
}
|
||||
|
||||
private _onModelAdded(model: editorCommon.IModel): void {
|
||||
// Same filter as in mainThreadEditorsTracker
|
||||
if (model.isTooLargeForHavingARichMode()) {
|
||||
// don't synchronize too large models
|
||||
return null;
|
||||
}
|
||||
let modelUrl = model.uri;
|
||||
this._modelIsSynced[modelUrl.toString()] = true;
|
||||
this._modelToDisposeMap[modelUrl.toString()] = model.onDidChangeContent((e) => {
|
||||
this._proxy.$acceptModelChanged(modelUrl.toString(), e, this._textFileService.isDirty(modelUrl));
|
||||
});
|
||||
}
|
||||
|
||||
private _onModelModeChanged(event: { model: editorCommon.IModel; oldModeId: string; }): void {
|
||||
let { model, oldModeId } = event;
|
||||
let modelUrl = model.uri;
|
||||
if (!this._modelIsSynced[modelUrl.toString()]) {
|
||||
return;
|
||||
}
|
||||
this._proxy.$acceptModelModeChanged(model.uri.toString(), oldModeId, model.getLanguageIdentifier().language);
|
||||
}
|
||||
|
||||
private _onModelRemoved(modelUrl: string): void {
|
||||
|
||||
if (!this._modelIsSynced[modelUrl]) {
|
||||
return;
|
||||
}
|
||||
delete this._modelIsSynced[modelUrl];
|
||||
this._modelToDisposeMap[modelUrl].dispose();
|
||||
delete this._modelToDisposeMap[modelUrl];
|
||||
}
|
||||
|
||||
// --- from extension host process
|
||||
|
||||
$trySaveDocument(uri: URI): TPromise<boolean> {
|
||||
return this._textFileService.save(uri);
|
||||
}
|
||||
|
||||
$tryOpenDocument(uri: URI): TPromise<any> {
|
||||
|
||||
if (!uri.scheme || !(uri.fsPath || uri.authority)) {
|
||||
return TPromise.wrapError(new Error(`Invalid uri. Scheme and authority or path must be set.`));
|
||||
}
|
||||
|
||||
let promise: TPromise<boolean>;
|
||||
switch (uri.scheme) {
|
||||
case 'untitled':
|
||||
promise = this._handleUnititledScheme(uri);
|
||||
break;
|
||||
case 'file':
|
||||
default:
|
||||
promise = this._handleAsResourceInput(uri);
|
||||
break;
|
||||
}
|
||||
|
||||
return promise.then(success => {
|
||||
if (!success) {
|
||||
return TPromise.wrapError(new Error('cannot open ' + uri.toString()));
|
||||
}
|
||||
return undefined;
|
||||
}, err => {
|
||||
return TPromise.wrapError(new Error('cannot open ' + uri.toString() + '. Detail: ' + toErrorMessage(err)));
|
||||
});
|
||||
}
|
||||
|
||||
$tryCreateDocument(options?: { language?: string, content?: string }): TPromise<URI> {
|
||||
return this._doCreateUntitled(void 0, options ? options.language : void 0, options ? options.content : void 0);
|
||||
}
|
||||
|
||||
private _handleAsResourceInput(uri: URI): TPromise<boolean> {
|
||||
return this._textModelResolverService.createModelReference(uri).then(ref => {
|
||||
this._modelReferenceCollection.add(ref);
|
||||
const result = !!ref.object;
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
private _handleUnititledScheme(uri: URI): TPromise<boolean> {
|
||||
let asFileUri = uri.with({ scheme: 'file' });
|
||||
return this._fileService.resolveFile(asFileUri).then(stats => {
|
||||
// don't create a new file ontop of an existing file
|
||||
return TPromise.wrapError<boolean>(new Error('file already exists on disk'));
|
||||
}, err => this._doCreateUntitled(asFileUri).then(resource => !!resource));
|
||||
}
|
||||
|
||||
private _doCreateUntitled(resource?: URI, modeId?: string, initialValue?: string): TPromise<URI> {
|
||||
return this._untitledEditorService.loadOrCreate({ resource, modeId, initialValue }).then(model => {
|
||||
const resource = model.getResource();
|
||||
|
||||
if (!this._modelIsSynced[resource.toString()]) {
|
||||
throw new Error(`expected URI ${resource.toString()} to have come to LIFE`);
|
||||
}
|
||||
|
||||
this._proxy.$acceptDirtyStateChanged(resource.toString(), true); // mark as dirty
|
||||
|
||||
return resource;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,378 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { IModel, ICommonCodeEditor, isCommonCodeEditor, isCommonDiffEditor } from 'vs/editor/common/editorCommon';
|
||||
import { compare } from 'vs/base/common/strings';
|
||||
import { delta } from 'vs/base/common/arrays';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { ICodeEditorService } from 'vs/editor/common/services/codeEditorService';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { ExtHostContext, ExtHostDocumentsAndEditorsShape, IModelAddedData, ITextEditorAddData, IDocumentsAndEditorsDelta, IExtHostContext, MainContext } from '../node/extHost.protocol';
|
||||
import { MainThreadTextEditor } from './mainThreadEditor';
|
||||
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { Position as EditorPosition, IEditor } from 'vs/platform/editor/common/editor';
|
||||
import { extHostCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
|
||||
import { MainThreadDocuments } from 'vs/workbench/api/electron-browser/mainThreadDocuments';
|
||||
import { MainThreadEditors } from 'vs/workbench/api/electron-browser/mainThreadEditors';
|
||||
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { ITextModelService } from 'vs/editor/common/services/resolverService';
|
||||
import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
|
||||
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
|
||||
namespace cmp {
|
||||
export function compareModels(a: IModel, b: IModel): number {
|
||||
return compare(a.uri.toString(), b.uri.toString());
|
||||
}
|
||||
export function compareEditors(a: EditorAndModel, b: EditorAndModel): number {
|
||||
let ret = compare(a.editor.getId(), b.editor.getId());
|
||||
if (ret === 0) {
|
||||
ret = compare(a.document.uri.toString(), b.document.uri.toString());
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
class EditorAndModel {
|
||||
|
||||
readonly id: string;
|
||||
|
||||
constructor(
|
||||
readonly editor: ICommonCodeEditor,
|
||||
readonly document: IModel,
|
||||
) {
|
||||
this.id = `${editor.getId()},${document.uri.toString()}`;
|
||||
}
|
||||
}
|
||||
|
||||
class DocumentAndEditorStateDelta {
|
||||
|
||||
readonly isEmpty: boolean;
|
||||
|
||||
constructor(
|
||||
readonly removedDocuments: IModel[],
|
||||
readonly addedDocuments: IModel[],
|
||||
readonly removedEditors: EditorAndModel[],
|
||||
readonly addedEditors: EditorAndModel[],
|
||||
readonly oldActiveEditor: string,
|
||||
readonly newActiveEditor: string,
|
||||
) {
|
||||
this.isEmpty = this.removedDocuments.length === 0
|
||||
&& this.addedDocuments.length === 0
|
||||
&& this.removedEditors.length === 0
|
||||
&& this.addedEditors.length === 0
|
||||
&& oldActiveEditor === newActiveEditor;
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
let ret = 'DocumentAndEditorStateDelta\n';
|
||||
ret += `\tRemoved Documents: [${this.removedDocuments.map(d => d.uri.toString(true)).join(', ')}]\n`;
|
||||
ret += `\tAdded Documents: [${this.addedDocuments.map(d => d.uri.toString(true)).join(', ')}]\n`;
|
||||
ret += `\tRemoved Editors: [${this.removedEditors.map(e => e.id).join(', ')}]\n`;
|
||||
ret += `\tAdded Editors: [${this.addedEditors.map(e => e.id).join(', ')}]\n`;
|
||||
ret += `\tNew Active Editor: ${this.newActiveEditor}\n`;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
class DocumentAndEditorState {
|
||||
|
||||
static compute(before: DocumentAndEditorState, after: DocumentAndEditorState): DocumentAndEditorStateDelta {
|
||||
if (!before) {
|
||||
return new DocumentAndEditorStateDelta([], after.documents, [], after.editors, undefined, after.activeEditor);
|
||||
}
|
||||
const documentDelta = delta(before.documents, after.documents, cmp.compareModels);
|
||||
const editorDelta = delta(before.editors, after.editors, cmp.compareEditors);
|
||||
const oldActiveEditor = before.activeEditor !== after.activeEditor ? before.activeEditor : undefined;
|
||||
const newActiveEditor = before.activeEditor !== after.activeEditor ? after.activeEditor : undefined;
|
||||
|
||||
return new DocumentAndEditorStateDelta(
|
||||
documentDelta.removed, documentDelta.added,
|
||||
editorDelta.removed, editorDelta.added,
|
||||
oldActiveEditor, newActiveEditor
|
||||
);
|
||||
}
|
||||
|
||||
constructor(
|
||||
readonly documents: IModel[],
|
||||
readonly editors: EditorAndModel[],
|
||||
readonly activeEditor: string,
|
||||
) {
|
||||
this.documents = documents.sort(cmp.compareModels);
|
||||
this.editors = editors.sort(cmp.compareEditors);
|
||||
}
|
||||
}
|
||||
|
||||
class MainThreadDocumentAndEditorStateComputer {
|
||||
|
||||
private _toDispose: IDisposable[] = [];
|
||||
private _toDisposeOnEditorRemove = new Map<string, IDisposable>();
|
||||
private _currentState: DocumentAndEditorState;
|
||||
|
||||
constructor(
|
||||
private readonly _onDidChangeState: (delta: DocumentAndEditorStateDelta) => void,
|
||||
@IModelService private _modelService: IModelService,
|
||||
@ICodeEditorService private _codeEditorService: ICodeEditorService,
|
||||
@IWorkbenchEditorService private _workbenchEditorService: IWorkbenchEditorService
|
||||
) {
|
||||
this._modelService.onModelAdded(this._updateState, this, this._toDispose);
|
||||
this._modelService.onModelRemoved(this._updateState, this, this._toDispose);
|
||||
|
||||
this._codeEditorService.onCodeEditorAdd(this._onDidAddEditor, this, this._toDispose);
|
||||
this._codeEditorService.onCodeEditorRemove(this._onDidRemoveEditor, this, this._toDispose);
|
||||
this._codeEditorService.listCodeEditors().forEach(this._onDidAddEditor, this);
|
||||
|
||||
this._updateState();
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._toDispose = dispose(this._toDispose);
|
||||
}
|
||||
|
||||
private _onDidAddEditor(e: ICommonCodeEditor): void {
|
||||
this._toDisposeOnEditorRemove.set(e.getId(), e.onDidChangeModel(() => this._updateState()));
|
||||
this._toDisposeOnEditorRemove.set(e.getId(), e.onDidFocusEditor(() => this._updateState()));
|
||||
this._toDisposeOnEditorRemove.set(e.getId(), e.onDidBlurEditor(() => this._updateState()));
|
||||
this._updateState();
|
||||
}
|
||||
|
||||
private _onDidRemoveEditor(e: ICommonCodeEditor): void {
|
||||
const sub = this._toDisposeOnEditorRemove.get(e.getId());
|
||||
if (sub) {
|
||||
this._toDisposeOnEditorRemove.delete(e.getId());
|
||||
sub.dispose();
|
||||
this._updateState();
|
||||
}
|
||||
}
|
||||
|
||||
private _updateState(): void {
|
||||
|
||||
// models: ignore too large models
|
||||
const models = this._modelService.getModels();
|
||||
for (let i = 0; i < models.length; i++) {
|
||||
if (models[i].isTooLargeForHavingARichMode()) {
|
||||
models.splice(i, 1);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
|
||||
// editor: only take those that have a not too large model
|
||||
const editors: EditorAndModel[] = [];
|
||||
let activeEditor: string = null;
|
||||
|
||||
for (const editor of this._codeEditorService.listCodeEditors()) {
|
||||
const model = editor.getModel();
|
||||
if (model && !model.isTooLargeForHavingARichMode()
|
||||
&& !model.isDisposed() // model disposed
|
||||
&& Boolean(this._modelService.getModel(model.uri)) // model disposing, the flag didn't flip yet but the model service already removed it
|
||||
) {
|
||||
const apiEditor = new EditorAndModel(editor, model);
|
||||
editors.push(apiEditor);
|
||||
if (editor.isFocused()) {
|
||||
activeEditor = apiEditor.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// active editor: if none of the previous editors had focus we try
|
||||
// to match the action workbench editor with one of editor we have
|
||||
// just computed
|
||||
if (!activeEditor) {
|
||||
const workbenchEditor = this._workbenchEditorService.getActiveEditor();
|
||||
if (workbenchEditor) {
|
||||
const workbenchEditorControl = workbenchEditor.getControl();
|
||||
let candidate: ICommonCodeEditor;
|
||||
if (isCommonCodeEditor(workbenchEditorControl)) {
|
||||
candidate = workbenchEditorControl;
|
||||
} else if (isCommonDiffEditor(workbenchEditorControl)) {
|
||||
candidate = workbenchEditorControl.getModifiedEditor();
|
||||
}
|
||||
if (candidate) {
|
||||
for (const { editor, id } of editors) {
|
||||
if (candidate === editor) {
|
||||
activeEditor = id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// compute new state and compare against old
|
||||
const newState = new DocumentAndEditorState(models, editors, activeEditor);
|
||||
const delta = DocumentAndEditorState.compute(this._currentState, newState);
|
||||
if (!delta.isEmpty) {
|
||||
this._currentState = newState;
|
||||
this._onDidChangeState(delta);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@extHostCustomer
|
||||
export class MainThreadDocumentsAndEditors {
|
||||
|
||||
private _toDispose: IDisposable[];
|
||||
private _proxy: ExtHostDocumentsAndEditorsShape;
|
||||
private _stateComputer: MainThreadDocumentAndEditorStateComputer;
|
||||
private _editors = <{ [id: string]: MainThreadTextEditor }>Object.create(null);
|
||||
|
||||
private _onTextEditorAdd = new Emitter<MainThreadTextEditor[]>();
|
||||
private _onTextEditorRemove = new Emitter<string[]>();
|
||||
private _onDocumentAdd = new Emitter<IModel[]>();
|
||||
private _onDocumentRemove = new Emitter<string[]>();
|
||||
|
||||
readonly onTextEditorAdd: Event<MainThreadTextEditor[]> = this._onTextEditorAdd.event;
|
||||
readonly onTextEditorRemove: Event<string[]> = this._onTextEditorRemove.event;
|
||||
readonly onDocumentAdd: Event<IModel[]> = this._onDocumentAdd.event;
|
||||
readonly onDocumentRemove: Event<string[]> = this._onDocumentRemove.event;
|
||||
|
||||
constructor(
|
||||
extHostContext: IExtHostContext,
|
||||
@IModelService private _modelService: IModelService,
|
||||
@ITextFileService private _textFileService: ITextFileService,
|
||||
@IWorkbenchEditorService private _workbenchEditorService: IWorkbenchEditorService,
|
||||
@ICodeEditorService codeEditorService: ICodeEditorService,
|
||||
@IModeService modeService: IModeService,
|
||||
@IFileService fileService: IFileService,
|
||||
@ITextModelService textModelResolverService: ITextModelService,
|
||||
@IUntitledEditorService untitledEditorService: IUntitledEditorService,
|
||||
@IEditorGroupService editorGroupService: IEditorGroupService,
|
||||
@ITelemetryService telemetryService: ITelemetryService
|
||||
) {
|
||||
this._proxy = extHostContext.get(ExtHostContext.ExtHostDocumentsAndEditors);
|
||||
|
||||
const mainThreadDocuments = new MainThreadDocuments(this, extHostContext, this._modelService, modeService, this._textFileService, fileService, textModelResolverService, untitledEditorService);
|
||||
extHostContext.set(MainContext.MainThreadDocuments, mainThreadDocuments);
|
||||
|
||||
const mainThreadEditors = new MainThreadEditors(this, extHostContext, codeEditorService, this._workbenchEditorService, editorGroupService, telemetryService);
|
||||
extHostContext.set(MainContext.MainThreadEditors, mainThreadEditors);
|
||||
|
||||
// It is expected that the ctor of the state computer calls our `_onDelta`.
|
||||
this._stateComputer = new MainThreadDocumentAndEditorStateComputer(delta => this._onDelta(delta), _modelService, codeEditorService, _workbenchEditorService);
|
||||
|
||||
this._toDispose = [
|
||||
mainThreadDocuments,
|
||||
mainThreadEditors,
|
||||
this._stateComputer,
|
||||
this._onTextEditorAdd,
|
||||
this._onTextEditorRemove,
|
||||
this._onDocumentAdd,
|
||||
this._onDocumentRemove,
|
||||
];
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._toDispose = dispose(this._toDispose);
|
||||
}
|
||||
|
||||
private _onDelta(delta: DocumentAndEditorStateDelta): void {
|
||||
|
||||
let removedDocuments: string[];
|
||||
let removedEditors: string[] = [];
|
||||
let addedEditors: MainThreadTextEditor[] = [];
|
||||
|
||||
// removed models
|
||||
removedDocuments = delta.removedDocuments.map(m => m.uri.toString());
|
||||
|
||||
// added editors
|
||||
for (const apiEditor of delta.addedEditors) {
|
||||
const mainThreadEditor = new MainThreadTextEditor(apiEditor.id, apiEditor.document,
|
||||
apiEditor.editor, { onGainedFocus() { }, onLostFocus() { } }, this._modelService);
|
||||
|
||||
this._editors[apiEditor.id] = mainThreadEditor;
|
||||
addedEditors.push(mainThreadEditor);
|
||||
}
|
||||
|
||||
// removed editors
|
||||
for (const { id } of delta.removedEditors) {
|
||||
const mainThreadEditor = this._editors[id];
|
||||
if (mainThreadEditor) {
|
||||
mainThreadEditor.dispose();
|
||||
delete this._editors[id];
|
||||
removedEditors.push(id);
|
||||
}
|
||||
}
|
||||
|
||||
let extHostDelta: IDocumentsAndEditorsDelta = Object.create(null);
|
||||
let empty = true;
|
||||
if (delta.newActiveEditor !== undefined) {
|
||||
empty = false;
|
||||
extHostDelta.newActiveEditor = delta.newActiveEditor;
|
||||
}
|
||||
if (removedDocuments.length > 0) {
|
||||
empty = false;
|
||||
extHostDelta.removedDocuments = removedDocuments;
|
||||
}
|
||||
if (removedEditors.length > 0) {
|
||||
empty = false;
|
||||
extHostDelta.removedEditors = removedEditors;
|
||||
}
|
||||
if (delta.addedDocuments.length > 0) {
|
||||
empty = false;
|
||||
extHostDelta.addedDocuments = delta.addedDocuments.map(m => this._toModelAddData(m));
|
||||
}
|
||||
if (delta.addedEditors.length > 0) {
|
||||
empty = false;
|
||||
extHostDelta.addedEditors = addedEditors.map(e => this._toTextEditorAddData(e));
|
||||
}
|
||||
|
||||
if (!empty) {
|
||||
// first update ext host
|
||||
this._proxy.$acceptDocumentsAndEditorsDelta(extHostDelta);
|
||||
// second update dependent state listener
|
||||
this._onDocumentRemove.fire(removedDocuments);
|
||||
this._onDocumentAdd.fire(delta.addedDocuments);
|
||||
this._onTextEditorRemove.fire(removedEditors);
|
||||
this._onTextEditorAdd.fire(addedEditors);
|
||||
}
|
||||
}
|
||||
|
||||
private _toModelAddData(model: IModel): IModelAddedData {
|
||||
return {
|
||||
url: model.uri,
|
||||
versionId: model.getVersionId(),
|
||||
lines: model.getLinesContent(),
|
||||
EOL: model.getEOL(),
|
||||
modeId: model.getLanguageIdentifier().language,
|
||||
isDirty: this._textFileService.isDirty(model.uri)
|
||||
};
|
||||
}
|
||||
|
||||
private _toTextEditorAddData(textEditor: MainThreadTextEditor): ITextEditorAddData {
|
||||
return {
|
||||
id: textEditor.getId(),
|
||||
document: textEditor.getModel().uri,
|
||||
options: textEditor.getConfiguration(),
|
||||
selections: textEditor.getSelections(),
|
||||
editorPosition: this._findEditorPosition(textEditor)
|
||||
};
|
||||
}
|
||||
|
||||
private _findEditorPosition(editor: MainThreadTextEditor): EditorPosition {
|
||||
for (let workbenchEditor of this._workbenchEditorService.getVisibleEditors()) {
|
||||
if (editor.matches(workbenchEditor)) {
|
||||
return workbenchEditor.position;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
findTextEditorIdFor(editor: IEditor): string {
|
||||
for (let id in this._editors) {
|
||||
if (this._editors[id].matches(editor)) {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
getEditor(id: string): MainThreadTextEditor {
|
||||
return this._editors[id];
|
||||
}
|
||||
}
|
||||
379
src/vs/workbench/api/electron-browser/mainThreadEditor.ts
Normal file
379
src/vs/workbench/api/electron-browser/mainThreadEditor.ts
Normal file
@@ -0,0 +1,379 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import EditorCommon = require('vs/editor/common/editorCommon');
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { IEditor } from 'vs/platform/editor/common/editor';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { Range, IRange } from 'vs/editor/common/core/range';
|
||||
import { Selection, ISelection } from 'vs/editor/common/core/selection';
|
||||
import { SnippetController2 } from 'vs/editor/contrib/snippet/browser/snippetController2';
|
||||
import { EndOfLine, TextEditorLineNumbersStyle } from 'vs/workbench/api/node/extHostTypes';
|
||||
import { TextEditorCursorStyle, cursorStyleToString } from 'vs/editor/common/config/editorOptions';
|
||||
import { ICursorSelectionChangedEvent } from 'vs/editor/common/controller/cursorEvents';
|
||||
import { IResolvedTextEditorConfiguration, ISelectionChangeEvent, ITextEditorConfigurationUpdate, TextEditorRevealType, IApplyEditsOptions, IUndoStopOptions } from 'vs/workbench/api/node/extHost.protocol';
|
||||
|
||||
function configurationsEqual(a: IResolvedTextEditorConfiguration, b: IResolvedTextEditorConfiguration) {
|
||||
if (a && !b || !a && b) {
|
||||
return false;
|
||||
}
|
||||
if (!a && !b) {
|
||||
return true;
|
||||
}
|
||||
return (
|
||||
a.tabSize === b.tabSize
|
||||
&& a.insertSpaces === b.insertSpaces
|
||||
);
|
||||
}
|
||||
|
||||
export interface IFocusTracker {
|
||||
onGainedFocus(): void;
|
||||
onLostFocus(): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Text Editor that is permanently bound to the same model.
|
||||
* It can be bound or not to a CodeEditor.
|
||||
*/
|
||||
export class MainThreadTextEditor {
|
||||
|
||||
private _id: string;
|
||||
private _model: EditorCommon.IModel;
|
||||
private _modelService: IModelService;
|
||||
private _modelListeners: IDisposable[];
|
||||
private _codeEditor: EditorCommon.ICommonCodeEditor;
|
||||
private _focusTracker: IFocusTracker;
|
||||
private _codeEditorListeners: IDisposable[];
|
||||
|
||||
private _lastSelection: Selection[];
|
||||
private _configuration: IResolvedTextEditorConfiguration;
|
||||
|
||||
private _onSelectionChanged: Emitter<ISelectionChangeEvent>;
|
||||
private _onConfigurationChanged: Emitter<IResolvedTextEditorConfiguration>;
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
model: EditorCommon.IModel,
|
||||
codeEditor: EditorCommon.ICommonCodeEditor,
|
||||
focusTracker: IFocusTracker,
|
||||
modelService: IModelService
|
||||
) {
|
||||
this._id = id;
|
||||
this._model = model;
|
||||
this._codeEditor = null;
|
||||
this._focusTracker = focusTracker;
|
||||
this._modelService = modelService;
|
||||
this._codeEditorListeners = [];
|
||||
|
||||
this._onSelectionChanged = new Emitter<ISelectionChangeEvent>();
|
||||
this._onConfigurationChanged = new Emitter<IResolvedTextEditorConfiguration>();
|
||||
|
||||
this._lastSelection = [new Selection(1, 1, 1, 1)];
|
||||
this._modelListeners = [];
|
||||
this._modelListeners.push(this._model.onDidChangeOptions((e) => {
|
||||
this._setConfiguration(this._readConfiguration(this._model, this._codeEditor));
|
||||
}));
|
||||
|
||||
this.setCodeEditor(codeEditor);
|
||||
this._setConfiguration(this._readConfiguration(this._model, this._codeEditor));
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._model = null;
|
||||
this._modelListeners = dispose(this._modelListeners);
|
||||
this._codeEditor = null;
|
||||
this._codeEditorListeners = dispose(this._codeEditorListeners);
|
||||
}
|
||||
|
||||
public getId(): string {
|
||||
return this._id;
|
||||
}
|
||||
|
||||
public getModel(): EditorCommon.IModel {
|
||||
return this._model;
|
||||
}
|
||||
|
||||
public getCodeEditor(): EditorCommon.ICommonCodeEditor {
|
||||
return this._codeEditor;
|
||||
}
|
||||
|
||||
public hasCodeEditor(codeEditor: EditorCommon.ICommonCodeEditor): boolean {
|
||||
return (this._codeEditor === codeEditor);
|
||||
}
|
||||
|
||||
public setCodeEditor(codeEditor: EditorCommon.ICommonCodeEditor): void {
|
||||
if (this.hasCodeEditor(codeEditor)) {
|
||||
// Nothing to do...
|
||||
return;
|
||||
}
|
||||
this._codeEditorListeners = dispose(this._codeEditorListeners);
|
||||
|
||||
this._codeEditor = codeEditor;
|
||||
if (this._codeEditor) {
|
||||
|
||||
// Catch early the case that this code editor gets a different model set and disassociate from this model
|
||||
this._codeEditorListeners.push(this._codeEditor.onDidChangeModel(() => {
|
||||
this.setCodeEditor(null);
|
||||
}));
|
||||
|
||||
let forwardSelection = (event?: ICursorSelectionChangedEvent) => {
|
||||
this._lastSelection = this._codeEditor.getSelections();
|
||||
this._onSelectionChanged.fire({
|
||||
selections: this._lastSelection,
|
||||
source: event && event.source
|
||||
});
|
||||
};
|
||||
this._codeEditorListeners.push(this._codeEditor.onDidChangeCursorSelection(forwardSelection));
|
||||
if (!Selection.selectionsArrEqual(this._lastSelection, this._codeEditor.getSelections())) {
|
||||
forwardSelection();
|
||||
}
|
||||
this._codeEditorListeners.push(this._codeEditor.onDidFocusEditor(() => {
|
||||
this._focusTracker.onGainedFocus();
|
||||
}));
|
||||
this._codeEditorListeners.push(this._codeEditor.onDidBlurEditor(() => {
|
||||
this._focusTracker.onLostFocus();
|
||||
}));
|
||||
this._codeEditorListeners.push(this._codeEditor.onDidChangeConfiguration(() => {
|
||||
this._setConfiguration(this._readConfiguration(this._model, this._codeEditor));
|
||||
}));
|
||||
this._setConfiguration(this._readConfiguration(this._model, this._codeEditor));
|
||||
}
|
||||
}
|
||||
|
||||
public isVisible(): boolean {
|
||||
return !!this._codeEditor;
|
||||
}
|
||||
|
||||
public get onSelectionChanged(): Event<ISelectionChangeEvent> {
|
||||
return this._onSelectionChanged.event;
|
||||
}
|
||||
|
||||
public get onConfigurationChanged(): Event<IResolvedTextEditorConfiguration> {
|
||||
return this._onConfigurationChanged.event;
|
||||
}
|
||||
|
||||
public getSelections(): Selection[] {
|
||||
if (this._codeEditor) {
|
||||
return this._codeEditor.getSelections();
|
||||
}
|
||||
return this._lastSelection;
|
||||
}
|
||||
|
||||
public setSelections(selections: ISelection[]): void {
|
||||
if (this._codeEditor) {
|
||||
this._codeEditor.setSelections(selections);
|
||||
return;
|
||||
}
|
||||
this._lastSelection = selections.map(Selection.liftSelection);
|
||||
}
|
||||
|
||||
public getConfiguration(): IResolvedTextEditorConfiguration {
|
||||
return this._configuration;
|
||||
}
|
||||
|
||||
private _setIndentConfiguration(newConfiguration: ITextEditorConfigurationUpdate): void {
|
||||
if (newConfiguration.tabSize === 'auto' || newConfiguration.insertSpaces === 'auto') {
|
||||
// one of the options was set to 'auto' => detect indentation
|
||||
|
||||
let creationOpts = this._modelService.getCreationOptions(this._model.getLanguageIdentifier().language, this._model.uri);
|
||||
let insertSpaces = creationOpts.insertSpaces;
|
||||
let tabSize = creationOpts.tabSize;
|
||||
|
||||
if (newConfiguration.insertSpaces !== 'auto' && typeof newConfiguration.insertSpaces !== 'undefined') {
|
||||
insertSpaces = newConfiguration.insertSpaces;
|
||||
}
|
||||
|
||||
if (newConfiguration.tabSize !== 'auto' && typeof newConfiguration.tabSize !== 'undefined') {
|
||||
tabSize = newConfiguration.tabSize;
|
||||
}
|
||||
|
||||
this._model.detectIndentation(insertSpaces, tabSize);
|
||||
return;
|
||||
}
|
||||
|
||||
let newOpts: EditorCommon.ITextModelUpdateOptions = {};
|
||||
if (typeof newConfiguration.insertSpaces !== 'undefined') {
|
||||
newOpts.insertSpaces = newConfiguration.insertSpaces;
|
||||
}
|
||||
if (typeof newConfiguration.tabSize !== 'undefined') {
|
||||
newOpts.tabSize = newConfiguration.tabSize;
|
||||
}
|
||||
this._model.updateOptions(newOpts);
|
||||
}
|
||||
|
||||
public setConfiguration(newConfiguration: ITextEditorConfigurationUpdate): void {
|
||||
this._setIndentConfiguration(newConfiguration);
|
||||
|
||||
if (!this._codeEditor) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (newConfiguration.cursorStyle) {
|
||||
let newCursorStyle = cursorStyleToString(newConfiguration.cursorStyle);
|
||||
this._codeEditor.updateOptions({
|
||||
cursorStyle: newCursorStyle
|
||||
});
|
||||
}
|
||||
|
||||
if (typeof newConfiguration.lineNumbers !== 'undefined') {
|
||||
let lineNumbers: 'on' | 'off' | 'relative';
|
||||
switch (newConfiguration.lineNumbers) {
|
||||
case TextEditorLineNumbersStyle.On:
|
||||
lineNumbers = 'on';
|
||||
break;
|
||||
case TextEditorLineNumbersStyle.Relative:
|
||||
lineNumbers = 'relative';
|
||||
break;
|
||||
default:
|
||||
lineNumbers = 'off';
|
||||
}
|
||||
this._codeEditor.updateOptions({
|
||||
lineNumbers: lineNumbers
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public setDecorations(key: string, ranges: EditorCommon.IDecorationOptions[]): void {
|
||||
if (!this._codeEditor) {
|
||||
return;
|
||||
}
|
||||
this._codeEditor.setDecorations(key, ranges);
|
||||
}
|
||||
|
||||
public revealRange(range: IRange, revealType: TextEditorRevealType): void {
|
||||
if (!this._codeEditor) {
|
||||
return;
|
||||
}
|
||||
switch (revealType) {
|
||||
case TextEditorRevealType.Default:
|
||||
this._codeEditor.revealRange(range, EditorCommon.ScrollType.Smooth);
|
||||
break;
|
||||
case TextEditorRevealType.InCenter:
|
||||
this._codeEditor.revealRangeInCenter(range, EditorCommon.ScrollType.Smooth);
|
||||
break;
|
||||
case TextEditorRevealType.InCenterIfOutsideViewport:
|
||||
this._codeEditor.revealRangeInCenterIfOutsideViewport(range, EditorCommon.ScrollType.Smooth);
|
||||
break;
|
||||
case TextEditorRevealType.AtTop:
|
||||
this._codeEditor.revealRangeAtTop(range, EditorCommon.ScrollType.Smooth);
|
||||
break;
|
||||
default:
|
||||
console.warn(`Unknown revealType: ${revealType}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private _readConfiguration(model: EditorCommon.IModel, codeEditor: EditorCommon.ICommonCodeEditor): IResolvedTextEditorConfiguration {
|
||||
if (model.isDisposed()) {
|
||||
// shutdown time
|
||||
return this._configuration;
|
||||
}
|
||||
let cursorStyle = this._configuration ? this._configuration.cursorStyle : TextEditorCursorStyle.Line;
|
||||
let lineNumbers: TextEditorLineNumbersStyle = this._configuration ? this._configuration.lineNumbers : TextEditorLineNumbersStyle.On;
|
||||
if (codeEditor) {
|
||||
let codeEditorOpts = codeEditor.getConfiguration();
|
||||
cursorStyle = codeEditorOpts.viewInfo.cursorStyle;
|
||||
|
||||
if (codeEditorOpts.viewInfo.renderRelativeLineNumbers) {
|
||||
lineNumbers = TextEditorLineNumbersStyle.Relative;
|
||||
} else if (codeEditorOpts.viewInfo.renderLineNumbers) {
|
||||
lineNumbers = TextEditorLineNumbersStyle.On;
|
||||
} else {
|
||||
lineNumbers = TextEditorLineNumbersStyle.Off;
|
||||
}
|
||||
}
|
||||
|
||||
let indent = model.getOptions();
|
||||
return {
|
||||
insertSpaces: indent.insertSpaces,
|
||||
tabSize: indent.tabSize,
|
||||
cursorStyle: cursorStyle,
|
||||
lineNumbers: lineNumbers
|
||||
};
|
||||
}
|
||||
|
||||
private _setConfiguration(newConfiguration: IResolvedTextEditorConfiguration): void {
|
||||
if (configurationsEqual(this._configuration, newConfiguration)) {
|
||||
return;
|
||||
}
|
||||
this._configuration = newConfiguration;
|
||||
this._onConfigurationChanged.fire(this._configuration);
|
||||
}
|
||||
|
||||
public isFocused(): boolean {
|
||||
if (this._codeEditor) {
|
||||
return this._codeEditor.isFocused();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public matches(editor: IEditor): boolean {
|
||||
if (!editor) {
|
||||
return false;
|
||||
}
|
||||
return editor.getControl() === this._codeEditor;
|
||||
}
|
||||
|
||||
public applyEdits(versionIdCheck: number, edits: EditorCommon.ISingleEditOperation[], opts: IApplyEditsOptions): boolean {
|
||||
if (this._model.getVersionId() !== versionIdCheck) {
|
||||
// throw new Error('Model has changed in the meantime!');
|
||||
// model changed in the meantime
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this._codeEditor) {
|
||||
// console.warn('applyEdits on invisible editor');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (opts.setEndOfLine === EndOfLine.CRLF) {
|
||||
this._model.setEOL(EditorCommon.EndOfLineSequence.CRLF);
|
||||
} else if (opts.setEndOfLine === EndOfLine.LF) {
|
||||
this._model.setEOL(EditorCommon.EndOfLineSequence.LF);
|
||||
}
|
||||
|
||||
let transformedEdits = edits.map((edit): EditorCommon.IIdentifiedSingleEditOperation => {
|
||||
return {
|
||||
identifier: null,
|
||||
range: Range.lift(edit.range),
|
||||
text: edit.text,
|
||||
forceMoveMarkers: edit.forceMoveMarkers
|
||||
};
|
||||
});
|
||||
|
||||
if (opts.undoStopBefore) {
|
||||
this._codeEditor.pushUndoStop();
|
||||
}
|
||||
this._codeEditor.executeEdits('MainThreadTextEditor', transformedEdits);
|
||||
if (opts.undoStopAfter) {
|
||||
this._codeEditor.pushUndoStop();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
insertSnippet(template: string, ranges: IRange[], opts: IUndoStopOptions) {
|
||||
|
||||
if (!this._codeEditor) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const snippetController = SnippetController2.get(this._codeEditor);
|
||||
|
||||
// // cancel previous snippet mode
|
||||
// snippetController.leaveSnippet();
|
||||
|
||||
// set selection, focus editor
|
||||
const selections = ranges.map(r => new Selection(r.startLineNumber, r.startColumn, r.endLineNumber, r.endColumn));
|
||||
this._codeEditor.setSelections(selections);
|
||||
this._codeEditor.focus();
|
||||
|
||||
// make modifications
|
||||
snippetController.insert(template, 0, 0, opts.undoStopBefore, opts.undoStopAfter);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
230
src/vs/workbench/api/electron-browser/mainThreadEditors.ts
Normal file
230
src/vs/workbench/api/electron-browser/mainThreadEditors.ts
Normal file
@@ -0,0 +1,230 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { disposed } from 'vs/base/common/errors';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { ISingleEditOperation, IDecorationRenderOptions, IDecorationOptions, ILineChange } from 'vs/editor/common/editorCommon';
|
||||
import { ICodeEditorService } from 'vs/editor/common/services/codeEditorService';
|
||||
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
|
||||
import { Position as EditorPosition, ITextEditorOptions } from 'vs/platform/editor/common/editor';
|
||||
import { MainThreadTextEditor } from './mainThreadEditor';
|
||||
import { ITextEditorConfigurationUpdate, TextEditorRevealType, IApplyEditsOptions, IUndoStopOptions } from 'vs/workbench/api/node/extHost.protocol';
|
||||
import { MainThreadDocumentsAndEditors } from './mainThreadDocumentsAndEditors';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { equals as objectEquals } from 'vs/base/common/objects';
|
||||
import { ExtHostContext, MainThreadEditorsShape, ExtHostEditorsShape, ITextDocumentShowOptions, ITextEditorPositionData, IExtHostContext } from '../node/extHost.protocol';
|
||||
import { IRange } from 'vs/editor/common/core/range';
|
||||
import { ISelection } from 'vs/editor/common/core/selection';
|
||||
|
||||
export class MainThreadEditors implements MainThreadEditorsShape {
|
||||
|
||||
private _proxy: ExtHostEditorsShape;
|
||||
private _documentsAndEditors: MainThreadDocumentsAndEditors;
|
||||
private _workbenchEditorService: IWorkbenchEditorService;
|
||||
private _telemetryService: ITelemetryService;
|
||||
private _toDispose: IDisposable[];
|
||||
private _textEditorsListenersMap: { [editorId: string]: IDisposable[]; };
|
||||
private _editorPositionData: ITextEditorPositionData;
|
||||
|
||||
constructor(
|
||||
documentsAndEditors: MainThreadDocumentsAndEditors,
|
||||
extHostContext: IExtHostContext,
|
||||
@ICodeEditorService private _codeEditorService: ICodeEditorService,
|
||||
@IWorkbenchEditorService workbenchEditorService: IWorkbenchEditorService,
|
||||
@IEditorGroupService editorGroupService: IEditorGroupService,
|
||||
@ITelemetryService telemetryService: ITelemetryService
|
||||
) {
|
||||
this._proxy = extHostContext.get(ExtHostContext.ExtHostEditors);
|
||||
this._documentsAndEditors = documentsAndEditors;
|
||||
this._workbenchEditorService = workbenchEditorService;
|
||||
this._telemetryService = telemetryService;
|
||||
this._toDispose = [];
|
||||
this._textEditorsListenersMap = Object.create(null);
|
||||
this._editorPositionData = null;
|
||||
|
||||
this._toDispose.push(documentsAndEditors.onTextEditorAdd(editors => editors.forEach(this._onTextEditorAdd, this)));
|
||||
this._toDispose.push(documentsAndEditors.onTextEditorRemove(editors => editors.forEach(this._onTextEditorRemove, this)));
|
||||
|
||||
this._toDispose.push(editorGroupService.onEditorsChanged(() => this._updateActiveAndVisibleTextEditors()));
|
||||
this._toDispose.push(editorGroupService.onEditorsMoved(() => this._updateActiveAndVisibleTextEditors()));
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
Object.keys(this._textEditorsListenersMap).forEach((editorId) => {
|
||||
dispose(this._textEditorsListenersMap[editorId]);
|
||||
});
|
||||
this._textEditorsListenersMap = Object.create(null);
|
||||
this._toDispose = dispose(this._toDispose);
|
||||
}
|
||||
|
||||
private _onTextEditorAdd(textEditor: MainThreadTextEditor): void {
|
||||
let id = textEditor.getId();
|
||||
let toDispose: IDisposable[] = [];
|
||||
toDispose.push(textEditor.onConfigurationChanged((opts) => {
|
||||
this._proxy.$acceptOptionsChanged(id, opts);
|
||||
}));
|
||||
toDispose.push(textEditor.onSelectionChanged((event) => {
|
||||
this._proxy.$acceptSelectionsChanged(id, event);
|
||||
}));
|
||||
|
||||
this._textEditorsListenersMap[id] = toDispose;
|
||||
}
|
||||
|
||||
private _onTextEditorRemove(id: string): void {
|
||||
dispose(this._textEditorsListenersMap[id]);
|
||||
delete this._textEditorsListenersMap[id];
|
||||
}
|
||||
|
||||
private _updateActiveAndVisibleTextEditors(): void {
|
||||
|
||||
// editor columns
|
||||
let editorPositionData = this._getTextEditorPositionData();
|
||||
if (!objectEquals(this._editorPositionData, editorPositionData)) {
|
||||
this._editorPositionData = editorPositionData;
|
||||
this._proxy.$acceptEditorPositionData(this._editorPositionData);
|
||||
}
|
||||
}
|
||||
|
||||
private _getTextEditorPositionData(): ITextEditorPositionData {
|
||||
let result: ITextEditorPositionData = Object.create(null);
|
||||
for (let workbenchEditor of this._workbenchEditorService.getVisibleEditors()) {
|
||||
const id = this._documentsAndEditors.findTextEditorIdFor(workbenchEditor);
|
||||
if (id) {
|
||||
result[id] = workbenchEditor.position;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// --- from extension host process
|
||||
|
||||
$tryShowTextDocument(resource: URI, options: ITextDocumentShowOptions): TPromise<string> {
|
||||
const editorOptions: ITextEditorOptions = {
|
||||
preserveFocus: options.preserveFocus,
|
||||
pinned: options.pinned,
|
||||
selection: options.selection
|
||||
};
|
||||
|
||||
const input = {
|
||||
resource,
|
||||
options: editorOptions
|
||||
};
|
||||
|
||||
return this._workbenchEditorService.openEditor(input, options.position).then(editor => {
|
||||
if (!editor) {
|
||||
return undefined;
|
||||
}
|
||||
return this._documentsAndEditors.findTextEditorIdFor(editor);
|
||||
});
|
||||
}
|
||||
|
||||
$tryShowEditor(id: string, position: EditorPosition): TPromise<void> {
|
||||
// check how often this is used
|
||||
this._telemetryService.publicLog('api.deprecated', { function: 'TextEditor.show' });
|
||||
|
||||
let mainThreadEditor = this._documentsAndEditors.getEditor(id);
|
||||
if (mainThreadEditor) {
|
||||
let model = mainThreadEditor.getModel();
|
||||
return this._workbenchEditorService.openEditor({
|
||||
resource: model.uri,
|
||||
options: { preserveFocus: false }
|
||||
}, position).then(() => { return; });
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
$tryHideEditor(id: string): TPromise<void> {
|
||||
// check how often this is used
|
||||
this._telemetryService.publicLog('api.deprecated', { function: 'TextEditor.hide' });
|
||||
|
||||
let mainThreadEditor = this._documentsAndEditors.getEditor(id);
|
||||
if (mainThreadEditor) {
|
||||
let editors = this._workbenchEditorService.getVisibleEditors();
|
||||
for (let editor of editors) {
|
||||
if (mainThreadEditor.matches(editor)) {
|
||||
return this._workbenchEditorService.closeEditor(editor.position, editor.input).then(() => { return; });
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
$trySetSelections(id: string, selections: ISelection[]): TPromise<any> {
|
||||
if (!this._documentsAndEditors.getEditor(id)) {
|
||||
return TPromise.wrapError(disposed(`TextEditor(${id})`));
|
||||
}
|
||||
this._documentsAndEditors.getEditor(id).setSelections(selections);
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
$trySetDecorations(id: string, key: string, ranges: IDecorationOptions[]): TPromise<any> {
|
||||
if (!this._documentsAndEditors.getEditor(id)) {
|
||||
return TPromise.wrapError(disposed(`TextEditor(${id})`));
|
||||
}
|
||||
this._documentsAndEditors.getEditor(id).setDecorations(key, ranges);
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
$tryRevealRange(id: string, range: IRange, revealType: TextEditorRevealType): TPromise<any> {
|
||||
if (!this._documentsAndEditors.getEditor(id)) {
|
||||
return TPromise.wrapError(disposed(`TextEditor(${id})`));
|
||||
}
|
||||
this._documentsAndEditors.getEditor(id).revealRange(range, revealType);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
$trySetOptions(id: string, options: ITextEditorConfigurationUpdate): TPromise<any> {
|
||||
if (!this._documentsAndEditors.getEditor(id)) {
|
||||
return TPromise.wrapError(disposed(`TextEditor(${id})`));
|
||||
}
|
||||
this._documentsAndEditors.getEditor(id).setConfiguration(options);
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
$tryApplyEdits(id: string, modelVersionId: number, edits: ISingleEditOperation[], opts: IApplyEditsOptions): TPromise<boolean> {
|
||||
if (!this._documentsAndEditors.getEditor(id)) {
|
||||
return TPromise.wrapError<boolean>(disposed(`TextEditor(${id})`));
|
||||
}
|
||||
return TPromise.as(this._documentsAndEditors.getEditor(id).applyEdits(modelVersionId, edits, opts));
|
||||
}
|
||||
|
||||
$tryInsertSnippet(id: string, template: string, ranges: IRange[], opts: IUndoStopOptions): TPromise<boolean> {
|
||||
if (!this._documentsAndEditors.getEditor(id)) {
|
||||
return TPromise.wrapError<boolean>(disposed(`TextEditor(${id})`));
|
||||
}
|
||||
return TPromise.as(this._documentsAndEditors.getEditor(id).insertSnippet(template, ranges, opts));
|
||||
}
|
||||
|
||||
$registerTextEditorDecorationType(key: string, options: IDecorationRenderOptions): void {
|
||||
this._codeEditorService.registerDecorationType(key, options);
|
||||
}
|
||||
|
||||
$removeTextEditorDecorationType(key: string): void {
|
||||
this._codeEditorService.removeDecorationType(key);
|
||||
}
|
||||
|
||||
$getDiffInformation(id: string): TPromise<ILineChange[]> {
|
||||
const editor = this._documentsAndEditors.getEditor(id);
|
||||
|
||||
if (!editor) {
|
||||
return TPromise.wrapError<ILineChange[]>(new Error('No such TextEditor'));
|
||||
}
|
||||
|
||||
const codeEditor = editor.getCodeEditor();
|
||||
const codeEditorId = codeEditor.getId();
|
||||
const diffEditors = this._codeEditorService.listDiffEditors();
|
||||
const [diffEditor] = diffEditors.filter(d => d.getOriginalEditor().getId() === codeEditorId || d.getModifiedEditor().getId() === codeEditorId);
|
||||
|
||||
if (!diffEditor) {
|
||||
return TPromise.as([]);
|
||||
}
|
||||
|
||||
return TPromise.as(diffEditor.getLineChanges());
|
||||
}
|
||||
}
|
||||
28
src/vs/workbench/api/electron-browser/mainThreadErrors.ts
Normal file
28
src/vs/workbench/api/electron-browser/mainThreadErrors.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { SerializedError, onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { MainThreadErrorsShape, MainContext } from '../node/extHost.protocol';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadErrors)
|
||||
export class MainThreadErrors implements MainThreadErrorsShape {
|
||||
|
||||
dispose(): void {
|
||||
//
|
||||
}
|
||||
|
||||
$onUnexpectedError(err: any | SerializedError, extensionId: string | undefined): void {
|
||||
if (err.$isError) {
|
||||
const { name, message, stack } = err;
|
||||
err = new Error();
|
||||
err.message = extensionId ? `[${extensionId}] ${message}` : message;
|
||||
err.name = name;
|
||||
err.stack = stack;
|
||||
}
|
||||
onUnexpectedError(err);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import { IExtensionService } from 'vs/platform/extensions/common/extensions';
|
||||
import { MainThreadExtensionServiceShape, MainContext, IExtHostContext } from '../node/extHost.protocol';
|
||||
import { ExtensionService } from 'vs/workbench/services/extensions/electron-browser/extensionService';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadExtensionService)
|
||||
export class MainThreadExtensionService implements MainThreadExtensionServiceShape {
|
||||
|
||||
private readonly _extensionService: ExtensionService;
|
||||
|
||||
constructor(
|
||||
extHostContext: IExtHostContext,
|
||||
@IExtensionService extensionService: IExtensionService
|
||||
) {
|
||||
if (extensionService instanceof ExtensionService) {
|
||||
this._extensionService = extensionService;
|
||||
}
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
}
|
||||
|
||||
$localShowMessage(severity: Severity, msg: string): void {
|
||||
this._extensionService._logOrShowMessage(severity, msg);
|
||||
}
|
||||
$onExtensionActivated(extensionId: string, startup: boolean, codeLoadingTime: number, activateCallTime: number, activateResolvedTime: number): void {
|
||||
this._extensionService._onExtensionActivated(extensionId, startup, codeLoadingTime, activateCallTime, activateResolvedTime);
|
||||
}
|
||||
$onExtensionActivationFailed(extensionId: string): void {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { FileChangeType, IFileService } from 'vs/platform/files/common/files';
|
||||
import { ExtHostContext, ExtHostFileSystemEventServiceShape, FileSystemEvents, IExtHostContext } from '../node/extHost.protocol';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { extHostCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
|
||||
|
||||
@extHostCustomer
|
||||
export class MainThreadFileSystemEventService {
|
||||
|
||||
private readonly _listener: IDisposable;
|
||||
|
||||
constructor(
|
||||
extHostContext: IExtHostContext,
|
||||
@IFileService fileService: IFileService
|
||||
) {
|
||||
|
||||
const proxy: ExtHostFileSystemEventServiceShape = extHostContext.get(ExtHostContext.ExtHostFileSystemEventService);
|
||||
const events: FileSystemEvents = {
|
||||
created: [],
|
||||
changed: [],
|
||||
deleted: []
|
||||
};
|
||||
|
||||
this._listener = fileService.onFileChanges(event => {
|
||||
for (let change of event.changes) {
|
||||
switch (change.type) {
|
||||
case FileChangeType.ADDED:
|
||||
events.created.push(change.resource);
|
||||
break;
|
||||
case FileChangeType.UPDATED:
|
||||
events.changed.push(change.resource);
|
||||
break;
|
||||
case FileChangeType.DELETED:
|
||||
events.deleted.push(change.resource);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
proxy.$onFileEvent(events);
|
||||
events.created.length = 0;
|
||||
events.changed.length = 0;
|
||||
events.deleted.length = 0;
|
||||
});
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._listener.dispose();
|
||||
}
|
||||
}
|
||||
139
src/vs/workbench/api/electron-browser/mainThreadHeapService.ts
Normal file
139
src/vs/workbench/api/electron-browser/mainThreadHeapService.ts
Normal file
@@ -0,0 +1,139 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { ExtHostContext, ObjectIdentifier, IExtHostContext } from '../node/extHost.protocol';
|
||||
import { consumeSignals, GCSignal } from 'gc-signals';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { extHostCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
|
||||
|
||||
export const IHeapService = createDecorator<IHeapService>('heapService');
|
||||
|
||||
export interface IHeapService {
|
||||
_serviceBrand: any;
|
||||
|
||||
readonly onGarbageCollection: Event<number[]>;
|
||||
|
||||
/**
|
||||
* Track gc-collection for all new objects that
|
||||
* have the $ident-value set.
|
||||
*/
|
||||
trackRecursive<T>(p: TPromise<T>): TPromise<T>;
|
||||
|
||||
/**
|
||||
* Track gc-collection for all new objects that
|
||||
* have the $ident-value set.
|
||||
*/
|
||||
trackRecursive<T>(obj: T): T;
|
||||
}
|
||||
|
||||
|
||||
export class HeapService implements IHeapService {
|
||||
|
||||
_serviceBrand: any;
|
||||
|
||||
private _onGarbageCollection: Emitter<number[]> = new Emitter<number[]>();
|
||||
public readonly onGarbageCollection: Event<number[]> = this._onGarbageCollection.event;
|
||||
|
||||
private _activeSignals = new WeakMap<any, GCSignal>();
|
||||
private _activeIds = new Set<number>();
|
||||
private _consumeHandle: number;
|
||||
|
||||
constructor() {
|
||||
this._consumeHandle = setInterval(() => {
|
||||
const ids = consumeSignals();
|
||||
|
||||
if (ids.length > 0) {
|
||||
// local book-keeping
|
||||
for (const id of ids) {
|
||||
this._activeIds.delete(id);
|
||||
}
|
||||
|
||||
// fire event
|
||||
this._onGarbageCollection.fire(ids);
|
||||
}
|
||||
|
||||
}, 15 * 1000);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
clearInterval(this._consumeHandle);
|
||||
}
|
||||
|
||||
trackRecursive<T>(p: TPromise<T>): TPromise<T>;
|
||||
trackRecursive<T>(obj: T): T;
|
||||
trackRecursive<T>(obj: any): any {
|
||||
if (TPromise.is(obj)) {
|
||||
return obj.then(result => this.trackRecursive(result));
|
||||
} else {
|
||||
return this._doTrackRecursive(obj);
|
||||
}
|
||||
}
|
||||
|
||||
private _doTrackRecursive(obj: any): any {
|
||||
|
||||
const stack = [obj];
|
||||
while (stack.length > 0) {
|
||||
|
||||
// remove first element
|
||||
let obj = stack.shift();
|
||||
|
||||
if (!obj || typeof obj !== 'object') {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (let key in obj) {
|
||||
if (!Object.prototype.hasOwnProperty.call(obj, key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const value = obj[key];
|
||||
// recurse -> object/array
|
||||
if (typeof value === 'object') {
|
||||
stack.push(value);
|
||||
|
||||
} else if (key === ObjectIdentifier.name) {
|
||||
// track new $ident-objects
|
||||
|
||||
if (typeof value === 'number' && !this._activeIds.has(value)) {
|
||||
this._activeIds.add(value);
|
||||
this._activeSignals.set(obj, new GCSignal(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
|
||||
@extHostCustomer
|
||||
export class MainThreadHeapService {
|
||||
|
||||
private _toDispose: IDisposable;
|
||||
|
||||
constructor(
|
||||
extHostContext: IExtHostContext,
|
||||
@IHeapService heapService: IHeapService,
|
||||
) {
|
||||
const proxy = extHostContext.get(ExtHostContext.ExtHostHeapService);
|
||||
this._toDispose = heapService.onGarbageCollection((ids) => {
|
||||
// send to ext host
|
||||
proxy.$onGarbageCollection(ids);
|
||||
});
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._toDispose.dispose();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
registerSingleton(IHeapService, HeapService);
|
||||
@@ -0,0 +1,358 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import * as vscode from 'vscode';
|
||||
import { IReadOnlyModel, ISingleEditOperation } from 'vs/editor/common/editorCommon';
|
||||
import * as modes from 'vs/editor/common/modes';
|
||||
import { WorkspaceSymbolProviderRegistry, IWorkspaceSymbolProvider } from 'vs/workbench/parts/search/common/search';
|
||||
import { wireCancellationToken } from 'vs/base/common/async';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { Position as EditorPosition } from 'vs/editor/common/core/position';
|
||||
import { Range as EditorRange } from 'vs/editor/common/core/range';
|
||||
import { ExtHostContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, IRawColorFormatMap, MainContext, IExtHostContext } from '../node/extHost.protocol';
|
||||
import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry';
|
||||
import { LanguageConfiguration } from 'vs/editor/common/modes/languageConfiguration';
|
||||
import { IHeapService } from './mainThreadHeapService';
|
||||
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
import { ColorFormatter, CombinedColorFormatter } from 'vs/editor/contrib/colorPicker/common/colorFormatter';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadLanguageFeatures)
|
||||
export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesShape {
|
||||
|
||||
private _proxy: ExtHostLanguageFeaturesShape;
|
||||
private _heapService: IHeapService;
|
||||
private _modeService: IModeService;
|
||||
private _registrations: { [handle: number]: IDisposable; } = Object.create(null);
|
||||
private _formatters: Map<number, ColorFormatter>;
|
||||
|
||||
constructor(
|
||||
extHostContext: IExtHostContext,
|
||||
@IHeapService heapService: IHeapService,
|
||||
@IModeService modeService: IModeService,
|
||||
) {
|
||||
this._proxy = extHostContext.get(ExtHostContext.ExtHostLanguageFeatures);
|
||||
this._heapService = heapService;
|
||||
this._modeService = modeService;
|
||||
this._formatters = new Map<number, ColorFormatter>();
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
for (const key in this._registrations) {
|
||||
this._registrations[key].dispose();
|
||||
}
|
||||
}
|
||||
|
||||
$unregister(handle: number): TPromise<any> {
|
||||
let registration = this._registrations[handle];
|
||||
if (registration) {
|
||||
registration.dispose();
|
||||
delete this._registrations[handle];
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// --- outline
|
||||
|
||||
$registerOutlineSupport(handle: number, selector: vscode.DocumentSelector): TPromise<any> {
|
||||
this._registrations[handle] = modes.DocumentSymbolProviderRegistry.register(selector, <modes.DocumentSymbolProvider>{
|
||||
provideDocumentSymbols: (model: IReadOnlyModel, token: CancellationToken): Thenable<modes.SymbolInformation[]> => {
|
||||
return wireCancellationToken(token, this._proxy.$provideDocumentSymbols(handle, model.uri));
|
||||
}
|
||||
});
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// --- code lens
|
||||
|
||||
$registerCodeLensSupport(handle: number, selector: vscode.DocumentSelector, eventHandle: number): TPromise<any> {
|
||||
|
||||
const provider = <modes.CodeLensProvider>{
|
||||
provideCodeLenses: (model: IReadOnlyModel, token: CancellationToken): modes.ICodeLensSymbol[] | Thenable<modes.ICodeLensSymbol[]> => {
|
||||
return this._heapService.trackRecursive(wireCancellationToken(token, this._proxy.$provideCodeLenses(handle, model.uri)));
|
||||
},
|
||||
resolveCodeLens: (model: IReadOnlyModel, codeLens: modes.ICodeLensSymbol, token: CancellationToken): modes.ICodeLensSymbol | Thenable<modes.ICodeLensSymbol> => {
|
||||
return this._heapService.trackRecursive(wireCancellationToken(token, this._proxy.$resolveCodeLens(handle, model.uri, codeLens)));
|
||||
}
|
||||
};
|
||||
|
||||
if (typeof eventHandle === 'number') {
|
||||
const emitter = new Emitter<modes.CodeLensProvider>();
|
||||
this._registrations[eventHandle] = emitter;
|
||||
provider.onDidChange = emitter.event;
|
||||
}
|
||||
|
||||
this._registrations[handle] = modes.CodeLensProviderRegistry.register(selector, provider);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
$emitCodeLensEvent(eventHandle: number, event?: any): TPromise<any> {
|
||||
const obj = this._registrations[eventHandle];
|
||||
if (obj instanceof Emitter) {
|
||||
obj.fire(event);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// --- declaration
|
||||
|
||||
$registerDeclaractionSupport(handle: number, selector: vscode.DocumentSelector): TPromise<any> {
|
||||
this._registrations[handle] = modes.DefinitionProviderRegistry.register(selector, <modes.DefinitionProvider>{
|
||||
provideDefinition: (model, position, token): Thenable<modes.Definition> => {
|
||||
return wireCancellationToken(token, this._proxy.$provideDefinition(handle, model.uri, position));
|
||||
}
|
||||
});
|
||||
return undefined;
|
||||
}
|
||||
|
||||
$registerImplementationSupport(handle: number, selector: vscode.DocumentSelector): TPromise<any> {
|
||||
this._registrations[handle] = modes.ImplementationProviderRegistry.register(selector, <modes.ImplementationProvider>{
|
||||
provideImplementation: (model, position, token): Thenable<modes.Definition> => {
|
||||
return wireCancellationToken(token, this._proxy.$provideImplementation(handle, model.uri, position));
|
||||
}
|
||||
});
|
||||
return undefined;
|
||||
}
|
||||
|
||||
$registerTypeDefinitionSupport(handle: number, selector: vscode.DocumentSelector): TPromise<any> {
|
||||
this._registrations[handle] = modes.TypeDefinitionProviderRegistry.register(selector, <modes.TypeDefinitionProvider>{
|
||||
provideTypeDefinition: (model, position, token): Thenable<modes.Definition> => {
|
||||
return wireCancellationToken(token, this._proxy.$provideTypeDefinition(handle, model.uri, position));
|
||||
}
|
||||
});
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// --- extra info
|
||||
|
||||
$registerHoverProvider(handle: number, selector: vscode.DocumentSelector): TPromise<any> {
|
||||
this._registrations[handle] = modes.HoverProviderRegistry.register(selector, <modes.HoverProvider>{
|
||||
provideHover: (model: IReadOnlyModel, position: EditorPosition, token: CancellationToken): Thenable<modes.Hover> => {
|
||||
return wireCancellationToken(token, this._proxy.$provideHover(handle, model.uri, position));
|
||||
}
|
||||
});
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// --- occurrences
|
||||
|
||||
$registerDocumentHighlightProvider(handle: number, selector: vscode.DocumentSelector): TPromise<any> {
|
||||
this._registrations[handle] = modes.DocumentHighlightProviderRegistry.register(selector, <modes.DocumentHighlightProvider>{
|
||||
provideDocumentHighlights: (model: IReadOnlyModel, position: EditorPosition, token: CancellationToken): Thenable<modes.DocumentHighlight[]> => {
|
||||
return wireCancellationToken(token, this._proxy.$provideDocumentHighlights(handle, model.uri, position));
|
||||
}
|
||||
});
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// --- references
|
||||
|
||||
$registerReferenceSupport(handle: number, selector: vscode.DocumentSelector): TPromise<any> {
|
||||
this._registrations[handle] = modes.ReferenceProviderRegistry.register(selector, <modes.ReferenceProvider>{
|
||||
provideReferences: (model: IReadOnlyModel, position: EditorPosition, context: modes.ReferenceContext, token: CancellationToken): Thenable<modes.Location[]> => {
|
||||
return wireCancellationToken(token, this._proxy.$provideReferences(handle, model.uri, position, context));
|
||||
}
|
||||
});
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// --- quick fix
|
||||
|
||||
$registerQuickFixSupport(handle: number, selector: vscode.DocumentSelector): TPromise<any> {
|
||||
this._registrations[handle] = modes.CodeActionProviderRegistry.register(selector, <modes.CodeActionProvider>{
|
||||
provideCodeActions: (model: IReadOnlyModel, range: EditorRange, token: CancellationToken): Thenable<modes.Command[]> => {
|
||||
return this._heapService.trackRecursive(wireCancellationToken(token, this._proxy.$provideCodeActions(handle, model.uri, range)));
|
||||
}
|
||||
});
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// --- formatting
|
||||
|
||||
$registerDocumentFormattingSupport(handle: number, selector: vscode.DocumentSelector): TPromise<any> {
|
||||
this._registrations[handle] = modes.DocumentFormattingEditProviderRegistry.register(selector, <modes.DocumentFormattingEditProvider>{
|
||||
provideDocumentFormattingEdits: (model: IReadOnlyModel, options: modes.FormattingOptions, token: CancellationToken): Thenable<ISingleEditOperation[]> => {
|
||||
return wireCancellationToken(token, this._proxy.$provideDocumentFormattingEdits(handle, model.uri, options));
|
||||
}
|
||||
});
|
||||
return undefined;
|
||||
}
|
||||
|
||||
$registerRangeFormattingSupport(handle: number, selector: vscode.DocumentSelector): TPromise<any> {
|
||||
this._registrations[handle] = modes.DocumentRangeFormattingEditProviderRegistry.register(selector, <modes.DocumentRangeFormattingEditProvider>{
|
||||
provideDocumentRangeFormattingEdits: (model: IReadOnlyModel, range: EditorRange, options: modes.FormattingOptions, token: CancellationToken): Thenable<ISingleEditOperation[]> => {
|
||||
return wireCancellationToken(token, this._proxy.$provideDocumentRangeFormattingEdits(handle, model.uri, range, options));
|
||||
}
|
||||
});
|
||||
return undefined;
|
||||
}
|
||||
|
||||
$registerOnTypeFormattingSupport(handle: number, selector: vscode.DocumentSelector, autoFormatTriggerCharacters: string[]): TPromise<any> {
|
||||
this._registrations[handle] = modes.OnTypeFormattingEditProviderRegistry.register(selector, <modes.OnTypeFormattingEditProvider>{
|
||||
|
||||
autoFormatTriggerCharacters,
|
||||
|
||||
provideOnTypeFormattingEdits: (model: IReadOnlyModel, position: EditorPosition, ch: string, options: modes.FormattingOptions, token: CancellationToken): Thenable<ISingleEditOperation[]> => {
|
||||
return wireCancellationToken(token, this._proxy.$provideOnTypeFormattingEdits(handle, model.uri, position, ch, options));
|
||||
}
|
||||
});
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// --- navigate type
|
||||
|
||||
$registerNavigateTypeSupport(handle: number): TPromise<any> {
|
||||
this._registrations[handle] = WorkspaceSymbolProviderRegistry.register(<IWorkspaceSymbolProvider>{
|
||||
provideWorkspaceSymbols: (search: string): TPromise<modes.SymbolInformation[]> => {
|
||||
return this._heapService.trackRecursive(this._proxy.$provideWorkspaceSymbols(handle, search));
|
||||
},
|
||||
resolveWorkspaceSymbol: (item: modes.SymbolInformation): TPromise<modes.SymbolInformation> => {
|
||||
return this._proxy.$resolveWorkspaceSymbol(handle, item);
|
||||
}
|
||||
});
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// --- rename
|
||||
|
||||
$registerRenameSupport(handle: number, selector: vscode.DocumentSelector): TPromise<any> {
|
||||
this._registrations[handle] = modes.RenameProviderRegistry.register(selector, <modes.RenameProvider>{
|
||||
provideRenameEdits: (model: IReadOnlyModel, position: EditorPosition, newName: string, token: CancellationToken): Thenable<modes.WorkspaceEdit> => {
|
||||
return wireCancellationToken(token, this._proxy.$provideRenameEdits(handle, model.uri, position, newName));
|
||||
}
|
||||
});
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// --- suggest
|
||||
|
||||
$registerSuggestSupport(handle: number, selector: vscode.DocumentSelector, triggerCharacters: string[]): TPromise<any> {
|
||||
this._registrations[handle] = modes.SuggestRegistry.register(selector, <modes.ISuggestSupport>{
|
||||
triggerCharacters,
|
||||
provideCompletionItems: (model: IReadOnlyModel, position: EditorPosition, token: CancellationToken): Thenable<modes.ISuggestResult> => {
|
||||
return this._heapService.trackRecursive(wireCancellationToken(token, this._proxy.$provideCompletionItems(handle, model.uri, position)));
|
||||
},
|
||||
resolveCompletionItem: (model: IReadOnlyModel, position: EditorPosition, suggestion: modes.ISuggestion, token: CancellationToken): Thenable<modes.ISuggestion> => {
|
||||
return wireCancellationToken(token, this._proxy.$resolveCompletionItem(handle, model.uri, position, suggestion));
|
||||
}
|
||||
});
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// --- parameter hints
|
||||
|
||||
$registerSignatureHelpProvider(handle: number, selector: vscode.DocumentSelector, triggerCharacter: string[]): TPromise<any> {
|
||||
this._registrations[handle] = modes.SignatureHelpProviderRegistry.register(selector, <modes.SignatureHelpProvider>{
|
||||
|
||||
signatureHelpTriggerCharacters: triggerCharacter,
|
||||
|
||||
provideSignatureHelp: (model: IReadOnlyModel, position: EditorPosition, token: CancellationToken): Thenable<modes.SignatureHelp> => {
|
||||
return wireCancellationToken(token, this._proxy.$provideSignatureHelp(handle, model.uri, position));
|
||||
}
|
||||
|
||||
});
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// --- links
|
||||
|
||||
$registerDocumentLinkProvider(handle: number, selector: vscode.DocumentSelector): TPromise<any> {
|
||||
this._registrations[handle] = modes.LinkProviderRegistry.register(selector, <modes.LinkProvider>{
|
||||
provideLinks: (model, token) => {
|
||||
return this._heapService.trackRecursive(wireCancellationToken(token, this._proxy.$provideDocumentLinks(handle, model.uri)));
|
||||
},
|
||||
resolveLink: (link, token) => {
|
||||
return wireCancellationToken(token, this._proxy.$resolveDocumentLink(handle, link));
|
||||
}
|
||||
});
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// --- colors
|
||||
|
||||
$registerDocumentColorProvider(handle: number, selector: vscode.DocumentSelector): TPromise<any> {
|
||||
const proxy = this._proxy;
|
||||
this._registrations[handle] = modes.ColorProviderRegistry.register(selector, <modes.DocumentColorProvider>{
|
||||
provideColorRanges: (model, token) => {
|
||||
return wireCancellationToken(token, proxy.$provideDocumentColors(handle, model.uri))
|
||||
.then(documentColors => {
|
||||
return documentColors.map(documentColor => {
|
||||
const formatters = documentColor.availableFormats.map(f => {
|
||||
if (typeof f === 'number') {
|
||||
return this._formatters.get(f);
|
||||
} else {
|
||||
return new CombinedColorFormatter(this._formatters.get(f[0]), this._formatters.get(f[1]));
|
||||
}
|
||||
});
|
||||
|
||||
const [red, green, blue, alpha] = documentColor.color;
|
||||
const color = {
|
||||
red: red / 255.0,
|
||||
green: green / 255.0,
|
||||
blue: blue / 255.0,
|
||||
alpha
|
||||
};
|
||||
|
||||
return {
|
||||
color,
|
||||
formatters,
|
||||
range: documentColor.range
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
$registerColorFormats(formats: IRawColorFormatMap): TPromise<any> {
|
||||
formats.forEach(f => this._formatters.set(f[0], new ColorFormatter(f[1])));
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
// --- configuration
|
||||
|
||||
$setLanguageConfiguration(handle: number, languageId: string, _configuration: vscode.LanguageConfiguration): TPromise<any> {
|
||||
|
||||
let configuration: LanguageConfiguration = {
|
||||
comments: _configuration.comments,
|
||||
brackets: _configuration.brackets,
|
||||
wordPattern: _configuration.wordPattern,
|
||||
indentationRules: _configuration.indentationRules,
|
||||
onEnterRules: _configuration.onEnterRules,
|
||||
|
||||
autoClosingPairs: null,
|
||||
surroundingPairs: null,
|
||||
__electricCharacterSupport: null
|
||||
};
|
||||
|
||||
if (_configuration.__characterPairSupport) {
|
||||
// backwards compatibility
|
||||
configuration.autoClosingPairs = _configuration.__characterPairSupport.autoClosingPairs;
|
||||
}
|
||||
|
||||
if (_configuration.__electricCharacterSupport && _configuration.__electricCharacterSupport.docComment) {
|
||||
configuration.__electricCharacterSupport = {
|
||||
docComment: {
|
||||
open: _configuration.__electricCharacterSupport.docComment.open,
|
||||
close: _configuration.__electricCharacterSupport.docComment.close
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let languageIdentifier = this._modeService.getLanguageIdentifier(languageId);
|
||||
if (languageIdentifier) {
|
||||
this._registrations[handle] = LanguageConfigurationRegistry.register(languageIdentifier, configuration);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
}
|
||||
30
src/vs/workbench/api/electron-browser/mainThreadLanguages.ts
Normal file
30
src/vs/workbench/api/electron-browser/mainThreadLanguages.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
import { MainThreadLanguagesShape, MainContext, IExtHostContext } from '../node/extHost.protocol';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadLanguages)
|
||||
export class MainThreadLanguages implements MainThreadLanguagesShape {
|
||||
|
||||
private _modeService: IModeService;
|
||||
|
||||
constructor(
|
||||
extHostContext: IExtHostContext,
|
||||
@IModeService modeService: IModeService
|
||||
) {
|
||||
this._modeService = modeService;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
}
|
||||
|
||||
$getLanguages(): TPromise<string[]> {
|
||||
return TPromise.as(this._modeService.getRegisteredModes());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import nls = require('vs/nls');
|
||||
import { IMessageService, IChoiceService } from 'vs/platform/message/common/message';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { TPromise as Promise } from 'vs/base/common/winjs.base';
|
||||
import { MainThreadMessageServiceShape, MainContext, IExtHostContext, MainThreadMessageOptions } from '../node/extHost.protocol';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
|
||||
import { IExtensionService, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadMessageService)
|
||||
export class MainThreadMessageService implements MainThreadMessageServiceShape {
|
||||
|
||||
constructor(
|
||||
extHostContext: IExtHostContext,
|
||||
@IExtensionService private readonly _extensionService: IExtensionService,
|
||||
@IMessageService private readonly _messageService: IMessageService,
|
||||
@IChoiceService private readonly _choiceService: IChoiceService
|
||||
) {
|
||||
//
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
//
|
||||
}
|
||||
|
||||
$showMessage(severity: Severity, message: string, options: MainThreadMessageOptions, commands: { title: string; isCloseAffordance: boolean; handle: number; }[]): Thenable<number> {
|
||||
if (options.modal) {
|
||||
return this._showModalMessage(severity, message, commands);
|
||||
} else {
|
||||
return this._showMessage(severity, message, commands, options.extension);
|
||||
}
|
||||
}
|
||||
|
||||
private _showMessage(severity: Severity, message: string, commands: { title: string; isCloseAffordance: boolean; handle: number; }[], extension: IExtensionDescription): Thenable<number> {
|
||||
|
||||
return new Promise<number>(resolve => {
|
||||
|
||||
let messageHide: Function;
|
||||
let actions: MessageItemAction[] = [];
|
||||
let hasCloseAffordance = false;
|
||||
|
||||
class MessageItemAction extends Action {
|
||||
constructor(id: string, label: string, handle: number) {
|
||||
super(id, label, undefined, true, () => {
|
||||
resolve(handle);
|
||||
messageHide(); // triggers dispose! make sure promise is already resolved
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
dispose(): void {
|
||||
resolve(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
commands.forEach(command => {
|
||||
if (command.isCloseAffordance === true) {
|
||||
hasCloseAffordance = true;
|
||||
}
|
||||
actions.push(new MessageItemAction('_extension_message_handle_' + command.handle, command.title, command.handle));
|
||||
});
|
||||
|
||||
if (!hasCloseAffordance) {
|
||||
actions.push(new MessageItemAction('__close', nls.localize('close', "Close"), undefined));
|
||||
}
|
||||
|
||||
messageHide = this._messageService.show(severity, {
|
||||
message,
|
||||
actions,
|
||||
source: extension && `${extension.displayName || extension.name}`
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private _showModalMessage(severity: Severity, message: string, commands: { title: string; isCloseAffordance: boolean; handle: number; }[]): Thenable<number> {
|
||||
let cancelId: number | undefined = void 0;
|
||||
|
||||
const options = commands.map((command, index) => {
|
||||
if (command.isCloseAffordance === true) {
|
||||
cancelId = index;
|
||||
}
|
||||
|
||||
return command.title;
|
||||
});
|
||||
|
||||
if (cancelId === void 0) {
|
||||
if (options.length > 0) {
|
||||
options.push(nls.localize('cancel', "Cancel"));
|
||||
} else {
|
||||
options.push(nls.localize('ok', "OK"));
|
||||
}
|
||||
|
||||
cancelId = options.length - 1;
|
||||
}
|
||||
|
||||
return this._choiceService.choose(severity, message, options, cancelId, true)
|
||||
.then(result => result === commands.length ? undefined : commands[result].handle);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IOutputService, IOutputChannel, OUTPUT_PANEL_ID, Extensions, IOutputChannelRegistry } from 'vs/workbench/parts/output/common/output';
|
||||
import { IPartService } from 'vs/workbench/services/part/common/partService';
|
||||
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
|
||||
import { MainThreadOutputServiceShape, MainContext, IExtHostContext } from '../node/extHost.protocol';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadOutputService)
|
||||
export class MainThreadOutputService implements MainThreadOutputServiceShape {
|
||||
|
||||
private readonly _outputService: IOutputService;
|
||||
private readonly _partService: IPartService;
|
||||
private readonly _panelService: IPanelService;
|
||||
|
||||
constructor(
|
||||
extHostContext: IExtHostContext,
|
||||
@IOutputService outputService: IOutputService,
|
||||
@IPartService partService: IPartService,
|
||||
@IPanelService panelService: IPanelService
|
||||
) {
|
||||
this._outputService = outputService;
|
||||
this._partService = partService;
|
||||
this._panelService = panelService;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
// Leave all the existing channels intact (e.g. might help with troubleshooting)
|
||||
}
|
||||
|
||||
public $append(channelId: string, label: string, value: string): TPromise<void> {
|
||||
this._getChannel(channelId, label).append(value);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public $clear(channelId: string, label: string): TPromise<void> {
|
||||
this._getChannel(channelId, label).clear();
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public $reveal(channelId: string, label: string, preserveFocus: boolean): TPromise<void> {
|
||||
this._getChannel(channelId, label).show(preserveFocus);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private _getChannel(channelId: string, label: string): IOutputChannel {
|
||||
if (!Registry.as<IOutputChannelRegistry>(Extensions.OutputChannels).getChannel(channelId)) {
|
||||
Registry.as<IOutputChannelRegistry>(Extensions.OutputChannels).registerChannel(channelId, label);
|
||||
}
|
||||
|
||||
return this._outputService.getChannel(channelId);
|
||||
}
|
||||
|
||||
public $close(channelId: string): TPromise<void> {
|
||||
const panel = this._panelService.getActivePanel();
|
||||
if (panel && panel.getId() === OUTPUT_PANEL_ID && channelId === this._outputService.getActiveChannel().id) {
|
||||
return this._partService.setPanelHidden(true);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public $dispose(channelId: string, label: string): TPromise<void> {
|
||||
this._getChannel(channelId, label).dispose();
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
51
src/vs/workbench/api/electron-browser/mainThreadProgress.ts
Normal file
51
src/vs/workbench/api/electron-browser/mainThreadProgress.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { IProgressService2, IProgress, IProgressOptions, IProgressStep } from 'vs/platform/progress/common/progress';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { MainThreadProgressShape, MainContext, IExtHostContext } from '../node/extHost.protocol';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadProgress)
|
||||
export class MainThreadProgress implements MainThreadProgressShape {
|
||||
|
||||
private _progressService: IProgressService2;
|
||||
private _progress = new Map<number, { resolve: Function, progress: IProgress<IProgressStep> }>();
|
||||
|
||||
constructor(
|
||||
extHostContext: IExtHostContext,
|
||||
@IProgressService2 progressService: IProgressService2
|
||||
) {
|
||||
this._progressService = progressService;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._progress.forEach(handle => handle.resolve());
|
||||
this._progress.clear();
|
||||
}
|
||||
|
||||
$startProgress(handle: number, options: IProgressOptions): void {
|
||||
const task = this._createTask(handle);
|
||||
this._progressService.withProgress(options, task);
|
||||
}
|
||||
|
||||
$progressReport(handle: number, message: IProgressStep): void {
|
||||
this._progress.get(handle).progress.report(message);
|
||||
}
|
||||
|
||||
$progressEnd(handle: number): void {
|
||||
this._progress.get(handle).resolve();
|
||||
this._progress.delete(handle);
|
||||
}
|
||||
|
||||
private _createTask(handle: number) {
|
||||
return (progress: IProgress<IProgressStep>) => {
|
||||
return new TPromise<any>(resolve => {
|
||||
this._progress.set(handle, { resolve, progress });
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
102
src/vs/workbench/api/electron-browser/mainThreadQuickOpen.ts
Normal file
102
src/vs/workbench/api/electron-browser/mainThreadQuickOpen.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { asWinJsPromise } from 'vs/base/common/async';
|
||||
import { IQuickOpenService, IPickOptions, IInputOptions } from 'vs/platform/quickOpen/common/quickOpen';
|
||||
import { InputBoxOptions } from 'vscode';
|
||||
import { ExtHostContext, MainThreadQuickOpenShape, ExtHostQuickOpenShape, MyQuickPickItems, MainContext, IExtHostContext } from '../node/extHost.protocol';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadQuickOpen)
|
||||
export class MainThreadQuickOpen implements MainThreadQuickOpenShape {
|
||||
|
||||
private _proxy: ExtHostQuickOpenShape;
|
||||
private _quickOpenService: IQuickOpenService;
|
||||
private _doSetItems: (items: MyQuickPickItems[]) => any;
|
||||
private _doSetError: (error: Error) => any;
|
||||
private _contents: TPromise<MyQuickPickItems[]>;
|
||||
private _token: number = 0;
|
||||
|
||||
constructor(
|
||||
extHostContext: IExtHostContext,
|
||||
@IQuickOpenService quickOpenService: IQuickOpenService
|
||||
) {
|
||||
this._proxy = extHostContext.get(ExtHostContext.ExtHostQuickOpen);
|
||||
this._quickOpenService = quickOpenService;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
}
|
||||
|
||||
$show(options: IPickOptions): TPromise<number> {
|
||||
|
||||
const myToken = ++this._token;
|
||||
|
||||
this._contents = new TPromise<MyQuickPickItems[]>((c, e) => {
|
||||
this._doSetItems = (items) => {
|
||||
if (myToken === this._token) {
|
||||
c(items);
|
||||
}
|
||||
};
|
||||
|
||||
this._doSetError = (error) => {
|
||||
if (myToken === this._token) {
|
||||
e(error);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
return asWinJsPromise(token => this._quickOpenService.pick(this._contents, options, token)).then(item => {
|
||||
if (item) {
|
||||
return item.handle;
|
||||
}
|
||||
return undefined;
|
||||
}, undefined, progress => {
|
||||
if (progress) {
|
||||
this._proxy.$onItemSelected((<MyQuickPickItems>progress).handle);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$setItems(items: MyQuickPickItems[]): TPromise<any> {
|
||||
if (this._doSetItems) {
|
||||
this._doSetItems(items);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
$setError(error: Error): TPromise<any> {
|
||||
if (this._doSetError) {
|
||||
this._doSetError(error);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// ---- input
|
||||
|
||||
$input(options: InputBoxOptions, validateInput: boolean): TPromise<string> {
|
||||
|
||||
const inputOptions: IInputOptions = Object.create(null);
|
||||
|
||||
if (options) {
|
||||
inputOptions.password = options.password;
|
||||
inputOptions.placeHolder = options.placeHolder;
|
||||
inputOptions.valueSelection = options.valueSelection;
|
||||
inputOptions.prompt = options.prompt;
|
||||
inputOptions.value = options.value;
|
||||
inputOptions.ignoreFocusLost = options.ignoreFocusOut;
|
||||
}
|
||||
|
||||
if (validateInput) {
|
||||
inputOptions.validateInput = (value) => {
|
||||
return this._proxy.$validateInput(value);
|
||||
};
|
||||
}
|
||||
|
||||
return asWinJsPromise(token => this._quickOpenService.input(inputOptions, token));
|
||||
}
|
||||
}
|
||||
346
src/vs/workbench/api/electron-browser/mainThreadSCM.ts
Normal file
346
src/vs/workbench/api/electron-browser/mainThreadSCM.ts
Normal file
@@ -0,0 +1,346 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { assign } from 'vs/base/common/objects';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { ISCMService, ISCMRepository, ISCMProvider, ISCMResource, ISCMResourceGroup, ISCMResourceDecorations } from 'vs/workbench/services/scm/common/scm';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { ExtHostContext, MainThreadSCMShape, ExtHostSCMShape, SCMProviderFeatures, SCMRawResource, SCMGroupFeatures, MainContext, IExtHostContext } from '../node/extHost.protocol';
|
||||
import { Command } from 'vs/editor/common/modes';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
|
||||
|
||||
class MainThreadSCMResourceGroup implements ISCMResourceGroup {
|
||||
|
||||
constructor(
|
||||
private sourceControlHandle: number,
|
||||
private handle: number,
|
||||
public provider: ISCMProvider,
|
||||
public features: SCMGroupFeatures,
|
||||
public label: string,
|
||||
public id: string,
|
||||
public resources: ISCMResource[]
|
||||
) { }
|
||||
|
||||
toJSON(): any {
|
||||
return {
|
||||
$mid: 4,
|
||||
sourceControlHandle: this.sourceControlHandle,
|
||||
groupHandle: this.handle
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class MainThreadSCMResource implements ISCMResource {
|
||||
|
||||
constructor(
|
||||
private sourceControlHandle: number,
|
||||
private groupHandle: number,
|
||||
private handle: number,
|
||||
public sourceUri: URI,
|
||||
public command: Command | undefined,
|
||||
public resourceGroup: ISCMResourceGroup,
|
||||
public decorations: ISCMResourceDecorations
|
||||
) { }
|
||||
|
||||
toJSON(): any {
|
||||
return {
|
||||
$mid: 3,
|
||||
sourceControlHandle: this.sourceControlHandle,
|
||||
groupHandle: this.groupHandle,
|
||||
handle: this.handle
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class MainThreadSCMProvider implements ISCMProvider {
|
||||
|
||||
private static ID_HANDLE = 0;
|
||||
private _id = `scm${MainThreadSCMProvider.ID_HANDLE++}`;
|
||||
get id(): string { return this._id; }
|
||||
|
||||
private _groups: MainThreadSCMResourceGroup[] = [];
|
||||
private _groupsByHandle: { [handle: number]: MainThreadSCMResourceGroup; } = Object.create(null);
|
||||
|
||||
get resources(): ISCMResourceGroup[] {
|
||||
return this._groups
|
||||
.filter(g => g.resources.length > 0 || !g.features.hideWhenEmpty);
|
||||
}
|
||||
|
||||
private _onDidChange = new Emitter<void>();
|
||||
get onDidChange(): Event<void> { return this._onDidChange.event; }
|
||||
|
||||
private features: SCMProviderFeatures = {};
|
||||
|
||||
get handle(): number { return this._handle; }
|
||||
get label(): string { return this._label; }
|
||||
get contextValue(): string { return this._contextValue; }
|
||||
|
||||
get commitTemplate(): string | undefined { return this.features.commitTemplate; }
|
||||
get acceptInputCommand(): Command | undefined { return this.features.acceptInputCommand; }
|
||||
get statusBarCommands(): Command[] | undefined { return this.features.statusBarCommands; }
|
||||
|
||||
private _onDidChangeCommitTemplate = new Emitter<string>();
|
||||
get onDidChangeCommitTemplate(): Event<string> { return this._onDidChangeCommitTemplate.event; }
|
||||
|
||||
private _count: number | undefined = undefined;
|
||||
get count(): number | undefined { return this._count; }
|
||||
|
||||
constructor(
|
||||
private proxy: ExtHostSCMShape,
|
||||
private _handle: number,
|
||||
private _contextValue: string,
|
||||
private _label: string,
|
||||
@ISCMService scmService: ISCMService,
|
||||
@ICommandService private commandService: ICommandService
|
||||
) { }
|
||||
|
||||
$updateSourceControl(features: SCMProviderFeatures): void {
|
||||
if ('count' in features) {
|
||||
this._count = features.count;
|
||||
}
|
||||
|
||||
this.features = assign(this.features, features);
|
||||
this._onDidChange.fire();
|
||||
|
||||
if (typeof features.commitTemplate !== 'undefined') {
|
||||
this._onDidChangeCommitTemplate.fire(this.commitTemplate);
|
||||
}
|
||||
}
|
||||
|
||||
$registerGroup(handle: number, id: string, label: string): void {
|
||||
const group = new MainThreadSCMResourceGroup(
|
||||
this.handle,
|
||||
handle,
|
||||
this,
|
||||
{},
|
||||
label,
|
||||
id,
|
||||
[]
|
||||
);
|
||||
|
||||
this._groups.push(group);
|
||||
this._groupsByHandle[handle] = group;
|
||||
}
|
||||
|
||||
$updateGroup(handle: number, features: SCMGroupFeatures): void {
|
||||
const group = this._groupsByHandle[handle];
|
||||
|
||||
if (!group) {
|
||||
return;
|
||||
}
|
||||
|
||||
group.features = assign(group.features, features);
|
||||
this._onDidChange.fire();
|
||||
}
|
||||
|
||||
$updateGroupLabel(handle: number, label: string): void {
|
||||
const group = this._groupsByHandle[handle];
|
||||
|
||||
if (!group) {
|
||||
return;
|
||||
}
|
||||
|
||||
group.label = label;
|
||||
this._onDidChange.fire();
|
||||
}
|
||||
|
||||
$updateGroupResourceStates(groupHandle: number, resources: SCMRawResource[]): void {
|
||||
const group = this._groupsByHandle[groupHandle];
|
||||
|
||||
if (!group) {
|
||||
return;
|
||||
}
|
||||
|
||||
group.resources = resources.map(rawResource => {
|
||||
const [handle, sourceUri, command, icons, tooltip, strikeThrough, faded] = rawResource;
|
||||
const icon = icons[0];
|
||||
const iconDark = icons[1] || icon;
|
||||
const decorations = {
|
||||
icon: icon && URI.parse(icon),
|
||||
iconDark: iconDark && URI.parse(iconDark),
|
||||
tooltip,
|
||||
strikeThrough,
|
||||
faded
|
||||
};
|
||||
|
||||
return new MainThreadSCMResource(
|
||||
this.handle,
|
||||
groupHandle,
|
||||
handle,
|
||||
URI.parse(sourceUri),
|
||||
command,
|
||||
group,
|
||||
decorations
|
||||
);
|
||||
});
|
||||
|
||||
this._onDidChange.fire();
|
||||
}
|
||||
|
||||
$unregisterGroup(handle: number): void {
|
||||
const group = this._groupsByHandle[handle];
|
||||
|
||||
if (!group) {
|
||||
return;
|
||||
}
|
||||
|
||||
delete this._groupsByHandle[handle];
|
||||
this._groups.splice(this._groups.indexOf(group), 1);
|
||||
}
|
||||
|
||||
getOriginalResource(uri: URI): TPromise<URI> {
|
||||
if (!this.features.hasQuickDiffProvider) {
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
return this.proxy.$provideOriginalResource(this.handle, uri);
|
||||
}
|
||||
|
||||
toJSON(): any {
|
||||
return {
|
||||
$mid: 5,
|
||||
handle: this.handle
|
||||
};
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadSCM)
|
||||
export class MainThreadSCM implements MainThreadSCMShape {
|
||||
|
||||
private _proxy: ExtHostSCMShape;
|
||||
private _repositories: { [handle: number]: ISCMRepository; } = Object.create(null);
|
||||
private _inputDisposables: { [handle: number]: IDisposable; } = Object.create(null);
|
||||
private _disposables: IDisposable[] = [];
|
||||
|
||||
constructor(
|
||||
extHostContext: IExtHostContext,
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@ISCMService private scmService: ISCMService,
|
||||
@ICommandService private commandService: ICommandService
|
||||
) {
|
||||
this._proxy = extHostContext.get(ExtHostContext.ExtHostSCM);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
Object.keys(this._repositories)
|
||||
.forEach(id => this._repositories[id].dispose());
|
||||
this._repositories = Object.create(null);
|
||||
|
||||
Object.keys(this._inputDisposables)
|
||||
.forEach(id => this._inputDisposables[id].dispose());
|
||||
this._inputDisposables = Object.create(null);
|
||||
|
||||
this._disposables = dispose(this._disposables);
|
||||
}
|
||||
|
||||
$registerSourceControl(handle: number, id: string, label: string): void {
|
||||
const provider = new MainThreadSCMProvider(this._proxy, handle, id, label, this.scmService, this.commandService);
|
||||
const repository = this.scmService.registerSCMProvider(provider);
|
||||
this._repositories[handle] = repository;
|
||||
|
||||
const inputDisposable = repository.input.onDidChange(value => this._proxy.$onInputBoxValueChange(handle, value));
|
||||
this._inputDisposables[handle] = inputDisposable;
|
||||
}
|
||||
|
||||
$updateSourceControl(handle: number, features: SCMProviderFeatures): void {
|
||||
const repository = this._repositories[handle];
|
||||
|
||||
if (!repository) {
|
||||
return;
|
||||
}
|
||||
|
||||
const provider = repository.provider as MainThreadSCMProvider;
|
||||
provider.$updateSourceControl(features);
|
||||
}
|
||||
|
||||
$unregisterSourceControl(handle: number): void {
|
||||
const repository = this._repositories[handle];
|
||||
|
||||
if (!repository) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._inputDisposables[handle].dispose();
|
||||
delete this._inputDisposables[handle];
|
||||
|
||||
repository.dispose();
|
||||
delete this._repositories[handle];
|
||||
}
|
||||
|
||||
$registerGroup(sourceControlHandle: number, groupHandle: number, id: string, label: string): void {
|
||||
const repository = this._repositories[sourceControlHandle];
|
||||
|
||||
if (!repository) {
|
||||
return;
|
||||
}
|
||||
|
||||
const provider = repository.provider as MainThreadSCMProvider;
|
||||
provider.$registerGroup(groupHandle, id, label);
|
||||
}
|
||||
|
||||
$updateGroup(sourceControlHandle: number, groupHandle: number, features: SCMGroupFeatures): void {
|
||||
const repository = this._repositories[sourceControlHandle];
|
||||
|
||||
if (!repository) {
|
||||
return;
|
||||
}
|
||||
|
||||
const provider = repository.provider as MainThreadSCMProvider;
|
||||
provider.$updateGroup(groupHandle, features);
|
||||
}
|
||||
|
||||
$updateGroupLabel(sourceControlHandle: number, groupHandle: number, label: string): void {
|
||||
const repository = this._repositories[sourceControlHandle];
|
||||
|
||||
if (!repository) {
|
||||
return;
|
||||
}
|
||||
|
||||
const provider = repository.provider as MainThreadSCMProvider;
|
||||
provider.$updateGroupLabel(groupHandle, label);
|
||||
}
|
||||
|
||||
$updateGroupResourceStates(sourceControlHandle: number, groupHandle: number, resources: SCMRawResource[]): void {
|
||||
const repository = this._repositories[sourceControlHandle];
|
||||
|
||||
if (!repository) {
|
||||
return;
|
||||
}
|
||||
|
||||
const provider = repository.provider as MainThreadSCMProvider;
|
||||
provider.$updateGroupResourceStates(groupHandle, resources);
|
||||
}
|
||||
|
||||
$unregisterGroup(sourceControlHandle: number, handle: number): void {
|
||||
const repository = this._repositories[sourceControlHandle];
|
||||
|
||||
if (!repository) {
|
||||
return;
|
||||
}
|
||||
|
||||
const provider = repository.provider as MainThreadSCMProvider;
|
||||
provider.$unregisterGroup(handle);
|
||||
}
|
||||
|
||||
$setInputBoxValue(sourceControlHandle: number, value: string): void {
|
||||
const repository = this._repositories[sourceControlHandle];
|
||||
|
||||
if (!repository) {
|
||||
return;
|
||||
}
|
||||
|
||||
repository.input.value = value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,272 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { sequence } from 'vs/base/common/async';
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
import { ICodeEditorService } from 'vs/editor/common/services/codeEditorService';
|
||||
import { ISaveParticipant, ITextFileEditorModel, SaveReason } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IModel, ICommonCodeEditor, ISingleEditOperation, IIdentifiedSingleEditOperation } from 'vs/editor/common/editorCommon';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { Selection } from 'vs/editor/common/core/selection';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { trimTrailingWhitespace } from 'vs/editor/common/commands/trimTrailingWhitespaceCommand';
|
||||
import { getDocumentFormattingEdits } from 'vs/editor/contrib/format/common/format';
|
||||
import { EditOperationsCommand } from 'vs/editor/contrib/format/common/formatCommand';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel';
|
||||
import { ExtHostContext, ExtHostDocumentSaveParticipantShape, IExtHostContext } from '../node/extHost.protocol';
|
||||
import { EditOperation } from 'vs/editor/common/core/editOperation';
|
||||
import { extHostCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
|
||||
|
||||
export interface INamedSaveParticpant extends ISaveParticipant {
|
||||
readonly name: string;
|
||||
}
|
||||
|
||||
class TrimWhitespaceParticipant implements INamedSaveParticpant {
|
||||
|
||||
readonly name = 'TrimWhitespaceParticipant';
|
||||
|
||||
constructor(
|
||||
@IConfigurationService private configurationService: IConfigurationService,
|
||||
@ICodeEditorService private codeEditorService: ICodeEditorService
|
||||
) {
|
||||
// Nothing
|
||||
}
|
||||
|
||||
public participate(model: ITextFileEditorModel, env: { reason: SaveReason }): void {
|
||||
if (this.configurationService.lookup('files.trimTrailingWhitespace', { overrideIdentifier: model.textEditorModel.getLanguageIdentifier().language, resource: model.getResource() }).value) {
|
||||
this.doTrimTrailingWhitespace(model.textEditorModel, env.reason === SaveReason.AUTO);
|
||||
}
|
||||
}
|
||||
|
||||
private doTrimTrailingWhitespace(model: IModel, isAutoSaved: boolean): void {
|
||||
let prevSelection: Selection[] = [new Selection(1, 1, 1, 1)];
|
||||
const cursors: Position[] = [];
|
||||
|
||||
let editor = findEditor(model, this.codeEditorService);
|
||||
if (editor) {
|
||||
// Find `prevSelection` in any case do ensure a good undo stack when pushing the edit
|
||||
// Collect active cursors in `cursors` only if `isAutoSaved` to avoid having the cursors jump
|
||||
prevSelection = editor.getSelections();
|
||||
if (isAutoSaved) {
|
||||
cursors.push(...prevSelection.map(s => new Position(s.positionLineNumber, s.positionColumn)));
|
||||
}
|
||||
}
|
||||
|
||||
const ops = trimTrailingWhitespace(model, cursors);
|
||||
if (!ops.length) {
|
||||
return; // Nothing to do
|
||||
}
|
||||
|
||||
model.pushEditOperations(prevSelection, ops, (edits) => prevSelection);
|
||||
}
|
||||
}
|
||||
|
||||
function findEditor(model: IModel, codeEditorService: ICodeEditorService): ICommonCodeEditor {
|
||||
let candidate: ICommonCodeEditor = null;
|
||||
|
||||
if (model.isAttachedToEditor()) {
|
||||
for (const editor of codeEditorService.listCodeEditors()) {
|
||||
if (editor.getModel() === model) {
|
||||
if (editor.isFocused()) {
|
||||
return editor; // favour focused editor if there are multiple
|
||||
}
|
||||
|
||||
candidate = editor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return candidate;
|
||||
}
|
||||
|
||||
export class FinalNewLineParticipant implements INamedSaveParticpant {
|
||||
|
||||
readonly name = 'FinalNewLineParticipant';
|
||||
|
||||
constructor(
|
||||
@IConfigurationService private configurationService: IConfigurationService,
|
||||
@ICodeEditorService private codeEditorService: ICodeEditorService
|
||||
) {
|
||||
// Nothing
|
||||
}
|
||||
|
||||
public participate(model: ITextFileEditorModel, env: { reason: SaveReason }): void {
|
||||
if (this.configurationService.lookup('files.insertFinalNewline', { overrideIdentifier: model.textEditorModel.getLanguageIdentifier().language, resource: model.getResource() }).value) {
|
||||
this.doInsertFinalNewLine(model.textEditorModel);
|
||||
}
|
||||
}
|
||||
|
||||
private doInsertFinalNewLine(model: IModel): void {
|
||||
const lineCount = model.getLineCount();
|
||||
const lastLine = model.getLineContent(lineCount);
|
||||
const lastLineIsEmptyOrWhitespace = strings.lastNonWhitespaceIndex(lastLine) === -1;
|
||||
|
||||
if (!lineCount || lastLineIsEmptyOrWhitespace) {
|
||||
return;
|
||||
}
|
||||
|
||||
let prevSelection: Selection[] = [new Selection(1, 1, 1, 1)];
|
||||
const editor = findEditor(model, this.codeEditorService);
|
||||
if (editor) {
|
||||
prevSelection = editor.getSelections();
|
||||
}
|
||||
|
||||
model.pushEditOperations(prevSelection, [EditOperation.insert(new Position(lineCount, model.getLineMaxColumn(lineCount)), model.getEOL())], edits => prevSelection);
|
||||
|
||||
if (editor) {
|
||||
editor.setSelections(prevSelection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class FormatOnSaveParticipant implements INamedSaveParticpant {
|
||||
|
||||
readonly name = 'FormatOnSaveParticipant';
|
||||
|
||||
constructor(
|
||||
@ICodeEditorService private _editorService: ICodeEditorService,
|
||||
@IConfigurationService private _configurationService: IConfigurationService
|
||||
) {
|
||||
// Nothing
|
||||
}
|
||||
|
||||
participate(editorModel: ITextFileEditorModel, env: { reason: SaveReason }): TPromise<void> {
|
||||
|
||||
const model = editorModel.textEditorModel;
|
||||
if (env.reason === SaveReason.AUTO
|
||||
|| !this._configurationService.lookup('editor.formatOnSave', { overrideIdentifier: model.getLanguageIdentifier().language, resource: editorModel.getResource() }).value) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const versionNow = model.getVersionId();
|
||||
const { tabSize, insertSpaces } = model.getOptions();
|
||||
|
||||
return new TPromise<ISingleEditOperation[]>((resolve, reject) => {
|
||||
setTimeout(reject, 750);
|
||||
getDocumentFormattingEdits(model, { tabSize, insertSpaces }).then(resolve, reject);
|
||||
|
||||
}).then(edits => {
|
||||
if (edits && versionNow === model.getVersionId()) {
|
||||
const editor = findEditor(model, this._editorService);
|
||||
if (editor) {
|
||||
this._editsWithEditor(editor, edits);
|
||||
} else {
|
||||
this._editWithModel(model, edits);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private _editsWithEditor(editor: ICommonCodeEditor, edits: ISingleEditOperation[]): void {
|
||||
EditOperationsCommand.execute(editor, edits);
|
||||
}
|
||||
|
||||
private _editWithModel(model: IModel, edits: ISingleEditOperation[]): void {
|
||||
|
||||
const [{ range }] = edits;
|
||||
const initialSelection = new Selection(range.startLineNumber, range.startColumn, range.endLineNumber, range.endColumn);
|
||||
|
||||
model.pushEditOperations([initialSelection], edits.map(FormatOnSaveParticipant._asIdentEdit), undoEdits => {
|
||||
for (const { range } of undoEdits) {
|
||||
if (Range.areIntersectingOrTouching(range, initialSelection)) {
|
||||
return [new Selection(range.startLineNumber, range.startColumn, range.endLineNumber, range.endColumn)];
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
|
||||
private static _asIdentEdit({ text, range }: ISingleEditOperation): IIdentifiedSingleEditOperation {
|
||||
return {
|
||||
text,
|
||||
range: Range.lift(range),
|
||||
identifier: undefined,
|
||||
forceMoveMarkers: true
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class ExtHostSaveParticipant implements INamedSaveParticpant {
|
||||
|
||||
private _proxy: ExtHostDocumentSaveParticipantShape;
|
||||
|
||||
readonly name = 'ExtHostSaveParticipant';
|
||||
|
||||
constructor(extHostContext: IExtHostContext) {
|
||||
this._proxy = extHostContext.get(ExtHostContext.ExtHostDocumentSaveParticipant);
|
||||
}
|
||||
|
||||
participate(editorModel: ITextFileEditorModel, env: { reason: SaveReason }): TPromise<void> {
|
||||
return new TPromise<any>((resolve, reject) => {
|
||||
setTimeout(reject, 1750);
|
||||
this._proxy.$participateInSave(editorModel.getResource(), env.reason).then(values => {
|
||||
for (const success of values) {
|
||||
if (!success) {
|
||||
return TPromise.wrapError(new Error('listener failed'));
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}).then(resolve, reject);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// The save participant can change a model before its saved to support various scenarios like trimming trailing whitespace
|
||||
@extHostCustomer
|
||||
export class SaveParticipant implements ISaveParticipant {
|
||||
|
||||
private _saveParticipants: INamedSaveParticpant[];
|
||||
|
||||
constructor(
|
||||
extHostContext: IExtHostContext,
|
||||
@ITelemetryService private _telemetryService: ITelemetryService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@ICodeEditorService codeEditorService: ICodeEditorService
|
||||
) {
|
||||
|
||||
this._saveParticipants = [
|
||||
new TrimWhitespaceParticipant(configurationService, codeEditorService),
|
||||
new FormatOnSaveParticipant(codeEditorService, configurationService),
|
||||
new FinalNewLineParticipant(configurationService, codeEditorService),
|
||||
new ExtHostSaveParticipant(extHostContext)
|
||||
];
|
||||
|
||||
// Hook into model
|
||||
TextFileEditorModel.setSaveParticipant(this);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
TextFileEditorModel.setSaveParticipant(undefined);
|
||||
}
|
||||
|
||||
participate(model: ITextFileEditorModel, env: { reason: SaveReason }): TPromise<void> {
|
||||
|
||||
const stats: { [name: string]: number } = Object.create(null);
|
||||
|
||||
const promiseFactory = this._saveParticipants.map(p => () => {
|
||||
|
||||
const { name } = p;
|
||||
const t1 = Date.now();
|
||||
|
||||
return TPromise.as(p.participate(model, env)).then(() => {
|
||||
stats[`Success-${name}`] = Date.now() - t1;
|
||||
}, err => {
|
||||
stats[`Failure-${name}`] = Date.now() - t1;
|
||||
// console.error(err);
|
||||
});
|
||||
});
|
||||
|
||||
return sequence(promiseFactory).then(() => {
|
||||
this._telemetryService.publicLog('saveParticipantStats', stats);
|
||||
});
|
||||
}
|
||||
}
|
||||
49
src/vs/workbench/api/electron-browser/mainThreadStatusBar.ts
Normal file
49
src/vs/workbench/api/electron-browser/mainThreadStatusBar.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { IStatusbarService, StatusbarAlignment as MainThreadStatusBarAlignment } from 'vs/platform/statusbar/common/statusbar';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { MainThreadStatusBarShape, MainContext, IExtHostContext } from '../node/extHost.protocol';
|
||||
import { ThemeColor } from 'vs/platform/theme/common/themeService';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadStatusBar)
|
||||
export class MainThreadStatusBar implements MainThreadStatusBarShape {
|
||||
|
||||
private readonly _entries: { [id: number]: IDisposable };
|
||||
|
||||
constructor(
|
||||
extHostContext: IExtHostContext,
|
||||
@IStatusbarService private readonly _statusbarService: IStatusbarService
|
||||
) {
|
||||
this._entries = Object.create(null);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
for (const key in this._entries) {
|
||||
this._entries[key].dispose();
|
||||
}
|
||||
}
|
||||
|
||||
$setEntry(id: number, extensionId: string, text: string, tooltip: string, command: string, color: string | ThemeColor, alignment: MainThreadStatusBarAlignment, priority: number): void {
|
||||
|
||||
// Dispose any old
|
||||
this.$dispose(id);
|
||||
|
||||
// Add new
|
||||
let entry = this._statusbarService.addEntry({ text, tooltip, command, color, extensionId }, alignment, priority);
|
||||
this._entries[id] = entry;
|
||||
}
|
||||
|
||||
$dispose(id: number) {
|
||||
let disposeable = this._entries[id];
|
||||
if (disposeable) {
|
||||
disposeable.dispose();
|
||||
}
|
||||
|
||||
delete this._entries[id];
|
||||
}
|
||||
}
|
||||
51
src/vs/workbench/api/electron-browser/mainThreadStorage.ts
Normal file
51
src/vs/workbench/api/electron-browser/mainThreadStorage.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { MainThreadStorageShape, MainContext, IExtHostContext } from '../node/extHost.protocol';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadStorage)
|
||||
export class MainThreadStorage implements MainThreadStorageShape {
|
||||
|
||||
private _storageService: IStorageService;
|
||||
|
||||
constructor(
|
||||
extHostContext: IExtHostContext,
|
||||
@IStorageService storageService: IStorageService
|
||||
) {
|
||||
this._storageService = storageService;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
}
|
||||
|
||||
$getValue<T>(shared: boolean, key: string): TPromise<T> {
|
||||
let jsonValue = this._storageService.get(key, shared ? StorageScope.GLOBAL : StorageScope.WORKSPACE);
|
||||
if (!jsonValue) {
|
||||
return TPromise.as(undefined);
|
||||
}
|
||||
let value: T;
|
||||
try {
|
||||
value = JSON.parse(jsonValue);
|
||||
return TPromise.as(value);
|
||||
} catch (err) {
|
||||
return TPromise.wrapError<T>(err);
|
||||
}
|
||||
}
|
||||
|
||||
$setValue(shared: boolean, key: string, value: any): TPromise<any> {
|
||||
let jsonValue: any;
|
||||
try {
|
||||
jsonValue = JSON.stringify(value);
|
||||
this._storageService.store(key, jsonValue, shared ? StorageScope.GLOBAL : StorageScope.WORKSPACE);
|
||||
} catch (err) {
|
||||
return TPromise.wrapError(err);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
50
src/vs/workbench/api/electron-browser/mainThreadTask.ts
Normal file
50
src/vs/workbench/api/electron-browser/mainThreadTask.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
|
||||
import { ITaskService } from 'vs/workbench/parts/tasks/common/taskService';
|
||||
|
||||
import { ExtHostContext, MainThreadTaskShape, ExtHostTaskShape, MainContext, IExtHostContext } from '../node/extHost.protocol';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadTask)
|
||||
export class MainThreadTask implements MainThreadTaskShape {
|
||||
|
||||
private _proxy: ExtHostTaskShape;
|
||||
private _activeHandles: { [handle: number]: boolean; };
|
||||
|
||||
constructor(
|
||||
extHostContext: IExtHostContext,
|
||||
@ITaskService private _taskService: ITaskService
|
||||
) {
|
||||
this._proxy = extHostContext.get(ExtHostContext.ExtHostTask);
|
||||
this._activeHandles = Object.create(null);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
Object.keys(this._activeHandles).forEach((handle) => {
|
||||
this._taskService.unregisterTaskProvider(parseInt(handle, 10));
|
||||
});
|
||||
this._activeHandles = Object.create(null);
|
||||
}
|
||||
|
||||
public $registerTaskProvider(handle: number): TPromise<void> {
|
||||
this._taskService.registerTaskProvider(handle, {
|
||||
provideTasks: () => {
|
||||
return this._proxy.$provideTasks(handle);
|
||||
}
|
||||
});
|
||||
this._activeHandles[handle] = true;
|
||||
return TPromise.as<void>(undefined);
|
||||
}
|
||||
|
||||
public $unregisterTaskProvider(handle: number): TPromise<any> {
|
||||
this._taskService.unregisterTaskProvider(handle);
|
||||
delete this._activeHandles[handle];
|
||||
return TPromise.as<void>(undefined);
|
||||
}
|
||||
}
|
||||
31
src/vs/workbench/api/electron-browser/mainThreadTelemetry.ts
Normal file
31
src/vs/workbench/api/electron-browser/mainThreadTelemetry.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { MainThreadTelemetryShape, MainContext, IExtHostContext } from '../node/extHost.protocol';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadTelemetry)
|
||||
export class MainThreadTelemetry implements MainThreadTelemetryShape {
|
||||
|
||||
private static _name = 'pluginHostTelemetry';
|
||||
|
||||
constructor(
|
||||
extHostContext: IExtHostContext,
|
||||
@ITelemetryService private readonly _telemetryService: ITelemetryService
|
||||
) {
|
||||
//
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
//
|
||||
}
|
||||
|
||||
$publicLog(eventName: string, data: any = Object.create(null)): void {
|
||||
data[MainThreadTelemetry._name] = true;
|
||||
this._telemetryService.publicLog(eventName, data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { ITerminalService, ITerminalInstance, IShellLaunchConfig } from 'vs/workbench/parts/terminal/common/terminal';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { ExtHostContext, ExtHostTerminalServiceShape, MainThreadTerminalServiceShape, MainContext, IExtHostContext } from '../node/extHost.protocol';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadTerminalService)
|
||||
export class MainThreadTerminalService implements MainThreadTerminalServiceShape {
|
||||
|
||||
private _proxy: ExtHostTerminalServiceShape;
|
||||
private _toDispose: IDisposable[];
|
||||
|
||||
constructor(
|
||||
extHostContext: IExtHostContext,
|
||||
@ITerminalService private terminalService: ITerminalService
|
||||
) {
|
||||
this._proxy = extHostContext.get(ExtHostContext.ExtHostTerminalService);
|
||||
this._toDispose = [];
|
||||
this._toDispose.push(terminalService.onInstanceDisposed((terminalInstance) => this._onTerminalDisposed(terminalInstance)));
|
||||
this._toDispose.push(terminalService.onInstanceProcessIdReady((terminalInstance) => this._onTerminalProcessIdReady(terminalInstance)));
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._toDispose = dispose(this._toDispose);
|
||||
|
||||
// TODO@Daniel: Should all the previously created terminals be disposed
|
||||
// when the extension host process goes down ?
|
||||
}
|
||||
|
||||
public $createTerminal(name?: string, shellPath?: string, shellArgs?: string[], waitOnExit?: boolean): TPromise<number> {
|
||||
const shellLaunchConfig: IShellLaunchConfig = {
|
||||
name,
|
||||
executable: shellPath,
|
||||
args: shellArgs,
|
||||
waitOnExit,
|
||||
ignoreConfigurationCwd: true
|
||||
};
|
||||
return TPromise.as(this.terminalService.createInstance(shellLaunchConfig).id);
|
||||
}
|
||||
|
||||
public $show(terminalId: number, preserveFocus: boolean): void {
|
||||
let terminalInstance = this.terminalService.getInstanceFromId(terminalId);
|
||||
if (terminalInstance) {
|
||||
this.terminalService.setActiveInstance(terminalInstance);
|
||||
this.terminalService.showPanel(!preserveFocus);
|
||||
}
|
||||
}
|
||||
|
||||
public $hide(terminalId: number): void {
|
||||
if (this.terminalService.getActiveInstance().id === terminalId) {
|
||||
this.terminalService.hidePanel();
|
||||
}
|
||||
}
|
||||
|
||||
public $dispose(terminalId: number): void {
|
||||
let terminalInstance = this.terminalService.getInstanceFromId(terminalId);
|
||||
if (terminalInstance) {
|
||||
terminalInstance.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public $sendText(terminalId: number, text: string, addNewLine: boolean): void {
|
||||
let terminalInstance = this.terminalService.getInstanceFromId(terminalId);
|
||||
if (terminalInstance) {
|
||||
terminalInstance.sendText(text, addNewLine);
|
||||
}
|
||||
}
|
||||
|
||||
private _onTerminalDisposed(terminalInstance: ITerminalInstance): void {
|
||||
this._proxy.$acceptTerminalClosed(terminalInstance.id);
|
||||
}
|
||||
|
||||
private _onTerminalProcessIdReady(terminalInstance: ITerminalInstance): void {
|
||||
this._proxy.$acceptTerminalProcessId(terminalInstance.id, terminalInstance.processId);
|
||||
}
|
||||
}
|
||||
145
src/vs/workbench/api/electron-browser/mainThreadTreeViews.ts
Normal file
145
src/vs/workbench/api/electron-browser/mainThreadTreeViews.ts
Normal file
@@ -0,0 +1,145 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { ExtHostContext, MainThreadTreeViewsShape, ExtHostTreeViewsShape, MainContext, IExtHostContext } from '../node/extHost.protocol';
|
||||
import { IMessageService, Severity } from 'vs/platform/message/common/message';
|
||||
import { ViewsRegistry } from 'vs/workbench/parts/views/browser/viewsRegistry';
|
||||
import { ITreeViewDataProvider, ITreeItem, TreeItemCollapsibleState } from 'vs/workbench/parts/views/common/views';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadTreeViews)
|
||||
export class MainThreadTreeViews extends Disposable implements MainThreadTreeViewsShape {
|
||||
|
||||
private _proxy: ExtHostTreeViewsShape;
|
||||
|
||||
constructor(
|
||||
extHostContext: IExtHostContext,
|
||||
@IMessageService private messageService: IMessageService
|
||||
) {
|
||||
super();
|
||||
this._proxy = extHostContext.get(ExtHostContext.ExtHostTreeViews);
|
||||
}
|
||||
|
||||
$registerView(treeViewId: string): void {
|
||||
ViewsRegistry.registerTreeViewDataProvider(treeViewId, this._register(new TreeViewDataProvider(treeViewId, this._proxy, this.messageService)));
|
||||
}
|
||||
|
||||
$refresh(treeViewId: string, treeItemHandles: number[]): void {
|
||||
const treeViewDataProvider: TreeViewDataProvider = <TreeViewDataProvider>ViewsRegistry.getTreeViewDataProvider(treeViewId);
|
||||
if (treeViewDataProvider) {
|
||||
treeViewDataProvider.refresh(treeItemHandles);
|
||||
}
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
ViewsRegistry.deregisterTreeViewDataProviders();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
type TreeItemHandle = number;
|
||||
|
||||
class TreeViewDataProvider implements ITreeViewDataProvider {
|
||||
|
||||
private _onDidChange: Emitter<ITreeItem[] | undefined | null> = new Emitter<ITreeItem[] | undefined | null>();
|
||||
readonly onDidChange: Event<ITreeItem[] | undefined | null> = this._onDidChange.event;
|
||||
|
||||
private _onDispose: Emitter<void> = new Emitter<void>();
|
||||
readonly onDispose: Event<void> = this._onDispose.event;
|
||||
|
||||
private childrenMap: Map<TreeItemHandle, TreeItemHandle[]> = new Map<TreeItemHandle, TreeItemHandle[]>();
|
||||
private itemsMap: Map<TreeItemHandle, ITreeItem> = new Map<TreeItemHandle, ITreeItem>();
|
||||
|
||||
constructor(private treeViewId: string,
|
||||
private _proxy: ExtHostTreeViewsShape,
|
||||
private messageService: IMessageService
|
||||
) {
|
||||
}
|
||||
|
||||
getElements(): TPromise<ITreeItem[]> {
|
||||
return this._proxy.$getElements(this.treeViewId)
|
||||
.then(elements => {
|
||||
this.postGetElements(null, elements);
|
||||
return elements;
|
||||
}, err => {
|
||||
this.messageService.show(Severity.Error, err);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
getChildren(treeItem: ITreeItem): TPromise<ITreeItem[]> {
|
||||
if (treeItem.children) {
|
||||
return TPromise.as(treeItem.children);
|
||||
}
|
||||
return this._proxy.$getChildren(this.treeViewId, treeItem.handle)
|
||||
.then(children => {
|
||||
this.postGetElements(treeItem.handle, children);
|
||||
return children;
|
||||
}, err => {
|
||||
this.messageService.show(Severity.Error, err);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
refresh(treeItemHandles: number[]) {
|
||||
if (treeItemHandles && treeItemHandles.length) {
|
||||
let treeItems = treeItemHandles.map(treeItemHandle => this.itemsMap.get(treeItemHandle))
|
||||
.filter(treeItem => !!treeItem);
|
||||
if (treeItems.length) {
|
||||
this._onDidChange.fire(treeItems);
|
||||
}
|
||||
} else {
|
||||
this._onDidChange.fire();
|
||||
}
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._onDispose.fire();
|
||||
}
|
||||
|
||||
private clearChildren(treeItemHandle: TreeItemHandle): void {
|
||||
const children = this.childrenMap.get(treeItemHandle);
|
||||
if (children) {
|
||||
for (const child of children) {
|
||||
this.clearChildren(child);
|
||||
this.itemsMap.delete(child);
|
||||
}
|
||||
this.childrenMap.delete(treeItemHandle);
|
||||
}
|
||||
}
|
||||
|
||||
private postGetElements(parent: TreeItemHandle, children: ITreeItem[]) {
|
||||
this.setElements(parent, children);
|
||||
}
|
||||
|
||||
private setElements(parent: TreeItemHandle, children: ITreeItem[]) {
|
||||
if (children && children.length) {
|
||||
for (const child of children) {
|
||||
this.itemsMap.set(child.handle, child);
|
||||
if (child.children && child.children.length) {
|
||||
this.setElements(child.handle, child.children);
|
||||
}
|
||||
}
|
||||
if (parent) {
|
||||
this.childrenMap.set(parent, children.map(child => child.handle));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private populateElementsToExpand(elements: ITreeItem[], toExpand: ITreeItem[]) {
|
||||
for (const element of elements) {
|
||||
if (element.collapsibleState === TreeItemCollapsibleState.Expanded) {
|
||||
toExpand.push(element);
|
||||
if (element.children && element.children.length) {
|
||||
this.populateElementsToExpand(element.children, toExpand);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
35
src/vs/workbench/api/electron-browser/mainThreadWindow.ts
Normal file
35
src/vs/workbench/api/electron-browser/mainThreadWindow.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IWindowService } from 'vs/platform/windows/common/windows';
|
||||
import { MainThreadWindowShape, ExtHostWindowShape, ExtHostContext, MainContext, IExtHostContext } from '../node/extHost.protocol';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadWindow)
|
||||
export class MainThreadWindow implements MainThreadWindowShape {
|
||||
|
||||
private readonly proxy: ExtHostWindowShape;
|
||||
private disposables: IDisposable[] = [];
|
||||
|
||||
constructor(
|
||||
extHostContext: IExtHostContext,
|
||||
@IWindowService private windowService: IWindowService
|
||||
) {
|
||||
this.proxy = extHostContext.get(ExtHostContext.ExtHostWindow);
|
||||
|
||||
windowService.onDidChangeFocus(this.proxy.$onDidChangeWindowFocus, this.proxy, this.disposables);
|
||||
}
|
||||
|
||||
$getWindowVisibility(): TPromise<boolean> {
|
||||
return this.windowService.isFocused();
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.disposables = dispose(this.disposables);
|
||||
}
|
||||
}
|
||||
201
src/vs/workbench/api/electron-browser/mainThreadWorkspace.ts
Normal file
201
src/vs/workbench/api/electron-browser/mainThreadWorkspace.ts
Normal file
@@ -0,0 +1,201 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { isPromiseCanceledError } from 'vs/base/common/errors';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { ISearchService, QueryType, ISearchQuery, ISearchProgressItem, ISearchComplete } from 'vs/platform/search/common/search';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { ICommonCodeEditor, isCommonCodeEditor } from 'vs/editor/common/editorCommon';
|
||||
import { bulkEdit, IResourceEdit } from 'vs/editor/common/services/bulkEdit';
|
||||
import { TPromise, PPromise } from 'vs/base/common/winjs.base';
|
||||
import { MainThreadWorkspaceShape, ExtHostWorkspaceShape, ExtHostContext, MainContext, IExtHostContext } from '../node/extHost.protocol';
|
||||
import { ITextModelService } from 'vs/editor/common/services/resolverService';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { IDisposable, dispose, combinedDisposable } from 'vs/base/common/lifecycle';
|
||||
import { RemoteFileService } from 'vs/workbench/services/files/electron-browser/remoteFileService';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadWorkspace)
|
||||
export class MainThreadWorkspace implements MainThreadWorkspaceShape {
|
||||
|
||||
private readonly _toDispose: IDisposable[] = [];
|
||||
private readonly _activeSearches: { [id: number]: TPromise<URI[]> } = Object.create(null);
|
||||
private readonly _proxy: ExtHostWorkspaceShape;
|
||||
|
||||
constructor(
|
||||
extHostContext: IExtHostContext,
|
||||
@ISearchService private readonly _searchService: ISearchService,
|
||||
@IWorkspaceContextService private readonly _contextService: IWorkspaceContextService,
|
||||
@ITextFileService private readonly _textFileService: ITextFileService,
|
||||
@IWorkbenchEditorService private readonly _editorService: IWorkbenchEditorService,
|
||||
@ITextModelService private readonly _textModelResolverService: ITextModelService,
|
||||
@IFileService private readonly _fileService: IFileService
|
||||
) {
|
||||
this._proxy = extHostContext.get(ExtHostContext.ExtHostWorkspace);
|
||||
this._contextService.onDidChangeWorkspaceRoots(this._onDidChangeWorkspace, this, this._toDispose);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
dispose(this._toDispose);
|
||||
|
||||
for (let requestId in this._activeSearches) {
|
||||
const search = this._activeSearches[requestId];
|
||||
search.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
// --- workspace ---
|
||||
|
||||
private _onDidChangeWorkspace(): void {
|
||||
this._proxy.$acceptWorkspaceData(this._contextService.getWorkspace());
|
||||
}
|
||||
|
||||
// --- search ---
|
||||
|
||||
$startSearch(include: string, exclude: string, maxResults: number, requestId: number): Thenable<URI[]> {
|
||||
const workspace = this._contextService.getWorkspace();
|
||||
if (!workspace) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const query: ISearchQuery = {
|
||||
folderQueries: workspace.roots.map(root => ({ folder: root })),
|
||||
type: QueryType.File,
|
||||
maxResults,
|
||||
includePattern: { [include]: true },
|
||||
excludePattern: { [exclude]: true },
|
||||
};
|
||||
this._searchService.extendQuery(query);
|
||||
|
||||
const search = this._searchService.search(query).then(result => {
|
||||
return result.results.map(m => m.resource);
|
||||
}, err => {
|
||||
if (!isPromiseCanceledError(err)) {
|
||||
return TPromise.wrapError(err);
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
|
||||
this._activeSearches[requestId] = search;
|
||||
const onDone = () => delete this._activeSearches[requestId];
|
||||
search.done(onDone, onDone);
|
||||
|
||||
return search;
|
||||
}
|
||||
|
||||
$cancelSearch(requestId: number): Thenable<boolean> {
|
||||
const search = this._activeSearches[requestId];
|
||||
if (search) {
|
||||
delete this._activeSearches[requestId];
|
||||
search.cancel();
|
||||
return TPromise.as(true);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// --- save & edit resources ---
|
||||
|
||||
$saveAll(includeUntitled?: boolean): Thenable<boolean> {
|
||||
return this._textFileService.saveAll(includeUntitled).then(result => {
|
||||
return result.results.every(each => each.success === true);
|
||||
});
|
||||
}
|
||||
|
||||
$applyWorkspaceEdit(edits: IResourceEdit[]): TPromise<boolean> {
|
||||
|
||||
let codeEditor: ICommonCodeEditor;
|
||||
let editor = this._editorService.getActiveEditor();
|
||||
if (editor) {
|
||||
let candidate = editor.getControl();
|
||||
if (isCommonCodeEditor(candidate)) {
|
||||
codeEditor = candidate;
|
||||
}
|
||||
}
|
||||
|
||||
return bulkEdit(this._textModelResolverService, codeEditor, edits, this._fileService)
|
||||
.then(() => true);
|
||||
}
|
||||
|
||||
// --- EXPERIMENT: workspace provider
|
||||
|
||||
private _idPool: number = 0;
|
||||
private readonly _provider = new Map<number, [IDisposable, Emitter<URI>]>();
|
||||
private readonly _searchSessions = new Map<number, { resolve: (result: ISearchComplete) => void, reject: Function, progress: (item: ISearchProgressItem) => void, matches: URI[] }>();
|
||||
|
||||
$registerFileSystemProvider(handle: number, authority: string): void {
|
||||
if (!(this._fileService instanceof RemoteFileService)) {
|
||||
throw new Error();
|
||||
}
|
||||
const emitter = new Emitter<URI>();
|
||||
const provider = {
|
||||
onDidChange: emitter.event,
|
||||
resolve: (resource) => {
|
||||
return this._proxy.$resolveFile(handle, resource);
|
||||
},
|
||||
update: (resource, value) => {
|
||||
return this._proxy.$storeFile(handle, resource, value);
|
||||
}
|
||||
};
|
||||
const searchProvider = {
|
||||
search: (query) => {
|
||||
if (query.type !== QueryType.File) {
|
||||
return undefined;
|
||||
}
|
||||
const session = ++this._idPool;
|
||||
return new PPromise<any, any>((resolve, reject, progress) => {
|
||||
this._searchSessions.set(session, { resolve, reject, progress, matches: [] });
|
||||
this._proxy.$startSearch(handle, session, query.filePattern);
|
||||
}, () => {
|
||||
this._proxy.$cancelSearch(handle, session);
|
||||
});
|
||||
}
|
||||
};
|
||||
const registrations = combinedDisposable([
|
||||
this._fileService.registerProvider(authority, provider),
|
||||
this._searchService.registerSearchResultProvider(searchProvider),
|
||||
]);
|
||||
this._provider.set(handle, [registrations, emitter]);
|
||||
}
|
||||
|
||||
$unregisterFileSystemProvider(handle: number): void {
|
||||
if (this._provider.has(handle)) {
|
||||
dispose(this._provider.get(handle)[0]);
|
||||
this._provider.delete(handle);
|
||||
}
|
||||
}
|
||||
|
||||
$onFileSystemChange(handle: number, resource: URI) {
|
||||
const [, emitter] = this._provider.get(handle);
|
||||
emitter.fire(resource);
|
||||
};
|
||||
|
||||
$updateSearchSession(session: number, data: URI): void {
|
||||
if (this._searchSessions.has(session)) {
|
||||
this._searchSessions.get(session).progress({ resource: data });
|
||||
this._searchSessions.get(session).matches.push(data);
|
||||
}
|
||||
}
|
||||
|
||||
$finishSearchSession(session: number, err?: any): void {
|
||||
if (this._searchSessions.has(session)) {
|
||||
const { matches, resolve, reject } = this._searchSessions.get(session);
|
||||
this._searchSessions.delete(session);
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve({
|
||||
limitHit: false,
|
||||
stats: undefined,
|
||||
results: matches.map(resource => ({ resource }))
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
667
src/vs/workbench/api/node/extHost.api.impl.ts
Normal file
667
src/vs/workbench/api/node/extHost.api.impl.ts
Normal file
@@ -0,0 +1,667 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { TrieMap } from 'vs/base/common/map';
|
||||
import { score } from 'vs/editor/common/modes/languageSelector';
|
||||
import * as Platform from 'vs/base/common/platform';
|
||||
import * as errors from 'vs/base/common/errors';
|
||||
import product from 'vs/platform/node/product';
|
||||
import pkg from 'vs/platform/node/package';
|
||||
import { ExtHostFileSystemEventService } from 'vs/workbench/api/node/extHostFileSystemEventService';
|
||||
import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/node/extHostDocumentsAndEditors';
|
||||
import { ExtHostDocuments } from 'vs/workbench/api/node/extHostDocuments';
|
||||
import { ExtHostDocumentContentProvider } from 'vs/workbench/api/node/extHostDocumentContentProviders';
|
||||
import { ExtHostDocumentSaveParticipant } from 'vs/workbench/api/node/extHostDocumentSaveParticipant';
|
||||
import { ExtHostConfiguration } from 'vs/workbench/api/node/extHostConfiguration';
|
||||
import { ExtHostDiagnostics } from 'vs/workbench/api/node/extHostDiagnostics';
|
||||
import { ExtHostTreeViews } from 'vs/workbench/api/node/extHostTreeViews';
|
||||
import { ExtHostWorkspace } from 'vs/workbench/api/node/extHostWorkspace';
|
||||
import { ExtHostQuickOpen } from 'vs/workbench/api/node/extHostQuickOpen';
|
||||
import { ExtHostProgress } from 'vs/workbench/api/node/extHostProgress';
|
||||
import { ExtHostSCM } from 'vs/workbench/api/node/extHostSCM';
|
||||
import { ExtHostHeapService } from 'vs/workbench/api/node/extHostHeapService';
|
||||
import { ExtHostStatusBar } from 'vs/workbench/api/node/extHostStatusBar';
|
||||
import { ExtHostCommands } from 'vs/workbench/api/node/extHostCommands';
|
||||
import { ExtHostOutputService } from 'vs/workbench/api/node/extHostOutputService';
|
||||
import { ExtHostTerminalService } from 'vs/workbench/api/node/extHostTerminalService';
|
||||
import { ExtHostMessageService } from 'vs/workbench/api/node/extHostMessageService';
|
||||
import { ExtHostEditors } from 'vs/workbench/api/node/extHostTextEditors';
|
||||
import { ExtHostLanguages } from 'vs/workbench/api/node/extHostLanguages';
|
||||
import { ExtHostLanguageFeatures } from 'vs/workbench/api/node/extHostLanguageFeatures';
|
||||
import { ExtHostApiCommands } from 'vs/workbench/api/node/extHostApiCommands';
|
||||
import { ExtHostTask } from 'vs/workbench/api/node/extHostTask';
|
||||
// {{SQL CARBON EDIT}}
|
||||
//import { ExtHostDebugService } from 'vs/workbench/api/node/extHostDebugService';
|
||||
import { ExtHostCredentials } from 'vs/workbench/api/node/extHostCredentials';
|
||||
import { ExtHostWindow } from 'vs/workbench/api/node/extHostWindow';
|
||||
import * as extHostTypes from 'vs/workbench/api/node/extHostTypes';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import EditorCommon = require('vs/editor/common/editorCommon');
|
||||
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { ExtHostExtensionService } from 'vs/workbench/api/node/extHostExtensionService';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import * as vscode from 'vscode';
|
||||
import * as paths from 'vs/base/common/paths';
|
||||
import { MainContext, ExtHostContext, IInitData } from './extHost.protocol';
|
||||
import * as languageConfiguration from 'vs/editor/common/modes/languageConfiguration';
|
||||
import { TextEditorCursorStyle } from 'vs/editor/common/config/editorOptions';
|
||||
import { ExtHostThreadService } from 'vs/workbench/services/thread/node/extHostThreadService';
|
||||
import { ProxyIdentifier } from 'vs/workbench/services/thread/common/threadService';
|
||||
import { ExtHostDialogs } from 'vs/workbench/api/node/extHostDialogs';
|
||||
import { MarkdownString } from 'vs/base/common/htmlContent';
|
||||
|
||||
export interface IExtensionApiFactory {
|
||||
(extension: IExtensionDescription): typeof vscode;
|
||||
}
|
||||
|
||||
function proposedApiFunction<T>(extension: IExtensionDescription, fn: T): T {
|
||||
if (extension.enableProposedApi) {
|
||||
return fn;
|
||||
} else {
|
||||
return <any>(() => {
|
||||
throw new Error(`[${extension.id}]: Proposed API is only available when running out of dev or with the following command line switch: --enable-proposed-api ${extension.id}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method instantiates and returns the extension API surface
|
||||
*/
|
||||
export function createApiFactory(
|
||||
initData: IInitData,
|
||||
threadService: ExtHostThreadService,
|
||||
extensionService: ExtHostExtensionService
|
||||
): IExtensionApiFactory {
|
||||
|
||||
const mainThreadTelemetry = threadService.get(MainContext.MainThreadTelemetry);
|
||||
|
||||
// Addressable instances
|
||||
const extHostHeapService = threadService.set(ExtHostContext.ExtHostHeapService, new ExtHostHeapService());
|
||||
const extHostDocumentsAndEditors = threadService.set(ExtHostContext.ExtHostDocumentsAndEditors, new ExtHostDocumentsAndEditors(threadService, extensionService));
|
||||
const extHostDocuments = threadService.set(ExtHostContext.ExtHostDocuments, new ExtHostDocuments(threadService, extHostDocumentsAndEditors));
|
||||
const extHostDocumentContentProviders = threadService.set(ExtHostContext.ExtHostDocumentContentProviders, new ExtHostDocumentContentProvider(threadService, extHostDocumentsAndEditors));
|
||||
const extHostDocumentSaveParticipant = threadService.set(ExtHostContext.ExtHostDocumentSaveParticipant, new ExtHostDocumentSaveParticipant(extHostDocuments, threadService.get(MainContext.MainThreadWorkspace)));
|
||||
const extHostEditors = threadService.set(ExtHostContext.ExtHostEditors, new ExtHostEditors(threadService, extHostDocumentsAndEditors));
|
||||
const extHostCommands = threadService.set(ExtHostContext.ExtHostCommands, new ExtHostCommands(threadService, extHostHeapService));
|
||||
const extHostTreeViews = threadService.set(ExtHostContext.ExtHostTreeViews, new ExtHostTreeViews(threadService.get(MainContext.MainThreadTreeViews), extHostCommands));
|
||||
const extHostWorkspace = threadService.set(ExtHostContext.ExtHostWorkspace, new ExtHostWorkspace(threadService, initData.workspace));
|
||||
// {{SQL CARBON EDIT}}
|
||||
// const extHostDebugService = threadService.set(ExtHostContext.ExtHostDebugService, new ExtHostDebugService(threadService, extHostWorkspace));
|
||||
const extHostConfiguration = threadService.set(ExtHostContext.ExtHostConfiguration, new ExtHostConfiguration(threadService.get(MainContext.MainThreadConfiguration), extHostWorkspace, initData.configuration));
|
||||
const extHostDiagnostics = threadService.set(ExtHostContext.ExtHostDiagnostics, new ExtHostDiagnostics(threadService));
|
||||
const languageFeatures = threadService.set(ExtHostContext.ExtHostLanguageFeatures, new ExtHostLanguageFeatures(threadService, extHostDocuments, extHostCommands, extHostHeapService, extHostDiagnostics));
|
||||
const extHostFileSystemEvent = threadService.set(ExtHostContext.ExtHostFileSystemEventService, new ExtHostFileSystemEventService());
|
||||
const extHostQuickOpen = threadService.set(ExtHostContext.ExtHostQuickOpen, new ExtHostQuickOpen(threadService));
|
||||
const extHostTerminalService = threadService.set(ExtHostContext.ExtHostTerminalService, new ExtHostTerminalService(threadService));
|
||||
const extHostSCM = threadService.set(ExtHostContext.ExtHostSCM, new ExtHostSCM(threadService, extHostCommands));
|
||||
const extHostTask = threadService.set(ExtHostContext.ExtHostTask, new ExtHostTask(threadService));
|
||||
const extHostCredentials = threadService.set(ExtHostContext.ExtHostCredentials, new ExtHostCredentials(threadService));
|
||||
const extHostWindow = threadService.set(ExtHostContext.ExtHostWindow, new ExtHostWindow(threadService));
|
||||
threadService.set(ExtHostContext.ExtHostExtensionService, extensionService);
|
||||
|
||||
// Check that no named customers are missing
|
||||
const expected: ProxyIdentifier<any>[] = Object.keys(ExtHostContext).map((key) => ExtHostContext[key]);
|
||||
threadService.assertRegistered(expected);
|
||||
|
||||
// Other instances
|
||||
const extHostMessageService = new ExtHostMessageService(threadService);
|
||||
const extHostDialogs = new ExtHostDialogs(threadService);
|
||||
const extHostStatusBar = new ExtHostStatusBar(threadService);
|
||||
const extHostProgress = new ExtHostProgress(threadService.get(MainContext.MainThreadProgress));
|
||||
const extHostOutputService = new ExtHostOutputService(threadService);
|
||||
const extHostLanguages = new ExtHostLanguages(threadService);
|
||||
|
||||
// Register API-ish commands
|
||||
ExtHostApiCommands.register(extHostCommands);
|
||||
|
||||
return function (extension: IExtensionDescription): typeof vscode {
|
||||
|
||||
if (extension.enableProposedApi && !extension.isBuiltin) {
|
||||
|
||||
if (
|
||||
!initData.environment.enableProposedApiForAll &&
|
||||
initData.environment.enableProposedApiFor.indexOf(extension.id) < 0
|
||||
) {
|
||||
extension.enableProposedApi = false;
|
||||
console.error(`Extension '${extension.id} cannot use PROPOSED API (must started out of dev or enabled via --enable-proposed-api)`);
|
||||
|
||||
} else {
|
||||
// proposed api is available when developing or when an extension was explicitly
|
||||
// spelled out via a command line argument
|
||||
console.warn(`Extension '${extension.id}' uses PROPOSED API which is subject to change and removal without notice.`);
|
||||
}
|
||||
}
|
||||
|
||||
const apiUsage = new class {
|
||||
private _seen = new Set<string>();
|
||||
publicLog(apiName: string) {
|
||||
if (this._seen.has(apiName)) {
|
||||
return undefined;
|
||||
}
|
||||
this._seen.add(apiName);
|
||||
return mainThreadTelemetry.$publicLog('apiUsage', {
|
||||
name: apiName,
|
||||
extension: extension.id
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// namespace: commands
|
||||
const commands: typeof vscode.commands = {
|
||||
registerCommand<T>(id: string, command: <T>(...args: any[]) => T | Thenable<T>, thisArgs?: any): vscode.Disposable {
|
||||
return extHostCommands.registerCommand(id, command, thisArgs);
|
||||
},
|
||||
registerTextEditorCommand(id: string, callback: (textEditor: vscode.TextEditor, edit: vscode.TextEditorEdit, ...args: any[]) => void, thisArg?: any): vscode.Disposable {
|
||||
return extHostCommands.registerCommand(id, (...args: any[]): any => {
|
||||
let activeTextEditor = extHostEditors.getActiveTextEditor();
|
||||
if (!activeTextEditor) {
|
||||
console.warn('Cannot execute ' + id + ' because there is no active text editor.');
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return activeTextEditor.edit((edit: vscode.TextEditorEdit) => {
|
||||
args.unshift(activeTextEditor, edit);
|
||||
callback.apply(thisArg, args);
|
||||
|
||||
}).then((result) => {
|
||||
if (!result) {
|
||||
console.warn('Edits from command ' + id + ' were not applied.');
|
||||
}
|
||||
}, (err) => {
|
||||
console.warn('An error occurred while running command ' + id, err);
|
||||
});
|
||||
});
|
||||
},
|
||||
registerDiffInformationCommand: proposedApiFunction(extension, (id: string, callback: (diff: vscode.LineChange[], ...args: any[]) => any, thisArg?: any): vscode.Disposable => {
|
||||
return extHostCommands.registerCommand(id, async (...args: any[]) => {
|
||||
let activeTextEditor = extHostEditors.getActiveTextEditor();
|
||||
if (!activeTextEditor) {
|
||||
console.warn('Cannot execute ' + id + ' because there is no active text editor.');
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const diff = await extHostEditors.getDiffInformation(activeTextEditor.id);
|
||||
callback.apply(thisArg, [diff, ...args]);
|
||||
});
|
||||
}),
|
||||
executeCommand<T>(id: string, ...args: any[]): Thenable<T> {
|
||||
return extHostCommands.executeCommand<T>(id, ...args);
|
||||
},
|
||||
getCommands(filterInternal: boolean = false): Thenable<string[]> {
|
||||
return extHostCommands.getCommands(filterInternal);
|
||||
}
|
||||
};
|
||||
|
||||
// namespace: env
|
||||
const env: typeof vscode.env = Object.freeze({
|
||||
get machineId() { return initData.telemetryInfo.machineId; },
|
||||
get sessionId() { return initData.telemetryInfo.sessionId; },
|
||||
get language() { return Platform.language; },
|
||||
get appName() { return product.nameLong; },
|
||||
get appRoot() { return initData.environment.appRoot; },
|
||||
});
|
||||
|
||||
// namespace: extensions
|
||||
const extensions: typeof vscode.extensions = {
|
||||
getExtension(extensionId: string): Extension<any> {
|
||||
let desc = extensionService.getExtensionDescription(extensionId);
|
||||
if (desc) {
|
||||
return new Extension(extensionService, desc);
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
get all(): Extension<any>[] {
|
||||
return extensionService.getAllExtensionDescriptions().map((desc) => new Extension(extensionService, desc));
|
||||
}
|
||||
};
|
||||
|
||||
// namespace: languages
|
||||
const languages: typeof vscode.languages = {
|
||||
createDiagnosticCollection(name?: string): vscode.DiagnosticCollection {
|
||||
return extHostDiagnostics.createDiagnosticCollection(name);
|
||||
},
|
||||
getLanguages(): TPromise<string[]> {
|
||||
return extHostLanguages.getLanguages();
|
||||
},
|
||||
match(selector: vscode.DocumentSelector, document: vscode.TextDocument): number {
|
||||
return score(selector, <any>document.uri, document.languageId);
|
||||
},
|
||||
registerCodeActionsProvider(selector: vscode.DocumentSelector, provider: vscode.CodeActionProvider): vscode.Disposable {
|
||||
return languageFeatures.registerCodeActionProvider(selector, provider);
|
||||
},
|
||||
registerCodeLensProvider(selector: vscode.DocumentSelector, provider: vscode.CodeLensProvider): vscode.Disposable {
|
||||
return languageFeatures.registerCodeLensProvider(selector, provider);
|
||||
},
|
||||
registerDefinitionProvider(selector: vscode.DocumentSelector, provider: vscode.DefinitionProvider): vscode.Disposable {
|
||||
return languageFeatures.registerDefinitionProvider(selector, provider);
|
||||
},
|
||||
registerImplementationProvider(selector: vscode.DocumentSelector, provider: vscode.ImplementationProvider): vscode.Disposable {
|
||||
return languageFeatures.registerImplementationProvider(selector, provider);
|
||||
},
|
||||
registerTypeDefinitionProvider(selector: vscode.DocumentSelector, provider: vscode.TypeDefinitionProvider): vscode.Disposable {
|
||||
return languageFeatures.registerTypeDefinitionProvider(selector, provider);
|
||||
},
|
||||
registerHoverProvider(selector: vscode.DocumentSelector, provider: vscode.HoverProvider): vscode.Disposable {
|
||||
return languageFeatures.registerHoverProvider(selector, provider, extension.id);
|
||||
},
|
||||
registerDocumentHighlightProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentHighlightProvider): vscode.Disposable {
|
||||
return languageFeatures.registerDocumentHighlightProvider(selector, provider);
|
||||
},
|
||||
registerReferenceProvider(selector: vscode.DocumentSelector, provider: vscode.ReferenceProvider): vscode.Disposable {
|
||||
return languageFeatures.registerReferenceProvider(selector, provider);
|
||||
},
|
||||
registerRenameProvider(selector: vscode.DocumentSelector, provider: vscode.RenameProvider): vscode.Disposable {
|
||||
return languageFeatures.registerRenameProvider(selector, provider);
|
||||
},
|
||||
registerDocumentSymbolProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentSymbolProvider): vscode.Disposable {
|
||||
return languageFeatures.registerDocumentSymbolProvider(selector, provider);
|
||||
},
|
||||
registerWorkspaceSymbolProvider(provider: vscode.WorkspaceSymbolProvider): vscode.Disposable {
|
||||
return languageFeatures.registerWorkspaceSymbolProvider(provider);
|
||||
},
|
||||
registerDocumentFormattingEditProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentFormattingEditProvider): vscode.Disposable {
|
||||
return languageFeatures.registerDocumentFormattingEditProvider(selector, provider);
|
||||
},
|
||||
registerDocumentRangeFormattingEditProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentRangeFormattingEditProvider): vscode.Disposable {
|
||||
return languageFeatures.registerDocumentRangeFormattingEditProvider(selector, provider);
|
||||
},
|
||||
registerOnTypeFormattingEditProvider(selector: vscode.DocumentSelector, provider: vscode.OnTypeFormattingEditProvider, firstTriggerCharacter: string, ...moreTriggerCharacters: string[]): vscode.Disposable {
|
||||
return languageFeatures.registerOnTypeFormattingEditProvider(selector, provider, [firstTriggerCharacter].concat(moreTriggerCharacters));
|
||||
},
|
||||
registerSignatureHelpProvider(selector: vscode.DocumentSelector, provider: vscode.SignatureHelpProvider, ...triggerCharacters: string[]): vscode.Disposable {
|
||||
return languageFeatures.registerSignatureHelpProvider(selector, provider, triggerCharacters);
|
||||
},
|
||||
registerCompletionItemProvider(selector: vscode.DocumentSelector, provider: vscode.CompletionItemProvider, ...triggerCharacters: string[]): vscode.Disposable {
|
||||
return languageFeatures.registerCompletionItemProvider(selector, provider, triggerCharacters);
|
||||
},
|
||||
registerDocumentLinkProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentLinkProvider): vscode.Disposable {
|
||||
return languageFeatures.registerDocumentLinkProvider(selector, provider);
|
||||
},
|
||||
setLanguageConfiguration: (language: string, configuration: vscode.LanguageConfiguration): vscode.Disposable => {
|
||||
return languageFeatures.setLanguageConfiguration(language, configuration);
|
||||
},
|
||||
// proposed API
|
||||
registerColorProvider: proposedApiFunction(extension, (selector: vscode.DocumentSelector, provider: vscode.DocumentColorProvider) => {
|
||||
return languageFeatures.registerColorProvider(selector, provider);
|
||||
})
|
||||
};
|
||||
|
||||
// namespace: window
|
||||
const window: typeof vscode.window = {
|
||||
get activeTextEditor() {
|
||||
return extHostEditors.getActiveTextEditor();
|
||||
},
|
||||
get visibleTextEditors() {
|
||||
return extHostEditors.getVisibleTextEditors();
|
||||
},
|
||||
showTextDocument(documentOrUri: vscode.TextDocument | vscode.Uri, columnOrOptions?: vscode.ViewColumn | vscode.TextDocumentShowOptions, preserveFocus?: boolean): TPromise<vscode.TextEditor> {
|
||||
let documentPromise: TPromise<vscode.TextDocument>;
|
||||
if (URI.isUri(documentOrUri)) {
|
||||
documentPromise = TPromise.wrap(workspace.openTextDocument(documentOrUri));
|
||||
} else {
|
||||
documentPromise = TPromise.wrap(<vscode.TextDocument>documentOrUri);
|
||||
}
|
||||
return documentPromise.then(document => {
|
||||
return extHostEditors.showTextDocument(document, columnOrOptions, preserveFocus);
|
||||
});
|
||||
},
|
||||
createTextEditorDecorationType(options: vscode.DecorationRenderOptions): vscode.TextEditorDecorationType {
|
||||
return extHostEditors.createTextEditorDecorationType(options);
|
||||
},
|
||||
onDidChangeActiveTextEditor(listener, thisArg?, disposables?) {
|
||||
return extHostEditors.onDidChangeActiveTextEditor(listener, thisArg, disposables);
|
||||
},
|
||||
onDidChangeVisibleTextEditors(listener, thisArg, disposables) {
|
||||
return extHostEditors.onDidChangeVisibleTextEditors(listener, thisArg, disposables);
|
||||
},
|
||||
onDidChangeTextEditorSelection(listener: (e: vscode.TextEditorSelectionChangeEvent) => any, thisArgs?: any, disposables?: extHostTypes.Disposable[]) {
|
||||
return extHostEditors.onDidChangeTextEditorSelection(listener, thisArgs, disposables);
|
||||
},
|
||||
onDidChangeTextEditorOptions(listener: (e: vscode.TextEditorOptionsChangeEvent) => any, thisArgs?: any, disposables?: extHostTypes.Disposable[]) {
|
||||
return extHostEditors.onDidChangeTextEditorOptions(listener, thisArgs, disposables);
|
||||
},
|
||||
onDidChangeTextEditorViewColumn(listener, thisArg?, disposables?) {
|
||||
return extHostEditors.onDidChangeTextEditorViewColumn(listener, thisArg, disposables);
|
||||
},
|
||||
onDidCloseTerminal(listener, thisArg?, disposables?) {
|
||||
return extHostTerminalService.onDidCloseTerminal(listener, thisArg, disposables);
|
||||
},
|
||||
get state() {
|
||||
return extHostWindow.state;
|
||||
},
|
||||
onDidChangeWindowState: proposedApiFunction(extension, (listener, thisArg?, disposables?) => {
|
||||
return extHostWindow.onDidChangeWindowState(listener, thisArg, disposables);
|
||||
}),
|
||||
showInformationMessage(message, first, ...rest) {
|
||||
return extHostMessageService.showMessage(extension, Severity.Info, message, first, rest);
|
||||
},
|
||||
showWarningMessage(message, first, ...rest) {
|
||||
return extHostMessageService.showMessage(extension, Severity.Warning, message, first, rest);
|
||||
},
|
||||
showErrorMessage(message, first, ...rest) {
|
||||
return extHostMessageService.showMessage(extension, Severity.Error, message, first, rest);
|
||||
},
|
||||
showQuickPick(items: any, options: vscode.QuickPickOptions, token?: vscode.CancellationToken) {
|
||||
return extHostQuickOpen.showQuickPick(items, options, token);
|
||||
},
|
||||
showInputBox(options?: vscode.InputBoxOptions, token?: vscode.CancellationToken) {
|
||||
return extHostQuickOpen.showInput(options, token);
|
||||
},
|
||||
createStatusBarItem(position?: vscode.StatusBarAlignment, priority?: number): vscode.StatusBarItem {
|
||||
return extHostStatusBar.createStatusBarEntry(extension.id, <number>position, priority);
|
||||
},
|
||||
setStatusBarMessage(text: string, timeoutOrThenable?: number | Thenable<any>): vscode.Disposable {
|
||||
return extHostStatusBar.setStatusBarMessage(text, timeoutOrThenable);
|
||||
},
|
||||
withScmProgress<R>(task: (progress: vscode.Progress<number>) => Thenable<R>) {
|
||||
console.warn(`[Deprecation Warning] function 'withScmProgress' is deprecated and should no longer be used. Use 'withProgress' instead.`);
|
||||
return extHostProgress.withProgress(extension, { location: extHostTypes.ProgressLocation.SourceControl }, (progress, token) => task({ report(n: number) { /*noop*/ } }));
|
||||
},
|
||||
withProgress<R>(options: vscode.ProgressOptions, task: (progress: vscode.Progress<{ message?: string; percentage?: number }>) => Thenable<R>) {
|
||||
return extHostProgress.withProgress(extension, options, task);
|
||||
},
|
||||
createOutputChannel(name: string): vscode.OutputChannel {
|
||||
return extHostOutputService.createOutputChannel(name);
|
||||
},
|
||||
createTerminal(nameOrOptions: vscode.TerminalOptions | string, shellPath?: string, shellArgs?: string[]): vscode.Terminal {
|
||||
if (typeof nameOrOptions === 'object') {
|
||||
return extHostTerminalService.createTerminalFromOptions(<vscode.TerminalOptions>nameOrOptions);
|
||||
}
|
||||
return extHostTerminalService.createTerminal(<string>nameOrOptions, shellPath, shellArgs);
|
||||
},
|
||||
registerTreeDataProvider(viewId: string, treeDataProvider: vscode.TreeDataProvider<any>): vscode.Disposable {
|
||||
return extHostTreeViews.registerTreeDataProvider(viewId, treeDataProvider);
|
||||
},
|
||||
// proposed API
|
||||
sampleFunction: proposedApiFunction(extension, () => {
|
||||
return extHostMessageService.showMessage(extension, Severity.Info, 'Hello Proposed Api!', {}, []);
|
||||
}),
|
||||
showOpenDialog: proposedApiFunction(extension, options => {
|
||||
return extHostDialogs.showOpenDialog(options);
|
||||
})
|
||||
};
|
||||
|
||||
// namespace: workspace
|
||||
const workspace: typeof vscode.workspace = {
|
||||
get rootPath() {
|
||||
apiUsage.publicLog('workspace#rootPath');
|
||||
return extHostWorkspace.getPath();
|
||||
},
|
||||
set rootPath(value) {
|
||||
throw errors.readonly();
|
||||
},
|
||||
getWorkspaceFolder(resource) {
|
||||
return extHostWorkspace.getWorkspaceFolder(resource);
|
||||
},
|
||||
get workspaceFolders() {
|
||||
apiUsage.publicLog('workspace#workspaceFolders');
|
||||
return extHostWorkspace.getWorkspaceFolders();
|
||||
},
|
||||
onDidChangeWorkspaceFolders: function (listener, thisArgs?, disposables?) {
|
||||
apiUsage.publicLog('workspace#onDidChangeWorkspaceFolders');
|
||||
return extHostWorkspace.onDidChangeWorkspace(listener, thisArgs, disposables);
|
||||
},
|
||||
asRelativePath: (pathOrUri, includeWorkspace) => {
|
||||
return extHostWorkspace.getRelativePath(pathOrUri, includeWorkspace);
|
||||
},
|
||||
findFiles: (include, exclude, maxResults?, token?) => {
|
||||
return extHostWorkspace.findFiles(include, exclude, maxResults, token);
|
||||
},
|
||||
saveAll: (includeUntitled?) => {
|
||||
return extHostWorkspace.saveAll(includeUntitled);
|
||||
},
|
||||
applyEdit(edit: vscode.WorkspaceEdit): TPromise<boolean> {
|
||||
return extHostWorkspace.appyEdit(edit);
|
||||
},
|
||||
createFileSystemWatcher: (pattern, ignoreCreate, ignoreChange, ignoreDelete): vscode.FileSystemWatcher => {
|
||||
return extHostFileSystemEvent.createFileSystemWatcher(pattern, ignoreCreate, ignoreChange, ignoreDelete);
|
||||
},
|
||||
get textDocuments() {
|
||||
return extHostDocuments.getAllDocumentData().map(data => data.document);
|
||||
},
|
||||
set textDocuments(value) {
|
||||
throw errors.readonly();
|
||||
},
|
||||
openTextDocument(uriOrFileNameOrOptions?: vscode.Uri | string | { language?: string; content?: string; }) {
|
||||
let uriPromise: TPromise<URI>;
|
||||
|
||||
let options = uriOrFileNameOrOptions as { language?: string; content?: string; };
|
||||
if (!options || typeof options.language === 'string') {
|
||||
uriPromise = extHostDocuments.createDocumentData(options);
|
||||
} else if (typeof uriOrFileNameOrOptions === 'string') {
|
||||
uriPromise = TPromise.as(URI.file(uriOrFileNameOrOptions));
|
||||
} else if (uriOrFileNameOrOptions instanceof URI) {
|
||||
uriPromise = TPromise.as(<URI>uriOrFileNameOrOptions);
|
||||
} else {
|
||||
throw new Error('illegal argument - uriOrFileNameOrOptions');
|
||||
}
|
||||
|
||||
return uriPromise.then(uri => {
|
||||
return extHostDocuments.ensureDocumentData(uri).then(() => {
|
||||
const data = extHostDocuments.getDocumentData(uri);
|
||||
return data && data.document;
|
||||
});
|
||||
});
|
||||
},
|
||||
onDidOpenTextDocument: (listener, thisArgs?, disposables?) => {
|
||||
return extHostDocuments.onDidAddDocument(listener, thisArgs, disposables);
|
||||
},
|
||||
onDidCloseTextDocument: (listener, thisArgs?, disposables?) => {
|
||||
return extHostDocuments.onDidRemoveDocument(listener, thisArgs, disposables);
|
||||
},
|
||||
onDidChangeTextDocument: (listener, thisArgs?, disposables?) => {
|
||||
return extHostDocuments.onDidChangeDocument(listener, thisArgs, disposables);
|
||||
},
|
||||
onDidSaveTextDocument: (listener, thisArgs?, disposables?) => {
|
||||
return extHostDocuments.onDidSaveDocument(listener, thisArgs, disposables);
|
||||
},
|
||||
onWillSaveTextDocument: (listener, thisArgs?, disposables?) => {
|
||||
return extHostDocumentSaveParticipant.onWillSaveTextDocumentEvent(listener, thisArgs, disposables);
|
||||
},
|
||||
onDidChangeConfiguration: (listener: (_: any) => any, thisArgs?: any, disposables?: extHostTypes.Disposable[]) => {
|
||||
return extHostConfiguration.onDidChangeConfiguration(listener, thisArgs, disposables);
|
||||
},
|
||||
getConfiguration: (section?: string, resource?: vscode.Uri): vscode.WorkspaceConfiguration => {
|
||||
return extHostConfiguration.getConfiguration(section, <URI>resource);
|
||||
},
|
||||
registerTextDocumentContentProvider(scheme: string, provider: vscode.TextDocumentContentProvider) {
|
||||
return extHostDocumentContentProviders.registerTextDocumentContentProvider(scheme, provider);
|
||||
},
|
||||
registerTaskProvider: (type: string, provider: vscode.TaskProvider) => {
|
||||
return extHostTask.registerTaskProvider(extension, provider);
|
||||
},
|
||||
registerFileSystemProvider: proposedApiFunction(extension, (authority, provider) => {
|
||||
return extHostWorkspace.registerFileSystemProvider(authority, provider);
|
||||
})
|
||||
};
|
||||
|
||||
// namespace: scm
|
||||
const scm: typeof vscode.scm = {
|
||||
get inputBox() {
|
||||
return extHostSCM.getLastInputBox(extension);
|
||||
},
|
||||
createSourceControl(id: string, label: string) {
|
||||
mainThreadTelemetry.$publicLog('registerSCMProvider', {
|
||||
extensionId: extension.id,
|
||||
providerId: id,
|
||||
providerLabel: label
|
||||
});
|
||||
|
||||
return extHostSCM.createSourceControl(extension, id, label);
|
||||
}
|
||||
};
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
// delete namespace: debug
|
||||
|
||||
// namespace: credentials
|
||||
const credentials = {
|
||||
readSecret(service: string, account: string): Thenable<string | undefined> {
|
||||
return extHostCredentials.readSecret(service, account);
|
||||
},
|
||||
writeSecret(service: string, account: string, secret: string): Thenable<void> {
|
||||
return extHostCredentials.writeSecret(service, account, secret);
|
||||
},
|
||||
deleteSecret(service: string, account: string): Thenable<boolean> {
|
||||
return extHostCredentials.deleteSecret(service, account);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const api: typeof vscode = {
|
||||
version: pkg.version,
|
||||
// namespaces
|
||||
commands,
|
||||
env,
|
||||
extensions,
|
||||
languages,
|
||||
window,
|
||||
workspace,
|
||||
scm,
|
||||
// {{SQL CARBON EDIT}}
|
||||
// debug,
|
||||
// types
|
||||
CancellationTokenSource: CancellationTokenSource,
|
||||
CodeLens: extHostTypes.CodeLens,
|
||||
Color: extHostTypes.Color,
|
||||
ColorRange: extHostTypes.ColorRange,
|
||||
EndOfLine: extHostTypes.EndOfLine,
|
||||
CompletionItem: extHostTypes.CompletionItem,
|
||||
CompletionItemKind: extHostTypes.CompletionItemKind,
|
||||
CompletionList: extHostTypes.CompletionList,
|
||||
Diagnostic: extHostTypes.Diagnostic,
|
||||
DiagnosticSeverity: extHostTypes.DiagnosticSeverity,
|
||||
Disposable: extHostTypes.Disposable,
|
||||
DocumentHighlight: extHostTypes.DocumentHighlight,
|
||||
DocumentHighlightKind: extHostTypes.DocumentHighlightKind,
|
||||
DocumentLink: extHostTypes.DocumentLink,
|
||||
EventEmitter: Emitter,
|
||||
Hover: extHostTypes.Hover,
|
||||
IndentAction: languageConfiguration.IndentAction,
|
||||
Location: extHostTypes.Location,
|
||||
MarkdownString: MarkdownString,
|
||||
OverviewRulerLane: EditorCommon.OverviewRulerLane,
|
||||
ParameterInformation: extHostTypes.ParameterInformation,
|
||||
Position: extHostTypes.Position,
|
||||
Range: extHostTypes.Range,
|
||||
Selection: extHostTypes.Selection,
|
||||
SignatureHelp: extHostTypes.SignatureHelp,
|
||||
SignatureInformation: extHostTypes.SignatureInformation,
|
||||
SnippetString: extHostTypes.SnippetString,
|
||||
StatusBarAlignment: extHostTypes.StatusBarAlignment,
|
||||
SymbolInformation: extHostTypes.SymbolInformation,
|
||||
SymbolKind: extHostTypes.SymbolKind,
|
||||
TextDocumentSaveReason: extHostTypes.TextDocumentSaveReason,
|
||||
TextEdit: extHostTypes.TextEdit,
|
||||
TextEditorCursorStyle: TextEditorCursorStyle,
|
||||
TextEditorLineNumbersStyle: extHostTypes.TextEditorLineNumbersStyle,
|
||||
TextEditorRevealType: extHostTypes.TextEditorRevealType,
|
||||
TextEditorSelectionChangeKind: extHostTypes.TextEditorSelectionChangeKind,
|
||||
DecorationRangeBehavior: extHostTypes.DecorationRangeBehavior,
|
||||
Uri: <any>URI,
|
||||
ViewColumn: extHostTypes.ViewColumn,
|
||||
WorkspaceEdit: extHostTypes.WorkspaceEdit,
|
||||
ProgressLocation: extHostTypes.ProgressLocation,
|
||||
TreeItemCollapsibleState: extHostTypes.TreeItemCollapsibleState,
|
||||
TreeItem: extHostTypes.TreeItem,
|
||||
ThemeColor: extHostTypes.ThemeColor,
|
||||
// functions
|
||||
TaskRevealKind: extHostTypes.TaskRevealKind,
|
||||
TaskPanelKind: extHostTypes.TaskPanelKind,
|
||||
TaskGroup: extHostTypes.TaskGroup,
|
||||
ProcessExecution: extHostTypes.ProcessExecution,
|
||||
ShellExecution: extHostTypes.ShellExecution,
|
||||
Task: extHostTypes.Task,
|
||||
ConfigurationTarget: extHostTypes.ConfigurationTarget
|
||||
};
|
||||
if (extension.enableProposedApi && extension.isBuiltin) {
|
||||
api['credentials'] = credentials;
|
||||
}
|
||||
return api;
|
||||
};
|
||||
}
|
||||
|
||||
class Extension<T> implements vscode.Extension<T> {
|
||||
|
||||
private _extensionService: ExtHostExtensionService;
|
||||
|
||||
public id: string;
|
||||
public extensionPath: string;
|
||||
public packageJSON: any;
|
||||
|
||||
constructor(extensionService: ExtHostExtensionService, description: IExtensionDescription) {
|
||||
this._extensionService = extensionService;
|
||||
this.id = description.id;
|
||||
this.extensionPath = paths.normalize(description.extensionFolderPath, true);
|
||||
this.packageJSON = description;
|
||||
}
|
||||
|
||||
get isActive(): boolean {
|
||||
return this._extensionService.isActivated(this.id);
|
||||
}
|
||||
|
||||
get exports(): T {
|
||||
return <T>this._extensionService.getExtensionExports(this.id);
|
||||
}
|
||||
|
||||
activate(): Thenable<T> {
|
||||
return this._extensionService.activateById(this.id, false).then(() => this.exports);
|
||||
}
|
||||
}
|
||||
|
||||
export function initializeExtensionApi(extensionService: ExtHostExtensionService, apiFactory: IExtensionApiFactory): TPromise<void> {
|
||||
return extensionService.getExtensionPathIndex().then(trie => defineAPI(apiFactory, trie));
|
||||
}
|
||||
|
||||
function defineAPI(factory: IExtensionApiFactory, extensionPaths: TrieMap<IExtensionDescription>): void {
|
||||
|
||||
// each extension is meant to get its own api implementation
|
||||
const extApiImpl = new Map<string, typeof vscode>();
|
||||
let defaultApiImpl: typeof vscode;
|
||||
|
||||
const node_module = <any>require.__$__nodeRequire('module');
|
||||
const original = node_module._load;
|
||||
node_module._load = function load(request, parent, isMain) {
|
||||
if (request !== 'vscode') {
|
||||
return original.apply(this, arguments);
|
||||
}
|
||||
|
||||
// get extension id from filename and api for extension
|
||||
const ext = extensionPaths.findSubstr(parent.filename);
|
||||
if (ext) {
|
||||
let apiImpl = extApiImpl.get(ext.id);
|
||||
if (!apiImpl) {
|
||||
apiImpl = factory(ext);
|
||||
extApiImpl.set(ext.id, apiImpl);
|
||||
}
|
||||
return apiImpl;
|
||||
}
|
||||
|
||||
// fall back to a default implementation
|
||||
if (!defaultApiImpl) {
|
||||
defaultApiImpl = factory(nullExtensionDescription);
|
||||
}
|
||||
return defaultApiImpl;
|
||||
};
|
||||
}
|
||||
|
||||
const nullExtensionDescription: IExtensionDescription = {
|
||||
id: 'nullExtensionDescription',
|
||||
name: 'Null Extension Description',
|
||||
publisher: 'vscode',
|
||||
activationEvents: undefined,
|
||||
contributes: undefined,
|
||||
enableProposedApi: false,
|
||||
engines: undefined,
|
||||
extensionDependencies: undefined,
|
||||
extensionFolderPath: undefined,
|
||||
isBuiltin: false,
|
||||
main: undefined,
|
||||
version: undefined
|
||||
};
|
||||
614
src/vs/workbench/api/node/extHost.protocol.ts
Normal file
614
src/vs/workbench/api/node/extHost.protocol.ts
Normal file
@@ -0,0 +1,614 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import {
|
||||
createMainContextProxyIdentifier as createMainId,
|
||||
createExtHostContextProxyIdentifier as createExtId,
|
||||
ProxyIdentifier
|
||||
} from 'vs/workbench/services/thread/common/threadService';
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
import URI from 'vs/base/common/uri';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
|
||||
import { IMarkerData } from 'vs/platform/markers/common/markers';
|
||||
import { Position as EditorPosition } from 'vs/platform/editor/common/editor';
|
||||
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { StatusbarAlignment as MainThreadStatusBarAlignment } from 'vs/platform/statusbar/common/statusbar';
|
||||
import { ITelemetryInfo } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { ICommandHandlerDescription } from 'vs/platform/commands/common/commands';
|
||||
import { IProgressOptions, IProgressStep } from 'vs/platform/progress/common/progress';
|
||||
|
||||
import * as editorCommon from 'vs/editor/common/editorCommon';
|
||||
import * as modes from 'vs/editor/common/modes';
|
||||
import { IResourceEdit } from 'vs/editor/common/services/bulkEdit';
|
||||
import { ITextSource } from 'vs/editor/common/model/textSource';
|
||||
|
||||
import { ConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditing';
|
||||
import { IConfigurationData } from 'vs/platform/configuration/common/configuration';
|
||||
|
||||
import { IPickOpenEntry, IPickOptions } from 'vs/platform/quickOpen/common/quickOpen';
|
||||
import { SaveReason } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { TextEditorCursorStyle } from 'vs/editor/common/config/editorOptions';
|
||||
import { EndOfLine, TextEditorLineNumbersStyle } from 'vs/workbench/api/node/extHostTypes';
|
||||
|
||||
|
||||
import { TaskSet } from 'vs/workbench/parts/tasks/common/tasks';
|
||||
import { IModelChangedEvent } from 'vs/editor/common/model/mirrorModel';
|
||||
import { IPosition } from 'vs/editor/common/core/position';
|
||||
import { IRange } from 'vs/editor/common/core/range';
|
||||
import { ISelection, Selection } from 'vs/editor/common/core/selection';
|
||||
|
||||
import { ITreeItem } from 'vs/workbench/parts/views/common/views';
|
||||
import { ThemeColor } from 'vs/platform/theme/common/themeService';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { SerializedError } from 'vs/base/common/errors';
|
||||
|
||||
export interface IEnvironment {
|
||||
isExtensionDevelopmentDebug: boolean;
|
||||
enableProposedApiForAll: boolean;
|
||||
enableProposedApiFor: string | string[];
|
||||
appRoot: string;
|
||||
appSettingsHome: string;
|
||||
disableExtensions: boolean;
|
||||
userExtensionsHome: string;
|
||||
extensionDevelopmentPath: string;
|
||||
extensionTestsPath: string;
|
||||
}
|
||||
|
||||
export interface IWorkspaceData {
|
||||
id: string;
|
||||
name: string;
|
||||
roots: URI[];
|
||||
}
|
||||
|
||||
export interface IInitData {
|
||||
parentPid: number;
|
||||
environment: IEnvironment;
|
||||
workspace: IWorkspaceData;
|
||||
extensions: IExtensionDescription[];
|
||||
configuration: IConfigurationData<any>;
|
||||
telemetryInfo: ITelemetryInfo;
|
||||
}
|
||||
|
||||
export interface IExtHostContext {
|
||||
/**
|
||||
* Returns a proxy to an object addressable/named in the extension host process.
|
||||
*/
|
||||
get<T>(identifier: ProxyIdentifier<T>): T;
|
||||
|
||||
/**
|
||||
* Register manually created instance.
|
||||
*/
|
||||
set<T, R extends T>(identifier: ProxyIdentifier<T>, instance: R): R;
|
||||
}
|
||||
|
||||
export interface IMainContext {
|
||||
/**
|
||||
* Returns a proxy to an object addressable/named in the main/renderer process.
|
||||
*/
|
||||
get<T>(identifier: ProxyIdentifier<T>): T;
|
||||
}
|
||||
|
||||
// --- main thread
|
||||
|
||||
export interface MainThreadCommandsShape extends IDisposable {
|
||||
$registerCommand(id: string): TPromise<any>;
|
||||
$unregisterCommand(id: string): TPromise<any>;
|
||||
$executeCommand<T>(id: string, args: any[]): Thenable<T>;
|
||||
$getCommands(): Thenable<string[]>;
|
||||
}
|
||||
|
||||
export interface MainThreadConfigurationShape extends IDisposable {
|
||||
$updateConfigurationOption(target: ConfigurationTarget, key: string, value: any, resource: URI): TPromise<void>;
|
||||
$removeConfigurationOption(target: ConfigurationTarget, key: string, resource: URI): TPromise<void>;
|
||||
}
|
||||
|
||||
export interface MainThreadDiagnosticsShape extends IDisposable {
|
||||
$changeMany(owner: string, entries: [URI, IMarkerData[]][]): TPromise<any>;
|
||||
$clear(owner: string): TPromise<any>;
|
||||
}
|
||||
|
||||
export interface MainThreadDialogOptions {
|
||||
uri?: URI;
|
||||
openLabel?: string;
|
||||
openFiles?: boolean;
|
||||
openFolders?: boolean;
|
||||
openMany?: boolean;
|
||||
}
|
||||
|
||||
export interface MainThreadDiaglogsShape extends IDisposable {
|
||||
$showOpenDialog(options: MainThreadDialogOptions): TPromise<string[]>;
|
||||
}
|
||||
|
||||
export interface MainThreadDocumentContentProvidersShape extends IDisposable {
|
||||
$registerTextContentProvider(handle: number, scheme: string): void;
|
||||
$unregisterTextContentProvider(handle: number): void;
|
||||
$onVirtualDocumentChange(uri: URI, value: ITextSource): void;
|
||||
}
|
||||
|
||||
export interface MainThreadDocumentsShape extends IDisposable {
|
||||
$tryCreateDocument(options?: { language?: string; content?: string; }): TPromise<any>;
|
||||
$tryOpenDocument(uri: URI): TPromise<any>;
|
||||
$trySaveDocument(uri: URI): TPromise<boolean>;
|
||||
}
|
||||
|
||||
export interface ISelectionChangeEvent {
|
||||
selections: Selection[];
|
||||
source?: string;
|
||||
}
|
||||
|
||||
export interface ITextEditorConfigurationUpdate {
|
||||
tabSize?: number | 'auto';
|
||||
insertSpaces?: boolean | 'auto';
|
||||
cursorStyle?: TextEditorCursorStyle;
|
||||
lineNumbers?: TextEditorLineNumbersStyle;
|
||||
}
|
||||
|
||||
export interface IResolvedTextEditorConfiguration {
|
||||
tabSize: number;
|
||||
insertSpaces: boolean;
|
||||
cursorStyle: TextEditorCursorStyle;
|
||||
lineNumbers: TextEditorLineNumbersStyle;
|
||||
}
|
||||
|
||||
export enum TextEditorRevealType {
|
||||
Default = 0,
|
||||
InCenter = 1,
|
||||
InCenterIfOutsideViewport = 2,
|
||||
AtTop = 3
|
||||
}
|
||||
|
||||
export interface IUndoStopOptions {
|
||||
undoStopBefore: boolean;
|
||||
undoStopAfter: boolean;
|
||||
}
|
||||
|
||||
export interface IApplyEditsOptions extends IUndoStopOptions {
|
||||
setEndOfLine: EndOfLine;
|
||||
}
|
||||
|
||||
|
||||
|
||||
export interface ITextDocumentShowOptions {
|
||||
position?: EditorPosition;
|
||||
preserveFocus?: boolean;
|
||||
pinned?: boolean;
|
||||
selection?: IRange;
|
||||
}
|
||||
|
||||
export interface MainThreadEditorsShape extends IDisposable {
|
||||
$tryShowTextDocument(resource: URI, options: ITextDocumentShowOptions): TPromise<string>;
|
||||
$registerTextEditorDecorationType(key: string, options: editorCommon.IDecorationRenderOptions): void;
|
||||
$removeTextEditorDecorationType(key: string): void;
|
||||
$tryShowEditor(id: string, position: EditorPosition): TPromise<void>;
|
||||
$tryHideEditor(id: string): TPromise<void>;
|
||||
$trySetOptions(id: string, options: ITextEditorConfigurationUpdate): TPromise<any>;
|
||||
$trySetDecorations(id: string, key: string, ranges: editorCommon.IDecorationOptions[]): TPromise<any>;
|
||||
$tryRevealRange(id: string, range: IRange, revealType: TextEditorRevealType): TPromise<any>;
|
||||
$trySetSelections(id: string, selections: ISelection[]): TPromise<any>;
|
||||
$tryApplyEdits(id: string, modelVersionId: number, edits: editorCommon.ISingleEditOperation[], opts: IApplyEditsOptions): TPromise<boolean>;
|
||||
$tryInsertSnippet(id: string, template: string, selections: IRange[], opts: IUndoStopOptions): TPromise<any>;
|
||||
$getDiffInformation(id: string): TPromise<editorCommon.ILineChange[]>;
|
||||
}
|
||||
|
||||
export interface MainThreadTreeViewsShape extends IDisposable {
|
||||
$registerView(treeViewId: string): void;
|
||||
$refresh(treeViewId: string, treeItemHandles: number[]): void;
|
||||
}
|
||||
|
||||
export interface MainThreadErrorsShape extends IDisposable {
|
||||
$onUnexpectedError(err: any | SerializedError, extensionId: string | undefined): void;
|
||||
}
|
||||
|
||||
export interface MainThreadLanguageFeaturesShape extends IDisposable {
|
||||
$unregister(handle: number): TPromise<any>;
|
||||
$registerOutlineSupport(handle: number, selector: vscode.DocumentSelector): TPromise<any>;
|
||||
$registerCodeLensSupport(handle: number, selector: vscode.DocumentSelector, eventHandle: number): TPromise<any>;
|
||||
$emitCodeLensEvent(eventHandle: number, event?: any): TPromise<any>;
|
||||
$registerDeclaractionSupport(handle: number, selector: vscode.DocumentSelector): TPromise<any>;
|
||||
$registerImplementationSupport(handle: number, selector: vscode.DocumentSelector): TPromise<any>;
|
||||
$registerTypeDefinitionSupport(handle: number, selector: vscode.DocumentSelector): TPromise<any>;
|
||||
$registerHoverProvider(handle: number, selector: vscode.DocumentSelector): TPromise<any>;
|
||||
$registerDocumentHighlightProvider(handle: number, selector: vscode.DocumentSelector): TPromise<any>;
|
||||
$registerReferenceSupport(handle: number, selector: vscode.DocumentSelector): TPromise<any>;
|
||||
$registerQuickFixSupport(handle: number, selector: vscode.DocumentSelector): TPromise<any>;
|
||||
$registerDocumentFormattingSupport(handle: number, selector: vscode.DocumentSelector): TPromise<any>;
|
||||
$registerRangeFormattingSupport(handle: number, selector: vscode.DocumentSelector): TPromise<any>;
|
||||
$registerOnTypeFormattingSupport(handle: number, selector: vscode.DocumentSelector, autoFormatTriggerCharacters: string[]): TPromise<any>;
|
||||
$registerNavigateTypeSupport(handle: number): TPromise<any>;
|
||||
$registerRenameSupport(handle: number, selector: vscode.DocumentSelector): TPromise<any>;
|
||||
$registerSuggestSupport(handle: number, selector: vscode.DocumentSelector, triggerCharacters: string[]): TPromise<any>;
|
||||
$registerSignatureHelpProvider(handle: number, selector: vscode.DocumentSelector, triggerCharacter: string[]): TPromise<any>;
|
||||
$registerDocumentLinkProvider(handle: number, selector: vscode.DocumentSelector): TPromise<any>;
|
||||
$registerColorFormats(formats: IRawColorFormatMap): TPromise<any>;
|
||||
$registerDocumentColorProvider(handle: number, selector: vscode.DocumentSelector): TPromise<any>;
|
||||
$setLanguageConfiguration(handle: number, languageId: string, configuration: vscode.LanguageConfiguration): TPromise<any>;
|
||||
}
|
||||
|
||||
export interface MainThreadLanguagesShape extends IDisposable {
|
||||
$getLanguages(): TPromise<string[]>;
|
||||
}
|
||||
|
||||
export interface MainThreadMessageOptions {
|
||||
extension?: IExtensionDescription;
|
||||
modal?: boolean;
|
||||
}
|
||||
|
||||
export interface MainThreadMessageServiceShape extends IDisposable {
|
||||
$showMessage(severity: Severity, message: string, options: MainThreadMessageOptions, commands: { title: string; isCloseAffordance: boolean; handle: number; }[]): Thenable<number>;
|
||||
}
|
||||
|
||||
export interface MainThreadOutputServiceShape extends IDisposable {
|
||||
$append(channelId: string, label: string, value: string): TPromise<void>;
|
||||
$clear(channelId: string, label: string): TPromise<void>;
|
||||
$dispose(channelId: string, label: string): TPromise<void>;
|
||||
$reveal(channelId: string, label: string, preserveFocus: boolean): TPromise<void>;
|
||||
$close(channelId: string): TPromise<void>;
|
||||
}
|
||||
|
||||
export interface MainThreadProgressShape extends IDisposable {
|
||||
|
||||
$startProgress(handle: number, options: IProgressOptions): void;
|
||||
$progressReport(handle: number, message: IProgressStep): void;
|
||||
$progressEnd(handle: number): void;
|
||||
}
|
||||
|
||||
export interface MainThreadTerminalServiceShape extends IDisposable {
|
||||
$createTerminal(name?: string, shellPath?: string, shellArgs?: string[], waitOnExit?: boolean): TPromise<number>;
|
||||
$dispose(terminalId: number): void;
|
||||
$hide(terminalId: number): void;
|
||||
$sendText(terminalId: number, text: string, addNewLine: boolean): void;
|
||||
$show(terminalId: number, preserveFocus: boolean): void;
|
||||
}
|
||||
|
||||
export interface MyQuickPickItems extends IPickOpenEntry {
|
||||
handle: number;
|
||||
}
|
||||
export interface MainThreadQuickOpenShape extends IDisposable {
|
||||
$show(options: IPickOptions): TPromise<number>;
|
||||
$setItems(items: MyQuickPickItems[]): TPromise<any>;
|
||||
$setError(error: Error): TPromise<any>;
|
||||
$input(options: vscode.InputBoxOptions, validateInput: boolean): TPromise<string>;
|
||||
}
|
||||
|
||||
export interface MainThreadStatusBarShape extends IDisposable {
|
||||
$setEntry(id: number, extensionId: string, text: string, tooltip: string, command: string, color: string | ThemeColor, alignment: MainThreadStatusBarAlignment, priority: number): void;
|
||||
$dispose(id: number);
|
||||
}
|
||||
|
||||
export interface MainThreadStorageShape extends IDisposable {
|
||||
$getValue<T>(shared: boolean, key: string): TPromise<T>;
|
||||
$setValue(shared: boolean, key: string, value: any): TPromise<any>;
|
||||
}
|
||||
|
||||
export interface MainThreadTelemetryShape extends IDisposable {
|
||||
$publicLog(eventName: string, data?: any): void;
|
||||
}
|
||||
|
||||
export interface MainThreadWorkspaceShape extends IDisposable {
|
||||
$startSearch(include: string, exclude: string, maxResults: number, requestId: number): Thenable<URI[]>;
|
||||
$cancelSearch(requestId: number): Thenable<boolean>;
|
||||
$saveAll(includeUntitled?: boolean): Thenable<boolean>;
|
||||
$applyWorkspaceEdit(edits: IResourceEdit[]): TPromise<boolean>;
|
||||
|
||||
$registerFileSystemProvider(handle: number, authority: string): void;
|
||||
$unregisterFileSystemProvider(handle): void;
|
||||
$onFileSystemChange(handle: number, resource: URI): void;
|
||||
$updateSearchSession(session: number, data): void;
|
||||
$finishSearchSession(session: number, err?: any): void;
|
||||
}
|
||||
|
||||
export interface MainThreadTaskShape extends IDisposable {
|
||||
$registerTaskProvider(handle: number): TPromise<any>;
|
||||
$unregisterTaskProvider(handle: number): TPromise<any>;
|
||||
}
|
||||
|
||||
export interface MainThreadExtensionServiceShape extends IDisposable {
|
||||
$localShowMessage(severity: Severity, msg: string): void;
|
||||
$onExtensionActivated(extensionId: string, startup: boolean, codeLoadingTime: number, activateCallTime: number, activateResolvedTime: number): void;
|
||||
$onExtensionActivationFailed(extensionId: string): void;
|
||||
}
|
||||
|
||||
export interface SCMProviderFeatures {
|
||||
hasQuickDiffProvider?: boolean;
|
||||
count?: number;
|
||||
commitTemplate?: string;
|
||||
acceptInputCommand?: modes.Command;
|
||||
statusBarCommands?: modes.Command[];
|
||||
}
|
||||
|
||||
export interface SCMGroupFeatures {
|
||||
hideWhenEmpty?: boolean;
|
||||
}
|
||||
|
||||
export type SCMRawResource = [
|
||||
number /*handle*/,
|
||||
string /*resourceUri*/,
|
||||
modes.Command /*command*/,
|
||||
string[] /*icons: light, dark*/,
|
||||
string /*tooltip*/,
|
||||
boolean /*strike through*/,
|
||||
boolean /*faded*/
|
||||
];
|
||||
|
||||
export interface MainThreadSCMShape extends IDisposable {
|
||||
$registerSourceControl(handle: number, id: string, label: string): void;
|
||||
$updateSourceControl(handle: number, features: SCMProviderFeatures): void;
|
||||
$unregisterSourceControl(handle: number): void;
|
||||
|
||||
$registerGroup(sourceControlHandle: number, handle: number, id: string, label: string): void;
|
||||
$updateGroup(sourceControlHandle: number, handle: number, features: SCMGroupFeatures): void;
|
||||
$updateGroupLabel(sourceControlHandle: number, handle: number, label: string): void;
|
||||
$updateGroupResourceStates(sourceControlHandle: number, groupHandle: number, resources: SCMRawResource[]): void;
|
||||
$unregisterGroup(sourceControlHandle: number, handle: number): void;
|
||||
|
||||
$setInputBoxValue(sourceControlHandle: number, value: string): void;
|
||||
}
|
||||
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
/*
|
||||
export type DebugSessionUUID = string;
|
||||
|
||||
export interface MainThreadDebugServiceShape extends IDisposable {
|
||||
$registerDebugConfigurationProvider(type: string, hasProvideMethod: boolean, hasResolveMethod: boolean, handle: number): TPromise<any>;
|
||||
$unregisterDebugConfigurationProvider(handle: number): TPromise<any>;
|
||||
$startDebugging(folderUri: URI | undefined, nameOrConfig: string | vscode.DebugConfiguration): TPromise<boolean>;
|
||||
$startDebugSession(folderUri: URI | undefined, config: vscode.DebugConfiguration): TPromise<DebugSessionUUID>;
|
||||
$customDebugAdapterRequest(id: DebugSessionUUID, command: string, args: any): TPromise<any>;
|
||||
}
|
||||
*/
|
||||
export interface MainThreadCredentialsShape extends IDisposable {
|
||||
$readSecret(service: string, account: string): Thenable<string | undefined>;
|
||||
$writeSecret(service: string, account: string, secret: string): Thenable<void>;
|
||||
$deleteSecret(service: string, account: string): Thenable<boolean>;
|
||||
}
|
||||
|
||||
export interface MainThreadWindowShape extends IDisposable {
|
||||
$getWindowVisibility(): TPromise<boolean>;
|
||||
}
|
||||
|
||||
// -- extension host
|
||||
|
||||
export interface ExtHostCommandsShape {
|
||||
$executeContributedCommand<T>(id: string, ...args: any[]): Thenable<T>;
|
||||
$getContributedCommandHandlerDescriptions(): TPromise<{ [id: string]: string | ICommandHandlerDescription }>;
|
||||
}
|
||||
|
||||
export interface ExtHostConfigurationShape {
|
||||
$acceptConfigurationChanged(data: IConfigurationData<any>);
|
||||
}
|
||||
|
||||
export interface ExtHostDiagnosticsShape {
|
||||
|
||||
}
|
||||
|
||||
export interface ExtHostDocumentContentProvidersShape {
|
||||
$provideTextDocumentContent(handle: number, uri: URI): TPromise<string>;
|
||||
}
|
||||
|
||||
export interface IModelAddedData {
|
||||
url: URI;
|
||||
versionId: number;
|
||||
lines: string[];
|
||||
EOL: string;
|
||||
modeId: string;
|
||||
isDirty: boolean;
|
||||
}
|
||||
export interface ExtHostDocumentsShape {
|
||||
$acceptModelModeChanged(strURL: string, oldModeId: string, newModeId: string): void;
|
||||
$acceptModelSaved(strURL: string): void;
|
||||
$acceptDirtyStateChanged(strURL: string, isDirty: boolean): void;
|
||||
$acceptModelChanged(strURL: string, e: IModelChangedEvent, isDirty: boolean): void;
|
||||
}
|
||||
|
||||
export interface ExtHostDocumentSaveParticipantShape {
|
||||
$participateInSave(resource: URI, reason: SaveReason): TPromise<boolean[]>;
|
||||
}
|
||||
|
||||
export interface ITextEditorAddData {
|
||||
id: string;
|
||||
document: URI;
|
||||
options: IResolvedTextEditorConfiguration;
|
||||
selections: ISelection[];
|
||||
editorPosition: EditorPosition;
|
||||
}
|
||||
export interface ITextEditorPositionData {
|
||||
[id: string]: EditorPosition;
|
||||
}
|
||||
export interface ExtHostEditorsShape {
|
||||
$acceptOptionsChanged(id: string, opts: IResolvedTextEditorConfiguration): void;
|
||||
$acceptSelectionsChanged(id: string, event: ISelectionChangeEvent): void;
|
||||
$acceptEditorPositionData(data: ITextEditorPositionData): void;
|
||||
}
|
||||
|
||||
export interface IDocumentsAndEditorsDelta {
|
||||
removedDocuments?: string[];
|
||||
addedDocuments?: IModelAddedData[];
|
||||
removedEditors?: string[];
|
||||
addedEditors?: ITextEditorAddData[];
|
||||
newActiveEditor?: string;
|
||||
}
|
||||
|
||||
export interface ExtHostDocumentsAndEditorsShape {
|
||||
$acceptDocumentsAndEditorsDelta(delta: IDocumentsAndEditorsDelta): void;
|
||||
}
|
||||
|
||||
export interface ExtHostTreeViewsShape {
|
||||
$getElements(treeViewId: string): TPromise<ITreeItem[]>;
|
||||
$getChildren(treeViewId: string, treeItemHandle: number): TPromise<ITreeItem[]>;
|
||||
}
|
||||
|
||||
export interface ExtHostWorkspaceShape {
|
||||
$acceptWorkspaceData(workspace: IWorkspaceData): void;
|
||||
|
||||
$resolveFile(handle: number, resource: URI): TPromise<string>;
|
||||
$storeFile(handle: number, resource: URI, content: string): TPromise<any>;
|
||||
$startSearch(handle: number, session: number, query: string): void;
|
||||
$cancelSearch(handle: number, session: number): void;
|
||||
}
|
||||
|
||||
export interface ExtHostExtensionServiceShape {
|
||||
$activateByEvent(activationEvent: string): TPromise<void>;
|
||||
}
|
||||
|
||||
export interface FileSystemEvents {
|
||||
created: URI[];
|
||||
changed: URI[];
|
||||
deleted: URI[];
|
||||
}
|
||||
export interface ExtHostFileSystemEventServiceShape {
|
||||
$onFileEvent(events: FileSystemEvents);
|
||||
}
|
||||
|
||||
export interface ObjectIdentifier {
|
||||
$ident: number;
|
||||
}
|
||||
|
||||
export namespace ObjectIdentifier {
|
||||
export const name = '$ident';
|
||||
export function mixin<T>(obj: T, id: number): T & ObjectIdentifier {
|
||||
Object.defineProperty(obj, name, { value: id, enumerable: true });
|
||||
return <T & ObjectIdentifier>obj;
|
||||
}
|
||||
export function of(obj: any): number {
|
||||
return obj[name];
|
||||
}
|
||||
}
|
||||
|
||||
export interface ExtHostHeapServiceShape {
|
||||
$onGarbageCollection(ids: number[]): void;
|
||||
}
|
||||
export interface IRawColorInfo {
|
||||
color: [number, number, number, number];
|
||||
availableFormats: (number | [number, number])[];
|
||||
range: IRange;
|
||||
}
|
||||
|
||||
export type IRawColorFormatMap = [number, string][];
|
||||
|
||||
export interface ExtHostLanguageFeaturesShape {
|
||||
$provideDocumentSymbols(handle: number, resource: URI): TPromise<modes.SymbolInformation[]>;
|
||||
$provideCodeLenses(handle: number, resource: URI): TPromise<modes.ICodeLensSymbol[]>;
|
||||
$resolveCodeLens(handle: number, resource: URI, symbol: modes.ICodeLensSymbol): TPromise<modes.ICodeLensSymbol>;
|
||||
$provideDefinition(handle: number, resource: URI, position: IPosition): TPromise<modes.Definition>;
|
||||
$provideImplementation(handle: number, resource: URI, position: IPosition): TPromise<modes.Definition>;
|
||||
$provideTypeDefinition(handle: number, resource: URI, position: IPosition): TPromise<modes.Definition>;
|
||||
$provideHover(handle: number, resource: URI, position: IPosition): TPromise<modes.Hover>;
|
||||
$provideDocumentHighlights(handle: number, resource: URI, position: IPosition): TPromise<modes.DocumentHighlight[]>;
|
||||
$provideReferences(handle: number, resource: URI, position: IPosition, context: modes.ReferenceContext): TPromise<modes.Location[]>;
|
||||
$provideCodeActions(handle: number, resource: URI, range: IRange): TPromise<modes.Command[]>;
|
||||
$provideDocumentFormattingEdits(handle: number, resource: URI, options: modes.FormattingOptions): TPromise<editorCommon.ISingleEditOperation[]>;
|
||||
$provideDocumentRangeFormattingEdits(handle: number, resource: URI, range: IRange, options: modes.FormattingOptions): TPromise<editorCommon.ISingleEditOperation[]>;
|
||||
$provideOnTypeFormattingEdits(handle: number, resource: URI, position: IPosition, ch: string, options: modes.FormattingOptions): TPromise<editorCommon.ISingleEditOperation[]>;
|
||||
$provideWorkspaceSymbols(handle: number, search: string): TPromise<modes.SymbolInformation[]>;
|
||||
$resolveWorkspaceSymbol(handle: number, symbol: modes.SymbolInformation): TPromise<modes.SymbolInformation>;
|
||||
$provideRenameEdits(handle: number, resource: URI, position: IPosition, newName: string): TPromise<modes.WorkspaceEdit>;
|
||||
$provideCompletionItems(handle: number, resource: URI, position: IPosition): TPromise<modes.ISuggestResult>;
|
||||
$resolveCompletionItem(handle: number, resource: URI, position: IPosition, suggestion: modes.ISuggestion): TPromise<modes.ISuggestion>;
|
||||
$provideSignatureHelp(handle: number, resource: URI, position: IPosition): TPromise<modes.SignatureHelp>;
|
||||
$provideDocumentLinks(handle: number, resource: URI): TPromise<modes.ILink[]>;
|
||||
$provideDocumentColors(handle: number, resource: URI): TPromise<IRawColorInfo[]>;
|
||||
$resolveDocumentLink(handle: number, link: modes.ILink): TPromise<modes.ILink>;
|
||||
}
|
||||
|
||||
export interface ExtHostQuickOpenShape {
|
||||
$onItemSelected(handle: number): void;
|
||||
$validateInput(input: string): TPromise<string>;
|
||||
}
|
||||
|
||||
export interface ExtHostTerminalServiceShape {
|
||||
$acceptTerminalClosed(id: number): void;
|
||||
$acceptTerminalProcessId(id: number, processId: number): void;
|
||||
}
|
||||
|
||||
export interface ExtHostSCMShape {
|
||||
$provideOriginalResource(sourceControlHandle: number, uri: URI): TPromise<URI>;
|
||||
$onInputBoxValueChange(sourceControlHandle: number, value: string): TPromise<void>;
|
||||
}
|
||||
|
||||
export interface ExtHostTaskShape {
|
||||
$provideTasks(handle: number): TPromise<TaskSet>;
|
||||
}
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
/*
|
||||
export interface ExtHostDebugServiceShape {
|
||||
$resolveDebugConfiguration(handle: number, folder: URI | undefined, debugConfiguration: any): TPromise<any>;
|
||||
$provideDebugConfigurations(handle: number, folder: URI | undefined): TPromise<any[]>;
|
||||
$acceptDebugSessionStarted(id: DebugSessionUUID, type: string, name: string): void;
|
||||
$acceptDebugSessionTerminated(id: DebugSessionUUID, type: string, name: string): void;
|
||||
$acceptDebugSessionActiveChanged(id: DebugSessionUUID | undefined, type?: string, name?: string): void;
|
||||
$acceptDebugSessionCustomEvent(id: DebugSessionUUID, type: string, name: string, event: any): void;
|
||||
}
|
||||
*/
|
||||
|
||||
export interface ExtHostCredentialsShape {
|
||||
}
|
||||
|
||||
export interface ExtHostWindowShape {
|
||||
$onDidChangeWindowFocus(value: boolean): void;
|
||||
}
|
||||
|
||||
// --- proxy identifiers
|
||||
|
||||
export const MainContext = {
|
||||
MainThreadCommands: createMainId<MainThreadCommandsShape>('MainThreadCommands'),
|
||||
MainThreadConfiguration: createMainId<MainThreadConfigurationShape>('MainThreadConfiguration'),
|
||||
// {{SQL CARBON EDIT}}
|
||||
// MainThreadDebugService: createMainId<MainThreadDebugServiceShape>('MainThreadDebugService'),
|
||||
MainThreadDiagnostics: createMainId<MainThreadDiagnosticsShape>('MainThreadDiagnostics'),
|
||||
MainThreadDialogs: createMainId<MainThreadDiaglogsShape>('MainThreadDiaglogs'),
|
||||
MainThreadDocuments: createMainId<MainThreadDocumentsShape>('MainThreadDocuments'),
|
||||
MainThreadDocumentContentProviders: createMainId<MainThreadDocumentContentProvidersShape>('MainThreadDocumentContentProviders'),
|
||||
MainThreadEditors: createMainId<MainThreadEditorsShape>('MainThreadEditors'),
|
||||
MainThreadErrors: createMainId<MainThreadErrorsShape>('MainThreadErrors'),
|
||||
MainThreadTreeViews: createMainId<MainThreadTreeViewsShape>('MainThreadTreeViews'),
|
||||
MainThreadLanguageFeatures: createMainId<MainThreadLanguageFeaturesShape>('MainThreadLanguageFeatures'),
|
||||
MainThreadLanguages: createMainId<MainThreadLanguagesShape>('MainThreadLanguages'),
|
||||
MainThreadMessageService: createMainId<MainThreadMessageServiceShape>('MainThreadMessageService'),
|
||||
MainThreadOutputService: createMainId<MainThreadOutputServiceShape>('MainThreadOutputService'),
|
||||
MainThreadProgress: createMainId<MainThreadProgressShape>('MainThreadProgress'),
|
||||
MainThreadQuickOpen: createMainId<MainThreadQuickOpenShape>('MainThreadQuickOpen'),
|
||||
MainThreadStatusBar: createMainId<MainThreadStatusBarShape>('MainThreadStatusBar'),
|
||||
MainThreadStorage: createMainId<MainThreadStorageShape>('MainThreadStorage'),
|
||||
MainThreadTelemetry: createMainId<MainThreadTelemetryShape>('MainThreadTelemetry'),
|
||||
MainThreadTerminalService: createMainId<MainThreadTerminalServiceShape>('MainThreadTerminalService'),
|
||||
MainThreadWorkspace: createMainId<MainThreadWorkspaceShape>('MainThreadWorkspace'),
|
||||
MainThreadExtensionService: createMainId<MainThreadExtensionServiceShape>('MainThreadExtensionService'),
|
||||
MainThreadSCM: createMainId<MainThreadSCMShape>('MainThreadSCM'),
|
||||
MainThreadTask: createMainId<MainThreadTaskShape>('MainThreadTask'),
|
||||
MainThreadCredentials: createMainId<MainThreadCredentialsShape>('MainThreadCredentials'),
|
||||
MainThreadWindow: createMainId<MainThreadWindowShape>('MainThreadWindow'),
|
||||
};
|
||||
|
||||
export const ExtHostContext = {
|
||||
ExtHostCommands: createExtId<ExtHostCommandsShape>('ExtHostCommands'),
|
||||
ExtHostConfiguration: createExtId<ExtHostConfigurationShape>('ExtHostConfiguration'),
|
||||
ExtHostDiagnostics: createExtId<ExtHostDiagnosticsShape>('ExtHostDiagnostics'),
|
||||
// {{SQL CARBON EDIT}}
|
||||
// ExtHostDebugService: createExtId<ExtHostDebugServiceShape>('ExtHostDebugService'),
|
||||
ExtHostDocumentsAndEditors: createExtId<ExtHostDocumentsAndEditorsShape>('ExtHostDocumentsAndEditors'),
|
||||
ExtHostDocuments: createExtId<ExtHostDocumentsShape>('ExtHostDocuments'),
|
||||
ExtHostDocumentContentProviders: createExtId<ExtHostDocumentContentProvidersShape>('ExtHostDocumentContentProviders'),
|
||||
ExtHostDocumentSaveParticipant: createExtId<ExtHostDocumentSaveParticipantShape>('ExtHostDocumentSaveParticipant'),
|
||||
ExtHostEditors: createExtId<ExtHostEditorsShape>('ExtHostEditors'),
|
||||
ExtHostTreeViews: createExtId<ExtHostTreeViewsShape>('ExtHostTreeViews'),
|
||||
ExtHostFileSystemEventService: createExtId<ExtHostFileSystemEventServiceShape>('ExtHostFileSystemEventService'),
|
||||
ExtHostHeapService: createExtId<ExtHostHeapServiceShape>('ExtHostHeapMonitor'),
|
||||
ExtHostLanguageFeatures: createExtId<ExtHostLanguageFeaturesShape>('ExtHostLanguageFeatures'),
|
||||
ExtHostQuickOpen: createExtId<ExtHostQuickOpenShape>('ExtHostQuickOpen'),
|
||||
ExtHostExtensionService: createExtId<ExtHostExtensionServiceShape>('ExtHostExtensionService'),
|
||||
ExtHostTerminalService: createExtId<ExtHostTerminalServiceShape>('ExtHostTerminalService'),
|
||||
ExtHostSCM: createExtId<ExtHostSCMShape>('ExtHostSCM'),
|
||||
ExtHostTask: createExtId<ExtHostTaskShape>('ExtHostTask'),
|
||||
ExtHostWorkspace: createExtId<ExtHostWorkspaceShape>('ExtHostWorkspace'),
|
||||
ExtHostCredentials: createExtId<ExtHostCredentialsShape>('ExtHostCredentials'),
|
||||
ExtHostWindow: createExtId<ExtHostWindowShape>('ExtHostWindow'),
|
||||
};
|
||||
474
src/vs/workbench/api/node/extHostApiCommands.ts
Normal file
474
src/vs/workbench/api/node/extHostApiCommands.ts
Normal file
@@ -0,0 +1,474 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import * as vscode from 'vscode';
|
||||
import * as typeConverters from 'vs/workbench/api/node/extHostTypeConverters';
|
||||
import * as types from 'vs/workbench/api/node/extHostTypes';
|
||||
import { ISingleEditOperation } from 'vs/editor/common/editorCommon';
|
||||
import * as modes from 'vs/editor/common/modes';
|
||||
import { ICommandHandlerDescription } from 'vs/platform/commands/common/commands';
|
||||
import { ExtHostCommands } from 'vs/workbench/api/node/extHostCommands';
|
||||
import { IWorkspaceSymbolProvider } from 'vs/workbench/parts/search/common/search';
|
||||
import { ITextEditorOptions } from 'vs/platform/editor/common/editor';
|
||||
|
||||
export class ExtHostApiCommands {
|
||||
|
||||
static register(commands: ExtHostCommands) {
|
||||
return new ExtHostApiCommands(commands).registerCommands();
|
||||
}
|
||||
|
||||
private _commands: ExtHostCommands;
|
||||
private _disposables: IDisposable[] = [];
|
||||
|
||||
private constructor(commands: ExtHostCommands) {
|
||||
this._commands = commands;
|
||||
}
|
||||
|
||||
registerCommands() {
|
||||
this._register('vscode.executeWorkspaceSymbolProvider', this._executeWorkspaceSymbolProvider, {
|
||||
description: 'Execute all workspace symbol provider.',
|
||||
args: [{ name: 'query', description: 'Search string', constraint: String }],
|
||||
returns: 'A promise that resolves to an array of SymbolInformation-instances.'
|
||||
|
||||
});
|
||||
this._register('vscode.executeDefinitionProvider', this._executeDefinitionProvider, {
|
||||
description: 'Execute all definition provider.',
|
||||
args: [
|
||||
{ name: 'uri', description: 'Uri of a text document', constraint: URI },
|
||||
{ name: 'position', description: 'Position of a symbol', constraint: types.Position }
|
||||
],
|
||||
returns: 'A promise that resolves to an array of Location-instances.'
|
||||
});
|
||||
this._register('vscode.executeImplementationProvider', this._executeImplementationProvider, {
|
||||
description: 'Execute all implementation providers.',
|
||||
args: [
|
||||
{ name: 'uri', description: 'Uri of a text document', constraint: URI },
|
||||
{ name: 'position', description: 'Position of a symbol', constraint: types.Position }
|
||||
],
|
||||
returns: 'A promise that resolves to an array of Location-instance.'
|
||||
});
|
||||
this._register('vscode.executeHoverProvider', this._executeHoverProvider, {
|
||||
description: 'Execute all hover provider.',
|
||||
args: [
|
||||
{ name: 'uri', description: 'Uri of a text document', constraint: URI },
|
||||
{ name: 'position', description: 'Position of a symbol', constraint: types.Position }
|
||||
],
|
||||
returns: 'A promise that resolves to an array of Hover-instances.'
|
||||
});
|
||||
this._register('vscode.executeDocumentHighlights', this._executeDocumentHighlights, {
|
||||
description: 'Execute document highlight provider.',
|
||||
args: [
|
||||
{ name: 'uri', description: 'Uri of a text document', constraint: URI },
|
||||
{ name: 'position', description: 'Position in a text document', constraint: types.Position }
|
||||
],
|
||||
returns: 'A promise that resolves to an array of DocumentHighlight-instances.'
|
||||
});
|
||||
this._register('vscode.executeReferenceProvider', this._executeReferenceProvider, {
|
||||
description: 'Execute reference provider.',
|
||||
args: [
|
||||
{ name: 'uri', description: 'Uri of a text document', constraint: URI },
|
||||
{ name: 'position', description: 'Position in a text document', constraint: types.Position }
|
||||
],
|
||||
returns: 'A promise that resolves to an array of Location-instances.'
|
||||
});
|
||||
this._register('vscode.executeDocumentRenameProvider', this._executeDocumentRenameProvider, {
|
||||
description: 'Execute rename provider.',
|
||||
args: [
|
||||
{ name: 'uri', description: 'Uri of a text document', constraint: URI },
|
||||
{ name: 'position', description: 'Position in a text document', constraint: types.Position },
|
||||
{ name: 'newName', description: 'The new symbol name', constraint: String }
|
||||
],
|
||||
returns: 'A promise that resolves to a WorkspaceEdit.'
|
||||
});
|
||||
this._register('vscode.executeSignatureHelpProvider', this._executeSignatureHelpProvider, {
|
||||
description: 'Execute signature help provider.',
|
||||
args: [
|
||||
{ name: 'uri', description: 'Uri of a text document', constraint: URI },
|
||||
{ name: 'position', description: 'Position in a text document', constraint: types.Position },
|
||||
{ name: 'triggerCharacter', description: '(optional) Trigger signature help when the user types the character, like `,` or `(`', constraint: value => value === void 0 || typeof value === 'string' }
|
||||
],
|
||||
returns: 'A promise that resolves to SignatureHelp.'
|
||||
});
|
||||
this._register('vscode.executeDocumentSymbolProvider', this._executeDocumentSymbolProvider, {
|
||||
description: 'Execute document symbol provider.',
|
||||
args: [
|
||||
{ name: 'uri', description: 'Uri of a text document', constraint: URI }
|
||||
],
|
||||
returns: 'A promise that resolves to an array of SymbolInformation-instances.'
|
||||
});
|
||||
this._register('vscode.executeCompletionItemProvider', this._executeCompletionItemProvider, {
|
||||
description: 'Execute completion item provider.',
|
||||
args: [
|
||||
{ name: 'uri', description: 'Uri of a text document', constraint: URI },
|
||||
{ name: 'position', description: 'Position in a text document', constraint: types.Position },
|
||||
{ name: 'triggerCharacter', description: '(optional) Trigger completion when the user types the character, like `,` or `(`', constraint: value => value === void 0 || typeof value === 'string' }
|
||||
],
|
||||
returns: 'A promise that resolves to a CompletionList-instance.'
|
||||
});
|
||||
this._register('vscode.executeCodeActionProvider', this._executeCodeActionProvider, {
|
||||
description: 'Execute code action provider.',
|
||||
args: [
|
||||
{ name: 'uri', description: 'Uri of a text document', constraint: URI },
|
||||
{ name: 'range', description: 'Range in a text document', constraint: types.Range }
|
||||
],
|
||||
returns: 'A promise that resolves to an array of Command-instances.'
|
||||
});
|
||||
this._register('vscode.executeCodeLensProvider', this._executeCodeLensProvider, {
|
||||
description: 'Execute CodeLens provider.',
|
||||
args: [
|
||||
{ name: 'uri', description: 'Uri of a text document', constraint: URI }
|
||||
],
|
||||
returns: 'A promise that resolves to an array of CodeLens-instances.'
|
||||
});
|
||||
this._register('vscode.executeFormatDocumentProvider', this._executeFormatDocumentProvider, {
|
||||
description: 'Execute document format provider.',
|
||||
args: [
|
||||
{ name: 'uri', description: 'Uri of a text document', constraint: URI },
|
||||
{ name: 'options', description: 'Formatting options' }
|
||||
],
|
||||
returns: 'A promise that resolves to an array of TextEdits.'
|
||||
});
|
||||
this._register('vscode.executeFormatRangeProvider', this._executeFormatRangeProvider, {
|
||||
description: 'Execute range format provider.',
|
||||
args: [
|
||||
{ name: 'uri', description: 'Uri of a text document', constraint: URI },
|
||||
{ name: 'range', description: 'Range in a text document', constraint: types.Range },
|
||||
{ name: 'options', description: 'Formatting options' }
|
||||
],
|
||||
returns: 'A promise that resolves to an array of TextEdits.'
|
||||
});
|
||||
this._register('vscode.executeFormatOnTypeProvider', this._executeFormatOnTypeProvider, {
|
||||
description: 'Execute document format provider.',
|
||||
args: [
|
||||
{ name: 'uri', description: 'Uri of a text document', constraint: URI },
|
||||
{ name: 'position', description: 'Position in a text document', constraint: types.Position },
|
||||
{ name: 'ch', description: 'Character that got typed', constraint: String },
|
||||
{ name: 'options', description: 'Formatting options' }
|
||||
],
|
||||
returns: 'A promise that resolves to an array of TextEdits.'
|
||||
});
|
||||
this._register('vscode.executeLinkProvider', this._executeDocumentLinkProvider, {
|
||||
description: 'Execute document link provider.',
|
||||
args: [
|
||||
{ name: 'uri', description: 'Uri of a text document', constraint: URI }
|
||||
],
|
||||
returns: 'A promise that resolves to an array of DocumentLink-instances.'
|
||||
});
|
||||
|
||||
this._register('vscode.previewHtml', (uri: URI, position?: vscode.ViewColumn, label?: string, options?: any) => {
|
||||
return this._commands.executeCommand('_workbench.previewHtml',
|
||||
uri,
|
||||
typeof position === 'number' && typeConverters.fromViewColumn(position),
|
||||
label,
|
||||
options);
|
||||
}, {
|
||||
description: `
|
||||
Render the html of the resource in an editor view.
|
||||
|
||||
See [working with the html preview](https://code.visualstudio.com/docs/extensionAPI/vscode-api-commands#working-with-the-html-preview) for more information about the html preview's intergration with the editor and for best practices for extension authors.
|
||||
`,
|
||||
args: [
|
||||
{ name: 'uri', description: 'Uri of the resource to preview.', constraint: value => value instanceof URI || typeof value === 'string' },
|
||||
{ name: 'column', description: '(optional) Column in which to preview.', constraint: value => typeof value === 'undefined' || (typeof value === 'number' && typeof types.ViewColumn[value] === 'string') },
|
||||
{ name: 'label', description: '(optional) An human readable string that is used as title for the preview.', constraint: v => typeof v === 'string' || typeof v === 'undefined' },
|
||||
{ name: 'options', description: '(optional) Options for controlling webview environment.', constraint: v => typeof v === 'object' || typeof v === 'undefined' }
|
||||
]
|
||||
});
|
||||
|
||||
this._register('vscode.openFolder', (uri?: URI, forceNewWindow?: boolean) => {
|
||||
if (!uri) {
|
||||
return this._commands.executeCommand('_files.pickFolderAndOpen', forceNewWindow);
|
||||
}
|
||||
|
||||
return this._commands.executeCommand('_files.windowOpen', [uri.fsPath], forceNewWindow);
|
||||
}, {
|
||||
description: 'Open a folder in the current window or new window depending on the newWindow argument. Note that opening in the same window will shutdown the current extension host process and start a new one on the given folder unless the newWindow parameter is set to true.',
|
||||
args: [
|
||||
{ name: 'uri', description: '(optional) Uri of the folder to open. If not provided, a native dialog will ask the user for the folder', constraint: value => value === void 0 || value instanceof URI },
|
||||
{ name: 'newWindow', description: '(optional) Whether to open the folder in a new window or the same. Defaults to opening in the same window.', constraint: value => value === void 0 || typeof value === 'boolean' }
|
||||
]
|
||||
});
|
||||
|
||||
this._register('vscode.startDebug', (configuration?: any, folderUri?: URI) => {
|
||||
return this._commands.executeCommand('_workbench.startDebug', configuration, folderUri);
|
||||
}, {
|
||||
description: 'Start a debugging session.',
|
||||
args: [
|
||||
{ name: 'configuration', description: '(optional) Name of the debug configuration from \'launch.json\' to use. Or a configuration json object to use.' }
|
||||
]
|
||||
});
|
||||
|
||||
this._register('vscode.diff', (left: URI, right: URI, label: string, options?: vscode.TextDocumentShowOptions) => {
|
||||
let editorOptions: ITextEditorOptions;
|
||||
if (options) {
|
||||
editorOptions = {
|
||||
pinned: typeof options.preview === 'boolean' ? !options.preview : undefined,
|
||||
preserveFocus: options.preserveFocus,
|
||||
selection: typeof options.selection === 'object' ? typeConverters.fromRange(options.selection) : undefined
|
||||
};
|
||||
}
|
||||
|
||||
return this._commands.executeCommand('_workbench.diff', [
|
||||
left, right,
|
||||
label,
|
||||
undefined,
|
||||
editorOptions,
|
||||
options ? typeConverters.fromViewColumn(options.viewColumn) : undefined
|
||||
]);
|
||||
}, {
|
||||
description: 'Opens the provided resources in the diff editor to compare their contents.',
|
||||
args: [
|
||||
{ name: 'left', description: 'Left-hand side resource of the diff editor', constraint: URI },
|
||||
{ name: 'right', description: 'Right-hand side resource of the diff editor', constraint: URI },
|
||||
{ name: 'title', description: '(optional) Human readable title for the diff editor', constraint: v => v === void 0 || typeof v === 'string' },
|
||||
{ name: 'options', description: '(optional) Editor options, see vscode.TextDocumentShowOptions' }
|
||||
]
|
||||
});
|
||||
|
||||
this._register('vscode.open', (resource: URI, column: vscode.ViewColumn) => {
|
||||
return this._commands.executeCommand('_workbench.open', [resource, typeConverters.fromViewColumn(column)]);
|
||||
}, {
|
||||
description: 'Opens the provided resource in the editor. Can be a text or binary file, or a http(s) url. If you need more control over the options for opening a text file, use vscode.window.showTextDocument instead.',
|
||||
args: [
|
||||
{ name: 'resource', description: 'Resource to open', constraint: URI },
|
||||
{ name: 'column', description: '(optional) Column in which to open', constraint: v => v === void 0 || typeof v === 'number' }
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
// --- command impl
|
||||
|
||||
private _register(id: string, handler: (...args: any[]) => any, description?: ICommandHandlerDescription): void {
|
||||
let disposable = this._commands.registerCommand(id, handler, this, description);
|
||||
this._disposables.push(disposable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute workspace symbol provider.
|
||||
*
|
||||
* @param query Search string to match query symbol names
|
||||
* @return A promise that resolves to an array of symbol information.
|
||||
*/
|
||||
private _executeWorkspaceSymbolProvider(query: string): Thenable<types.SymbolInformation[]> {
|
||||
return this._commands.executeCommand<[IWorkspaceSymbolProvider, modes.SymbolInformation[]][]>('_executeWorkspaceSymbolProvider', { query }).then(value => {
|
||||
const result: types.SymbolInformation[] = [];
|
||||
if (Array.isArray(value)) {
|
||||
for (let tuple of value) {
|
||||
result.push(...tuple[1].map(typeConverters.toSymbolInformation));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
private _executeDefinitionProvider(resource: URI, position: types.Position): Thenable<types.Location[]> {
|
||||
const args = {
|
||||
resource,
|
||||
position: position && typeConverters.fromPosition(position)
|
||||
};
|
||||
return this._commands.executeCommand<modes.Location[]>('_executeDefinitionProvider', args).then(value => {
|
||||
if (Array.isArray(value)) {
|
||||
return value.map(typeConverters.location.to);
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
|
||||
private _executeImplementationProvider(resource: URI, position: types.Position): Thenable<types.Location[]> {
|
||||
const args = {
|
||||
resource,
|
||||
position: position && typeConverters.fromPosition(position)
|
||||
};
|
||||
return this._commands.executeCommand<modes.Location[]>('_executeImplementationProvider', args).then(value => {
|
||||
if (Array.isArray(value)) {
|
||||
return value.map(typeConverters.location.to);
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
|
||||
private _executeHoverProvider(resource: URI, position: types.Position): Thenable<types.Hover[]> {
|
||||
const args = {
|
||||
resource,
|
||||
position: position && typeConverters.fromPosition(position)
|
||||
};
|
||||
return this._commands.executeCommand<modes.Hover[]>('_executeHoverProvider', args).then(value => {
|
||||
if (Array.isArray(value)) {
|
||||
return value.map(typeConverters.toHover);
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
|
||||
private _executeDocumentHighlights(resource: URI, position: types.Position): Thenable<types.DocumentHighlight[]> {
|
||||
const args = {
|
||||
resource,
|
||||
position: position && typeConverters.fromPosition(position)
|
||||
};
|
||||
return this._commands.executeCommand<modes.DocumentHighlight[]>('_executeDocumentHighlights', args).then(value => {
|
||||
if (Array.isArray(value)) {
|
||||
return value.map(typeConverters.toDocumentHighlight);
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
|
||||
private _executeReferenceProvider(resource: URI, position: types.Position): Thenable<types.Location[]> {
|
||||
const args = {
|
||||
resource,
|
||||
position: position && typeConverters.fromPosition(position)
|
||||
};
|
||||
return this._commands.executeCommand<modes.Location[]>('_executeReferenceProvider', args).then(value => {
|
||||
if (Array.isArray(value)) {
|
||||
return value.map(typeConverters.location.to);
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
|
||||
private _executeDocumentRenameProvider(resource: URI, position: types.Position, newName: string): Thenable<types.WorkspaceEdit> {
|
||||
const args = {
|
||||
resource,
|
||||
position: position && typeConverters.fromPosition(position),
|
||||
newName
|
||||
};
|
||||
return this._commands.executeCommand<modes.WorkspaceEdit>('_executeDocumentRenameProvider', args).then(value => {
|
||||
if (!value) {
|
||||
return undefined;
|
||||
}
|
||||
if (value.rejectReason) {
|
||||
return TPromise.wrapError<types.WorkspaceEdit>(new Error(value.rejectReason));
|
||||
}
|
||||
let workspaceEdit = new types.WorkspaceEdit();
|
||||
for (let edit of value.edits) {
|
||||
workspaceEdit.replace(edit.resource, typeConverters.toRange(edit.range), edit.newText);
|
||||
}
|
||||
return workspaceEdit;
|
||||
});
|
||||
}
|
||||
|
||||
private _executeSignatureHelpProvider(resource: URI, position: types.Position, triggerCharacter: string): Thenable<types.SignatureHelp> {
|
||||
const args = {
|
||||
resource,
|
||||
position: position && typeConverters.fromPosition(position),
|
||||
triggerCharacter
|
||||
};
|
||||
return this._commands.executeCommand<modes.SignatureHelp>('_executeSignatureHelpProvider', args).then(value => {
|
||||
if (value) {
|
||||
return typeConverters.SignatureHelp.to(value);
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
|
||||
private _executeCompletionItemProvider(resource: URI, position: types.Position, triggerCharacter: string): Thenable<types.CompletionList> {
|
||||
const args = {
|
||||
resource,
|
||||
position: position && typeConverters.fromPosition(position),
|
||||
triggerCharacter
|
||||
};
|
||||
return this._commands.executeCommand<modes.ISuggestResult>('_executeCompletionItemProvider', args).then(result => {
|
||||
if (result) {
|
||||
const items = result.suggestions.map(suggestion => typeConverters.Suggest.to(position, suggestion));
|
||||
return new types.CompletionList(items, result.incomplete);
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
|
||||
private _executeDocumentSymbolProvider(resource: URI): Thenable<types.SymbolInformation[]> {
|
||||
const args = {
|
||||
resource
|
||||
};
|
||||
return this._commands.executeCommand<modes.IOutline>('_executeDocumentSymbolProvider', args).then(value => {
|
||||
if (value && Array.isArray(value.entries)) {
|
||||
return value.entries.map(typeConverters.toSymbolInformation);
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
|
||||
private _executeCodeActionProvider(resource: URI, range: types.Range): Thenable<vscode.Command[]> {
|
||||
const args = {
|
||||
resource,
|
||||
range: typeConverters.fromRange(range)
|
||||
};
|
||||
return this._commands.executeCommand<modes.Command[]>('_executeCodeActionProvider', args).then(value => {
|
||||
if (!Array.isArray(value)) {
|
||||
return undefined;
|
||||
}
|
||||
return value.map(quickFix => this._commands.converter.fromInternal(quickFix));
|
||||
});
|
||||
}
|
||||
|
||||
private _executeCodeLensProvider(resource: URI): Thenable<vscode.CodeLens[]> {
|
||||
const args = { resource };
|
||||
return this._commands.executeCommand<modes.ICodeLensSymbol[]>('_executeCodeLensProvider', args).then(value => {
|
||||
if (Array.isArray(value)) {
|
||||
return value.map(item => {
|
||||
return new types.CodeLens(
|
||||
typeConverters.toRange(item.range),
|
||||
this._commands.converter.fromInternal(item.command));
|
||||
});
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
|
||||
private _executeFormatDocumentProvider(resource: URI, options: vscode.FormattingOptions): Thenable<vscode.TextEdit[]> {
|
||||
const args = {
|
||||
resource,
|
||||
options
|
||||
};
|
||||
return this._commands.executeCommand<ISingleEditOperation[]>('_executeFormatDocumentProvider', args).then(value => {
|
||||
if (Array.isArray(value)) {
|
||||
return value.map(edit => new types.TextEdit(typeConverters.toRange(edit.range), edit.text));
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
|
||||
private _executeFormatRangeProvider(resource: URI, range: types.Range, options: vscode.FormattingOptions): Thenable<vscode.TextEdit[]> {
|
||||
const args = {
|
||||
resource,
|
||||
range: typeConverters.fromRange(range),
|
||||
options
|
||||
};
|
||||
return this._commands.executeCommand<ISingleEditOperation[]>('_executeFormatRangeProvider', args).then(value => {
|
||||
if (Array.isArray(value)) {
|
||||
return value.map(edit => new types.TextEdit(typeConverters.toRange(edit.range), edit.text));
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
|
||||
private _executeFormatOnTypeProvider(resource: URI, position: types.Position, ch: string, options: vscode.FormattingOptions): Thenable<vscode.TextEdit[]> {
|
||||
const args = {
|
||||
resource,
|
||||
position: typeConverters.fromPosition(position),
|
||||
ch,
|
||||
options
|
||||
};
|
||||
return this._commands.executeCommand<ISingleEditOperation[]>('_executeFormatOnTypeProvider', args).then(value => {
|
||||
if (Array.isArray(value)) {
|
||||
return value.map(edit => new types.TextEdit(typeConverters.toRange(edit.range), edit.text));
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
|
||||
private _executeDocumentLinkProvider(resource: URI): Thenable<vscode.DocumentLink[]> {
|
||||
return this._commands.executeCommand<modes.ILink[]>('_executeLinkProvider', resource).then(value => {
|
||||
if (Array.isArray(value)) {
|
||||
return value.map(typeConverters.DocumentLink.to);
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
}
|
||||
224
src/vs/workbench/api/node/extHostCommands.ts
Normal file
224
src/vs/workbench/api/node/extHostCommands.ts
Normal file
@@ -0,0 +1,224 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { validateConstraint } from 'vs/base/common/types';
|
||||
import { ICommandHandlerDescription } from 'vs/platform/commands/common/commands';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import * as extHostTypes from 'vs/workbench/api/node/extHostTypes';
|
||||
import * as extHostTypeConverter from 'vs/workbench/api/node/extHostTypeConverters';
|
||||
import { cloneAndChange } from 'vs/base/common/objects';
|
||||
import { MainContext, MainThreadCommandsShape, ExtHostCommandsShape, ObjectIdentifier, IMainContext } from './extHost.protocol';
|
||||
import { ExtHostHeapService } from 'vs/workbench/api/node/extHostHeapService';
|
||||
import { isFalsyOrEmpty } from 'vs/base/common/arrays';
|
||||
import * as modes from 'vs/editor/common/modes';
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
interface CommandHandler {
|
||||
callback: Function;
|
||||
thisArg: any;
|
||||
description: ICommandHandlerDescription;
|
||||
}
|
||||
|
||||
export interface ArgumentProcessor {
|
||||
processArgument(arg: any): any;
|
||||
}
|
||||
|
||||
export class ExtHostCommands implements ExtHostCommandsShape {
|
||||
|
||||
private _commands = new Map<string, CommandHandler>();
|
||||
private _proxy: MainThreadCommandsShape;
|
||||
private _converter: CommandsConverter;
|
||||
private _argumentProcessors: ArgumentProcessor[] = [];
|
||||
|
||||
constructor(
|
||||
mainContext: IMainContext,
|
||||
heapService: ExtHostHeapService
|
||||
) {
|
||||
this._proxy = mainContext.get(MainContext.MainThreadCommands);
|
||||
this._converter = new CommandsConverter(this, heapService);
|
||||
}
|
||||
|
||||
get converter(): CommandsConverter {
|
||||
return this._converter;
|
||||
}
|
||||
|
||||
registerArgumentProcessor(processor: ArgumentProcessor): void {
|
||||
this._argumentProcessors.push(processor);
|
||||
}
|
||||
|
||||
registerCommand(id: string, callback: <T>(...args: any[]) => T | Thenable<T>, thisArg?: any, description?: ICommandHandlerDescription): extHostTypes.Disposable {
|
||||
|
||||
if (!id.trim().length) {
|
||||
throw new Error('invalid id');
|
||||
}
|
||||
|
||||
if (this._commands.has(id)) {
|
||||
throw new Error(`command '${id}' already exists`);
|
||||
}
|
||||
|
||||
this._commands.set(id, { callback, thisArg, description });
|
||||
this._proxy.$registerCommand(id);
|
||||
|
||||
return new extHostTypes.Disposable(() => {
|
||||
if (this._commands.delete(id)) {
|
||||
this._proxy.$unregisterCommand(id);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
executeCommand<T>(id: string, ...args: any[]): Thenable<T> {
|
||||
|
||||
if (this._commands.has(id)) {
|
||||
// we stay inside the extension host and support
|
||||
// to pass any kind of parameters around
|
||||
return this.$executeContributedCommand<T>(id, ...args);
|
||||
|
||||
} else {
|
||||
// automagically convert some argument types
|
||||
|
||||
args = cloneAndChange(args, function (value) {
|
||||
if (value instanceof extHostTypes.Position) {
|
||||
return extHostTypeConverter.fromPosition(value);
|
||||
}
|
||||
if (value instanceof extHostTypes.Range) {
|
||||
return extHostTypeConverter.fromRange(value);
|
||||
}
|
||||
if (value instanceof extHostTypes.Location) {
|
||||
return extHostTypeConverter.location.from(value);
|
||||
}
|
||||
if (!Array.isArray(value)) {
|
||||
return value;
|
||||
}
|
||||
});
|
||||
|
||||
return this._proxy.$executeCommand<T>(id, args);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$executeContributedCommand<T>(id: string, ...args: any[]): Thenable<T> {
|
||||
let command = this._commands.get(id);
|
||||
if (!command) {
|
||||
return TPromise.wrapError<T>(new Error(`Contributed command '${id}' does not exist.`));
|
||||
}
|
||||
|
||||
let { callback, thisArg, description } = command;
|
||||
|
||||
if (description) {
|
||||
for (let i = 0; i < description.args.length; i++) {
|
||||
try {
|
||||
validateConstraint(args[i], description.args[i].constraint);
|
||||
} catch (err) {
|
||||
return TPromise.wrapError<T>(new Error(`Running the contributed command:'${id}' failed. Illegal argument '${description.args[i].name}' - ${description.args[i].description}`));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
args = args.map(arg => this._argumentProcessors.reduce((r, p) => p.processArgument(r), arg));
|
||||
|
||||
try {
|
||||
let result = callback.apply(thisArg, args);
|
||||
return TPromise.as(result);
|
||||
} catch (err) {
|
||||
// console.log(err);
|
||||
// try {
|
||||
// console.log(toErrorMessage(err));
|
||||
// } catch (err) {
|
||||
// //
|
||||
// }
|
||||
return TPromise.wrapError<T>(new Error(`Running the contributed command:'${id}' failed.`));
|
||||
}
|
||||
}
|
||||
|
||||
getCommands(filterUnderscoreCommands: boolean = false): Thenable<string[]> {
|
||||
return this._proxy.$getCommands().then(result => {
|
||||
if (filterUnderscoreCommands) {
|
||||
result = result.filter(command => command[0] !== '_');
|
||||
}
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
$getContributedCommandHandlerDescriptions(): TPromise<{ [id: string]: string | ICommandHandlerDescription }> {
|
||||
const result: { [id: string]: string | ICommandHandlerDescription } = Object.create(null);
|
||||
this._commands.forEach((command, id) => {
|
||||
let { description } = command;
|
||||
if (description) {
|
||||
result[id] = description;
|
||||
}
|
||||
});
|
||||
return TPromise.as(result);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class CommandsConverter {
|
||||
|
||||
private _commands: ExtHostCommands;
|
||||
private _heap: ExtHostHeapService;
|
||||
|
||||
// --- conversion between internal and api commands
|
||||
constructor(commands: ExtHostCommands, heap: ExtHostHeapService) {
|
||||
|
||||
this._commands = commands;
|
||||
this._heap = heap;
|
||||
this._commands.registerCommand('_internal_command_delegation', this._executeConvertedCommand, this);
|
||||
}
|
||||
|
||||
toInternal(command: vscode.Command): modes.Command {
|
||||
|
||||
if (!command) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const result: modes.Command = {
|
||||
id: command.command,
|
||||
title: command.title
|
||||
};
|
||||
|
||||
if (command.command && !isFalsyOrEmpty(command.arguments)) {
|
||||
// we have a contributed command with arguments. that
|
||||
// means we don't want to send the arguments around
|
||||
|
||||
const id = this._heap.keep(command);
|
||||
ObjectIdentifier.mixin(result, id);
|
||||
|
||||
result.id = '_internal_command_delegation';
|
||||
result.arguments = [id];
|
||||
}
|
||||
|
||||
if (command.tooltip) {
|
||||
result.tooltip = command.tooltip;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
fromInternal(command: modes.Command): vscode.Command {
|
||||
|
||||
if (!command) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const id = ObjectIdentifier.of(command);
|
||||
if (typeof id === 'number') {
|
||||
return this._heap.get<vscode.Command>(id);
|
||||
|
||||
} else {
|
||||
return {
|
||||
command: command.id,
|
||||
title: command.title,
|
||||
arguments: command.arguments
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private _executeConvertedCommand<R>(...args: any[]): Thenable<R> {
|
||||
const actualCmd = this._heap.get<vscode.Command>(args[0]);
|
||||
return this._commands.executeCommand(actualCmd.command, ...actualCmd.arguments);
|
||||
}
|
||||
|
||||
}
|
||||
120
src/vs/workbench/api/node/extHostConfiguration.ts
Normal file
120
src/vs/workbench/api/node/extHostConfiguration.ts
Normal file
@@ -0,0 +1,120 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { mixin } from 'vs/base/common/objects';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { WorkspaceConfiguration } from 'vscode';
|
||||
import { ExtHostWorkspace } from 'vs/workbench/api/node/extHostWorkspace';
|
||||
import { ExtHostConfigurationShape, MainThreadConfigurationShape } from './extHost.protocol';
|
||||
import { ConfigurationTarget as ExtHostConfigurationTarget } from './extHostTypes';
|
||||
import { IConfigurationData, Configuration } from 'vs/platform/configuration/common/configuration';
|
||||
import { ConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditing';
|
||||
|
||||
function lookUp(tree: any, key: string) {
|
||||
if (key) {
|
||||
const parts = key.split('.');
|
||||
let node = tree;
|
||||
for (let i = 0; node && i < parts.length; i++) {
|
||||
node = node[parts[i]];
|
||||
}
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
type ConfigurationInspect<T> = {
|
||||
key: string;
|
||||
defaultValue?: T;
|
||||
globalValue?: T;
|
||||
workspaceValue?: T;
|
||||
workspaceFolderValue?: T;
|
||||
};
|
||||
|
||||
export class ExtHostConfiguration implements ExtHostConfigurationShape {
|
||||
|
||||
private readonly _onDidChangeConfiguration = new Emitter<void>();
|
||||
private readonly _proxy: MainThreadConfigurationShape;
|
||||
private readonly _extHostWorkspace: ExtHostWorkspace;
|
||||
private _configuration: Configuration<any>;
|
||||
|
||||
constructor(proxy: MainThreadConfigurationShape, extHostWorkspace: ExtHostWorkspace, data: IConfigurationData<any>) {
|
||||
this._proxy = proxy;
|
||||
this._extHostWorkspace = extHostWorkspace;
|
||||
this._configuration = Configuration.parse(data, extHostWorkspace.workspace);
|
||||
}
|
||||
|
||||
get onDidChangeConfiguration(): Event<void> {
|
||||
return this._onDidChangeConfiguration && this._onDidChangeConfiguration.event;
|
||||
}
|
||||
|
||||
$acceptConfigurationChanged(data: IConfigurationData<any>) {
|
||||
this._configuration = Configuration.parse(data, this._extHostWorkspace.workspace);
|
||||
this._onDidChangeConfiguration.fire(undefined);
|
||||
}
|
||||
|
||||
getConfiguration(section?: string, resource?: URI): WorkspaceConfiguration {
|
||||
const config = section
|
||||
? lookUp(this._configuration.getValue(null, { resource }), section)
|
||||
: this._configuration.getValue(null, { resource });
|
||||
|
||||
function parseConfigurationTarget(arg: boolean | ExtHostConfigurationTarget): ConfigurationTarget {
|
||||
if (arg === void 0 || arg === null) {
|
||||
return null;
|
||||
}
|
||||
if (typeof arg === 'boolean') {
|
||||
return arg ? ConfigurationTarget.USER : ConfigurationTarget.WORKSPACE;
|
||||
}
|
||||
|
||||
switch (arg) {
|
||||
case ExtHostConfigurationTarget.Global: return ConfigurationTarget.USER;
|
||||
case ExtHostConfigurationTarget.Workspace: return ConfigurationTarget.WORKSPACE;
|
||||
case ExtHostConfigurationTarget.WorkspaceFolder: return ConfigurationTarget.FOLDER;
|
||||
}
|
||||
}
|
||||
|
||||
const result: WorkspaceConfiguration = {
|
||||
has(key: string): boolean {
|
||||
return typeof lookUp(config, key) !== 'undefined';
|
||||
},
|
||||
get<T>(key: string, defaultValue?: T): T {
|
||||
let result = lookUp(config, key);
|
||||
if (typeof result === 'undefined') {
|
||||
result = defaultValue;
|
||||
}
|
||||
return result;
|
||||
},
|
||||
update: (key: string, value: any, arg: ExtHostConfigurationTarget | boolean) => {
|
||||
key = section ? `${section}.${key}` : key;
|
||||
const target = parseConfigurationTarget(arg);
|
||||
if (value !== void 0) {
|
||||
return this._proxy.$updateConfigurationOption(target, key, value, resource);
|
||||
} else {
|
||||
return this._proxy.$removeConfigurationOption(target, key, resource);
|
||||
}
|
||||
},
|
||||
inspect: <T>(key: string): ConfigurationInspect<T> => {
|
||||
key = section ? `${section}.${key}` : key;
|
||||
const config = this._configuration.lookup<T>(key, { resource });
|
||||
if (config) {
|
||||
return {
|
||||
key,
|
||||
defaultValue: config.default,
|
||||
globalValue: config.user,
|
||||
workspaceValue: config.workspace,
|
||||
workspaceFolderValue: config.folder
|
||||
};
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
if (typeof config === 'object') {
|
||||
mixin(result, config, false);
|
||||
}
|
||||
|
||||
return <WorkspaceConfiguration>Object.freeze(result);
|
||||
}
|
||||
}
|
||||
29
src/vs/workbench/api/node/extHostCredentials.ts
Normal file
29
src/vs/workbench/api/node/extHostCredentials.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { MainContext, MainThreadCredentialsShape, ExtHostCredentialsShape, IMainContext } from 'vs/workbench/api/node/extHost.protocol';
|
||||
|
||||
|
||||
export class ExtHostCredentials implements ExtHostCredentialsShape {
|
||||
|
||||
private _proxy: MainThreadCredentialsShape;
|
||||
|
||||
constructor(mainContext: IMainContext) {
|
||||
this._proxy = mainContext.get(MainContext.MainThreadCredentials);
|
||||
};
|
||||
|
||||
readSecret(service: string, account: string): Thenable<string | undefined> {
|
||||
return this._proxy.$readSecret(service, account);
|
||||
}
|
||||
|
||||
writeSecret(service: string, account: string, secret: string): Thenable<void> {
|
||||
return this._proxy.$writeSecret(service, account, secret);
|
||||
}
|
||||
|
||||
deleteSecret(service: string, account: string): Thenable<boolean> {
|
||||
return this._proxy.$deleteSecret(service, account);
|
||||
}
|
||||
}
|
||||
213
src/vs/workbench/api/node/extHostDebugService.ts
Normal file
213
src/vs/workbench/api/node/extHostDebugService.ts
Normal file
@@ -0,0 +1,213 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
/*
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { asWinJsPromise } from 'vs/base/common/async';
|
||||
import { MainContext, MainThreadDebugServiceShape, ExtHostDebugServiceShape, DebugSessionUUID, IMainContext } from 'vs/workbench/api/node/extHost.protocol';
|
||||
import { ExtHostWorkspace } from 'vs/workbench/api/node/extHostWorkspace';
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import * as types from 'vs/workbench/api/node/extHostTypes';
|
||||
|
||||
|
||||
export class ExtHostDebugService implements ExtHostDebugServiceShape {
|
||||
|
||||
private _workspace: ExtHostWorkspace;
|
||||
|
||||
private _handleCounter: number;
|
||||
private _handlers: Map<number, vscode.DebugConfigurationProvider>;
|
||||
|
||||
private _debugServiceProxy: MainThreadDebugServiceShape;
|
||||
private _debugSessions: Map<DebugSessionUUID, ExtHostDebugSession> = new Map<DebugSessionUUID, ExtHostDebugSession>();
|
||||
|
||||
private _onDidStartDebugSession: Emitter<vscode.DebugSession>;
|
||||
get onDidStartDebugSession(): Event<vscode.DebugSession> { return this._onDidStartDebugSession.event; }
|
||||
|
||||
private _onDidTerminateDebugSession: Emitter<vscode.DebugSession>;
|
||||
get onDidTerminateDebugSession(): Event<vscode.DebugSession> { return this._onDidTerminateDebugSession.event; }
|
||||
|
||||
private _onDidChangeActiveDebugSession: Emitter<vscode.DebugSession | undefined>;
|
||||
get onDidChangeActiveDebugSession(): Event<vscode.DebugSession | undefined> { return this._onDidChangeActiveDebugSession.event; }
|
||||
|
||||
private _activeDebugSession: ExtHostDebugSession | undefined;
|
||||
get activeDebugSession(): ExtHostDebugSession | undefined { return this._activeDebugSession; }
|
||||
|
||||
private _onDidReceiveDebugSessionCustomEvent: Emitter<vscode.DebugSessionCustomEvent>;
|
||||
get onDidReceiveDebugSessionCustomEvent(): Event<vscode.DebugSessionCustomEvent> { return this._onDidReceiveDebugSessionCustomEvent.event; }
|
||||
|
||||
|
||||
constructor(mainContext: IMainContext, workspace: ExtHostWorkspace) {
|
||||
|
||||
this._workspace = workspace;
|
||||
|
||||
this._handleCounter = 0;
|
||||
this._handlers = new Map<number, vscode.DebugConfigurationProvider>();
|
||||
|
||||
this._onDidStartDebugSession = new Emitter<vscode.DebugSession>();
|
||||
this._onDidTerminateDebugSession = new Emitter<vscode.DebugSession>();
|
||||
this._onDidChangeActiveDebugSession = new Emitter<vscode.DebugSession>();
|
||||
this._onDidReceiveDebugSessionCustomEvent = new Emitter<vscode.DebugSessionCustomEvent>();
|
||||
|
||||
this._debugServiceProxy = mainContext.get(MainContext.MainThreadDebugService);
|
||||
}
|
||||
|
||||
public registerDebugConfigurationProvider(type: string, provider: vscode.DebugConfigurationProvider): vscode.Disposable {
|
||||
if (!provider) {
|
||||
return new types.Disposable(() => { });
|
||||
}
|
||||
|
||||
let handle = this.nextHandle();
|
||||
this._handlers.set(handle, provider);
|
||||
this._debugServiceProxy.$registerDebugConfigurationProvider(type, !!provider.provideDebugConfigurations, !!provider.resolveDebugConfiguration, handle);
|
||||
|
||||
return new types.Disposable(() => {
|
||||
this._handlers.delete(handle);
|
||||
this._debugServiceProxy.$unregisterDebugConfigurationProvider(handle);
|
||||
});
|
||||
}
|
||||
|
||||
public $provideDebugConfigurations(handle: number, folderUri: URI | undefined): TPromise<vscode.DebugConfiguration[]> {
|
||||
let handler = this._handlers.get(handle);
|
||||
if (!handler) {
|
||||
return TPromise.wrapError<vscode.DebugConfiguration[]>(new Error('no handler found'));
|
||||
}
|
||||
if (!handler.provideDebugConfigurations) {
|
||||
return TPromise.wrapError<vscode.DebugConfiguration[]>(new Error('handler has no method provideDebugConfigurations'));
|
||||
}
|
||||
return asWinJsPromise(token => handler.provideDebugConfigurations(this.getFolder(folderUri), token));
|
||||
}
|
||||
|
||||
|
||||
public $resolveDebugConfiguration(handle: number, folderUri: URI | undefined, debugConfiguration: vscode.DebugConfiguration): TPromise<vscode.DebugConfiguration> {
|
||||
let handler = this._handlers.get(handle);
|
||||
if (!handler) {
|
||||
return TPromise.wrapError<vscode.DebugConfiguration>(new Error('no handler found'));
|
||||
}
|
||||
if (!handler.resolveDebugConfiguration) {
|
||||
return TPromise.wrapError<vscode.DebugConfiguration>(new Error('handler has no method resolveDebugConfiguration'));
|
||||
}
|
||||
return asWinJsPromise(token => handler.resolveDebugConfiguration(this.getFolder(folderUri), debugConfiguration, token));
|
||||
}
|
||||
|
||||
public startDebugging(folder: vscode.WorkspaceFolder | undefined, nameOrConfig: string | vscode.DebugConfiguration): TPromise<boolean> {
|
||||
|
||||
return this._debugServiceProxy.$startDebugging(folder ? <URI>folder.uri : undefined, nameOrConfig);
|
||||
}
|
||||
|
||||
public startDebugSession(folder: vscode.WorkspaceFolder | undefined, config: vscode.DebugConfiguration): TPromise<vscode.DebugSession> {
|
||||
|
||||
return this._debugServiceProxy.$startDebugSession(folder ? <URI>folder.uri : undefined, config).then((id: DebugSessionUUID) => {
|
||||
const debugSession = new ExtHostDebugSession(this._debugServiceProxy, id, config.type, config.name);
|
||||
this._debugSessions.set(id, debugSession);
|
||||
return debugSession;
|
||||
});
|
||||
}
|
||||
|
||||
public $acceptDebugSessionStarted(id: DebugSessionUUID, type: string, name: string): void {
|
||||
|
||||
let debugSession = this._debugSessions.get(id);
|
||||
if (!debugSession) {
|
||||
debugSession = new ExtHostDebugSession(this._debugServiceProxy, id, type, name);
|
||||
this._debugSessions.set(id, debugSession);
|
||||
}
|
||||
this._onDidStartDebugSession.fire(debugSession);
|
||||
}
|
||||
|
||||
public $acceptDebugSessionTerminated(id: DebugSessionUUID, type: string, name: string): void {
|
||||
|
||||
let debugSession = this._debugSessions.get(id);
|
||||
if (!debugSession) {
|
||||
debugSession = new ExtHostDebugSession(this._debugServiceProxy, id, type, name);
|
||||
this._debugSessions.set(id, debugSession);
|
||||
}
|
||||
this._onDidTerminateDebugSession.fire(debugSession);
|
||||
this._debugSessions.delete(id);
|
||||
}
|
||||
|
||||
public $acceptDebugSessionActiveChanged(id: DebugSessionUUID | undefined, type?: string, name?: string): void {
|
||||
|
||||
if (id) {
|
||||
this._activeDebugSession = this._debugSessions.get(id);
|
||||
if (!this._activeDebugSession) {
|
||||
this._activeDebugSession = new ExtHostDebugSession(this._debugServiceProxy, id, type, name);
|
||||
this._debugSessions.set(id, this._activeDebugSession);
|
||||
}
|
||||
} else {
|
||||
this._activeDebugSession = undefined;
|
||||
}
|
||||
this._onDidChangeActiveDebugSession.fire(this._activeDebugSession);
|
||||
}
|
||||
|
||||
public $acceptDebugSessionCustomEvent(id: DebugSessionUUID, type: string, name: string, event: any): void {
|
||||
|
||||
let debugSession = this._debugSessions.get(id);
|
||||
if (!debugSession) {
|
||||
debugSession = new ExtHostDebugSession(this._debugServiceProxy, id, type, name);
|
||||
this._debugSessions.set(id, debugSession);
|
||||
}
|
||||
const ee: vscode.DebugSessionCustomEvent = {
|
||||
session: debugSession,
|
||||
event: event.event,
|
||||
body: event.body
|
||||
};
|
||||
this._onDidReceiveDebugSessionCustomEvent.fire(ee);
|
||||
}
|
||||
|
||||
private getFolder(folderUri: URI | undefined) {
|
||||
if (folderUri) {
|
||||
const folders = this._workspace.getWorkspaceFolders();
|
||||
const found = folders.filter(f => f.uri.toString() === folderUri.toString());
|
||||
if (found && found.length > 0) {
|
||||
return found[0];
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private nextHandle(): number {
|
||||
return this._handleCounter++;
|
||||
}
|
||||
}
|
||||
|
||||
export class ExtHostDebugSession implements vscode.DebugSession {
|
||||
|
||||
private _debugServiceProxy: MainThreadDebugServiceShape;
|
||||
|
||||
private _id: DebugSessionUUID;
|
||||
|
||||
private _type: string;
|
||||
private _name: string;
|
||||
|
||||
constructor(proxy: MainThreadDebugServiceShape, id: DebugSessionUUID, type: string, name: string) {
|
||||
this._debugServiceProxy = proxy;
|
||||
this._id = id;
|
||||
this._type = type;
|
||||
this._name = name;
|
||||
};
|
||||
|
||||
public get id(): string {
|
||||
return this._id;
|
||||
}
|
||||
|
||||
public get type(): string {
|
||||
return this._type;
|
||||
}
|
||||
|
||||
public get name(): string {
|
||||
return this._name;
|
||||
}
|
||||
|
||||
public customRequest(command: string, args: any): Thenable<any> {
|
||||
return this._debugServiceProxy.$customDebugAdapterRequest(this._id, command, args);
|
||||
}
|
||||
}
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
*/
|
||||
258
src/vs/workbench/api/node/extHostDiagnostics.ts
Normal file
258
src/vs/workbench/api/node/extHostDiagnostics.ts
Normal file
@@ -0,0 +1,258 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { localize } from 'vs/nls';
|
||||
import { IMarkerData } from 'vs/platform/markers/common/markers';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import * as vscode from 'vscode';
|
||||
import { MainContext, MainThreadDiagnosticsShape, ExtHostDiagnosticsShape, IMainContext } from './extHost.protocol';
|
||||
import { DiagnosticSeverity } from './extHostTypes';
|
||||
import { mergeSort } from 'vs/base/common/arrays';
|
||||
|
||||
export class DiagnosticCollection implements vscode.DiagnosticCollection {
|
||||
|
||||
private static readonly _maxDiagnosticsPerFile: number = 250;
|
||||
|
||||
private readonly _name: string;
|
||||
|
||||
private _proxy: MainThreadDiagnosticsShape;
|
||||
private _isDisposed = false;
|
||||
private _data = new Map<string, vscode.Diagnostic[]>();
|
||||
|
||||
constructor(name: string, proxy: MainThreadDiagnosticsShape) {
|
||||
this._name = name;
|
||||
this._proxy = proxy;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
if (!this._isDisposed) {
|
||||
this._proxy.$clear(this.name);
|
||||
this._proxy = undefined;
|
||||
this._data = undefined;
|
||||
this._isDisposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
get name(): string {
|
||||
this._checkDisposed();
|
||||
return this._name;
|
||||
}
|
||||
|
||||
set(uri: vscode.Uri, diagnostics: vscode.Diagnostic[]): void;
|
||||
set(entries: [vscode.Uri, vscode.Diagnostic[]][]): void;
|
||||
set(first: vscode.Uri | [vscode.Uri, vscode.Diagnostic[]][], diagnostics?: vscode.Diagnostic[]) {
|
||||
|
||||
if (!first) {
|
||||
// this set-call is a clear-call
|
||||
this.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
// the actual implementation for #set
|
||||
|
||||
this._checkDisposed();
|
||||
let toSync: vscode.Uri[];
|
||||
|
||||
if (first instanceof URI) {
|
||||
|
||||
if (!diagnostics) {
|
||||
// remove this entry
|
||||
this.delete(first);
|
||||
return;
|
||||
}
|
||||
|
||||
// update single row
|
||||
this._data.set(first.toString(), diagnostics);
|
||||
toSync = [first];
|
||||
|
||||
} else if (Array.isArray(first)) {
|
||||
// update many rows
|
||||
toSync = [];
|
||||
let lastUri: vscode.Uri;
|
||||
|
||||
// ensure stable-sort
|
||||
mergeSort(first, DiagnosticCollection._compareIndexedTuplesByUri);
|
||||
|
||||
for (const tuple of first) {
|
||||
const [uri, diagnostics] = tuple;
|
||||
if (!lastUri || uri.toString() !== lastUri.toString()) {
|
||||
if (lastUri && this._data.get(lastUri.toString()).length === 0) {
|
||||
this._data.delete(lastUri.toString());
|
||||
}
|
||||
lastUri = uri;
|
||||
toSync.push(uri);
|
||||
this._data.set(uri.toString(), []);
|
||||
}
|
||||
|
||||
if (!diagnostics) {
|
||||
// [Uri, undefined] means clear this
|
||||
this._data.get(uri.toString()).length = 0;
|
||||
} else {
|
||||
this._data.get(uri.toString()).push(...diagnostics);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// compute change and send to main side
|
||||
const entries: [URI, IMarkerData[]][] = [];
|
||||
for (let uri of toSync) {
|
||||
let marker: IMarkerData[];
|
||||
let diagnostics = this._data.get(uri.toString());
|
||||
if (diagnostics) {
|
||||
|
||||
// no more than 250 diagnostics per file
|
||||
if (diagnostics.length > DiagnosticCollection._maxDiagnosticsPerFile) {
|
||||
marker = [];
|
||||
const order = [DiagnosticSeverity.Error, DiagnosticSeverity.Warning, DiagnosticSeverity.Information, DiagnosticSeverity.Hint];
|
||||
orderLoop: for (let i = 0; i < 4; i++) {
|
||||
for (let diagnostic of diagnostics) {
|
||||
if (diagnostic.severity === order[i]) {
|
||||
const len = marker.push(DiagnosticCollection._toMarkerData(diagnostic));
|
||||
if (len === DiagnosticCollection._maxDiagnosticsPerFile) {
|
||||
break orderLoop;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// add 'signal' marker for showing omitted errors/warnings
|
||||
marker.push({
|
||||
severity: Severity.Error,
|
||||
message: localize({ key: 'limitHit', comment: ['amount of errors/warning skipped due to limits'] }, "Not showing {0} further errors and warnings.", diagnostics.length - DiagnosticCollection._maxDiagnosticsPerFile),
|
||||
startLineNumber: marker[marker.length - 1].startLineNumber,
|
||||
startColumn: marker[marker.length - 1].startColumn,
|
||||
endLineNumber: marker[marker.length - 1].endLineNumber,
|
||||
endColumn: marker[marker.length - 1].endColumn
|
||||
});
|
||||
} else {
|
||||
marker = diagnostics.map(DiagnosticCollection._toMarkerData);
|
||||
}
|
||||
}
|
||||
|
||||
entries.push([<URI>uri, marker]);
|
||||
}
|
||||
|
||||
this._proxy.$changeMany(this.name, entries);
|
||||
}
|
||||
|
||||
delete(uri: vscode.Uri): void {
|
||||
this._checkDisposed();
|
||||
this._data.delete(uri.toString());
|
||||
this._proxy.$changeMany(this.name, [[<URI>uri, undefined]]);
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this._checkDisposed();
|
||||
this._data.clear();
|
||||
this._proxy.$clear(this.name);
|
||||
}
|
||||
|
||||
forEach(callback: (uri: URI, diagnostics: vscode.Diagnostic[], collection: DiagnosticCollection) => any, thisArg?: any): void {
|
||||
this._checkDisposed();
|
||||
this._data.forEach((value, key) => {
|
||||
let uri = URI.parse(key);
|
||||
callback.apply(thisArg, [uri, this.get(uri), this]);
|
||||
});
|
||||
}
|
||||
|
||||
get(uri: URI): vscode.Diagnostic[] {
|
||||
this._checkDisposed();
|
||||
let result = this._data.get(uri.toString());
|
||||
if (Array.isArray(result)) {
|
||||
return <vscode.Diagnostic[]>Object.freeze(result.slice(0));
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
has(uri: URI): boolean {
|
||||
this._checkDisposed();
|
||||
return Array.isArray(this._data.get(uri.toString()));
|
||||
}
|
||||
|
||||
private _checkDisposed() {
|
||||
if (this._isDisposed) {
|
||||
throw new Error('illegal state - object is disposed');
|
||||
}
|
||||
}
|
||||
|
||||
private static _toMarkerData(diagnostic: vscode.Diagnostic): IMarkerData {
|
||||
|
||||
let range = diagnostic.range;
|
||||
|
||||
return <IMarkerData>{
|
||||
startLineNumber: range.start.line + 1,
|
||||
startColumn: range.start.character + 1,
|
||||
endLineNumber: range.end.line + 1,
|
||||
endColumn: range.end.character + 1,
|
||||
message: diagnostic.message,
|
||||
source: diagnostic.source,
|
||||
severity: DiagnosticCollection._convertDiagnosticsSeverity(diagnostic.severity),
|
||||
code: String(diagnostic.code)
|
||||
};
|
||||
}
|
||||
|
||||
private static _convertDiagnosticsSeverity(severity: number): Severity {
|
||||
switch (severity) {
|
||||
case 0: return Severity.Error;
|
||||
case 1: return Severity.Warning;
|
||||
case 2: return Severity.Info;
|
||||
case 3: return Severity.Ignore;
|
||||
default: return Severity.Error;
|
||||
}
|
||||
}
|
||||
|
||||
private static _compareIndexedTuplesByUri(a: [vscode.Uri, vscode.Diagnostic[]], b: [vscode.Uri, vscode.Diagnostic[]]): number {
|
||||
if (a[0].toString() < b[0].toString()) {
|
||||
return -1;
|
||||
} else if (a[0].toString() > b[0].toString()) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class ExtHostDiagnostics implements ExtHostDiagnosticsShape {
|
||||
|
||||
private static _idPool: number = 0;
|
||||
|
||||
private _proxy: MainThreadDiagnosticsShape;
|
||||
private _collections: DiagnosticCollection[];
|
||||
|
||||
constructor(mainContext: IMainContext) {
|
||||
this._proxy = mainContext.get(MainContext.MainThreadDiagnostics);
|
||||
this._collections = [];
|
||||
}
|
||||
|
||||
createDiagnosticCollection(name: string): vscode.DiagnosticCollection {
|
||||
if (!name) {
|
||||
name = '_generated_diagnostic_collection_name_#' + ExtHostDiagnostics._idPool++;
|
||||
}
|
||||
|
||||
const { _collections, _proxy } = this;
|
||||
const result = new class extends DiagnosticCollection {
|
||||
constructor() {
|
||||
super(name, _proxy);
|
||||
_collections.push(this);
|
||||
}
|
||||
dispose() {
|
||||
super.dispose();
|
||||
let idx = _collections.indexOf(this);
|
||||
if (idx !== -1) {
|
||||
_collections.splice(idx, 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
forEach(callback: (collection: DiagnosticCollection) => any): void {
|
||||
this._collections.forEach(callback);
|
||||
}
|
||||
}
|
||||
|
||||
24
src/vs/workbench/api/node/extHostDialogs.ts
Normal file
24
src/vs/workbench/api/node/extHostDialogs.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { MainContext, MainThreadDiaglogsShape, IMainContext } from 'vs/workbench/api/node/extHost.protocol';
|
||||
|
||||
export class ExtHostDialogs {
|
||||
|
||||
private readonly _proxy: MainThreadDiaglogsShape;
|
||||
|
||||
constructor(mainContext: IMainContext) {
|
||||
this._proxy = mainContext.get(MainContext.MainThreadDialogs);
|
||||
}
|
||||
|
||||
showOpenDialog(options: vscode.OpenDialogOptions): Thenable<URI[]> {
|
||||
return this._proxy.$showOpenDialog(<any>options).then(filepaths => {
|
||||
return filepaths && filepaths.map(URI.file);
|
||||
});
|
||||
}
|
||||
}
|
||||
88
src/vs/workbench/api/node/extHostDocumentContentProviders.ts
Normal file
88
src/vs/workbench/api/node/extHostDocumentContentProviders.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import * as editorCommon from 'vs/editor/common/editorCommon';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Disposable } from 'vs/workbench/api/node/extHostTypes';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import * as vscode from 'vscode';
|
||||
import { asWinJsPromise } from 'vs/base/common/async';
|
||||
import { TextSource } from 'vs/editor/common/model/textSource';
|
||||
import { MainContext, ExtHostDocumentContentProvidersShape, MainThreadDocumentContentProvidersShape, IMainContext } from './extHost.protocol';
|
||||
import { ExtHostDocumentsAndEditors } from './extHostDocumentsAndEditors';
|
||||
|
||||
export class ExtHostDocumentContentProvider implements ExtHostDocumentContentProvidersShape {
|
||||
|
||||
private static _handlePool = 0;
|
||||
|
||||
private readonly _documentContentProviders = new Map<number, vscode.TextDocumentContentProvider>();
|
||||
private readonly _proxy: MainThreadDocumentContentProvidersShape;
|
||||
private readonly _documentsAndEditors: ExtHostDocumentsAndEditors;
|
||||
|
||||
constructor(mainContext: IMainContext, documentsAndEditors: ExtHostDocumentsAndEditors) {
|
||||
this._proxy = mainContext.get(MainContext.MainThreadDocumentContentProviders);
|
||||
this._documentsAndEditors = documentsAndEditors;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
// todo@joh
|
||||
}
|
||||
|
||||
registerTextDocumentContentProvider(scheme: string, provider: vscode.TextDocumentContentProvider): vscode.Disposable {
|
||||
if (scheme === 'file' || scheme === 'untitled') {
|
||||
throw new Error(`scheme '${scheme}' already registered`);
|
||||
}
|
||||
|
||||
const handle = ExtHostDocumentContentProvider._handlePool++;
|
||||
|
||||
this._documentContentProviders.set(handle, provider);
|
||||
this._proxy.$registerTextContentProvider(handle, scheme);
|
||||
|
||||
let subscription: IDisposable;
|
||||
if (typeof provider.onDidChange === 'function') {
|
||||
subscription = provider.onDidChange(uri => {
|
||||
if (this._documentsAndEditors.getDocument(uri.toString())) {
|
||||
this.$provideTextDocumentContent(handle, <URI>uri).then(value => {
|
||||
|
||||
const document = this._documentsAndEditors.getDocument(uri.toString());
|
||||
if (!document) {
|
||||
// disposed in the meantime
|
||||
return;
|
||||
}
|
||||
|
||||
// create lines and compare
|
||||
const textSource = TextSource.fromString(value, editorCommon.DefaultEndOfLine.CRLF);
|
||||
|
||||
// broadcast event when content changed
|
||||
if (!document.equalLines(textSource)) {
|
||||
return this._proxy.$onVirtualDocumentChange(<URI>uri, textSource);
|
||||
}
|
||||
|
||||
}, onUnexpectedError);
|
||||
}
|
||||
});
|
||||
}
|
||||
return new Disposable(() => {
|
||||
if (this._documentContentProviders.delete(handle)) {
|
||||
this._proxy.$unregisterTextContentProvider(handle);
|
||||
}
|
||||
if (subscription) {
|
||||
subscription.dispose();
|
||||
subscription = undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$provideTextDocumentContent(handle: number, uri: URI): TPromise<string> {
|
||||
const provider = this._documentContentProviders.get(handle);
|
||||
if (!provider) {
|
||||
return TPromise.wrapError<string>(new Error(`unsupported uri-scheme: ${uri.scheme}`));
|
||||
}
|
||||
return asWinJsPromise(token => provider.provideTextDocumentContent(uri, token));
|
||||
}
|
||||
}
|
||||
268
src/vs/workbench/api/node/extHostDocumentData.ts
Normal file
268
src/vs/workbench/api/node/extHostDocumentData.ts
Normal file
@@ -0,0 +1,268 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { ok } from 'vs/base/common/assert';
|
||||
import { regExpLeadsToEndlessLoop } from 'vs/base/common/strings';
|
||||
import { MirrorModel } from 'vs/editor/common/model/mirrorModel';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { Range, Position, EndOfLine } from 'vs/workbench/api/node/extHostTypes';
|
||||
import * as vscode from 'vscode';
|
||||
import { getWordAtText, ensureValidWordDefinition } from 'vs/editor/common/model/wordHelper';
|
||||
import { MainThreadDocumentsShape } from './extHost.protocol';
|
||||
import { ITextSource } from 'vs/editor/common/model/textSource';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
|
||||
const _modeId2WordDefinition = new Map<string, RegExp>();
|
||||
export function setWordDefinitionFor(modeId: string, wordDefinition: RegExp): void {
|
||||
_modeId2WordDefinition.set(modeId, wordDefinition);
|
||||
}
|
||||
export function getWordDefinitionFor(modeId: string): RegExp {
|
||||
return _modeId2WordDefinition.get(modeId);
|
||||
}
|
||||
|
||||
export class ExtHostDocumentData extends MirrorModel {
|
||||
|
||||
private _proxy: MainThreadDocumentsShape;
|
||||
private _languageId: string;
|
||||
private _isDirty: boolean;
|
||||
private _document: vscode.TextDocument;
|
||||
private _textLines: vscode.TextLine[] = [];
|
||||
private _isDisposed: boolean = false;
|
||||
|
||||
constructor(proxy: MainThreadDocumentsShape, uri: URI, lines: string[], eol: string,
|
||||
languageId: string, versionId: number, isDirty: boolean
|
||||
) {
|
||||
super(uri, lines, eol, versionId);
|
||||
this._proxy = proxy;
|
||||
this._languageId = languageId;
|
||||
this._isDirty = isDirty;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
// we don't really dispose documents but let
|
||||
// extensions still read from them. some
|
||||
// operations, live saving, will now error tho
|
||||
ok(!this._isDisposed);
|
||||
this._isDisposed = true;
|
||||
this._isDirty = false;
|
||||
}
|
||||
|
||||
equalLines({ lines }: ITextSource): boolean {
|
||||
const len = lines.length;
|
||||
if (len !== this._lines.length) {
|
||||
return false;
|
||||
}
|
||||
for (let i = 0; i < len; i++) {
|
||||
if (lines[i] !== this._lines[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
get document(): vscode.TextDocument {
|
||||
if (!this._document) {
|
||||
const data = this;
|
||||
this._document = {
|
||||
get uri() { return data._uri; },
|
||||
get fileName() { return data._uri.fsPath; },
|
||||
get isUntitled() { return data._uri.scheme !== 'file'; },
|
||||
get languageId() { return data._languageId; },
|
||||
get version() { return data._versionId; },
|
||||
get isClosed() { return data._isDisposed; },
|
||||
get isDirty() { return data._isDirty; },
|
||||
save() { return data._save(); },
|
||||
getText(range?) { return range ? data._getTextInRange(range) : data.getText(); },
|
||||
get eol() { return data._eol === '\n' ? EndOfLine.LF : EndOfLine.CRLF; },
|
||||
get lineCount() { return data._lines.length; },
|
||||
lineAt(lineOrPos) { return data._lineAt(lineOrPos); },
|
||||
offsetAt(pos) { return data._offsetAt(pos); },
|
||||
positionAt(offset) { return data._positionAt(offset); },
|
||||
validateRange(ran) { return data._validateRange(ran); },
|
||||
validatePosition(pos) { return data._validatePosition(pos); },
|
||||
getWordRangeAtPosition(pos, regexp?) { return data._getWordRangeAtPosition(pos, regexp); }
|
||||
};
|
||||
}
|
||||
return Object.freeze(this._document);
|
||||
}
|
||||
|
||||
_acceptLanguageId(newLanguageId: string): void {
|
||||
ok(!this._isDisposed);
|
||||
this._languageId = newLanguageId;
|
||||
}
|
||||
|
||||
_acceptIsDirty(isDirty: boolean): void {
|
||||
ok(!this._isDisposed);
|
||||
this._isDirty = isDirty;
|
||||
}
|
||||
|
||||
private _save(): TPromise<boolean> {
|
||||
if (this._isDisposed) {
|
||||
return TPromise.wrapError<boolean>(new Error('Document has been closed'));
|
||||
}
|
||||
return this._proxy.$trySaveDocument(this._uri);
|
||||
}
|
||||
|
||||
private _getTextInRange(_range: vscode.Range): string {
|
||||
let range = this._validateRange(_range);
|
||||
|
||||
if (range.isEmpty) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (range.isSingleLine) {
|
||||
return this._lines[range.start.line].substring(range.start.character, range.end.character);
|
||||
}
|
||||
|
||||
let lineEnding = this._eol,
|
||||
startLineIndex = range.start.line,
|
||||
endLineIndex = range.end.line,
|
||||
resultLines: string[] = [];
|
||||
|
||||
resultLines.push(this._lines[startLineIndex].substring(range.start.character));
|
||||
for (let i = startLineIndex + 1; i < endLineIndex; i++) {
|
||||
resultLines.push(this._lines[i]);
|
||||
}
|
||||
resultLines.push(this._lines[endLineIndex].substring(0, range.end.character));
|
||||
|
||||
return resultLines.join(lineEnding);
|
||||
}
|
||||
|
||||
private _lineAt(lineOrPosition: number | vscode.Position): vscode.TextLine {
|
||||
|
||||
let line: number;
|
||||
if (lineOrPosition instanceof Position) {
|
||||
line = lineOrPosition.line;
|
||||
} else if (typeof lineOrPosition === 'number') {
|
||||
line = lineOrPosition;
|
||||
}
|
||||
|
||||
if (line < 0 || line >= this._lines.length) {
|
||||
throw new Error('Illegal value for `line`');
|
||||
}
|
||||
|
||||
let result = this._textLines[line];
|
||||
if (!result || result.lineNumber !== line || result.text !== this._lines[line]) {
|
||||
|
||||
const text = this._lines[line];
|
||||
const firstNonWhitespaceCharacterIndex = /^(\s*)/.exec(text)[1].length;
|
||||
const range = new Range(line, 0, line, text.length);
|
||||
const rangeIncludingLineBreak = line < this._lines.length - 1
|
||||
? new Range(line, 0, line + 1, 0)
|
||||
: range;
|
||||
|
||||
result = Object.freeze({
|
||||
lineNumber: line,
|
||||
range,
|
||||
rangeIncludingLineBreak,
|
||||
text,
|
||||
firstNonWhitespaceCharacterIndex, //TODO@api, rename to 'leadingWhitespaceLength'
|
||||
isEmptyOrWhitespace: firstNonWhitespaceCharacterIndex === text.length
|
||||
});
|
||||
|
||||
this._textLines[line] = result;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private _offsetAt(position: vscode.Position): number {
|
||||
position = this._validatePosition(position);
|
||||
this._ensureLineStarts();
|
||||
return this._lineStarts.getAccumulatedValue(position.line - 1) + position.character;
|
||||
}
|
||||
|
||||
private _positionAt(offset: number): vscode.Position {
|
||||
offset = Math.floor(offset);
|
||||
offset = Math.max(0, offset);
|
||||
|
||||
this._ensureLineStarts();
|
||||
let out = this._lineStarts.getIndexOf(offset);
|
||||
|
||||
let lineLength = this._lines[out.index].length;
|
||||
|
||||
// Ensure we return a valid position
|
||||
return new Position(out.index, Math.min(out.remainder, lineLength));
|
||||
}
|
||||
|
||||
// ---- range math
|
||||
|
||||
private _validateRange(range: vscode.Range): vscode.Range {
|
||||
if (!(range instanceof Range)) {
|
||||
throw new Error('Invalid argument');
|
||||
}
|
||||
|
||||
let start = this._validatePosition(range.start);
|
||||
let end = this._validatePosition(range.end);
|
||||
|
||||
if (start === range.start && end === range.end) {
|
||||
return range;
|
||||
}
|
||||
return new Range(start.line, start.character, end.line, end.character);
|
||||
}
|
||||
|
||||
private _validatePosition(position: vscode.Position): vscode.Position {
|
||||
if (!(position instanceof Position)) {
|
||||
throw new Error('Invalid argument');
|
||||
}
|
||||
|
||||
let { line, character } = position;
|
||||
let hasChanged = false;
|
||||
|
||||
if (line < 0) {
|
||||
line = 0;
|
||||
character = 0;
|
||||
hasChanged = true;
|
||||
}
|
||||
else if (line >= this._lines.length) {
|
||||
line = this._lines.length - 1;
|
||||
character = this._lines[line].length;
|
||||
hasChanged = true;
|
||||
}
|
||||
else {
|
||||
let maxCharacter = this._lines[line].length;
|
||||
if (character < 0) {
|
||||
character = 0;
|
||||
hasChanged = true;
|
||||
}
|
||||
else if (character > maxCharacter) {
|
||||
character = maxCharacter;
|
||||
hasChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasChanged) {
|
||||
return position;
|
||||
}
|
||||
return new Position(line, character);
|
||||
}
|
||||
|
||||
private _getWordRangeAtPosition(_position: vscode.Position, regexp?: RegExp): vscode.Range {
|
||||
let position = this._validatePosition(_position);
|
||||
|
||||
if (!regexp) {
|
||||
// use default when custom-regexp isn't provided
|
||||
regexp = getWordDefinitionFor(this._languageId);
|
||||
|
||||
} else if (regExpLeadsToEndlessLoop(regexp)) {
|
||||
// use default when custom-regexp is bad
|
||||
console.warn(`[getWordRangeAtPosition]: ignoring custom regexp '${regexp.source}' because it matches the empty string.`);
|
||||
regexp = getWordDefinitionFor(this._languageId);
|
||||
}
|
||||
|
||||
let wordAtText = getWordAtText(
|
||||
position.character + 1,
|
||||
ensureValidWordDefinition(regexp),
|
||||
this._lines[position.line],
|
||||
0
|
||||
);
|
||||
|
||||
if (wordAtText) {
|
||||
return new Range(position.line, wordAtText.startColumn - 1, position.line, wordAtText.endColumn - 1);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
165
src/vs/workbench/api/node/extHostDocumentSaveParticipant.ts
Normal file
165
src/vs/workbench/api/node/extHostDocumentSaveParticipant.ts
Normal file
@@ -0,0 +1,165 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import Event from 'vs/base/common/event';
|
||||
import CallbackList from 'vs/base/common/callbackList';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { sequence, always } from 'vs/base/common/async';
|
||||
import { illegalState } from 'vs/base/common/errors';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { MainThreadWorkspaceShape, ExtHostDocumentSaveParticipantShape } from 'vs/workbench/api/node/extHost.protocol';
|
||||
import { TextEdit } from 'vs/workbench/api/node/extHostTypes';
|
||||
import { fromRange, TextDocumentSaveReason, EndOfLine } from 'vs/workbench/api/node/extHostTypeConverters';
|
||||
import { IResourceEdit } from 'vs/editor/common/services/bulkEdit';
|
||||
import { ExtHostDocuments } from 'vs/workbench/api/node/extHostDocuments';
|
||||
import { SaveReason } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export class ExtHostDocumentSaveParticipant implements ExtHostDocumentSaveParticipantShape {
|
||||
|
||||
private _documents: ExtHostDocuments;
|
||||
private _workspace: MainThreadWorkspaceShape;
|
||||
private _callbacks = new CallbackList();
|
||||
private _badListeners = new WeakMap<Function, number>();
|
||||
private _thresholds: { timeout: number; errors: number; };
|
||||
|
||||
constructor(documents: ExtHostDocuments, workspace: MainThreadWorkspaceShape, thresholds: { timeout: number; errors: number; } = { timeout: 1500, errors: 3 }) {
|
||||
this._documents = documents;
|
||||
this._workspace = workspace;
|
||||
this._thresholds = thresholds;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._callbacks.dispose();
|
||||
}
|
||||
|
||||
get onWillSaveTextDocumentEvent(): Event<vscode.TextDocumentWillSaveEvent> {
|
||||
return (listener, thisArg, disposables) => {
|
||||
this._callbacks.add(listener, thisArg);
|
||||
const result = {
|
||||
dispose: () => {
|
||||
this._callbacks.remove(listener, thisArg);
|
||||
}
|
||||
};
|
||||
if (Array.isArray(disposables)) {
|
||||
disposables.push(result);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
$participateInSave(resource: URI, reason: SaveReason): TPromise<boolean[]> {
|
||||
const entries = this._callbacks.entries();
|
||||
|
||||
let didTimeout = false;
|
||||
let didTimeoutHandle = setTimeout(() => didTimeout = true, this._thresholds.timeout);
|
||||
|
||||
const promise = sequence(entries.map(([fn, thisArg]) => {
|
||||
return () => {
|
||||
|
||||
if (didTimeout) {
|
||||
// timeout - no more listeners
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const document = this._documents.getDocumentData(resource).document;
|
||||
return this._deliverEventAsyncAndBlameBadListeners(fn, thisArg, <any>{ document, reason: TextDocumentSaveReason.to(reason) });
|
||||
};
|
||||
}));
|
||||
|
||||
return always(promise, () => clearTimeout(didTimeoutHandle));
|
||||
}
|
||||
|
||||
private _deliverEventAsyncAndBlameBadListeners(listener: Function, thisArg: any, stubEvent: vscode.TextDocumentWillSaveEvent): TPromise<any> {
|
||||
const errors = this._badListeners.get(listener);
|
||||
if (errors > this._thresholds.errors) {
|
||||
// bad listener - ignore
|
||||
return TPromise.wrap(false);
|
||||
}
|
||||
|
||||
return this._deliverEventAsync(listener, thisArg, stubEvent).then(() => {
|
||||
// don't send result across the wire
|
||||
return true;
|
||||
|
||||
}, err => {
|
||||
if (!(err instanceof Error) || (<Error>err).message !== 'concurrent_edits') {
|
||||
const errors = this._badListeners.get(listener);
|
||||
this._badListeners.set(listener, !errors ? 1 : errors + 1);
|
||||
|
||||
// todo@joh signal to the listener?
|
||||
// if (errors === this._thresholds.errors) {
|
||||
// console.warn('BAD onWillSaveTextDocumentEvent-listener is from now on being ignored');
|
||||
// }
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
private _deliverEventAsync(listener: Function, thisArg: any, stubEvent: vscode.TextDocumentWillSaveEvent): TPromise<any> {
|
||||
|
||||
const promises: TPromise<vscode.TextEdit[]>[] = [];
|
||||
|
||||
const { document, reason } = stubEvent;
|
||||
const { version } = document;
|
||||
|
||||
const event = Object.freeze(<vscode.TextDocumentWillSaveEvent>{
|
||||
document,
|
||||
reason,
|
||||
waitUntil(p: Thenable<any | vscode.TextEdit[]>) {
|
||||
if (Object.isFrozen(promises)) {
|
||||
throw illegalState('waitUntil can not be called async');
|
||||
}
|
||||
promises.push(TPromise.wrap(p));
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
// fire event
|
||||
listener.apply(thisArg, [event]);
|
||||
} catch (err) {
|
||||
return TPromise.wrapError(err);
|
||||
}
|
||||
|
||||
// freeze promises after event call
|
||||
Object.freeze(promises);
|
||||
|
||||
return new TPromise<vscode.TextEdit[][]>((resolve, reject) => {
|
||||
// join on all listener promises, reject after timeout
|
||||
const handle = setTimeout(() => reject(new Error('timeout')), this._thresholds.timeout);
|
||||
return always(TPromise.join(promises), () => clearTimeout(handle)).then(resolve, reject);
|
||||
|
||||
}).then(values => {
|
||||
|
||||
let edits: IResourceEdit[] = [];
|
||||
|
||||
for (const value of values) {
|
||||
if (Array.isArray(value) && (<vscode.TextEdit[]>value).every(e => e instanceof TextEdit)) {
|
||||
for (const { newText, newEol, range } of value) {
|
||||
edits.push({
|
||||
resource: <URI>document.uri,
|
||||
range: range && fromRange(range),
|
||||
newText,
|
||||
newEol: EndOfLine.from(newEol)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// apply edits if any and if document
|
||||
// didn't change somehow in the meantime
|
||||
if (edits.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (version === document.version) {
|
||||
return this._workspace.$applyWorkspaceEdit(edits);
|
||||
}
|
||||
|
||||
// TODO@joh bubble this to listener?
|
||||
return TPromise.wrapError(new Error('concurrent_edits'));
|
||||
});
|
||||
}
|
||||
}
|
||||
142
src/vs/workbench/api/node/extHostDocuments.ts
Normal file
142
src/vs/workbench/api/node/extHostDocuments.ts
Normal file
@@ -0,0 +1,142 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import * as TypeConverters from './extHostTypeConverters';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import * as vscode from 'vscode';
|
||||
import { MainContext, MainThreadDocumentsShape, ExtHostDocumentsShape, IMainContext } from './extHost.protocol';
|
||||
import { ExtHostDocumentData, setWordDefinitionFor } from './extHostDocumentData';
|
||||
import { ExtHostDocumentsAndEditors } from './extHostDocumentsAndEditors';
|
||||
import { IModelChangedEvent } from 'vs/editor/common/model/mirrorModel';
|
||||
|
||||
export class ExtHostDocuments implements ExtHostDocumentsShape {
|
||||
|
||||
private _onDidAddDocument = new Emitter<vscode.TextDocument>();
|
||||
private _onDidRemoveDocument = new Emitter<vscode.TextDocument>();
|
||||
private _onDidChangeDocument = new Emitter<vscode.TextDocumentChangeEvent>();
|
||||
private _onDidSaveDocument = new Emitter<vscode.TextDocument>();
|
||||
|
||||
readonly onDidAddDocument: Event<vscode.TextDocument> = this._onDidAddDocument.event;
|
||||
readonly onDidRemoveDocument: Event<vscode.TextDocument> = this._onDidRemoveDocument.event;
|
||||
readonly onDidChangeDocument: Event<vscode.TextDocumentChangeEvent> = this._onDidChangeDocument.event;
|
||||
readonly onDidSaveDocument: Event<vscode.TextDocument> = this._onDidSaveDocument.event;
|
||||
|
||||
private _toDispose: IDisposable[];
|
||||
private _proxy: MainThreadDocumentsShape;
|
||||
private _documentsAndEditors: ExtHostDocumentsAndEditors;
|
||||
private _documentLoader = new Map<string, TPromise<ExtHostDocumentData>>();
|
||||
|
||||
constructor(mainContext: IMainContext, documentsAndEditors: ExtHostDocumentsAndEditors) {
|
||||
this._proxy = mainContext.get(MainContext.MainThreadDocuments);
|
||||
this._documentsAndEditors = documentsAndEditors;
|
||||
|
||||
this._toDispose = [
|
||||
this._documentsAndEditors.onDidRemoveDocuments(documents => {
|
||||
for (const data of documents) {
|
||||
this._onDidRemoveDocument.fire(data.document);
|
||||
}
|
||||
}),
|
||||
this._documentsAndEditors.onDidAddDocuments(documents => {
|
||||
for (const data of documents) {
|
||||
this._onDidAddDocument.fire(data.document);
|
||||
}
|
||||
})
|
||||
];
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
dispose(this._toDispose);
|
||||
}
|
||||
|
||||
public getAllDocumentData(): ExtHostDocumentData[] {
|
||||
return this._documentsAndEditors.allDocuments();
|
||||
}
|
||||
|
||||
public getDocumentData(resource: vscode.Uri): ExtHostDocumentData {
|
||||
if (!resource) {
|
||||
return undefined;
|
||||
}
|
||||
const data = this._documentsAndEditors.getDocument(resource.toString());
|
||||
if (data) {
|
||||
return data;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public ensureDocumentData(uri: URI): TPromise<ExtHostDocumentData> {
|
||||
|
||||
let cached = this._documentsAndEditors.getDocument(uri.toString());
|
||||
if (cached) {
|
||||
return TPromise.as(cached);
|
||||
}
|
||||
|
||||
let promise = this._documentLoader.get(uri.toString());
|
||||
if (!promise) {
|
||||
promise = this._proxy.$tryOpenDocument(uri).then(() => {
|
||||
this._documentLoader.delete(uri.toString());
|
||||
return this._documentsAndEditors.getDocument(uri.toString());
|
||||
}, err => {
|
||||
this._documentLoader.delete(uri.toString());
|
||||
return TPromise.wrapError<ExtHostDocumentData>(err);
|
||||
});
|
||||
this._documentLoader.set(uri.toString(), promise);
|
||||
}
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
public createDocumentData(options?: { language?: string; content?: string }): TPromise<URI> {
|
||||
return this._proxy.$tryCreateDocument(options);
|
||||
}
|
||||
|
||||
public $acceptModelModeChanged(strURL: string, oldModeId: string, newModeId: string): void {
|
||||
let data = this._documentsAndEditors.getDocument(strURL);
|
||||
|
||||
// Treat a mode change as a remove + add
|
||||
|
||||
this._onDidRemoveDocument.fire(data.document);
|
||||
data._acceptLanguageId(newModeId);
|
||||
this._onDidAddDocument.fire(data.document);
|
||||
}
|
||||
|
||||
public $acceptModelSaved(strURL: string): void {
|
||||
let data = this._documentsAndEditors.getDocument(strURL);
|
||||
this.$acceptDirtyStateChanged(strURL, false);
|
||||
this._onDidSaveDocument.fire(data.document);
|
||||
}
|
||||
|
||||
public $acceptDirtyStateChanged(strURL: string, isDirty: boolean): void {
|
||||
let data = this._documentsAndEditors.getDocument(strURL);
|
||||
data._acceptIsDirty(isDirty);
|
||||
this._onDidChangeDocument.fire({
|
||||
document: data.document,
|
||||
contentChanges: []
|
||||
});
|
||||
}
|
||||
|
||||
public $acceptModelChanged(strURL: string, events: IModelChangedEvent, isDirty: boolean): void {
|
||||
let data = this._documentsAndEditors.getDocument(strURL);
|
||||
data._acceptIsDirty(isDirty);
|
||||
data.onEvents(events);
|
||||
this._onDidChangeDocument.fire({
|
||||
document: data.document,
|
||||
contentChanges: events.changes.map((change) => {
|
||||
return {
|
||||
range: TypeConverters.toRange(change.range),
|
||||
rangeLength: change.rangeLength,
|
||||
text: change.text
|
||||
};
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
public setWordDefinitionFor(modeId: string, wordDefinition: RegExp): void {
|
||||
setWordDefinitionFor(modeId, wordDefinition);
|
||||
}
|
||||
}
|
||||
149
src/vs/workbench/api/node/extHostDocumentsAndEditors.ts
Normal file
149
src/vs/workbench/api/node/extHostDocumentsAndEditors.ts
Normal file
@@ -0,0 +1,149 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { dispose } from 'vs/base/common/lifecycle';
|
||||
import { MainContext, ExtHostDocumentsAndEditorsShape, IDocumentsAndEditorsDelta, IMainContext } from './extHost.protocol';
|
||||
import { ExtHostDocumentData } from './extHostDocumentData';
|
||||
import { ExtHostTextEditor, ExtHostTextEditor2 } from './extHostTextEditor';
|
||||
import * as assert from 'assert';
|
||||
import * as typeConverters from './extHostTypeConverters';
|
||||
import { ExtHostExtensionService } from 'vs/workbench/api/node/extHostExtensionService';
|
||||
|
||||
export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsShape {
|
||||
|
||||
private _activeEditorId: string;
|
||||
private readonly _editors = new Map<string, ExtHostTextEditor>();
|
||||
private readonly _documents = new Map<string, ExtHostDocumentData>();
|
||||
|
||||
private readonly _onDidAddDocuments = new Emitter<ExtHostDocumentData[]>();
|
||||
private readonly _onDidRemoveDocuments = new Emitter<ExtHostDocumentData[]>();
|
||||
private readonly _onDidChangeVisibleTextEditors = new Emitter<ExtHostTextEditor[]>();
|
||||
private readonly _onDidChangeActiveTextEditor = new Emitter<ExtHostTextEditor>();
|
||||
|
||||
readonly onDidAddDocuments: Event<ExtHostDocumentData[]> = this._onDidAddDocuments.event;
|
||||
readonly onDidRemoveDocuments: Event<ExtHostDocumentData[]> = this._onDidRemoveDocuments.event;
|
||||
readonly onDidChangeVisibleTextEditors: Event<ExtHostTextEditor[]> = this._onDidChangeVisibleTextEditors.event;
|
||||
readonly onDidChangeActiveTextEditor: Event<ExtHostTextEditor> = this._onDidChangeActiveTextEditor.event;
|
||||
|
||||
constructor(
|
||||
private readonly _mainContext: IMainContext,
|
||||
private readonly _extHostExtensions?: ExtHostExtensionService
|
||||
) {
|
||||
}
|
||||
|
||||
$acceptDocumentsAndEditorsDelta(delta: IDocumentsAndEditorsDelta): void {
|
||||
|
||||
const removedDocuments: ExtHostDocumentData[] = [];
|
||||
const addedDocuments: ExtHostDocumentData[] = [];
|
||||
const removedEditors: ExtHostTextEditor[] = [];
|
||||
|
||||
if (delta.removedDocuments) {
|
||||
for (const id of delta.removedDocuments) {
|
||||
const data = this._documents.get(id);
|
||||
this._documents.delete(id);
|
||||
removedDocuments.push(data);
|
||||
}
|
||||
}
|
||||
|
||||
if (delta.addedDocuments) {
|
||||
for (const data of delta.addedDocuments) {
|
||||
assert.ok(!this._documents.has(data.url.toString()), `document '${data.url} already exists!'`);
|
||||
|
||||
const documentData = new ExtHostDocumentData(
|
||||
this._mainContext.get(MainContext.MainThreadDocuments),
|
||||
data.url,
|
||||
data.lines,
|
||||
data.EOL,
|
||||
data.modeId,
|
||||
data.versionId,
|
||||
data.isDirty
|
||||
);
|
||||
this._documents.set(data.url.toString(), documentData);
|
||||
addedDocuments.push(documentData);
|
||||
}
|
||||
}
|
||||
|
||||
if (delta.removedEditors) {
|
||||
for (const id of delta.removedEditors) {
|
||||
const editor = this._editors.get(id);
|
||||
this._editors.delete(id);
|
||||
removedEditors.push(editor);
|
||||
}
|
||||
}
|
||||
|
||||
if (delta.addedEditors) {
|
||||
for (const data of delta.addedEditors) {
|
||||
assert.ok(this._documents.has(data.document.toString()), `document '${data.document}' does not exist`);
|
||||
assert.ok(!this._editors.has(data.id), `editor '${data.id}' already exists!`);
|
||||
|
||||
const documentData = this._documents.get(data.document.toString());
|
||||
const editor = new ExtHostTextEditor2(
|
||||
this._extHostExtensions,
|
||||
this._mainContext.get(MainContext.MainThreadTelemetry),
|
||||
this._mainContext.get(MainContext.MainThreadEditors),
|
||||
data.id,
|
||||
documentData,
|
||||
data.selections.map(typeConverters.toSelection),
|
||||
data.options,
|
||||
typeConverters.toViewColumn(data.editorPosition)
|
||||
);
|
||||
this._editors.set(data.id, editor);
|
||||
}
|
||||
}
|
||||
|
||||
if (delta.newActiveEditor !== undefined) {
|
||||
assert.ok(delta.newActiveEditor === null || this._editors.has(delta.newActiveEditor), `active editor '${delta.newActiveEditor}' does not exist`);
|
||||
this._activeEditorId = delta.newActiveEditor;
|
||||
}
|
||||
|
||||
dispose(removedDocuments);
|
||||
dispose(removedEditors);
|
||||
|
||||
// now that the internal state is complete, fire events
|
||||
if (delta.removedDocuments) {
|
||||
this._onDidRemoveDocuments.fire(removedDocuments);
|
||||
}
|
||||
if (delta.addedDocuments) {
|
||||
this._onDidAddDocuments.fire(addedDocuments);
|
||||
}
|
||||
|
||||
if (delta.removedEditors || delta.addedEditors) {
|
||||
this._onDidChangeVisibleTextEditors.fire(this.allEditors());
|
||||
}
|
||||
if (delta.newActiveEditor !== undefined) {
|
||||
this._onDidChangeActiveTextEditor.fire(this.activeEditor());
|
||||
}
|
||||
}
|
||||
|
||||
getDocument(strUrl: string): ExtHostDocumentData {
|
||||
return this._documents.get(strUrl);
|
||||
}
|
||||
|
||||
allDocuments(): ExtHostDocumentData[] {
|
||||
const result: ExtHostDocumentData[] = [];
|
||||
this._documents.forEach(data => result.push(data));
|
||||
return result;
|
||||
}
|
||||
|
||||
getEditor(id: string): ExtHostTextEditor {
|
||||
return this._editors.get(id);
|
||||
}
|
||||
|
||||
activeEditor(): ExtHostTextEditor | undefined {
|
||||
if (!this._activeEditorId) {
|
||||
return undefined;
|
||||
} else {
|
||||
return this._editors.get(this._activeEditorId);
|
||||
}
|
||||
}
|
||||
|
||||
allEditors(): ExtHostTextEditor[] {
|
||||
const result: ExtHostTextEditor[] = [];
|
||||
this._editors.forEach(data => result.push(data));
|
||||
return result;
|
||||
}
|
||||
}
|
||||
327
src/vs/workbench/api/node/extHostExtensionActivator.ts
Normal file
327
src/vs/workbench/api/node/extHostExtensionActivator.ts
Normal file
@@ -0,0 +1,327 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/node/extensionDescriptionRegistry';
|
||||
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
|
||||
const hasOwnProperty = Object.hasOwnProperty;
|
||||
const NO_OP_VOID_PROMISE = TPromise.as<void>(void 0);
|
||||
|
||||
export interface IExtensionMemento {
|
||||
get<T>(key: string, defaultValue: T): T;
|
||||
update(key: string, value: any): Thenable<boolean>;
|
||||
}
|
||||
|
||||
export interface IExtensionContext {
|
||||
subscriptions: IDisposable[];
|
||||
workspaceState: IExtensionMemento;
|
||||
globalState: IExtensionMemento;
|
||||
extensionPath: string;
|
||||
storagePath: string;
|
||||
asAbsolutePath(relativePath: string): string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the source code (module) of an extension.
|
||||
*/
|
||||
export interface IExtensionModule {
|
||||
activate(ctx: IExtensionContext): TPromise<IExtensionAPI>;
|
||||
deactivate(): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the API of an extension (return value of `activate`).
|
||||
*/
|
||||
export interface IExtensionAPI {
|
||||
// _extensionAPIBrand: any;
|
||||
}
|
||||
|
||||
export class ExtensionActivationTimes {
|
||||
|
||||
public static NONE = new ExtensionActivationTimes(false, -1, -1, -1);
|
||||
|
||||
public readonly startup: boolean;
|
||||
public readonly codeLoadingTime: number;
|
||||
public readonly activateCallTime: number;
|
||||
public readonly activateResolvedTime: number;
|
||||
|
||||
constructor(startup: boolean, codeLoadingTime: number, activateCallTime: number, activateResolvedTime: number) {
|
||||
this.startup = startup;
|
||||
this.codeLoadingTime = codeLoadingTime;
|
||||
this.activateCallTime = activateCallTime;
|
||||
this.activateResolvedTime = activateResolvedTime;
|
||||
}
|
||||
}
|
||||
|
||||
export class ExtensionActivationTimesBuilder {
|
||||
|
||||
private readonly _startup: boolean;
|
||||
private _codeLoadingStart: number;
|
||||
private _codeLoadingStop: number;
|
||||
private _activateCallStart: number;
|
||||
private _activateCallStop: number;
|
||||
private _activateResolveStart: number;
|
||||
private _activateResolveStop: number;
|
||||
|
||||
constructor(startup: boolean) {
|
||||
this._startup = startup;
|
||||
this._codeLoadingStart = -1;
|
||||
this._codeLoadingStop = -1;
|
||||
this._activateCallStart = -1;
|
||||
this._activateCallStop = -1;
|
||||
this._activateResolveStart = -1;
|
||||
this._activateResolveStop = -1;
|
||||
}
|
||||
|
||||
private _delta(start: number, stop: number): number {
|
||||
if (start === -1 || stop === -1) {
|
||||
return -1;
|
||||
}
|
||||
return stop - start;
|
||||
}
|
||||
|
||||
public build(): ExtensionActivationTimes {
|
||||
return new ExtensionActivationTimes(
|
||||
this._startup,
|
||||
this._delta(this._codeLoadingStart, this._codeLoadingStop),
|
||||
this._delta(this._activateCallStart, this._activateCallStop),
|
||||
this._delta(this._activateResolveStart, this._activateResolveStop)
|
||||
);
|
||||
}
|
||||
|
||||
public codeLoadingStart(): void {
|
||||
this._codeLoadingStart = Date.now();
|
||||
}
|
||||
|
||||
public codeLoadingStop(): void {
|
||||
this._codeLoadingStop = Date.now();
|
||||
}
|
||||
|
||||
public activateCallStart(): void {
|
||||
this._activateCallStart = Date.now();
|
||||
}
|
||||
|
||||
public activateCallStop(): void {
|
||||
this._activateCallStop = Date.now();
|
||||
}
|
||||
|
||||
public activateResolveStart(): void {
|
||||
this._activateResolveStart = Date.now();
|
||||
}
|
||||
|
||||
public activateResolveStop(): void {
|
||||
this._activateResolveStop = Date.now();
|
||||
}
|
||||
}
|
||||
|
||||
export class ActivatedExtension {
|
||||
|
||||
public readonly activationFailed: boolean;
|
||||
public readonly activationTimes: ExtensionActivationTimes;
|
||||
public readonly module: IExtensionModule;
|
||||
public readonly exports: IExtensionAPI;
|
||||
public readonly subscriptions: IDisposable[];
|
||||
|
||||
constructor(
|
||||
activationFailed: boolean,
|
||||
activationTimes: ExtensionActivationTimes,
|
||||
module: IExtensionModule,
|
||||
exports: IExtensionAPI,
|
||||
subscriptions: IDisposable[]
|
||||
) {
|
||||
this.activationFailed = activationFailed;
|
||||
this.activationTimes = activationTimes;
|
||||
this.module = module;
|
||||
this.exports = exports;
|
||||
this.subscriptions = subscriptions;
|
||||
}
|
||||
}
|
||||
|
||||
export class EmptyExtension extends ActivatedExtension {
|
||||
constructor(activationTimes: ExtensionActivationTimes) {
|
||||
super(false, activationTimes, { activate: undefined, deactivate: undefined }, undefined, []);
|
||||
}
|
||||
}
|
||||
|
||||
export class FailedExtension extends ActivatedExtension {
|
||||
constructor(activationTimes: ExtensionActivationTimes) {
|
||||
super(true, activationTimes, { activate: undefined, deactivate: undefined }, undefined, []);
|
||||
}
|
||||
}
|
||||
|
||||
export interface IExtensionsActivatorHost {
|
||||
showMessage(severity: Severity, message: string): void;
|
||||
|
||||
actualActivateExtension(extensionDescription: IExtensionDescription, startup: boolean): TPromise<ActivatedExtension>;
|
||||
}
|
||||
|
||||
export class ExtensionsActivator {
|
||||
|
||||
private readonly _registry: ExtensionDescriptionRegistry;
|
||||
private readonly _host: IExtensionsActivatorHost;
|
||||
private readonly _activatingExtensions: { [extensionId: string]: TPromise<void>; };
|
||||
private readonly _activatedExtensions: { [extensionId: string]: ActivatedExtension; };
|
||||
/**
|
||||
* A map of already activated events to speed things up if the same activation event is triggered multiple times.
|
||||
*/
|
||||
private readonly _alreadyActivatedEvents: { [activationEvent: string]: boolean; };
|
||||
|
||||
constructor(registry: ExtensionDescriptionRegistry, host: IExtensionsActivatorHost) {
|
||||
this._registry = registry;
|
||||
this._host = host;
|
||||
this._activatingExtensions = {};
|
||||
this._activatedExtensions = {};
|
||||
this._alreadyActivatedEvents = Object.create(null);
|
||||
}
|
||||
|
||||
public isActivated(extensionId: string): boolean {
|
||||
return hasOwnProperty.call(this._activatedExtensions, extensionId);
|
||||
}
|
||||
|
||||
public getActivatedExtension(extensionId: string): ActivatedExtension {
|
||||
if (!hasOwnProperty.call(this._activatedExtensions, extensionId)) {
|
||||
throw new Error('Extension `' + extensionId + '` is not known or not activated');
|
||||
}
|
||||
return this._activatedExtensions[extensionId];
|
||||
}
|
||||
|
||||
public activateByEvent(activationEvent: string, startup: boolean): TPromise<void> {
|
||||
if (this._alreadyActivatedEvents[activationEvent]) {
|
||||
return NO_OP_VOID_PROMISE;
|
||||
}
|
||||
let activateExtensions = this._registry.getExtensionDescriptionsForActivationEvent(activationEvent);
|
||||
return this._activateExtensions(activateExtensions, startup, 0).then(() => {
|
||||
this._alreadyActivatedEvents[activationEvent] = true;
|
||||
});
|
||||
}
|
||||
|
||||
public activateById(extensionId: string, startup: boolean): TPromise<void> {
|
||||
let desc = this._registry.getExtensionDescription(extensionId);
|
||||
if (!desc) {
|
||||
throw new Error('Extension `' + extensionId + '` is not known');
|
||||
}
|
||||
|
||||
return this._activateExtensions([desc], startup, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle semantics related to dependencies for `currentExtension`.
|
||||
* semantics: `redExtensions` must wait for `greenExtensions`.
|
||||
*/
|
||||
private _handleActivateRequest(currentExtension: IExtensionDescription, greenExtensions: { [id: string]: IExtensionDescription; }, redExtensions: IExtensionDescription[]): void {
|
||||
let depIds = (typeof currentExtension.extensionDependencies === 'undefined' ? [] : currentExtension.extensionDependencies);
|
||||
let currentExtensionGetsGreenLight = true;
|
||||
|
||||
for (let j = 0, lenJ = depIds.length; j < lenJ; j++) {
|
||||
let depId = depIds[j];
|
||||
let depDesc = this._registry.getExtensionDescription(depId);
|
||||
|
||||
if (!depDesc) {
|
||||
// Error condition 1: unknown dependency
|
||||
this._host.showMessage(Severity.Error, nls.localize('unknownDep', "Extension `{1}` failed to activate. Reason: unknown dependency `{0}`.", depId, currentExtension.id));
|
||||
this._activatedExtensions[currentExtension.id] = new FailedExtension(ExtensionActivationTimes.NONE);
|
||||
return;
|
||||
}
|
||||
|
||||
if (hasOwnProperty.call(this._activatedExtensions, depId)) {
|
||||
let dep = this._activatedExtensions[depId];
|
||||
if (dep.activationFailed) {
|
||||
// Error condition 2: a dependency has already failed activation
|
||||
this._host.showMessage(Severity.Error, nls.localize('failedDep1', "Extension `{1}` failed to activate. Reason: dependency `{0}` failed to activate.", depId, currentExtension.id));
|
||||
this._activatedExtensions[currentExtension.id] = new FailedExtension(ExtensionActivationTimes.NONE);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// must first wait for the dependency to activate
|
||||
currentExtensionGetsGreenLight = false;
|
||||
greenExtensions[depId] = depDesc;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentExtensionGetsGreenLight) {
|
||||
greenExtensions[currentExtension.id] = currentExtension;
|
||||
} else {
|
||||
redExtensions.push(currentExtension);
|
||||
}
|
||||
}
|
||||
|
||||
private _activateExtensions(extensionDescriptions: IExtensionDescription[], startup: boolean, recursionLevel: number): TPromise<void> {
|
||||
// console.log(recursionLevel, '_activateExtensions: ', extensionDescriptions.map(p => p.id));
|
||||
if (extensionDescriptions.length === 0) {
|
||||
return TPromise.as(void 0);
|
||||
}
|
||||
|
||||
extensionDescriptions = extensionDescriptions.filter((p) => !hasOwnProperty.call(this._activatedExtensions, p.id));
|
||||
if (extensionDescriptions.length === 0) {
|
||||
return TPromise.as(void 0);
|
||||
}
|
||||
|
||||
if (recursionLevel > 10) {
|
||||
// More than 10 dependencies deep => most likely a dependency loop
|
||||
for (let i = 0, len = extensionDescriptions.length; i < len; i++) {
|
||||
// Error condition 3: dependency loop
|
||||
this._host.showMessage(Severity.Error, nls.localize('failedDep2', "Extension `{0}` failed to activate. Reason: more than 10 levels of dependencies (most likely a dependency loop).", extensionDescriptions[i].id));
|
||||
this._activatedExtensions[extensionDescriptions[i].id] = new FailedExtension(ExtensionActivationTimes.NONE);
|
||||
}
|
||||
return TPromise.as(void 0);
|
||||
}
|
||||
|
||||
let greenMap: { [id: string]: IExtensionDescription; } = Object.create(null),
|
||||
red: IExtensionDescription[] = [];
|
||||
|
||||
for (let i = 0, len = extensionDescriptions.length; i < len; i++) {
|
||||
this._handleActivateRequest(extensionDescriptions[i], greenMap, red);
|
||||
}
|
||||
|
||||
// Make sure no red is also green
|
||||
for (let i = 0, len = red.length; i < len; i++) {
|
||||
if (greenMap[red[i].id]) {
|
||||
delete greenMap[red[i].id];
|
||||
}
|
||||
}
|
||||
|
||||
let green = Object.keys(greenMap).map(id => greenMap[id]);
|
||||
|
||||
// console.log('greenExtensions: ', green.map(p => p.id));
|
||||
// console.log('redExtensions: ', red.map(p => p.id));
|
||||
|
||||
if (red.length === 0) {
|
||||
// Finally reached only leafs!
|
||||
return TPromise.join(green.map((p) => this._activateExtension(p, startup))).then(_ => void 0);
|
||||
}
|
||||
|
||||
return this._activateExtensions(green, startup, recursionLevel + 1).then(_ => {
|
||||
return this._activateExtensions(red, startup, recursionLevel + 1);
|
||||
});
|
||||
}
|
||||
|
||||
private _activateExtension(extensionDescription: IExtensionDescription, startup: boolean): TPromise<void> {
|
||||
if (hasOwnProperty.call(this._activatedExtensions, extensionDescription.id)) {
|
||||
return TPromise.as(void 0);
|
||||
}
|
||||
|
||||
if (hasOwnProperty.call(this._activatingExtensions, extensionDescription.id)) {
|
||||
return this._activatingExtensions[extensionDescription.id];
|
||||
}
|
||||
|
||||
this._activatingExtensions[extensionDescription.id] = this._host.actualActivateExtension(extensionDescription, startup).then(null, (err) => {
|
||||
this._host.showMessage(Severity.Error, nls.localize('activationError', "Activating extension `{0}` failed: {1}.", extensionDescription.id, err.message));
|
||||
console.error('Activating extension `' + extensionDescription.id + '` failed: ', err.message);
|
||||
console.log('Here is the error stack: ', err.stack);
|
||||
// Treat the extension as being empty
|
||||
return new FailedExtension(ExtensionActivationTimes.NONE);
|
||||
}).then((x: ActivatedExtension) => {
|
||||
this._activatedExtensions[extensionDescription.id] = x;
|
||||
delete this._activatingExtensions[extensionDescription.id];
|
||||
});
|
||||
|
||||
return this._activatingExtensions[extensionDescription.id];
|
||||
}
|
||||
}
|
||||
422
src/vs/workbench/api/node/extHostExtensionService.ts
Normal file
422
src/vs/workbench/api/node/extHostExtensionService.ts
Normal file
@@ -0,0 +1,422 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { dispose } from 'vs/base/common/lifecycle';
|
||||
import { join } from 'path';
|
||||
import { mkdirp, dirExists } from 'vs/base/node/pfs';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/node/extensionDescriptionRegistry';
|
||||
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { ExtHostStorage } from 'vs/workbench/api/node/extHostStorage';
|
||||
// {{SQL CARBON EDIT}}
|
||||
import { createApiFactory, initializeExtensionApi } from 'sql/workbench/api/node/sqlExtHost.api.impl';
|
||||
import { MainContext, MainThreadExtensionServiceShape, IWorkspaceData, IEnvironment, IInitData, ExtHostExtensionServiceShape, MainThreadTelemetryShape } from './extHost.protocol';
|
||||
import { IExtensionMemento, ExtensionsActivator, ActivatedExtension, IExtensionAPI, IExtensionContext, EmptyExtension, IExtensionModule, ExtensionActivationTimesBuilder, ExtensionActivationTimes } from 'vs/workbench/api/node/extHostExtensionActivator';
|
||||
import { Barrier } from 'vs/workbench/services/extensions/node/barrier';
|
||||
import { ExtHostThreadService } from 'vs/workbench/services/thread/node/extHostThreadService';
|
||||
import { realpath } from 'fs';
|
||||
import { TrieMap } from 'vs/base/common/map';
|
||||
|
||||
class ExtensionMemento implements IExtensionMemento {
|
||||
|
||||
private _id: string;
|
||||
private _shared: boolean;
|
||||
private _storage: ExtHostStorage;
|
||||
|
||||
private _init: TPromise<ExtensionMemento>;
|
||||
private _value: { [n: string]: any; };
|
||||
|
||||
constructor(id: string, global: boolean, storage: ExtHostStorage) {
|
||||
this._id = id;
|
||||
this._shared = global;
|
||||
this._storage = storage;
|
||||
|
||||
this._init = this._storage.getValue(this._shared, this._id, Object.create(null)).then(value => {
|
||||
this._value = value;
|
||||
return this;
|
||||
});
|
||||
}
|
||||
|
||||
get whenReady(): TPromise<ExtensionMemento> {
|
||||
return this._init;
|
||||
}
|
||||
|
||||
get<T>(key: string, defaultValue: T): T {
|
||||
let value = this._value[key];
|
||||
if (typeof value === 'undefined') {
|
||||
value = defaultValue;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
update(key: string, value: any): Thenable<boolean> {
|
||||
this._value[key] = value;
|
||||
return this._storage
|
||||
.setValue(this._shared, this._id, this._value)
|
||||
.then(() => true);
|
||||
}
|
||||
}
|
||||
|
||||
class ExtensionStoragePath {
|
||||
|
||||
private readonly _workspace: IWorkspaceData;
|
||||
private readonly _environment: IEnvironment;
|
||||
|
||||
private readonly _ready: TPromise<string>;
|
||||
private _value: string;
|
||||
|
||||
constructor(workspace: IWorkspaceData, environment: IEnvironment) {
|
||||
this._workspace = workspace;
|
||||
this._environment = environment;
|
||||
this._ready = this._getOrCreateWorkspaceStoragePath().then(value => this._value = value);
|
||||
}
|
||||
|
||||
get whenReady(): TPromise<any> {
|
||||
return this._ready;
|
||||
}
|
||||
|
||||
value(extension: IExtensionDescription): string {
|
||||
if (this._value) {
|
||||
return join(this._value, extension.id);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private _getOrCreateWorkspaceStoragePath(): TPromise<string> {
|
||||
if (!this._workspace) {
|
||||
return TPromise.as(undefined);
|
||||
}
|
||||
const storageName = this._workspace.id;
|
||||
const storagePath = join(this._environment.appSettingsHome, 'workspaceStorage', storageName);
|
||||
|
||||
return dirExists(storagePath).then(exists => {
|
||||
if (exists) {
|
||||
return storagePath;
|
||||
}
|
||||
|
||||
return mkdirp(storagePath).then(success => {
|
||||
return storagePath;
|
||||
}, err => {
|
||||
return undefined;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class ExtHostExtensionService implements ExtHostExtensionServiceShape {
|
||||
|
||||
private readonly _barrier: Barrier;
|
||||
private readonly _registry: ExtensionDescriptionRegistry;
|
||||
private readonly _threadService: ExtHostThreadService;
|
||||
private readonly _mainThreadTelemetry: MainThreadTelemetryShape;
|
||||
private readonly _storage: ExtHostStorage;
|
||||
private readonly _storagePath: ExtensionStoragePath;
|
||||
private readonly _proxy: MainThreadExtensionServiceShape;
|
||||
private _activator: ExtensionsActivator;
|
||||
private _extensionPathIndex: TPromise<TrieMap<IExtensionDescription>>;
|
||||
/**
|
||||
* This class is constructed manually because it is a service, so it doesn't use any ctor injection
|
||||
*/
|
||||
constructor(initData: IInitData, threadService: ExtHostThreadService) {
|
||||
this._barrier = new Barrier();
|
||||
this._registry = new ExtensionDescriptionRegistry(initData.extensions);
|
||||
this._threadService = threadService;
|
||||
this._mainThreadTelemetry = threadService.get(MainContext.MainThreadTelemetry);
|
||||
this._storage = new ExtHostStorage(threadService);
|
||||
this._storagePath = new ExtensionStoragePath(initData.workspace, initData.environment);
|
||||
this._proxy = this._threadService.get(MainContext.MainThreadExtensionService);
|
||||
this._activator = null;
|
||||
|
||||
// initialize API first (i.e. do not release barrier until the API is initialized)
|
||||
const apiFactory = createApiFactory(initData, threadService, this);
|
||||
|
||||
initializeExtensionApi(this, apiFactory).then(() => {
|
||||
|
||||
this._activator = new ExtensionsActivator(this._registry, {
|
||||
showMessage: (severity: Severity, message: string): void => {
|
||||
this._proxy.$localShowMessage(severity, message);
|
||||
|
||||
switch (severity) {
|
||||
case Severity.Error:
|
||||
console.error(message);
|
||||
break;
|
||||
case Severity.Warning:
|
||||
console.warn(message);
|
||||
break;
|
||||
default:
|
||||
console.log(message);
|
||||
}
|
||||
},
|
||||
|
||||
actualActivateExtension: (extensionDescription: IExtensionDescription, startup: boolean): TPromise<ActivatedExtension> => {
|
||||
return this._activateExtension(extensionDescription, startup);
|
||||
}
|
||||
});
|
||||
|
||||
this._barrier.open();
|
||||
});
|
||||
}
|
||||
|
||||
public onExtensionAPIReady(): TPromise<boolean> {
|
||||
return this._barrier.wait();
|
||||
}
|
||||
|
||||
public isActivated(extensionId: string): boolean {
|
||||
if (this._barrier.isOpen()) {
|
||||
return this._activator.isActivated(extensionId);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public activateByEvent(activationEvent: string, startup: boolean): TPromise<void> {
|
||||
if (this._barrier.isOpen()) {
|
||||
return this._activator.activateByEvent(activationEvent, startup);
|
||||
} else {
|
||||
return this._barrier.wait().then(() => this._activator.activateByEvent(activationEvent, startup));
|
||||
}
|
||||
}
|
||||
|
||||
public activateById(extensionId: string, startup: boolean): TPromise<void> {
|
||||
if (this._barrier.isOpen()) {
|
||||
return this._activator.activateById(extensionId, startup);
|
||||
} else {
|
||||
return this._barrier.wait().then(() => this._activator.activateById(extensionId, startup));
|
||||
}
|
||||
}
|
||||
|
||||
public getAllExtensionDescriptions(): IExtensionDescription[] {
|
||||
return this._registry.getAllExtensionDescriptions();
|
||||
}
|
||||
|
||||
public getExtensionDescription(extensionId: string): IExtensionDescription {
|
||||
return this._registry.getExtensionDescription(extensionId);
|
||||
}
|
||||
|
||||
public getExtensionExports(extensionId: string): IExtensionAPI {
|
||||
if (this._barrier.isOpen()) {
|
||||
return this._activator.getActivatedExtension(extensionId).exports;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// create trie to enable fast 'filename -> extension id' look up
|
||||
public getExtensionPathIndex(): TPromise<TrieMap<IExtensionDescription>> {
|
||||
if (!this._extensionPathIndex) {
|
||||
const trie = new TrieMap<IExtensionDescription>();
|
||||
const extensions = this.getAllExtensionDescriptions().map(ext => {
|
||||
if (!ext.main) {
|
||||
return undefined;
|
||||
}
|
||||
return new TPromise((resolve, reject) => {
|
||||
realpath(ext.extensionFolderPath, (err, path) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
trie.insert(path, ext);
|
||||
resolve(void 0);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
this._extensionPathIndex = TPromise.join(extensions).then(() => trie);
|
||||
}
|
||||
return this._extensionPathIndex;
|
||||
}
|
||||
|
||||
|
||||
public deactivate(extensionId: string): TPromise<void> {
|
||||
let result: TPromise<void> = TPromise.as(void 0);
|
||||
|
||||
if (!this._barrier.isOpen()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (!this._activator.isActivated(extensionId)) {
|
||||
return result;
|
||||
}
|
||||
|
||||
let extension = this._activator.getActivatedExtension(extensionId);
|
||||
if (!extension) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// call deactivate if available
|
||||
try {
|
||||
if (typeof extension.module.deactivate === 'function') {
|
||||
result = TPromise.wrap(extension.module.deactivate()).then(null, (err) => {
|
||||
// TODO: Do something with err if this is not the shutdown case
|
||||
return TPromise.as(void 0);
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
// TODO: Do something with err if this is not the shutdown case
|
||||
}
|
||||
|
||||
// clean up subscriptions
|
||||
try {
|
||||
dispose(extension.subscriptions);
|
||||
} catch (err) {
|
||||
// TODO: Do something with err if this is not the shutdown case
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// --- impl
|
||||
|
||||
private _activateExtension(extensionDescription: IExtensionDescription, startup: boolean): TPromise<ActivatedExtension> {
|
||||
return this._doActivateExtension(extensionDescription, startup).then((activatedExtension) => {
|
||||
const activationTimes = activatedExtension.activationTimes;
|
||||
this._proxy.$onExtensionActivated(extensionDescription.id, activationTimes.startup, activationTimes.codeLoadingTime, activationTimes.activateCallTime, activationTimes.activateResolvedTime);
|
||||
return activatedExtension;
|
||||
}, (err) => {
|
||||
this._proxy.$onExtensionActivationFailed(extensionDescription.id);
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
|
||||
private _doActivateExtension(extensionDescription: IExtensionDescription, startup: boolean): TPromise<ActivatedExtension> {
|
||||
let event = getTelemetryActivationEvent(extensionDescription);
|
||||
this._mainThreadTelemetry.$publicLog('activatePlugin', event);
|
||||
if (!extensionDescription.main) {
|
||||
// Treat the extension as being empty => NOT AN ERROR CASE
|
||||
return TPromise.as(new EmptyExtension(ExtensionActivationTimes.NONE));
|
||||
}
|
||||
|
||||
const activationTimesBuilder = new ExtensionActivationTimesBuilder(startup);
|
||||
return TPromise.join<any>([
|
||||
loadCommonJSModule(extensionDescription.main, activationTimesBuilder),
|
||||
this._loadExtensionContext(extensionDescription)
|
||||
]).then(values => {
|
||||
return ExtHostExtensionService._callActivate(<IExtensionModule>values[0], <IExtensionContext>values[1], activationTimesBuilder);
|
||||
}, (errors: any[]) => {
|
||||
// Avoid failing with an array of errors, fail with a single error
|
||||
if (errors[0]) {
|
||||
return TPromise.wrapError<ActivatedExtension>(errors[0]);
|
||||
}
|
||||
if (errors[1]) {
|
||||
return TPromise.wrapError<ActivatedExtension>(errors[1]);
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
|
||||
private _loadExtensionContext(extensionDescription: IExtensionDescription): TPromise<IExtensionContext> {
|
||||
|
||||
let globalState = new ExtensionMemento(extensionDescription.id, true, this._storage);
|
||||
let workspaceState = new ExtensionMemento(extensionDescription.id, false, this._storage);
|
||||
|
||||
return TPromise.join([
|
||||
globalState.whenReady,
|
||||
workspaceState.whenReady,
|
||||
this._storagePath.whenReady
|
||||
]).then(() => {
|
||||
return Object.freeze(<IExtensionContext>{
|
||||
globalState,
|
||||
workspaceState,
|
||||
subscriptions: [],
|
||||
get extensionPath() { return extensionDescription.extensionFolderPath; },
|
||||
storagePath: this._storagePath.value(extensionDescription),
|
||||
asAbsolutePath: (relativePath: string) => { return join(extensionDescription.extensionFolderPath, relativePath); }
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private static _callActivate(extensionModule: IExtensionModule, context: IExtensionContext, activationTimesBuilder: ExtensionActivationTimesBuilder): TPromise<ActivatedExtension> {
|
||||
// Make sure the extension's surface is not undefined
|
||||
extensionModule = extensionModule || {
|
||||
activate: undefined,
|
||||
deactivate: undefined
|
||||
};
|
||||
|
||||
return this._callActivateOptional(extensionModule, context, activationTimesBuilder).then((extensionExports) => {
|
||||
return new ActivatedExtension(false, activationTimesBuilder.build(), extensionModule, extensionExports, context.subscriptions);
|
||||
});
|
||||
}
|
||||
|
||||
private static _callActivateOptional(extensionModule: IExtensionModule, context: IExtensionContext, activationTimesBuilder: ExtensionActivationTimesBuilder): TPromise<IExtensionAPI> {
|
||||
if (typeof extensionModule.activate === 'function') {
|
||||
try {
|
||||
activationTimesBuilder.activateCallStart();
|
||||
const activateResult = extensionModule.activate.apply(global, [context]);
|
||||
activationTimesBuilder.activateCallStop();
|
||||
|
||||
activationTimesBuilder.activateResolveStart();
|
||||
return TPromise.as(activateResult).then((value) => {
|
||||
activationTimesBuilder.activateResolveStop();
|
||||
return value;
|
||||
});
|
||||
} catch (err) {
|
||||
return TPromise.wrapError(err);
|
||||
}
|
||||
} else {
|
||||
// No activate found => the module is the extension's exports
|
||||
return TPromise.as<IExtensionAPI>(extensionModule);
|
||||
}
|
||||
}
|
||||
|
||||
// -- called by main thread
|
||||
|
||||
public $activateByEvent(activationEvent: string): TPromise<void> {
|
||||
return this.activateByEvent(activationEvent, false);
|
||||
}
|
||||
}
|
||||
|
||||
function loadCommonJSModule<T>(modulePath: string, activationTimesBuilder: ExtensionActivationTimesBuilder): TPromise<T> {
|
||||
let r: T = null;
|
||||
activationTimesBuilder.codeLoadingStart();
|
||||
try {
|
||||
r = require.__$__nodeRequire<T>(modulePath);
|
||||
} catch (e) {
|
||||
return TPromise.wrapError<T>(e);
|
||||
} finally {
|
||||
activationTimesBuilder.codeLoadingStop();
|
||||
}
|
||||
return TPromise.as(r);
|
||||
}
|
||||
|
||||
function getTelemetryActivationEvent(extensionDescription: IExtensionDescription): any {
|
||||
let event = {
|
||||
id: extensionDescription.id,
|
||||
name: extensionDescription.name,
|
||||
publisherDisplayName: extensionDescription.publisher,
|
||||
activationEvents: extensionDescription.activationEvents ? extensionDescription.activationEvents.join(',') : null,
|
||||
isBuiltin: extensionDescription.isBuiltin
|
||||
};
|
||||
|
||||
for (let contribution in extensionDescription.contributes) {
|
||||
let contributionDetails = extensionDescription.contributes[contribution];
|
||||
|
||||
if (!contributionDetails) {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (contribution) {
|
||||
case 'debuggers':
|
||||
let types = contributionDetails.reduce((p, c) => p ? p + ',' + c['type'] : c['type'], '');
|
||||
event['contribution.debuggers'] = types;
|
||||
break;
|
||||
case 'grammars':
|
||||
let grammers = contributionDetails.reduce((p, c) => p ? p + ',' + c['language'] : c['language'], '');
|
||||
event['contribution.grammars'] = grammers;
|
||||
break;
|
||||
case 'languages':
|
||||
let languages = contributionDetails.reduce((p, c) => p ? p + ',' + c['id'] : c['id'], '');
|
||||
event['contribution.languages'] = languages;
|
||||
break;
|
||||
case 'tmSnippets':
|
||||
let tmSnippets = contributionDetails.reduce((p, c) => p ? p + ',' + c['languageId'] : c['languageId'], '');
|
||||
event['contribution.tmSnippets'] = tmSnippets;
|
||||
break;
|
||||
default:
|
||||
event[`contribution.${contribution}`] = true;
|
||||
}
|
||||
}
|
||||
|
||||
return event;
|
||||
}
|
||||
104
src/vs/workbench/api/node/extHostFileSystemEventService.ts
Normal file
104
src/vs/workbench/api/node/extHostFileSystemEventService.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { Disposable } from './extHostTypes';
|
||||
import { match } from 'vs/base/common/glob';
|
||||
import { Uri, FileSystemWatcher as _FileSystemWatcher } from 'vscode';
|
||||
import { FileSystemEvents, ExtHostFileSystemEventServiceShape } from './extHost.protocol';
|
||||
|
||||
class FileSystemWatcher implements _FileSystemWatcher {
|
||||
|
||||
private _onDidCreate = new Emitter<Uri>();
|
||||
private _onDidChange = new Emitter<Uri>();
|
||||
private _onDidDelete = new Emitter<Uri>();
|
||||
private _disposable: Disposable;
|
||||
private _config: number;
|
||||
|
||||
get ignoreCreateEvents(): boolean {
|
||||
return Boolean(this._config & 0b001);
|
||||
}
|
||||
|
||||
get ignoreChangeEvents(): boolean {
|
||||
return Boolean(this._config & 0b010);
|
||||
}
|
||||
|
||||
get ignoreDeleteEvents(): boolean {
|
||||
return Boolean(this._config & 0b100);
|
||||
}
|
||||
|
||||
constructor(dispatcher: Event<FileSystemEvents>, globPattern: string, ignoreCreateEvents?: boolean, ignoreChangeEvents?: boolean, ignoreDeleteEvents?: boolean) {
|
||||
|
||||
this._config = 0;
|
||||
if (ignoreCreateEvents) {
|
||||
this._config += 0b001;
|
||||
}
|
||||
if (ignoreChangeEvents) {
|
||||
this._config += 0b010;
|
||||
}
|
||||
if (ignoreDeleteEvents) {
|
||||
this._config += 0b100;
|
||||
}
|
||||
|
||||
let subscription = dispatcher(events => {
|
||||
if (!ignoreCreateEvents) {
|
||||
for (let created of events.created) {
|
||||
if (match(globPattern, created.fsPath)) {
|
||||
this._onDidCreate.fire(created);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!ignoreChangeEvents) {
|
||||
for (let changed of events.changed) {
|
||||
if (match(globPattern, changed.fsPath)) {
|
||||
this._onDidChange.fire(changed);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!ignoreDeleteEvents) {
|
||||
for (let deleted of events.deleted) {
|
||||
if (match(globPattern, deleted.fsPath)) {
|
||||
this._onDidDelete.fire(deleted);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this._disposable = Disposable.from(this._onDidCreate, this._onDidChange, this._onDidDelete, subscription);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this._disposable.dispose();
|
||||
}
|
||||
|
||||
get onDidCreate(): Event<Uri> {
|
||||
return this._onDidCreate.event;
|
||||
}
|
||||
|
||||
get onDidChange(): Event<Uri> {
|
||||
return this._onDidChange.event;
|
||||
}
|
||||
|
||||
get onDidDelete(): Event<Uri> {
|
||||
return this._onDidDelete.event;
|
||||
}
|
||||
}
|
||||
|
||||
export class ExtHostFileSystemEventService implements ExtHostFileSystemEventServiceShape {
|
||||
|
||||
private _emitter = new Emitter<FileSystemEvents>();
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
public createFileSystemWatcher(globPattern: string, ignoreCreateEvents?: boolean, ignoreChangeEvents?: boolean, ignoreDeleteEvents?: boolean): _FileSystemWatcher {
|
||||
return new FileSystemWatcher(this._emitter.event, globPattern, ignoreCreateEvents, ignoreChangeEvents, ignoreDeleteEvents);
|
||||
}
|
||||
|
||||
$onFileEvent(events: FileSystemEvents) {
|
||||
this._emitter.fire(events);
|
||||
}
|
||||
}
|
||||
34
src/vs/workbench/api/node/extHostHeapService.ts
Normal file
34
src/vs/workbench/api/node/extHostHeapService.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { ExtHostHeapServiceShape } from './extHost.protocol';
|
||||
|
||||
export class ExtHostHeapService implements ExtHostHeapServiceShape {
|
||||
|
||||
private static _idPool = 0;
|
||||
|
||||
private _data = new Map<number, any>();
|
||||
|
||||
keep(obj: any): number {
|
||||
const id = ExtHostHeapService._idPool++;
|
||||
this._data.set(id, obj);
|
||||
return id;
|
||||
}
|
||||
|
||||
delete(id: number): boolean {
|
||||
return this._data.delete(id);
|
||||
}
|
||||
|
||||
get<T>(id: number): T {
|
||||
return this._data.get(id);
|
||||
}
|
||||
|
||||
$onGarbageCollection(ids: number[]): void {
|
||||
for (const id of ids) {
|
||||
this.delete(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
1072
src/vs/workbench/api/node/extHostLanguageFeatures.ts
Normal file
1072
src/vs/workbench/api/node/extHostLanguageFeatures.ts
Normal file
File diff suppressed because it is too large
Load Diff
24
src/vs/workbench/api/node/extHostLanguages.ts
Normal file
24
src/vs/workbench/api/node/extHostLanguages.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { MainContext, MainThreadLanguagesShape, IMainContext } from './extHost.protocol';
|
||||
|
||||
export class ExtHostLanguages {
|
||||
|
||||
private _proxy: MainThreadLanguagesShape;
|
||||
|
||||
constructor(
|
||||
mainContext: IMainContext
|
||||
) {
|
||||
this._proxy = mainContext.get(MainContext.MainThreadLanguages);
|
||||
}
|
||||
|
||||
getLanguages(): TPromise<string[]> {
|
||||
return this._proxy.$getLanguages();
|
||||
}
|
||||
}
|
||||
|
||||
60
src/vs/workbench/api/node/extHostMessageService.ts
Normal file
60
src/vs/workbench/api/node/extHostMessageService.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import vscode = require('vscode');
|
||||
import { MainContext, MainThreadMessageServiceShape, MainThreadMessageOptions, IMainContext } from './extHost.protocol';
|
||||
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
|
||||
|
||||
function isMessageItem<T>(item: any): item is vscode.MessageItem {
|
||||
return item && item.title;
|
||||
}
|
||||
|
||||
export class ExtHostMessageService {
|
||||
|
||||
private _proxy: MainThreadMessageServiceShape;
|
||||
|
||||
constructor(mainContext: IMainContext) {
|
||||
this._proxy = mainContext.get(MainContext.MainThreadMessageService);
|
||||
}
|
||||
|
||||
showMessage(extension: IExtensionDescription, severity: Severity, message: string, optionsOrFirstItem: vscode.MessageOptions | string, rest: string[]): Thenable<string | undefined>;
|
||||
showMessage(extension: IExtensionDescription, severity: Severity, message: string, optionsOrFirstItem: vscode.MessageOptions | vscode.MessageItem, rest: vscode.MessageItem[]): Thenable<vscode.MessageItem | undefined>;
|
||||
showMessage(extension: IExtensionDescription, severity: Severity, message: string, optionsOrFirstItem: vscode.MessageOptions | string | vscode.MessageItem, rest: (string | vscode.MessageItem)[]): Thenable<string | vscode.MessageItem | undefined> {
|
||||
|
||||
let options: MainThreadMessageOptions = { extension };
|
||||
let items: (string | vscode.MessageItem)[];
|
||||
|
||||
if (typeof optionsOrFirstItem === 'string' || isMessageItem(optionsOrFirstItem)) {
|
||||
items = [optionsOrFirstItem, ...rest];
|
||||
} else {
|
||||
options.modal = optionsOrFirstItem && optionsOrFirstItem.modal;
|
||||
items = rest;
|
||||
}
|
||||
|
||||
const commands: { title: string; isCloseAffordance: boolean; handle: number; }[] = [];
|
||||
|
||||
for (let handle = 0; handle < items.length; handle++) {
|
||||
let command = items[handle];
|
||||
if (typeof command === 'string') {
|
||||
commands.push({ title: command, handle, isCloseAffordance: false });
|
||||
} else if (typeof command === 'object') {
|
||||
let { title, isCloseAffordance } = command;
|
||||
commands.push({ title, isCloseAffordance, handle });
|
||||
} else {
|
||||
console.warn('Invalid message item:', command);
|
||||
}
|
||||
}
|
||||
|
||||
return this._proxy.$showMessage(severity, message, options, commands).then(handle => {
|
||||
if (typeof handle === 'number') {
|
||||
return items[handle];
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
}
|
||||
78
src/vs/workbench/api/node/extHostOutputService.ts
Normal file
78
src/vs/workbench/api/node/extHostOutputService.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { MainContext, MainThreadOutputServiceShape, IMainContext } from './extHost.protocol';
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export class ExtHostOutputChannel implements vscode.OutputChannel {
|
||||
|
||||
private static _idPool = 1;
|
||||
|
||||
private _proxy: MainThreadOutputServiceShape;
|
||||
private _name: string;
|
||||
private _id: string;
|
||||
private _disposed: boolean;
|
||||
|
||||
constructor(name: string, proxy: MainThreadOutputServiceShape) {
|
||||
this._name = name;
|
||||
this._id = 'extension-output-#' + (ExtHostOutputChannel._idPool++);
|
||||
this._proxy = proxy;
|
||||
}
|
||||
|
||||
get name(): string {
|
||||
return this._name;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
if (!this._disposed) {
|
||||
this._proxy.$dispose(this._id, this._name).then(() => {
|
||||
this._disposed = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
append(value: string): void {
|
||||
this._proxy.$append(this._id, this._name, value);
|
||||
}
|
||||
|
||||
appendLine(value: string): void {
|
||||
this.append(value + '\n');
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this._proxy.$clear(this._id, this._name);
|
||||
}
|
||||
|
||||
show(columnOrPreserveFocus?: vscode.ViewColumn | boolean, preserveFocus?: boolean): void {
|
||||
if (typeof columnOrPreserveFocus === 'boolean') {
|
||||
preserveFocus = columnOrPreserveFocus;
|
||||
}
|
||||
|
||||
this._proxy.$reveal(this._id, this._name, preserveFocus);
|
||||
}
|
||||
|
||||
hide(): void {
|
||||
this._proxy.$close(this._id);
|
||||
}
|
||||
}
|
||||
|
||||
export class ExtHostOutputService {
|
||||
|
||||
private _proxy: MainThreadOutputServiceShape;
|
||||
|
||||
constructor(mainContext: IMainContext) {
|
||||
this._proxy = mainContext.get(MainContext.MainThreadOutputService);
|
||||
}
|
||||
|
||||
createOutputChannel(name: string): vscode.OutputChannel {
|
||||
name = name.trim();
|
||||
if (!name) {
|
||||
throw new Error('illegal argument `name`. must not be falsy');
|
||||
} else {
|
||||
return new ExtHostOutputChannel(name, this._proxy);
|
||||
}
|
||||
}
|
||||
}
|
||||
50
src/vs/workbench/api/node/extHostProgress.ts
Normal file
50
src/vs/workbench/api/node/extHostProgress.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { Progress, ProgressOptions, CancellationToken } from 'vscode';
|
||||
import { MainThreadProgressShape } from './extHost.protocol';
|
||||
import { ProgressLocation } from './extHostTypeConverters';
|
||||
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { IProgressStep } from 'vs/platform/progress/common/progress';
|
||||
|
||||
export class ExtHostProgress {
|
||||
|
||||
private _proxy: MainThreadProgressShape;
|
||||
private _handles: number = 0;
|
||||
|
||||
constructor(proxy: MainThreadProgressShape) {
|
||||
this._proxy = proxy;
|
||||
}
|
||||
|
||||
withProgress<R>(extension: IExtensionDescription, options: ProgressOptions, task: (progress: Progress<IProgressStep>, token: CancellationToken) => Thenable<R>): Thenable<R> {
|
||||
const handle = this._handles++;
|
||||
const { title, location } = options;
|
||||
this._proxy.$startProgress(handle, { location: ProgressLocation.from(location), title, tooltip: extension.name });
|
||||
return this._withProgress(handle, task);
|
||||
}
|
||||
|
||||
private _withProgress<R>(handle: number, task: (progress: Progress<IProgressStep>, token: CancellationToken) => Thenable<R>): Thenable<R> {
|
||||
|
||||
const progress = {
|
||||
report: (p: IProgressStep) => {
|
||||
this._proxy.$progressReport(handle, p);
|
||||
}
|
||||
};
|
||||
|
||||
let p: Thenable<R>;
|
||||
|
||||
try {
|
||||
p = task(progress, null);
|
||||
} catch (err) {
|
||||
this._proxy.$progressEnd(handle);
|
||||
throw err;
|
||||
}
|
||||
|
||||
p.then(result => this._proxy.$progressEnd(handle), err => this._proxy.$progressEnd(handle));
|
||||
return p;
|
||||
}
|
||||
}
|
||||
|
||||
120
src/vs/workbench/api/node/extHostQuickOpen.ts
Normal file
120
src/vs/workbench/api/node/extHostQuickOpen.ts
Normal file
@@ -0,0 +1,120 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { wireCancellationToken } from 'vs/base/common/async';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { QuickPickOptions, QuickPickItem, InputBoxOptions } from 'vscode';
|
||||
import { MainContext, MainThreadQuickOpenShape, ExtHostQuickOpenShape, MyQuickPickItems, IMainContext } from './extHost.protocol';
|
||||
|
||||
export type Item = string | QuickPickItem;
|
||||
|
||||
export class ExtHostQuickOpen implements ExtHostQuickOpenShape {
|
||||
|
||||
private _proxy: MainThreadQuickOpenShape;
|
||||
private _onDidSelectItem: (handle: number) => void;
|
||||
private _validateInput: (input: string) => string;
|
||||
|
||||
constructor(mainContext: IMainContext) {
|
||||
this._proxy = mainContext.get(MainContext.MainThreadQuickOpen);
|
||||
}
|
||||
|
||||
showQuickPick(itemsOrItemsPromise: string[] | Thenable<string[]>, options?: QuickPickOptions, token?: CancellationToken): Thenable<string | undefined>;
|
||||
showQuickPick(itemsOrItemsPromise: QuickPickItem[] | Thenable<QuickPickItem[]>, options?: QuickPickOptions, token?: CancellationToken): Thenable<QuickPickItem | undefined>;
|
||||
showQuickPick(itemsOrItemsPromise: Item[] | Thenable<Item[]>, options?: QuickPickOptions, token: CancellationToken = CancellationToken.None): Thenable<Item | undefined> {
|
||||
|
||||
// clear state from last invocation
|
||||
this._onDidSelectItem = undefined;
|
||||
|
||||
const itemsPromise = <TPromise<Item[]>>TPromise.wrap(itemsOrItemsPromise);
|
||||
|
||||
const quickPickWidget = this._proxy.$show({
|
||||
autoFocus: { autoFocusFirstEntry: true },
|
||||
placeHolder: options && options.placeHolder,
|
||||
matchOnDescription: options && options.matchOnDescription,
|
||||
matchOnDetail: options && options.matchOnDetail,
|
||||
ignoreFocusLost: options && options.ignoreFocusOut
|
||||
});
|
||||
|
||||
const promise = TPromise.any(<TPromise<number | Item[]>[]>[quickPickWidget, itemsPromise]).then(values => {
|
||||
if (values.key === '0') {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return itemsPromise.then(items => {
|
||||
|
||||
let pickItems: MyQuickPickItems[] = [];
|
||||
for (let handle = 0; handle < items.length; handle++) {
|
||||
|
||||
let item = items[handle];
|
||||
let label: string;
|
||||
let description: string;
|
||||
let detail: string;
|
||||
|
||||
if (typeof item === 'string') {
|
||||
label = item;
|
||||
} else {
|
||||
label = item.label;
|
||||
description = item.description;
|
||||
detail = item.detail;
|
||||
}
|
||||
pickItems.push({
|
||||
label,
|
||||
description,
|
||||
handle,
|
||||
detail
|
||||
});
|
||||
}
|
||||
|
||||
// handle selection changes
|
||||
if (options && typeof options.onDidSelectItem === 'function') {
|
||||
this._onDidSelectItem = (handle) => {
|
||||
options.onDidSelectItem(items[handle]);
|
||||
};
|
||||
}
|
||||
|
||||
// show items
|
||||
this._proxy.$setItems(pickItems);
|
||||
|
||||
return quickPickWidget.then(handle => {
|
||||
if (typeof handle === 'number') {
|
||||
return items[handle];
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
}, (err) => {
|
||||
this._proxy.$setError(err);
|
||||
|
||||
return TPromise.wrapError(err);
|
||||
});
|
||||
});
|
||||
return wireCancellationToken<Item>(token, promise, true);
|
||||
}
|
||||
|
||||
$onItemSelected(handle: number): void {
|
||||
if (this._onDidSelectItem) {
|
||||
this._onDidSelectItem(handle);
|
||||
}
|
||||
}
|
||||
|
||||
// ---- input
|
||||
|
||||
showInput(options?: InputBoxOptions, token: CancellationToken = CancellationToken.None): Thenable<string> {
|
||||
|
||||
// global validate fn used in callback below
|
||||
this._validateInput = options && options.validateInput;
|
||||
|
||||
const promise = this._proxy.$input(options, typeof this._validateInput === 'function');
|
||||
return wireCancellationToken(token, promise, true);
|
||||
}
|
||||
|
||||
$validateInput(input: string): TPromise<string> {
|
||||
if (this._validateInput) {
|
||||
return TPromise.as(this._validateInput(input));
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
367
src/vs/workbench/api/node/extHostSCM.ts
Normal file
367
src/vs/workbench/api/node/extHostSCM.ts
Normal file
@@ -0,0 +1,367 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { asWinJsPromise } from 'vs/base/common/async';
|
||||
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { ExtHostCommands, CommandsConverter } from 'vs/workbench/api/node/extHostCommands';
|
||||
import { MainContext, MainThreadSCMShape, SCMRawResource, IMainContext } from './extHost.protocol';
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
function getIconPath(decorations: vscode.SourceControlResourceThemableDecorations) {
|
||||
if (!decorations) {
|
||||
return undefined;
|
||||
} else if (typeof decorations.iconPath === 'string') {
|
||||
return URI.file(decorations.iconPath).toString();
|
||||
} else if (decorations.iconPath) {
|
||||
return `${decorations.iconPath}`;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export class ExtHostSCMInputBox {
|
||||
|
||||
private _value: string = '';
|
||||
|
||||
get value(): string {
|
||||
return this._value;
|
||||
}
|
||||
|
||||
set value(value: string) {
|
||||
this._proxy.$setInputBoxValue(this._sourceControlHandle, value);
|
||||
this.updateValue(value);
|
||||
}
|
||||
|
||||
private _onDidChange = new Emitter<string>();
|
||||
|
||||
get onDidChange(): Event<string> {
|
||||
return this._onDidChange.event;
|
||||
}
|
||||
|
||||
constructor(private _proxy: MainThreadSCMShape, private _sourceControlHandle: number) {
|
||||
// noop
|
||||
}
|
||||
|
||||
$onInputBoxValueChange(value: string): void {
|
||||
this.updateValue(value);
|
||||
}
|
||||
|
||||
private updateValue(value: string): void {
|
||||
this._value = value;
|
||||
this._onDidChange.fire(value);
|
||||
}
|
||||
}
|
||||
|
||||
class ExtHostSourceControlResourceGroup implements vscode.SourceControlResourceGroup {
|
||||
|
||||
private static _handlePool: number = 0;
|
||||
private _resourceHandlePool: number = 0;
|
||||
private _resourceStates: vscode.SourceControlResourceState[] = [];
|
||||
private _resourceStatesRollingDisposables: { (): void }[] = [];
|
||||
private _resourceStatesMap: Map<ResourceStateHandle, vscode.SourceControlResourceState> = new Map<ResourceStateHandle, vscode.SourceControlResourceState>();
|
||||
|
||||
get id(): string {
|
||||
return this._id;
|
||||
}
|
||||
|
||||
get label(): string {
|
||||
return this._label;
|
||||
}
|
||||
|
||||
set label(label: string) {
|
||||
this._label = label;
|
||||
this._proxy.$updateGroupLabel(this._sourceControlHandle, this._handle, label);
|
||||
}
|
||||
|
||||
private _hideWhenEmpty: boolean | undefined = undefined;
|
||||
|
||||
get hideWhenEmpty(): boolean | undefined {
|
||||
return this._hideWhenEmpty;
|
||||
}
|
||||
|
||||
set hideWhenEmpty(hideWhenEmpty: boolean | undefined) {
|
||||
this._hideWhenEmpty = hideWhenEmpty;
|
||||
this._proxy.$updateGroup(this._sourceControlHandle, this._handle, { hideWhenEmpty });
|
||||
}
|
||||
|
||||
get resourceStates(): vscode.SourceControlResourceState[] {
|
||||
return [...this._resourceStates];
|
||||
}
|
||||
|
||||
set resourceStates(resources: vscode.SourceControlResourceState[]) {
|
||||
this._resourceStates = [...resources];
|
||||
|
||||
const handles: number[] = [];
|
||||
const rawResources = resources.map(r => {
|
||||
const handle = this._resourceHandlePool++;
|
||||
this._resourceStatesMap.set(handle, r);
|
||||
handles.push(handle);
|
||||
|
||||
const sourceUri = r.resourceUri.toString();
|
||||
const command = this._commands.toInternal(r.command);
|
||||
const iconPath = getIconPath(r.decorations);
|
||||
const lightIconPath = r.decorations && getIconPath(r.decorations.light) || iconPath;
|
||||
const darkIconPath = r.decorations && getIconPath(r.decorations.dark) || iconPath;
|
||||
const icons: string[] = [];
|
||||
|
||||
if (lightIconPath || darkIconPath) {
|
||||
icons.push(lightIconPath);
|
||||
}
|
||||
|
||||
if (darkIconPath !== lightIconPath) {
|
||||
icons.push(darkIconPath);
|
||||
}
|
||||
|
||||
const tooltip = (r.decorations && r.decorations.tooltip) || '';
|
||||
const strikeThrough = r.decorations && !!r.decorations.strikeThrough;
|
||||
const faded = r.decorations && !!r.decorations.faded;
|
||||
|
||||
return [handle, sourceUri, command, icons, tooltip, strikeThrough, faded] as SCMRawResource;
|
||||
});
|
||||
|
||||
const disposable = () => handles.forEach(handle => this._resourceStatesMap.delete(handle));
|
||||
this._resourceStatesRollingDisposables.push(disposable);
|
||||
|
||||
while (this._resourceStatesRollingDisposables.length >= 10) {
|
||||
this._resourceStatesRollingDisposables.shift()();
|
||||
}
|
||||
|
||||
this._proxy.$updateGroupResourceStates(this._sourceControlHandle, this._handle, rawResources);
|
||||
}
|
||||
|
||||
private _handle: GroupHandle = ExtHostSourceControlResourceGroup._handlePool++;
|
||||
get handle(): GroupHandle {
|
||||
return this._handle;
|
||||
}
|
||||
|
||||
constructor(
|
||||
private _proxy: MainThreadSCMShape,
|
||||
private _commands: CommandsConverter,
|
||||
private _sourceControlHandle: number,
|
||||
private _id: string,
|
||||
private _label: string,
|
||||
) {
|
||||
this._proxy.$registerGroup(_sourceControlHandle, this._handle, _id, _label);
|
||||
}
|
||||
|
||||
getResourceState(handle: number): vscode.SourceControlResourceState | undefined {
|
||||
return this._resourceStatesMap.get(handle);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._proxy.$unregisterGroup(this._sourceControlHandle, this._handle);
|
||||
}
|
||||
}
|
||||
|
||||
class ExtHostSourceControl implements vscode.SourceControl {
|
||||
|
||||
private static _handlePool: number = 0;
|
||||
private _groups: Map<GroupHandle, ExtHostSourceControlResourceGroup> = new Map<GroupHandle, ExtHostSourceControlResourceGroup>();
|
||||
|
||||
get id(): string {
|
||||
return this._id;
|
||||
}
|
||||
|
||||
get label(): string {
|
||||
return this._label;
|
||||
}
|
||||
|
||||
private _inputBox: ExtHostSCMInputBox;
|
||||
get inputBox(): ExtHostSCMInputBox { return this._inputBox; }
|
||||
|
||||
private _count: number | undefined = undefined;
|
||||
|
||||
get count(): number | undefined {
|
||||
return this._count;
|
||||
}
|
||||
|
||||
set count(count: number | undefined) {
|
||||
this._count = count;
|
||||
this._proxy.$updateSourceControl(this._handle, { count });
|
||||
}
|
||||
|
||||
private _quickDiffProvider: vscode.QuickDiffProvider | undefined = undefined;
|
||||
|
||||
get quickDiffProvider(): vscode.QuickDiffProvider | undefined {
|
||||
return this._quickDiffProvider;
|
||||
}
|
||||
|
||||
set quickDiffProvider(quickDiffProvider: vscode.QuickDiffProvider | undefined) {
|
||||
this._quickDiffProvider = quickDiffProvider;
|
||||
this._proxy.$updateSourceControl(this._handle, { hasQuickDiffProvider: !!quickDiffProvider });
|
||||
}
|
||||
|
||||
private _commitTemplate: string | undefined = undefined;
|
||||
|
||||
get commitTemplate(): string | undefined {
|
||||
return this._commitTemplate;
|
||||
}
|
||||
|
||||
set commitTemplate(commitTemplate: string | undefined) {
|
||||
this._commitTemplate = commitTemplate;
|
||||
this._proxy.$updateSourceControl(this._handle, { commitTemplate });
|
||||
}
|
||||
|
||||
private _acceptInputCommand: vscode.Command | undefined = undefined;
|
||||
|
||||
get acceptInputCommand(): vscode.Command | undefined {
|
||||
return this._acceptInputCommand;
|
||||
}
|
||||
|
||||
set acceptInputCommand(acceptInputCommand: vscode.Command | undefined) {
|
||||
this._acceptInputCommand = acceptInputCommand;
|
||||
|
||||
const internal = this._commands.toInternal(acceptInputCommand);
|
||||
this._proxy.$updateSourceControl(this._handle, { acceptInputCommand: internal });
|
||||
}
|
||||
|
||||
private _statusBarCommands: vscode.Command[] | undefined = undefined;
|
||||
|
||||
get statusBarCommands(): vscode.Command[] | undefined {
|
||||
return this._statusBarCommands;
|
||||
}
|
||||
|
||||
set statusBarCommands(statusBarCommands: vscode.Command[] | undefined) {
|
||||
this._statusBarCommands = statusBarCommands;
|
||||
|
||||
const internal = (statusBarCommands || []).map(c => this._commands.toInternal(c));
|
||||
this._proxy.$updateSourceControl(this._handle, { statusBarCommands: internal });
|
||||
}
|
||||
|
||||
private _handle: number = ExtHostSourceControl._handlePool++;
|
||||
|
||||
constructor(
|
||||
private _proxy: MainThreadSCMShape,
|
||||
private _commands: CommandsConverter,
|
||||
private _id: string,
|
||||
private _label: string,
|
||||
) {
|
||||
this._inputBox = new ExtHostSCMInputBox(this._proxy, this._handle);
|
||||
this._proxy.$registerSourceControl(this._handle, _id, _label);
|
||||
}
|
||||
|
||||
createResourceGroup(id: string, label: string): ExtHostSourceControlResourceGroup {
|
||||
const group = new ExtHostSourceControlResourceGroup(this._proxy, this._commands, this._handle, id, label);
|
||||
this._groups.set(group.handle, group);
|
||||
return group;
|
||||
}
|
||||
|
||||
getResourceGroup(handle: GroupHandle): ExtHostSourceControlResourceGroup | undefined {
|
||||
return this._groups.get(handle);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._proxy.$unregisterSourceControl(this._handle);
|
||||
}
|
||||
}
|
||||
|
||||
type ProviderHandle = number;
|
||||
type GroupHandle = number;
|
||||
type ResourceStateHandle = number;
|
||||
|
||||
export class ExtHostSCM {
|
||||
|
||||
private static _handlePool: number = 0;
|
||||
|
||||
private _proxy: MainThreadSCMShape;
|
||||
private _sourceControls: Map<ProviderHandle, ExtHostSourceControl> = new Map<ProviderHandle, ExtHostSourceControl>();
|
||||
private _sourceControlsByExtension: Map<string, ExtHostSourceControl[]> = new Map<string, ExtHostSourceControl[]>();
|
||||
|
||||
private _onDidChangeActiveProvider = new Emitter<vscode.SourceControl>();
|
||||
get onDidChangeActiveProvider(): Event<vscode.SourceControl> { return this._onDidChangeActiveProvider.event; }
|
||||
|
||||
constructor(
|
||||
mainContext: IMainContext,
|
||||
private _commands: ExtHostCommands
|
||||
) {
|
||||
this._proxy = mainContext.get(MainContext.MainThreadSCM);
|
||||
|
||||
_commands.registerArgumentProcessor({
|
||||
processArgument: arg => {
|
||||
if (arg && arg.$mid === 3) {
|
||||
const sourceControl = this._sourceControls.get(arg.sourceControlHandle);
|
||||
|
||||
if (!sourceControl) {
|
||||
return arg;
|
||||
}
|
||||
|
||||
const group = sourceControl.getResourceGroup(arg.groupHandle);
|
||||
|
||||
if (!group) {
|
||||
return arg;
|
||||
}
|
||||
|
||||
return group.getResourceState(arg.handle);
|
||||
} else if (arg && arg.$mid === 4) {
|
||||
const sourceControl = this._sourceControls.get(arg.sourceControlHandle);
|
||||
|
||||
if (!sourceControl) {
|
||||
return arg;
|
||||
}
|
||||
|
||||
return sourceControl.getResourceGroup(arg.groupHandle);
|
||||
} else if (arg && arg.$mid === 5) {
|
||||
const sourceControl = this._sourceControls.get(arg.handle);
|
||||
|
||||
if (!sourceControl) {
|
||||
return arg;
|
||||
}
|
||||
|
||||
return sourceControl;
|
||||
}
|
||||
|
||||
return arg;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
createSourceControl(extension: IExtensionDescription, id: string, label: string): vscode.SourceControl {
|
||||
const handle = ExtHostSCM._handlePool++;
|
||||
const sourceControl = new ExtHostSourceControl(this._proxy, this._commands.converter, id, label);
|
||||
this._sourceControls.set(handle, sourceControl);
|
||||
|
||||
const sourceControls = this._sourceControlsByExtension.get(extension.id) || [];
|
||||
sourceControls.push(sourceControl);
|
||||
this._sourceControlsByExtension.set(extension.id, sourceControls);
|
||||
|
||||
return sourceControl;
|
||||
}
|
||||
|
||||
// Deprecated
|
||||
getLastInputBox(extension: IExtensionDescription): ExtHostSCMInputBox {
|
||||
const sourceControls = this._sourceControlsByExtension.get(extension.id);
|
||||
const sourceControl = sourceControls && sourceControls[sourceControls.length - 1];
|
||||
const inputBox = sourceControl && sourceControl.inputBox;
|
||||
|
||||
return inputBox;
|
||||
}
|
||||
|
||||
$provideOriginalResource(sourceControlHandle: number, uri: URI): TPromise<URI> {
|
||||
const sourceControl = this._sourceControls.get(sourceControlHandle);
|
||||
|
||||
if (!sourceControl || !sourceControl.quickDiffProvider) {
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
return asWinJsPromise(token => {
|
||||
const result = sourceControl.quickDiffProvider.provideOriginalResource(uri, token);
|
||||
return result && URI.parse(result.toString());
|
||||
});
|
||||
}
|
||||
|
||||
$onInputBoxValueChange(sourceControlHandle: number, value: string): TPromise<void> {
|
||||
const sourceControl = this._sourceControls.get(sourceControlHandle);
|
||||
|
||||
if (!sourceControl || !sourceControl.quickDiffProvider) {
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
sourceControl.inputBox.$onInputBoxValueChange(value);
|
||||
return TPromise.as(null);
|
||||
}
|
||||
}
|
||||
190
src/vs/workbench/api/node/extHostStatusBar.ts
Normal file
190
src/vs/workbench/api/node/extHostStatusBar.ts
Normal file
@@ -0,0 +1,190 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { StatusbarAlignment as MainThreadStatusBarAlignment } from 'vs/platform/statusbar/common/statusbar';
|
||||
import { StatusBarAlignment as ExtHostStatusBarAlignment, Disposable, ThemeColor } from './extHostTypes';
|
||||
import { StatusBarItem, StatusBarAlignment } from 'vscode';
|
||||
import { MainContext, MainThreadStatusBarShape, IMainContext } from './extHost.protocol';
|
||||
|
||||
export class ExtHostStatusBarEntry implements StatusBarItem {
|
||||
private static ID_GEN = 0;
|
||||
|
||||
private _id: number;
|
||||
private _alignment: number;
|
||||
private _priority: number;
|
||||
private _disposed: boolean;
|
||||
private _visible: boolean;
|
||||
|
||||
private _text: string;
|
||||
private _tooltip: string;
|
||||
private _color: string | ThemeColor;
|
||||
private _command: string;
|
||||
|
||||
private _timeoutHandle: number;
|
||||
private _proxy: MainThreadStatusBarShape;
|
||||
|
||||
private _extensionId: string;
|
||||
|
||||
constructor(proxy: MainThreadStatusBarShape, extensionId: string, alignment: ExtHostStatusBarAlignment = ExtHostStatusBarAlignment.Left, priority?: number) {
|
||||
this._id = ExtHostStatusBarEntry.ID_GEN++;
|
||||
this._proxy = proxy;
|
||||
this._alignment = alignment;
|
||||
this._priority = priority;
|
||||
this._extensionId = extensionId;
|
||||
}
|
||||
|
||||
public get id(): number {
|
||||
return this._id;
|
||||
}
|
||||
|
||||
public get alignment(): StatusBarAlignment {
|
||||
return this._alignment;
|
||||
}
|
||||
|
||||
public get priority(): number {
|
||||
return this._priority;
|
||||
}
|
||||
|
||||
public get text(): string {
|
||||
return this._text;
|
||||
}
|
||||
|
||||
public get tooltip(): string {
|
||||
return this._tooltip;
|
||||
}
|
||||
|
||||
public get color(): string | ThemeColor {
|
||||
return this._color;
|
||||
}
|
||||
|
||||
public get command(): string {
|
||||
return this._command;
|
||||
}
|
||||
|
||||
public set text(text: string) {
|
||||
this._text = text;
|
||||
this.update();
|
||||
}
|
||||
|
||||
public set tooltip(tooltip: string) {
|
||||
this._tooltip = tooltip;
|
||||
this.update();
|
||||
}
|
||||
|
||||
public set color(color: string | ThemeColor) {
|
||||
this._color = color;
|
||||
this.update();
|
||||
}
|
||||
|
||||
public set command(command: string) {
|
||||
this._command = command;
|
||||
this.update();
|
||||
}
|
||||
|
||||
public show(): void {
|
||||
this._visible = true;
|
||||
this.update();
|
||||
}
|
||||
|
||||
public hide(): void {
|
||||
clearTimeout(this._timeoutHandle);
|
||||
this._visible = false;
|
||||
this._proxy.$dispose(this.id);
|
||||
}
|
||||
|
||||
private update(): void {
|
||||
if (this._disposed || !this._visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
clearTimeout(this._timeoutHandle);
|
||||
|
||||
// Defer the update so that multiple changes to setters dont cause a redraw each
|
||||
this._timeoutHandle = setTimeout(() => {
|
||||
this._timeoutHandle = undefined;
|
||||
|
||||
// Set to status bar
|
||||
this._proxy.$setEntry(this.id, this._extensionId, this.text, this.tooltip, this.command, this.color,
|
||||
this._alignment === ExtHostStatusBarAlignment.Left ? MainThreadStatusBarAlignment.LEFT : MainThreadStatusBarAlignment.RIGHT,
|
||||
this._priority);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this.hide();
|
||||
this._disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
class StatusBarMessage {
|
||||
|
||||
private _item: StatusBarItem;
|
||||
private _messages: { message: string }[] = [];
|
||||
|
||||
constructor(statusBar: ExtHostStatusBar) {
|
||||
this._item = statusBar.createStatusBarEntry(void 0, ExtHostStatusBarAlignment.Left, Number.MIN_VALUE);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this._messages.length = 0;
|
||||
this._item.dispose();
|
||||
}
|
||||
|
||||
setMessage(message: string): Disposable {
|
||||
const data: { message: string } = { message }; // use object to not confuse equal strings
|
||||
this._messages.unshift(data);
|
||||
this._update();
|
||||
|
||||
return new Disposable(() => {
|
||||
let idx = this._messages.indexOf(data);
|
||||
if (idx >= 0) {
|
||||
this._messages.splice(idx, 1);
|
||||
this._update();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private _update() {
|
||||
if (this._messages.length > 0) {
|
||||
this._item.text = this._messages[0].message;
|
||||
this._item.show();
|
||||
} else {
|
||||
this._item.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class ExtHostStatusBar {
|
||||
|
||||
private _proxy: MainThreadStatusBarShape;
|
||||
private _statusMessage: StatusBarMessage;
|
||||
|
||||
constructor(mainContext: IMainContext) {
|
||||
this._proxy = mainContext.get(MainContext.MainThreadStatusBar);
|
||||
this._statusMessage = new StatusBarMessage(this);
|
||||
}
|
||||
|
||||
createStatusBarEntry(extensionId: string, alignment?: ExtHostStatusBarAlignment, priority?: number): StatusBarItem {
|
||||
return new ExtHostStatusBarEntry(this._proxy, extensionId, alignment, priority);
|
||||
}
|
||||
|
||||
setStatusBarMessage(text: string, timeoutOrThenable?: number | Thenable<any>): Disposable {
|
||||
|
||||
let d = this._statusMessage.setMessage(text);
|
||||
let handle: number;
|
||||
|
||||
if (typeof timeoutOrThenable === 'number') {
|
||||
handle = setTimeout(() => d.dispose(), timeoutOrThenable);
|
||||
} else if (typeof timeoutOrThenable !== 'undefined') {
|
||||
timeoutOrThenable.then(() => d.dispose(), () => d.dispose());
|
||||
}
|
||||
|
||||
return new Disposable(() => {
|
||||
d.dispose();
|
||||
clearTimeout(handle);
|
||||
});
|
||||
}
|
||||
}
|
||||
25
src/vs/workbench/api/node/extHostStorage.ts
Normal file
25
src/vs/workbench/api/node/extHostStorage.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { MainContext, MainThreadStorageShape, IMainContext } from './extHost.protocol';
|
||||
|
||||
export class ExtHostStorage {
|
||||
|
||||
private _proxy: MainThreadStorageShape;
|
||||
|
||||
constructor(mainContext: IMainContext) {
|
||||
this._proxy = mainContext.get(MainContext.MainThreadStorage);
|
||||
}
|
||||
|
||||
getValue<T>(shared: boolean, key: string, defaultValue?: T): TPromise<T> {
|
||||
return this._proxy.$getValue<T>(shared, key).then(value => value || defaultValue);
|
||||
}
|
||||
|
||||
setValue(shared: boolean, key: string, value: any): TPromise<void> {
|
||||
return this._proxy.$setValue(shared, key, value);
|
||||
}
|
||||
}
|
||||
439
src/vs/workbench/api/node/extHostTask.ts
Normal file
439
src/vs/workbench/api/node/extHostTask.ts
Normal file
@@ -0,0 +1,439 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import * as Objects from 'vs/base/common/objects';
|
||||
import { asWinJsPromise } from 'vs/base/common/async';
|
||||
|
||||
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import * as TaskSystem from 'vs/workbench/parts/tasks/common/tasks';
|
||||
|
||||
import { MainContext, MainThreadTaskShape, ExtHostTaskShape, IMainContext } from 'vs/workbench/api/node/extHost.protocol';
|
||||
|
||||
import * as types from 'vs/workbench/api/node/extHostTypes';
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
interface StringMap<V> {
|
||||
[key: string]: V;
|
||||
}
|
||||
|
||||
/*
|
||||
namespace ProblemPattern {
|
||||
export function from(value: vscode.ProblemPattern | vscode.MultiLineProblemPattern): Problems.ProblemPattern | Problems.MultiLineProblemPattern {
|
||||
if (value === void 0 || value === null) {
|
||||
return undefined;
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
let result: Problems.ProblemPattern[] = [];
|
||||
for (let pattern of value) {
|
||||
let converted = fromSingle(pattern);
|
||||
if (!converted) {
|
||||
return undefined;
|
||||
}
|
||||
result.push(converted);
|
||||
}
|
||||
return result;
|
||||
} else {
|
||||
return fromSingle(value);
|
||||
}
|
||||
}
|
||||
|
||||
function copyProperty(target: Problems.ProblemPattern, source: vscode.ProblemPattern, tk: keyof Problems.ProblemPattern) {
|
||||
let sk: keyof vscode.ProblemPattern = tk;
|
||||
let value = source[sk];
|
||||
if (typeof value === 'number') {
|
||||
target[tk] = value;
|
||||
}
|
||||
}
|
||||
|
||||
function getValue(value: number, defaultValue: number): number {
|
||||
if (value !== void 0 && value === null) {
|
||||
return value;
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
function fromSingle(problemPattern: vscode.ProblemPattern): Problems.ProblemPattern {
|
||||
if (problemPattern === void 0 || problemPattern === null || !(problemPattern.regexp instanceof RegExp)) {
|
||||
return undefined;
|
||||
}
|
||||
let result: Problems.ProblemPattern = {
|
||||
regexp: problemPattern.regexp
|
||||
};
|
||||
copyProperty(result, problemPattern, 'file');
|
||||
copyProperty(result, problemPattern, 'location');
|
||||
copyProperty(result, problemPattern, 'line');
|
||||
copyProperty(result, problemPattern, 'character');
|
||||
copyProperty(result, problemPattern, 'endLine');
|
||||
copyProperty(result, problemPattern, 'endCharacter');
|
||||
copyProperty(result, problemPattern, 'severity');
|
||||
copyProperty(result, problemPattern, 'code');
|
||||
copyProperty(result, problemPattern, 'message');
|
||||
if (problemPattern.loop === true || problemPattern.loop === false) {
|
||||
result.loop = problemPattern.loop;
|
||||
}
|
||||
if (result.location) {
|
||||
result.file = getValue(result.file, 1);
|
||||
result.message = getValue(result.message, 0);
|
||||
} else {
|
||||
result.file = getValue(result.file, 1);
|
||||
result.line = getValue(result.line, 2);
|
||||
result.character = getValue(result.character, 3);
|
||||
result.message = getValue(result.message, 0);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
namespace ApplyTo {
|
||||
export function from(value: vscode.ApplyToKind): Problems.ApplyToKind {
|
||||
if (value === void 0 || value === null) {
|
||||
return Problems.ApplyToKind.allDocuments;
|
||||
}
|
||||
switch (value) {
|
||||
case types.ApplyToKind.OpenDocuments:
|
||||
return Problems.ApplyToKind.openDocuments;
|
||||
case types.ApplyToKind.ClosedDocuments:
|
||||
return Problems.ApplyToKind.closedDocuments;
|
||||
}
|
||||
return Problems.ApplyToKind.allDocuments;
|
||||
}
|
||||
}
|
||||
|
||||
namespace FileLocation {
|
||||
export function from(value: vscode.FileLocationKind | string): { kind: Problems.FileLocationKind; prefix?: string } {
|
||||
if (value === void 0 || value === null) {
|
||||
return { kind: Problems.FileLocationKind.Auto };
|
||||
}
|
||||
if (typeof value === 'string') {
|
||||
return { kind: Problems.FileLocationKind.Relative, prefix: value };
|
||||
}
|
||||
switch (value) {
|
||||
case types.FileLocationKind.Absolute:
|
||||
return { kind: Problems.FileLocationKind.Absolute };
|
||||
case types.FileLocationKind.Relative:
|
||||
return { kind: Problems.FileLocationKind.Relative, prefix: '${workspaceRoot}' };
|
||||
}
|
||||
return { kind: Problems.FileLocationKind.Auto };
|
||||
}
|
||||
}
|
||||
|
||||
namespace WatchingPattern {
|
||||
export function from(value: RegExp | vscode.BackgroundPattern): Problems.WatchingPattern {
|
||||
if (value === void 0 || value === null) {
|
||||
return undefined;
|
||||
}
|
||||
if (value instanceof RegExp) {
|
||||
return { regexp: value };
|
||||
}
|
||||
if (!(value.regexp instanceof RegExp)) {
|
||||
return undefined;
|
||||
}
|
||||
let result: Problems.WatchingPattern = {
|
||||
regexp: value.regexp
|
||||
};
|
||||
if (typeof value.file === 'number') {
|
||||
result.file = value.file;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
namespace BackgroundMonitor {
|
||||
export function from(value: vscode.BackgroundMonitor): Problems.WatchingMatcher {
|
||||
if (value === void 0 || value === null) {
|
||||
return undefined;
|
||||
}
|
||||
let result: Problems.WatchingMatcher = {
|
||||
activeOnStart: !!value.activeOnStart,
|
||||
beginsPattern: WatchingPattern.from(value.beginsPattern),
|
||||
endsPattern: WatchingPattern.from(value.endsPattern)
|
||||
};
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
namespace ProblemMatcher {
|
||||
export function from(values: (string | vscode.ProblemMatcher)[]): (string | Problems.ProblemMatcher)[] {
|
||||
if (values === void 0 || values === null) {
|
||||
return undefined;
|
||||
}
|
||||
let result: (string | Problems.ProblemMatcher)[] = [];
|
||||
for (let value of values) {
|
||||
let converted = typeof value === 'string' ? value : fromSingle(value);
|
||||
if (converted) {
|
||||
result.push(converted);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function fromSingle(problemMatcher: vscode.ProblemMatcher): Problems.ProblemMatcher {
|
||||
if (problemMatcher === void 0 || problemMatcher === null) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let location = FileLocation.from(problemMatcher.fileLocation);
|
||||
let result: Problems.ProblemMatcher = {
|
||||
owner: typeof problemMatcher.owner === 'string' ? problemMatcher.owner : UUID.generateUuid(),
|
||||
applyTo: ApplyTo.from(problemMatcher.applyTo),
|
||||
fileLocation: location.kind,
|
||||
filePrefix: location.prefix,
|
||||
pattern: ProblemPattern.from(problemMatcher.pattern),
|
||||
severity: fromDiagnosticSeverity(problemMatcher.severity),
|
||||
};
|
||||
return result;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
namespace TaskRevealKind {
|
||||
export function from(value: vscode.TaskRevealKind): TaskSystem.RevealKind {
|
||||
if (value === void 0 || value === null) {
|
||||
return TaskSystem.RevealKind.Always;
|
||||
}
|
||||
switch (value) {
|
||||
case types.TaskRevealKind.Silent:
|
||||
return TaskSystem.RevealKind.Silent;
|
||||
case types.TaskRevealKind.Never:
|
||||
return TaskSystem.RevealKind.Never;
|
||||
}
|
||||
return TaskSystem.RevealKind.Always;
|
||||
}
|
||||
}
|
||||
|
||||
namespace TaskPanelKind {
|
||||
export function from(value: vscode.TaskPanelKind): TaskSystem.PanelKind {
|
||||
if (value === void 0 || value === null) {
|
||||
return TaskSystem.PanelKind.Shared;
|
||||
}
|
||||
switch (value) {
|
||||
case types.TaskPanelKind.Dedicated:
|
||||
return TaskSystem.PanelKind.Dedicated;
|
||||
case types.TaskPanelKind.New:
|
||||
return TaskSystem.PanelKind.New;
|
||||
default:
|
||||
return TaskSystem.PanelKind.Shared;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace PresentationOptions {
|
||||
export function from(value: vscode.TaskPresentationOptions): TaskSystem.PresentationOptions {
|
||||
if (value === void 0 || value === null) {
|
||||
return { reveal: TaskSystem.RevealKind.Always, echo: true, focus: false, panel: TaskSystem.PanelKind.Shared };
|
||||
}
|
||||
return {
|
||||
reveal: TaskRevealKind.from(value.reveal),
|
||||
echo: value.echo === void 0 ? true : !!value.echo,
|
||||
focus: !!value.focus,
|
||||
panel: TaskPanelKind.from(value.panel)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
namespace Strings {
|
||||
export function from(value: string[]): string[] {
|
||||
if (value === void 0 || value === null) {
|
||||
return undefined;
|
||||
}
|
||||
for (let element of value) {
|
||||
if (typeof element !== 'string') {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
namespace CommandOptions {
|
||||
function isShellConfiguration(value: any): value is { executable: string; shellArgs?: string[] } {
|
||||
return value && typeof value.executable === 'string';
|
||||
}
|
||||
export function from(value: vscode.ShellExecutionOptions | vscode.ProcessExecutionOptions): TaskSystem.CommandOptions {
|
||||
if (value === void 0 || value === null) {
|
||||
return undefined;
|
||||
}
|
||||
let result: TaskSystem.CommandOptions = {
|
||||
};
|
||||
if (typeof value.cwd === 'string') {
|
||||
result.cwd = value.cwd;
|
||||
}
|
||||
if (value.env) {
|
||||
result.env = Object.create(null);
|
||||
Object.keys(value.env).forEach(key => {
|
||||
let envValue = value.env[key];
|
||||
if (typeof envValue === 'string') {
|
||||
result.env[key] = envValue;
|
||||
}
|
||||
});
|
||||
}
|
||||
if (isShellConfiguration(value)) {
|
||||
result.shell = ShellConfiguration.from(value);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
namespace ShellConfiguration {
|
||||
export function from(value: { executable?: string, shellArgs?: string[] }): TaskSystem.ShellConfiguration {
|
||||
if (value === void 0 || value === null || !value.executable) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let result: TaskSystem.ShellConfiguration = {
|
||||
executable: value.executable,
|
||||
args: Strings.from(value.shellArgs)
|
||||
};
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
namespace Tasks {
|
||||
|
||||
export function from(tasks: vscode.Task[], extension: IExtensionDescription): TaskSystem.Task[] {
|
||||
if (tasks === void 0 || tasks === null) {
|
||||
return [];
|
||||
}
|
||||
let result: TaskSystem.Task[] = [];
|
||||
for (let task of tasks) {
|
||||
let converted = fromSingle(task, extension);
|
||||
if (converted) {
|
||||
result.push(converted);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function fromSingle(task: vscode.Task, extension: IExtensionDescription): TaskSystem.ContributedTask {
|
||||
if (typeof task.name !== 'string') {
|
||||
return undefined;
|
||||
}
|
||||
let command: TaskSystem.CommandConfiguration;
|
||||
let execution = task.execution;
|
||||
if (execution instanceof types.ProcessExecution) {
|
||||
command = getProcessCommand(execution);
|
||||
} else if (execution instanceof types.ShellExecution) {
|
||||
command = getShellCommand(execution);
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
if (command === void 0) {
|
||||
return undefined;
|
||||
}
|
||||
command.presentation = PresentationOptions.from(task.presentationOptions);
|
||||
let source = {
|
||||
kind: TaskSystem.TaskSourceKind.Extension,
|
||||
label: typeof task.source === 'string' ? task.source : extension.name,
|
||||
extension: extension.id
|
||||
};
|
||||
let label = nls.localize('task.label', '{0}: {1}', source.label, task.name);
|
||||
let key = (task as types.Task).definitionKey;
|
||||
let kind = (task as types.Task).definition;
|
||||
let id = `${extension.id}.${key}`;
|
||||
let taskKind: TaskSystem.TaskIdentifier = {
|
||||
_key: key,
|
||||
type: kind.type
|
||||
};
|
||||
Objects.assign(taskKind, kind);
|
||||
let result: TaskSystem.ContributedTask = {
|
||||
_id: id, // uuidMap.getUUID(identifier),
|
||||
_source: source,
|
||||
_label: label,
|
||||
type: kind.type,
|
||||
defines: taskKind,
|
||||
name: task.name,
|
||||
identifier: label,
|
||||
group: task.group ? (task.group as types.TaskGroup).id : undefined,
|
||||
command: command,
|
||||
isBackground: !!task.isBackground,
|
||||
problemMatchers: task.problemMatchers.slice(),
|
||||
hasDefinedMatchers: (task as types.Task).hasDefinedMatchers
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
function getProcessCommand(value: vscode.ProcessExecution): TaskSystem.CommandConfiguration {
|
||||
if (typeof value.process !== 'string') {
|
||||
return undefined;
|
||||
}
|
||||
let result: TaskSystem.CommandConfiguration = {
|
||||
name: value.process,
|
||||
args: Strings.from(value.args),
|
||||
runtime: TaskSystem.RuntimeType.Process,
|
||||
suppressTaskName: true,
|
||||
presentation: undefined
|
||||
};
|
||||
if (value.options) {
|
||||
result.options = CommandOptions.from(value.options);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function getShellCommand(value: vscode.ShellExecution): TaskSystem.CommandConfiguration {
|
||||
if (typeof value.commandLine !== 'string') {
|
||||
return undefined;
|
||||
}
|
||||
let result: TaskSystem.CommandConfiguration = {
|
||||
name: value.commandLine,
|
||||
runtime: TaskSystem.RuntimeType.Shell,
|
||||
presentation: undefined
|
||||
};
|
||||
if (value.options) {
|
||||
result.options = CommandOptions.from(value.options);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
interface HandlerData {
|
||||
provider: vscode.TaskProvider;
|
||||
extension: IExtensionDescription;
|
||||
}
|
||||
|
||||
export class ExtHostTask implements ExtHostTaskShape {
|
||||
|
||||
private _proxy: MainThreadTaskShape;
|
||||
private _handleCounter: number;
|
||||
private _handlers: Map<number, HandlerData>;
|
||||
|
||||
constructor(mainContext: IMainContext) {
|
||||
this._proxy = mainContext.get(MainContext.MainThreadTask);
|
||||
this._handleCounter = 0;
|
||||
this._handlers = new Map<number, HandlerData>();
|
||||
};
|
||||
|
||||
public registerTaskProvider(extension: IExtensionDescription, provider: vscode.TaskProvider): vscode.Disposable {
|
||||
if (!provider) {
|
||||
return new types.Disposable(() => { });
|
||||
}
|
||||
let handle = this.nextHandle();
|
||||
this._handlers.set(handle, { provider, extension });
|
||||
this._proxy.$registerTaskProvider(handle);
|
||||
return new types.Disposable(() => {
|
||||
this._handlers.delete(handle);
|
||||
this._proxy.$unregisterTaskProvider(handle);
|
||||
});
|
||||
}
|
||||
|
||||
public $provideTasks(handle: number): TPromise<TaskSystem.TaskSet> {
|
||||
let handler = this._handlers.get(handle);
|
||||
if (!handler) {
|
||||
return TPromise.wrapError<TaskSystem.TaskSet>(new Error('no handler found'));
|
||||
}
|
||||
return asWinJsPromise(token => handler.provider.provideTasks(token)).then(value => {
|
||||
return {
|
||||
tasks: Tasks.from(value, handler.extension),
|
||||
extension: handler.extension
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private nextHandle(): number {
|
||||
return this._handleCounter++;
|
||||
}
|
||||
}
|
||||
172
src/vs/workbench/api/node/extHostTerminalService.ts
Normal file
172
src/vs/workbench/api/node/extHostTerminalService.ts
Normal file
@@ -0,0 +1,172 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import vscode = require('vscode');
|
||||
import { TPromise, TValueCallback } from 'vs/base/common/winjs.base';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { ExtHostTerminalServiceShape, MainContext, MainThreadTerminalServiceShape, IMainContext } from './extHost.protocol';
|
||||
|
||||
export class ExtHostTerminal implements vscode.Terminal {
|
||||
|
||||
private _name: string;
|
||||
private _id: number;
|
||||
private _proxy: MainThreadTerminalServiceShape;
|
||||
private _disposed: boolean;
|
||||
private _queuedRequests: ApiRequest[];
|
||||
private _pidPromise: TPromise<number>;
|
||||
private _pidPromiseComplete: TValueCallback<number>;
|
||||
|
||||
constructor(
|
||||
proxy: MainThreadTerminalServiceShape,
|
||||
name?: string,
|
||||
shellPath?: string,
|
||||
shellArgs?: string[],
|
||||
waitOnExit?: boolean
|
||||
) {
|
||||
this._name = name;
|
||||
this._queuedRequests = [];
|
||||
this._proxy = proxy;
|
||||
this._pidPromise = new TPromise<number>(c => {
|
||||
this._pidPromiseComplete = c;
|
||||
});
|
||||
this._proxy.$createTerminal(name, shellPath, shellArgs, waitOnExit).then((id) => {
|
||||
this._id = id;
|
||||
this._queuedRequests.forEach((r) => {
|
||||
r.run(this._proxy, this._id);
|
||||
});
|
||||
this._queuedRequests = [];
|
||||
});
|
||||
}
|
||||
|
||||
public get name(): string {
|
||||
this._checkDisposed();
|
||||
return this._name;
|
||||
}
|
||||
|
||||
public get processId(): Thenable<number> {
|
||||
this._checkDisposed();
|
||||
return this._pidPromise;
|
||||
}
|
||||
|
||||
public sendText(text: string, addNewLine: boolean = true): void {
|
||||
this._checkDisposed();
|
||||
this._queueApiRequest(this._proxy.$sendText, [text, addNewLine]);
|
||||
}
|
||||
|
||||
public show(preserveFocus: boolean): void {
|
||||
this._checkDisposed();
|
||||
this._queueApiRequest(this._proxy.$show, [preserveFocus]);
|
||||
}
|
||||
|
||||
public hide(): void {
|
||||
this._checkDisposed();
|
||||
this._queueApiRequest(this._proxy.$hide, []);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
if (!this._disposed) {
|
||||
this._disposed = true;
|
||||
this._queueApiRequest(this._proxy.$dispose, []);
|
||||
}
|
||||
}
|
||||
|
||||
public _setProcessId(processId: number): void {
|
||||
this._pidPromiseComplete(processId);
|
||||
this._pidPromiseComplete = null;
|
||||
}
|
||||
|
||||
private _queueApiRequest(callback: (...args: any[]) => void, args: any[]) {
|
||||
let request: ApiRequest = new ApiRequest(callback, args);
|
||||
if (!this._id) {
|
||||
this._queuedRequests.push(request);
|
||||
return;
|
||||
}
|
||||
request.run(this._proxy, this._id);
|
||||
}
|
||||
|
||||
private _checkDisposed() {
|
||||
if (this._disposed) {
|
||||
throw new Error('Terminal has already been disposed');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class ExtHostTerminalService implements ExtHostTerminalServiceShape {
|
||||
|
||||
private _onDidCloseTerminal: Emitter<vscode.Terminal>;
|
||||
private _proxy: MainThreadTerminalServiceShape;
|
||||
private _terminals: ExtHostTerminal[];
|
||||
|
||||
constructor(mainContext: IMainContext) {
|
||||
this._onDidCloseTerminal = new Emitter<vscode.Terminal>();
|
||||
this._proxy = mainContext.get(MainContext.MainThreadTerminalService);
|
||||
this._terminals = [];
|
||||
}
|
||||
|
||||
public createTerminal(name?: string, shellPath?: string, shellArgs?: string[]): vscode.Terminal {
|
||||
let terminal = new ExtHostTerminal(this._proxy, name, shellPath, shellArgs);
|
||||
this._terminals.push(terminal);
|
||||
return terminal;
|
||||
}
|
||||
|
||||
public createTerminalFromOptions(options: vscode.TerminalOptions): vscode.Terminal {
|
||||
let terminal = new ExtHostTerminal(this._proxy, options.name, options.shellPath, options.shellArgs/*, options.waitOnExit*/);
|
||||
this._terminals.push(terminal);
|
||||
return terminal;
|
||||
}
|
||||
|
||||
public get onDidCloseTerminal(): Event<vscode.Terminal> {
|
||||
return this._onDidCloseTerminal && this._onDidCloseTerminal.event;
|
||||
}
|
||||
|
||||
public $acceptTerminalClosed(id: number): void {
|
||||
let index = this._getTerminalIndexById(id);
|
||||
if (index === null) {
|
||||
// The terminal was not created by the terminal API, ignore it
|
||||
return;
|
||||
}
|
||||
let terminal = this._terminals.splice(index, 1)[0];
|
||||
this._onDidCloseTerminal.fire(terminal);
|
||||
}
|
||||
|
||||
public $acceptTerminalProcessId(id: number, processId: number): void {
|
||||
let terminal = this._getTerminalById(id);
|
||||
terminal._setProcessId(processId);
|
||||
}
|
||||
|
||||
private _getTerminalById(id: number): ExtHostTerminal {
|
||||
let index = this._getTerminalIndexById(id);
|
||||
return index !== null ? this._terminals[index] : null;
|
||||
}
|
||||
|
||||
private _getTerminalIndexById(id: number): number {
|
||||
let index: number = null;
|
||||
this._terminals.some((terminal, i) => {
|
||||
let thisId = (<any>terminal)._id;
|
||||
if (thisId === id) {
|
||||
index = i;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
return index;
|
||||
}
|
||||
}
|
||||
|
||||
class ApiRequest {
|
||||
|
||||
private _callback: (...args: any[]) => void;
|
||||
private _args: any[];
|
||||
|
||||
constructor(callback: (...args: any[]) => void, args: any[]) {
|
||||
this._callback = callback;
|
||||
this._args = args;
|
||||
}
|
||||
|
||||
public run(proxy: MainThreadTerminalServiceShape, id: number) {
|
||||
this._callback.apply(proxy, [id].concat(this._args));
|
||||
}
|
||||
}
|
||||
623
src/vs/workbench/api/node/extHostTextEditor.ts
Normal file
623
src/vs/workbench/api/node/extHostTextEditor.ts
Normal file
@@ -0,0 +1,623 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
|
||||
import { ok } from 'vs/base/common/assert';
|
||||
import { readonly, illegalArgument, V8CallSite } from 'vs/base/common/errors';
|
||||
import { IdGenerator } from 'vs/base/common/idGenerator';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { ExtHostDocumentData } from 'vs/workbench/api/node/extHostDocumentData';
|
||||
import { Selection, Range, Position, EndOfLine, TextEditorRevealType, TextEditorLineNumbersStyle, SnippetString } from './extHostTypes';
|
||||
import { ISingleEditOperation } from 'vs/editor/common/editorCommon';
|
||||
import * as TypeConverters from './extHostTypeConverters';
|
||||
import { MainThreadEditorsShape, MainThreadTelemetryShape, IResolvedTextEditorConfiguration, ITextEditorConfigurationUpdate } from './extHost.protocol';
|
||||
import * as vscode from 'vscode';
|
||||
import { TextEditorCursorStyle } from 'vs/editor/common/config/editorOptions';
|
||||
import { IRange } from 'vs/editor/common/core/range';
|
||||
import { containsCommandLink } from 'vs/base/common/htmlContent';
|
||||
import { ExtHostExtensionService } from 'vs/workbench/api/node/extHostExtensionService';
|
||||
|
||||
export class TextEditorDecorationType implements vscode.TextEditorDecorationType {
|
||||
|
||||
private static _Keys = new IdGenerator('TextEditorDecorationType');
|
||||
|
||||
private _proxy: MainThreadEditorsShape;
|
||||
public key: string;
|
||||
|
||||
constructor(proxy: MainThreadEditorsShape, options: vscode.DecorationRenderOptions) {
|
||||
this.key = TextEditorDecorationType._Keys.nextId();
|
||||
this._proxy = proxy;
|
||||
this._proxy.$registerTextEditorDecorationType(this.key, <any>/* URI vs Uri */ options);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._proxy.$removeTextEditorDecorationType(this.key);
|
||||
}
|
||||
}
|
||||
|
||||
export interface ITextEditOperation {
|
||||
range: vscode.Range;
|
||||
text: string;
|
||||
forceMoveMarkers: boolean;
|
||||
}
|
||||
|
||||
export interface IEditData {
|
||||
documentVersionId: number;
|
||||
edits: ITextEditOperation[];
|
||||
setEndOfLine: EndOfLine;
|
||||
undoStopBefore: boolean;
|
||||
undoStopAfter: boolean;
|
||||
}
|
||||
|
||||
export class TextEditorEdit {
|
||||
|
||||
private readonly _document: vscode.TextDocument;
|
||||
private readonly _documentVersionId: number;
|
||||
private _collectedEdits: ITextEditOperation[];
|
||||
private _setEndOfLine: EndOfLine;
|
||||
private readonly _undoStopBefore: boolean;
|
||||
private readonly _undoStopAfter: boolean;
|
||||
|
||||
constructor(document: vscode.TextDocument, options: { undoStopBefore: boolean; undoStopAfter: boolean; }) {
|
||||
this._document = document;
|
||||
this._documentVersionId = document.version;
|
||||
this._collectedEdits = [];
|
||||
this._setEndOfLine = 0;
|
||||
this._undoStopBefore = options.undoStopBefore;
|
||||
this._undoStopAfter = options.undoStopAfter;
|
||||
}
|
||||
|
||||
finalize(): IEditData {
|
||||
return {
|
||||
documentVersionId: this._documentVersionId,
|
||||
edits: this._collectedEdits,
|
||||
setEndOfLine: this._setEndOfLine,
|
||||
undoStopBefore: this._undoStopBefore,
|
||||
undoStopAfter: this._undoStopAfter
|
||||
};
|
||||
}
|
||||
|
||||
replace(location: Position | Range | Selection, value: string): void {
|
||||
let range: Range = null;
|
||||
|
||||
if (location instanceof Position) {
|
||||
range = new Range(location, location);
|
||||
} else if (location instanceof Range) {
|
||||
range = location;
|
||||
} else {
|
||||
throw new Error('Unrecognized location');
|
||||
}
|
||||
|
||||
this._pushEdit(range, value, false);
|
||||
}
|
||||
|
||||
insert(location: Position, value: string): void {
|
||||
this._pushEdit(new Range(location, location), value, true);
|
||||
}
|
||||
|
||||
delete(location: Range | Selection): void {
|
||||
let range: Range = null;
|
||||
|
||||
if (location instanceof Range) {
|
||||
range = location;
|
||||
} else {
|
||||
throw new Error('Unrecognized location');
|
||||
}
|
||||
|
||||
this._pushEdit(range, null, true);
|
||||
}
|
||||
|
||||
private _pushEdit(range: Range, text: string, forceMoveMarkers: boolean): void {
|
||||
let validRange = this._document.validateRange(range);
|
||||
this._collectedEdits.push({
|
||||
range: validRange,
|
||||
text: text,
|
||||
forceMoveMarkers: forceMoveMarkers
|
||||
});
|
||||
}
|
||||
|
||||
setEndOfLine(endOfLine: EndOfLine): void {
|
||||
if (endOfLine !== EndOfLine.LF && endOfLine !== EndOfLine.CRLF) {
|
||||
throw illegalArgument('endOfLine');
|
||||
}
|
||||
|
||||
this._setEndOfLine = endOfLine;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function deprecated(name: string, message: string = 'Refer to the documentation for further details.') {
|
||||
return (target: Object, key: string, descriptor: TypedPropertyDescriptor<any>) => {
|
||||
const originalMethod = descriptor.value;
|
||||
descriptor.value = function (...args: any[]) {
|
||||
console.warn(`[Deprecation Warning] method '${name}' is deprecated and should no longer be used. ${message}`);
|
||||
return originalMethod.apply(this, args);
|
||||
};
|
||||
|
||||
return descriptor;
|
||||
};
|
||||
}
|
||||
|
||||
export class ExtHostTextEditorOptions implements vscode.TextEditorOptions {
|
||||
|
||||
private _proxy: MainThreadEditorsShape;
|
||||
private _id: string;
|
||||
|
||||
private _tabSize: number;
|
||||
private _insertSpaces: boolean;
|
||||
private _cursorStyle: TextEditorCursorStyle;
|
||||
private _lineNumbers: TextEditorLineNumbersStyle;
|
||||
|
||||
constructor(proxy: MainThreadEditorsShape, id: string, source: IResolvedTextEditorConfiguration) {
|
||||
this._proxy = proxy;
|
||||
this._id = id;
|
||||
this._accept(source);
|
||||
}
|
||||
|
||||
public _accept(source: IResolvedTextEditorConfiguration): void {
|
||||
this._tabSize = source.tabSize;
|
||||
this._insertSpaces = source.insertSpaces;
|
||||
this._cursorStyle = source.cursorStyle;
|
||||
this._lineNumbers = source.lineNumbers;
|
||||
}
|
||||
|
||||
public get tabSize(): number | string {
|
||||
return this._tabSize;
|
||||
}
|
||||
|
||||
private _validateTabSize(value: number | string): number | 'auto' | null {
|
||||
if (value === 'auto') {
|
||||
return 'auto';
|
||||
}
|
||||
if (typeof value === 'number') {
|
||||
let r = Math.floor(value);
|
||||
return (r > 0 ? r : null);
|
||||
}
|
||||
if (typeof value === 'string') {
|
||||
let r = parseInt(value, 10);
|
||||
if (isNaN(r)) {
|
||||
return null;
|
||||
}
|
||||
return (r > 0 ? r : null);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public set tabSize(value: number | string) {
|
||||
let tabSize = this._validateTabSize(value);
|
||||
if (tabSize === null) {
|
||||
// ignore invalid call
|
||||
return;
|
||||
}
|
||||
if (typeof tabSize === 'number') {
|
||||
if (this._tabSize === tabSize) {
|
||||
// nothing to do
|
||||
return;
|
||||
}
|
||||
// reflect the new tabSize value immediately
|
||||
this._tabSize = tabSize;
|
||||
}
|
||||
warnOnError(this._proxy.$trySetOptions(this._id, {
|
||||
tabSize: tabSize
|
||||
}));
|
||||
}
|
||||
|
||||
public get insertSpaces(): boolean | string {
|
||||
return this._insertSpaces;
|
||||
}
|
||||
|
||||
private _validateInsertSpaces(value: boolean | string): boolean | 'auto' {
|
||||
if (value === 'auto') {
|
||||
return 'auto';
|
||||
}
|
||||
return (value === 'false' ? false : Boolean(value));
|
||||
}
|
||||
|
||||
public set insertSpaces(value: boolean | string) {
|
||||
let insertSpaces = this._validateInsertSpaces(value);
|
||||
if (typeof insertSpaces === 'boolean') {
|
||||
if (this._insertSpaces === insertSpaces) {
|
||||
// nothing to do
|
||||
return;
|
||||
}
|
||||
// reflect the new insertSpaces value immediately
|
||||
this._insertSpaces = insertSpaces;
|
||||
}
|
||||
warnOnError(this._proxy.$trySetOptions(this._id, {
|
||||
insertSpaces: insertSpaces
|
||||
}));
|
||||
}
|
||||
|
||||
public get cursorStyle(): TextEditorCursorStyle {
|
||||
return this._cursorStyle;
|
||||
}
|
||||
|
||||
public set cursorStyle(value: TextEditorCursorStyle) {
|
||||
if (this._cursorStyle === value) {
|
||||
// nothing to do
|
||||
return;
|
||||
}
|
||||
this._cursorStyle = value;
|
||||
warnOnError(this._proxy.$trySetOptions(this._id, {
|
||||
cursorStyle: value
|
||||
}));
|
||||
}
|
||||
|
||||
public get lineNumbers(): TextEditorLineNumbersStyle {
|
||||
return this._lineNumbers;
|
||||
}
|
||||
|
||||
public set lineNumbers(value: TextEditorLineNumbersStyle) {
|
||||
if (this._lineNumbers === value) {
|
||||
// nothing to do
|
||||
return;
|
||||
}
|
||||
this._lineNumbers = value;
|
||||
warnOnError(this._proxy.$trySetOptions(this._id, {
|
||||
lineNumbers: value
|
||||
}));
|
||||
}
|
||||
|
||||
public assign(newOptions: vscode.TextEditorOptions) {
|
||||
let bulkConfigurationUpdate: ITextEditorConfigurationUpdate = {};
|
||||
let hasUpdate = false;
|
||||
|
||||
if (typeof newOptions.tabSize !== 'undefined') {
|
||||
let tabSize = this._validateTabSize(newOptions.tabSize);
|
||||
if (tabSize === 'auto') {
|
||||
hasUpdate = true;
|
||||
bulkConfigurationUpdate.tabSize = tabSize;
|
||||
} else if (typeof tabSize === 'number' && this._tabSize !== tabSize) {
|
||||
// reflect the new tabSize value immediately
|
||||
this._tabSize = tabSize;
|
||||
hasUpdate = true;
|
||||
bulkConfigurationUpdate.tabSize = tabSize;
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof newOptions.insertSpaces !== 'undefined') {
|
||||
let insertSpaces = this._validateInsertSpaces(newOptions.insertSpaces);
|
||||
if (insertSpaces === 'auto') {
|
||||
hasUpdate = true;
|
||||
bulkConfigurationUpdate.insertSpaces = insertSpaces;
|
||||
} else if (this._insertSpaces !== insertSpaces) {
|
||||
// reflect the new insertSpaces value immediately
|
||||
this._insertSpaces = insertSpaces;
|
||||
hasUpdate = true;
|
||||
bulkConfigurationUpdate.insertSpaces = insertSpaces;
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof newOptions.cursorStyle !== 'undefined') {
|
||||
if (this._cursorStyle !== newOptions.cursorStyle) {
|
||||
this._cursorStyle = newOptions.cursorStyle;
|
||||
hasUpdate = true;
|
||||
bulkConfigurationUpdate.cursorStyle = newOptions.cursorStyle;
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof newOptions.lineNumbers !== 'undefined') {
|
||||
if (this._lineNumbers !== newOptions.lineNumbers) {
|
||||
this._lineNumbers = newOptions.lineNumbers;
|
||||
hasUpdate = true;
|
||||
bulkConfigurationUpdate.lineNumbers = newOptions.lineNumbers;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasUpdate) {
|
||||
warnOnError(this._proxy.$trySetOptions(this._id, bulkConfigurationUpdate));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class ExtHostTextEditor implements vscode.TextEditor {
|
||||
|
||||
private readonly _proxy: MainThreadEditorsShape;
|
||||
private readonly _id: string;
|
||||
private readonly _documentData: ExtHostDocumentData;
|
||||
|
||||
private _selections: Selection[];
|
||||
private _options: ExtHostTextEditorOptions;
|
||||
private _viewColumn: vscode.ViewColumn;
|
||||
private _disposed: boolean = false;
|
||||
|
||||
get id(): string { return this._id; }
|
||||
|
||||
constructor(proxy: MainThreadEditorsShape, id: string, document: ExtHostDocumentData, selections: Selection[], options: IResolvedTextEditorConfiguration, viewColumn: vscode.ViewColumn) {
|
||||
this._proxy = proxy;
|
||||
this._id = id;
|
||||
this._documentData = document;
|
||||
this._selections = selections;
|
||||
this._options = new ExtHostTextEditorOptions(this._proxy, this._id, options);
|
||||
this._viewColumn = viewColumn;
|
||||
}
|
||||
|
||||
dispose() {
|
||||
ok(!this._disposed);
|
||||
this._disposed = true;
|
||||
}
|
||||
|
||||
@deprecated('TextEditor.show') show(column: vscode.ViewColumn) {
|
||||
this._proxy.$tryShowEditor(this._id, TypeConverters.fromViewColumn(column));
|
||||
}
|
||||
|
||||
@deprecated('TextEditor.hide') hide() {
|
||||
this._proxy.$tryHideEditor(this._id);
|
||||
}
|
||||
|
||||
// ---- the document
|
||||
|
||||
get document(): vscode.TextDocument {
|
||||
return this._documentData.document;
|
||||
}
|
||||
|
||||
set document(value) {
|
||||
throw readonly('document');
|
||||
}
|
||||
|
||||
// ---- options
|
||||
|
||||
get options(): vscode.TextEditorOptions {
|
||||
return this._options;
|
||||
}
|
||||
|
||||
set options(value: vscode.TextEditorOptions) {
|
||||
if (!this._disposed) {
|
||||
this._options.assign(value);
|
||||
}
|
||||
}
|
||||
|
||||
_acceptOptions(options: IResolvedTextEditorConfiguration): void {
|
||||
ok(!this._disposed);
|
||||
this._options._accept(options);
|
||||
}
|
||||
|
||||
// ---- view column
|
||||
|
||||
get viewColumn(): vscode.ViewColumn {
|
||||
return this._viewColumn;
|
||||
}
|
||||
|
||||
set viewColumn(value) {
|
||||
throw readonly('viewColumn');
|
||||
}
|
||||
|
||||
_acceptViewColumn(value: vscode.ViewColumn) {
|
||||
ok(!this._disposed);
|
||||
this._viewColumn = value;
|
||||
}
|
||||
|
||||
// ---- selections
|
||||
|
||||
get selection(): Selection {
|
||||
return this._selections && this._selections[0];
|
||||
}
|
||||
|
||||
set selection(value: Selection) {
|
||||
if (!(value instanceof Selection)) {
|
||||
throw illegalArgument('selection');
|
||||
}
|
||||
this._selections = [value];
|
||||
this._trySetSelection();
|
||||
}
|
||||
|
||||
get selections(): Selection[] {
|
||||
return this._selections;
|
||||
}
|
||||
|
||||
set selections(value: Selection[]) {
|
||||
if (!Array.isArray(value) || value.some(a => !(a instanceof Selection))) {
|
||||
throw illegalArgument('selections');
|
||||
}
|
||||
this._selections = value;
|
||||
this._trySetSelection();
|
||||
}
|
||||
|
||||
setDecorations(decorationType: vscode.TextEditorDecorationType, ranges: Range[] | vscode.DecorationOptions[]): void {
|
||||
this._runOnProxy(
|
||||
() => this._proxy.$trySetDecorations(
|
||||
this._id,
|
||||
decorationType.key,
|
||||
TypeConverters.fromRangeOrRangeWithMessage(ranges)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
revealRange(range: Range, revealType: vscode.TextEditorRevealType): void {
|
||||
this._runOnProxy(
|
||||
() => this._proxy.$tryRevealRange(
|
||||
this._id,
|
||||
TypeConverters.fromRange(range),
|
||||
(revealType || TextEditorRevealType.Default)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private _trySetSelection(): TPromise<vscode.TextEditor> {
|
||||
let selection = this._selections.map(TypeConverters.fromSelection);
|
||||
return this._runOnProxy(() => this._proxy.$trySetSelections(this._id, selection));
|
||||
}
|
||||
|
||||
_acceptSelections(selections: Selection[]): void {
|
||||
ok(!this._disposed);
|
||||
this._selections = selections;
|
||||
}
|
||||
|
||||
// ---- editing
|
||||
|
||||
edit(callback: (edit: TextEditorEdit) => void, options: { undoStopBefore: boolean; undoStopAfter: boolean; } = { undoStopBefore: true, undoStopAfter: true }): Thenable<boolean> {
|
||||
if (this._disposed) {
|
||||
return TPromise.wrapError<boolean>(new Error('TextEditor#edit not possible on closed editors'));
|
||||
}
|
||||
let edit = new TextEditorEdit(this._documentData.document, options);
|
||||
callback(edit);
|
||||
return this._applyEdit(edit);
|
||||
}
|
||||
|
||||
private _applyEdit(editBuilder: TextEditorEdit): TPromise<boolean> {
|
||||
let editData = editBuilder.finalize();
|
||||
|
||||
// check that the edits are not overlapping (i.e. illegal)
|
||||
let editRanges = editData.edits.map(edit => edit.range);
|
||||
|
||||
// sort ascending (by end and then by start)
|
||||
editRanges.sort((a, b) => {
|
||||
if (a.end.line === b.end.line) {
|
||||
if (a.end.character === b.end.character) {
|
||||
if (a.start.line === b.start.line) {
|
||||
return a.start.character - b.start.character;
|
||||
}
|
||||
return a.start.line - b.start.line;
|
||||
}
|
||||
return a.end.character - b.end.character;
|
||||
}
|
||||
return a.end.line - b.end.line;
|
||||
});
|
||||
|
||||
// check that no edits are overlapping
|
||||
for (let i = 0, count = editRanges.length - 1; i < count; i++) {
|
||||
const rangeEnd = editRanges[i].end;
|
||||
const nextRangeStart = editRanges[i + 1].start;
|
||||
|
||||
if (nextRangeStart.isBefore(rangeEnd)) {
|
||||
// overlapping ranges
|
||||
return TPromise.wrapError<boolean>(
|
||||
new Error('Overlapping ranges are not allowed!')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// prepare data for serialization
|
||||
let edits: ISingleEditOperation[] = editData.edits.map((edit) => {
|
||||
return {
|
||||
range: TypeConverters.fromRange(edit.range),
|
||||
text: edit.text,
|
||||
forceMoveMarkers: edit.forceMoveMarkers
|
||||
};
|
||||
});
|
||||
|
||||
return this._proxy.$tryApplyEdits(this._id, editData.documentVersionId, edits, {
|
||||
setEndOfLine: editData.setEndOfLine,
|
||||
undoStopBefore: editData.undoStopBefore,
|
||||
undoStopAfter: editData.undoStopAfter
|
||||
});
|
||||
}
|
||||
|
||||
insertSnippet(snippet: SnippetString, where?: Position | Position[] | Range | Range[], options: { undoStopBefore: boolean; undoStopAfter: boolean; } = { undoStopBefore: true, undoStopAfter: true }): Thenable<boolean> {
|
||||
if (this._disposed) {
|
||||
return TPromise.wrapError<boolean>(new Error('TextEditor#insertSnippet not possible on closed editors'));
|
||||
}
|
||||
let ranges: IRange[];
|
||||
|
||||
if (!where || (Array.isArray(where) && where.length === 0)) {
|
||||
ranges = this._selections.map(TypeConverters.fromRange);
|
||||
|
||||
} else if (where instanceof Position) {
|
||||
const { lineNumber, column } = TypeConverters.fromPosition(where);
|
||||
ranges = [{ startLineNumber: lineNumber, startColumn: column, endLineNumber: lineNumber, endColumn: column }];
|
||||
|
||||
} else if (where instanceof Range) {
|
||||
ranges = [TypeConverters.fromRange(where)];
|
||||
} else {
|
||||
ranges = [];
|
||||
for (const posOrRange of where) {
|
||||
if (posOrRange instanceof Range) {
|
||||
ranges.push(TypeConverters.fromRange(posOrRange));
|
||||
} else {
|
||||
const { lineNumber, column } = TypeConverters.fromPosition(posOrRange);
|
||||
ranges.push({ startLineNumber: lineNumber, startColumn: column, endLineNumber: lineNumber, endColumn: column });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this._proxy.$tryInsertSnippet(this._id, snippet.value, ranges, options);
|
||||
}
|
||||
|
||||
// ---- util
|
||||
|
||||
private _runOnProxy(callback: () => TPromise<any>): TPromise<ExtHostTextEditor> {
|
||||
if (this._disposed) {
|
||||
console.warn('TextEditor is closed/disposed');
|
||||
return TPromise.as(undefined);
|
||||
}
|
||||
return callback().then(() => this, err => {
|
||||
if (!(err instanceof Error && err.name === 'DISPOSED')) {
|
||||
console.warn(err);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class ExtHostTextEditor2 extends ExtHostTextEditor {
|
||||
|
||||
constructor(
|
||||
private readonly _extHostExtensions: ExtHostExtensionService,
|
||||
private readonly _mainThreadTelemetry: MainThreadTelemetryShape,
|
||||
proxy: MainThreadEditorsShape,
|
||||
id: string,
|
||||
document: ExtHostDocumentData,
|
||||
selections: Selection[],
|
||||
options: IResolvedTextEditorConfiguration,
|
||||
viewColumn: vscode.ViewColumn
|
||||
) {
|
||||
super(proxy, id, document, selections, options, viewColumn);
|
||||
}
|
||||
|
||||
setDecorations(decorationType: vscode.TextEditorDecorationType, rangesOrOptions: Range[] | vscode.DecorationOptions[]): void {
|
||||
// (1) find out if this decoration is important for us
|
||||
let usesCommandLink = false;
|
||||
outer: for (const rangeOrOption of rangesOrOptions) {
|
||||
if (Range.isRange(rangeOrOption)) {
|
||||
break;
|
||||
}
|
||||
if (typeof rangeOrOption.hoverMessage === 'string' && containsCommandLink(rangeOrOption.hoverMessage)) {
|
||||
usesCommandLink = true;
|
||||
break;
|
||||
} else if (Array.isArray(rangeOrOption.hoverMessage)) {
|
||||
for (const message of rangeOrOption.hoverMessage) {
|
||||
if (typeof message === 'string' && containsCommandLink(message)) {
|
||||
usesCommandLink = true;
|
||||
break outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// (2) send event for important decorations
|
||||
if (usesCommandLink) {
|
||||
let tag = new Error();
|
||||
this._extHostExtensions.getExtensionPathIndex().then(index => {
|
||||
const oldHandler = (<any>Error).prepareStackTrace;
|
||||
(<any>Error).prepareStackTrace = (error: Error, stackTrace: V8CallSite[]) => {
|
||||
for (const call of stackTrace) {
|
||||
const extension = index.findSubstr(call.getFileName());
|
||||
if (extension) {
|
||||
this._mainThreadTelemetry.$publicLog('usesCommandLink', {
|
||||
extension: extension.id,
|
||||
from: 'decoration',
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
// it all happens here...
|
||||
// tslint:disable-next-line:no-unused-expression
|
||||
tag.stack;
|
||||
(<any>Error).prepareStackTrace = oldHandler;
|
||||
});
|
||||
}
|
||||
|
||||
// (3) do it
|
||||
super.setDecorations(decorationType, rangesOrOptions);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function warnOnError(promise: TPromise<any>): void {
|
||||
promise.then(null, (err) => {
|
||||
console.warn(err);
|
||||
});
|
||||
}
|
||||
131
src/vs/workbench/api/node/extHostTextEditors.ts
Normal file
131
src/vs/workbench/api/node/extHostTextEditors.ts
Normal file
@@ -0,0 +1,131 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import URI from 'vs/base/common/uri';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { toThenable } from 'vs/base/common/async';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { TextEditorSelectionChangeKind } from './extHostTypes';
|
||||
import * as TypeConverters from './extHostTypeConverters';
|
||||
import { TextEditorDecorationType, ExtHostTextEditor } from './extHostTextEditor';
|
||||
import { ExtHostDocumentsAndEditors } from './extHostDocumentsAndEditors';
|
||||
import { Position as EditorPosition } from 'vs/platform/editor/common/editor';
|
||||
import { MainContext, MainThreadEditorsShape, ExtHostEditorsShape, ITextDocumentShowOptions, ITextEditorPositionData, IResolvedTextEditorConfiguration, ISelectionChangeEvent, IMainContext } from './extHost.protocol';
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export class ExtHostEditors implements ExtHostEditorsShape {
|
||||
|
||||
private readonly _onDidChangeTextEditorSelection = new Emitter<vscode.TextEditorSelectionChangeEvent>();
|
||||
private readonly _onDidChangeTextEditorOptions = new Emitter<vscode.TextEditorOptionsChangeEvent>();
|
||||
private readonly _onDidChangeTextEditorViewColumn = new Emitter<vscode.TextEditorViewColumnChangeEvent>();
|
||||
private readonly _onDidChangeActiveTextEditor = new Emitter<vscode.TextEditor>();
|
||||
private readonly _onDidChangeVisibleTextEditors = new Emitter<vscode.TextEditor[]>();
|
||||
|
||||
readonly onDidChangeTextEditorSelection: Event<vscode.TextEditorSelectionChangeEvent> = this._onDidChangeTextEditorSelection.event;
|
||||
readonly onDidChangeTextEditorOptions: Event<vscode.TextEditorOptionsChangeEvent> = this._onDidChangeTextEditorOptions.event;
|
||||
readonly onDidChangeTextEditorViewColumn: Event<vscode.TextEditorViewColumnChangeEvent> = this._onDidChangeTextEditorViewColumn.event;
|
||||
readonly onDidChangeActiveTextEditor: Event<vscode.TextEditor> = this._onDidChangeActiveTextEditor.event;
|
||||
readonly onDidChangeVisibleTextEditors: Event<vscode.TextEditor[]> = this._onDidChangeVisibleTextEditors.event;
|
||||
|
||||
|
||||
private _proxy: MainThreadEditorsShape;
|
||||
private _extHostDocumentsAndEditors: ExtHostDocumentsAndEditors;
|
||||
|
||||
constructor(
|
||||
mainContext: IMainContext,
|
||||
extHostDocumentsAndEditors: ExtHostDocumentsAndEditors,
|
||||
) {
|
||||
this._proxy = mainContext.get(MainContext.MainThreadEditors);
|
||||
this._extHostDocumentsAndEditors = extHostDocumentsAndEditors;
|
||||
|
||||
this._extHostDocumentsAndEditors.onDidChangeVisibleTextEditors(e => this._onDidChangeVisibleTextEditors.fire(e));
|
||||
this._extHostDocumentsAndEditors.onDidChangeActiveTextEditor(e => this._onDidChangeActiveTextEditor.fire(e));
|
||||
}
|
||||
|
||||
getActiveTextEditor(): ExtHostTextEditor {
|
||||
return this._extHostDocumentsAndEditors.activeEditor();
|
||||
}
|
||||
|
||||
getVisibleTextEditors(): vscode.TextEditor[] {
|
||||
return this._extHostDocumentsAndEditors.allEditors();
|
||||
}
|
||||
|
||||
showTextDocument(document: vscode.TextDocument, column: vscode.ViewColumn, preserveFocus: boolean): TPromise<vscode.TextEditor>;
|
||||
showTextDocument(document: vscode.TextDocument, options: { column: vscode.ViewColumn, preserveFocus: boolean, pinned: boolean }): TPromise<vscode.TextEditor>;
|
||||
showTextDocument(document: vscode.TextDocument, columnOrOptions: vscode.ViewColumn | vscode.TextDocumentShowOptions, preserveFocus?: boolean): TPromise<vscode.TextEditor>;
|
||||
showTextDocument(document: vscode.TextDocument, columnOrOptions: vscode.ViewColumn | vscode.TextDocumentShowOptions, preserveFocus?: boolean): TPromise<vscode.TextEditor> {
|
||||
let options: ITextDocumentShowOptions;
|
||||
if (typeof columnOrOptions === 'number') {
|
||||
options = {
|
||||
position: TypeConverters.fromViewColumn(columnOrOptions),
|
||||
preserveFocus
|
||||
};
|
||||
} else if (typeof columnOrOptions === 'object') {
|
||||
options = {
|
||||
position: TypeConverters.fromViewColumn(columnOrOptions.viewColumn),
|
||||
preserveFocus: columnOrOptions.preserveFocus,
|
||||
selection: typeof columnOrOptions.selection === 'object' ? TypeConverters.fromRange(columnOrOptions.selection) : undefined,
|
||||
pinned: typeof columnOrOptions.preview === 'boolean' ? !columnOrOptions.preview : undefined
|
||||
};
|
||||
} else {
|
||||
options = {
|
||||
position: EditorPosition.ONE,
|
||||
preserveFocus: false
|
||||
};
|
||||
}
|
||||
|
||||
return this._proxy.$tryShowTextDocument(<URI>document.uri, options).then(id => {
|
||||
let editor = this._extHostDocumentsAndEditors.getEditor(id);
|
||||
if (editor) {
|
||||
return editor;
|
||||
} else {
|
||||
throw new Error(`Failed to show text document ${document.uri.toString()}, should show in editor #${id}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
createTextEditorDecorationType(options: vscode.DecorationRenderOptions): vscode.TextEditorDecorationType {
|
||||
return new TextEditorDecorationType(this._proxy, options);
|
||||
}
|
||||
|
||||
// --- called from main thread
|
||||
|
||||
$acceptOptionsChanged(id: string, opts: IResolvedTextEditorConfiguration): void {
|
||||
let editor = this._extHostDocumentsAndEditors.getEditor(id);
|
||||
editor._acceptOptions(opts);
|
||||
this._onDidChangeTextEditorOptions.fire({
|
||||
textEditor: editor,
|
||||
options: opts
|
||||
});
|
||||
}
|
||||
|
||||
$acceptSelectionsChanged(id: string, event: ISelectionChangeEvent): void {
|
||||
const kind = TextEditorSelectionChangeKind.fromValue(event.source);
|
||||
const selections = event.selections.map(TypeConverters.toSelection);
|
||||
const textEditor = this._extHostDocumentsAndEditors.getEditor(id);
|
||||
textEditor._acceptSelections(selections);
|
||||
this._onDidChangeTextEditorSelection.fire({
|
||||
textEditor,
|
||||
selections,
|
||||
kind
|
||||
});
|
||||
}
|
||||
|
||||
$acceptEditorPositionData(data: ITextEditorPositionData): void {
|
||||
for (let id in data) {
|
||||
let textEditor = this._extHostDocumentsAndEditors.getEditor(id);
|
||||
let viewColumn = TypeConverters.toViewColumn(data[id]);
|
||||
if (textEditor.viewColumn !== viewColumn) {
|
||||
textEditor._acceptViewColumn(viewColumn);
|
||||
this._onDidChangeTextEditorViewColumn.fire({ textEditor, viewColumn });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getDiffInformation(id: string): Thenable<vscode.LineChange[]> {
|
||||
return toThenable(this._proxy.$getDiffInformation(id));
|
||||
}
|
||||
}
|
||||
227
src/vs/workbench/api/node/extHostTreeViews.ts
Normal file
227
src/vs/workbench/api/node/extHostTreeViews.ts
Normal file
@@ -0,0 +1,227 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { localize } from 'vs/nls';
|
||||
import * as vscode from 'vscode';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { distinct } from 'vs/base/common/arrays';
|
||||
import { debounceEvent } from 'vs/base/common/event';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { ExtHostTreeViewsShape, MainThreadTreeViewsShape } from './extHost.protocol';
|
||||
import { ITreeItem, TreeViewItemHandleArg } from 'vs/workbench/parts/views/common/views';
|
||||
import { TreeItemCollapsibleState } from './extHostTypes';
|
||||
import { ExtHostCommands, CommandsConverter } from 'vs/workbench/api/node/extHostCommands';
|
||||
import { asWinJsPromise } from 'vs/base/common/async';
|
||||
|
||||
type TreeItemHandle = number;
|
||||
|
||||
export class ExtHostTreeViews implements ExtHostTreeViewsShape {
|
||||
|
||||
private treeViews: Map<string, ExtHostTreeView<any>> = new Map<string, ExtHostTreeView<any>>();
|
||||
|
||||
constructor(
|
||||
private _proxy: MainThreadTreeViewsShape,
|
||||
private commands: ExtHostCommands
|
||||
) {
|
||||
commands.registerArgumentProcessor({
|
||||
processArgument: arg => {
|
||||
if (arg && arg.$treeViewId && arg.$treeItemHandle) {
|
||||
return this.convertArgument(arg);
|
||||
}
|
||||
return arg;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
registerTreeDataProvider<T>(id: string, treeDataProvider: vscode.TreeDataProvider<T>): vscode.Disposable {
|
||||
const treeView = new ExtHostTreeView<T>(id, treeDataProvider, this._proxy, this.commands.converter);
|
||||
this.treeViews.set(id, treeView);
|
||||
return {
|
||||
dispose: () => {
|
||||
this.treeViews.delete(id);
|
||||
treeView.dispose();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
$getElements(treeViewId: string): TPromise<ITreeItem[]> {
|
||||
const treeView = this.treeViews.get(treeViewId);
|
||||
if (!treeView) {
|
||||
return TPromise.wrapError<ITreeItem[]>(new Error(localize('treeView.notRegistered', 'No tree view with id \'{0}\' registered.', treeViewId)));
|
||||
}
|
||||
return treeView.getTreeItems();
|
||||
}
|
||||
|
||||
$getChildren(treeViewId: string, treeItemHandle?: number): TPromise<ITreeItem[]> {
|
||||
const treeView = this.treeViews.get(treeViewId);
|
||||
if (!treeView) {
|
||||
return TPromise.wrapError<ITreeItem[]>(new Error(localize('treeView.notRegistered', 'No tree view with id \'{0}\' registered.', treeViewId)));
|
||||
}
|
||||
return treeView.getChildren(treeItemHandle);
|
||||
}
|
||||
|
||||
private convertArgument(arg: TreeViewItemHandleArg): any {
|
||||
const treeView = this.treeViews.get(arg.$treeViewId);
|
||||
return treeView ? treeView.getExtensionElement(arg.$treeItemHandle) : null;
|
||||
}
|
||||
}
|
||||
|
||||
class ExtHostTreeView<T> extends Disposable {
|
||||
|
||||
private _itemHandlePool = 0;
|
||||
|
||||
private extElementsMap: Map<TreeItemHandle, T> = new Map<TreeItemHandle, T>();
|
||||
private itemHandlesMap: Map<T, TreeItemHandle> = new Map<T, TreeItemHandle>();
|
||||
private extChildrenElementsMap: Map<T, T[]> = new Map<T, T[]>();
|
||||
|
||||
constructor(private viewId: string, private dataProvider: vscode.TreeDataProvider<T>, private proxy: MainThreadTreeViewsShape, private commands: CommandsConverter) {
|
||||
super();
|
||||
this.proxy.$registerView(viewId);
|
||||
if (dataProvider.onDidChangeTreeData) {
|
||||
this._register(debounceEvent<T, T[]>(dataProvider.onDidChangeTreeData, (last, current) => last ? [...last, current] : [current], 200)(elements => this._refresh(elements)));
|
||||
}
|
||||
}
|
||||
|
||||
getTreeItems(): TPromise<ITreeItem[]> {
|
||||
this.extChildrenElementsMap.clear();
|
||||
this.extElementsMap.clear();
|
||||
this.itemHandlesMap.clear();
|
||||
|
||||
return asWinJsPromise(() => this.dataProvider.getChildren())
|
||||
.then(elements => this.processAndMapElements(elements));
|
||||
}
|
||||
|
||||
getChildren(treeItemHandle: TreeItemHandle): TPromise<ITreeItem[]> {
|
||||
let extElement = this.getExtensionElement(treeItemHandle);
|
||||
if (extElement) {
|
||||
this.clearChildren(extElement);
|
||||
} else {
|
||||
return TPromise.wrapError<ITreeItem[]>(new Error(localize('treeItem.notFound', 'No tree item with id \'{0}\' found.', treeItemHandle)));
|
||||
}
|
||||
|
||||
return asWinJsPromise(() => this.dataProvider.getChildren(extElement))
|
||||
.then(childrenElements => this.processAndMapElements(childrenElements));
|
||||
}
|
||||
|
||||
getExtensionElement(treeItemHandle: TreeItemHandle): T {
|
||||
return this.extElementsMap.get(treeItemHandle);
|
||||
}
|
||||
|
||||
private _refresh(elements: T[]): void {
|
||||
const hasRoot = elements.some(element => !element);
|
||||
if (hasRoot) {
|
||||
this.proxy.$refresh(this.viewId, []);
|
||||
} else {
|
||||
const itemHandles = distinct(elements.map(element => this.itemHandlesMap.get(element))
|
||||
.filter(itemHandle => !!itemHandle));
|
||||
if (itemHandles.length) {
|
||||
this.proxy.$refresh(this.viewId, itemHandles);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private processAndMapElements(elements: T[]): TPromise<ITreeItem[]> {
|
||||
if (elements && elements.length) {
|
||||
return TPromise.join(
|
||||
elements.filter(element => !!element)
|
||||
.map(element => {
|
||||
if (this.extChildrenElementsMap.has(element)) {
|
||||
return TPromise.wrapError<ITreeItem>(new Error(localize('treeView.duplicateElement', 'Element {0} is already registered', element)));
|
||||
}
|
||||
return this.resolveElement(element);
|
||||
}))
|
||||
.then(treeItems => treeItems.filter(treeItem => !!treeItem));
|
||||
}
|
||||
return TPromise.as([]);
|
||||
}
|
||||
|
||||
private resolveElement(element: T): TPromise<ITreeItem> {
|
||||
return asWinJsPromise(() => this.dataProvider.getTreeItem(element))
|
||||
.then(extTreeItem => {
|
||||
const treeItem = this.massageTreeItem(extTreeItem);
|
||||
if (treeItem) {
|
||||
this.itemHandlesMap.set(element, treeItem.handle);
|
||||
this.extElementsMap.set(treeItem.handle, element);
|
||||
if (treeItem.collapsibleState === TreeItemCollapsibleState.Expanded) {
|
||||
return this.getChildren(treeItem.handle).then(children => {
|
||||
treeItem.children = children;
|
||||
return treeItem;
|
||||
});
|
||||
} else {
|
||||
return treeItem;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
private massageTreeItem(extensionTreeItem: vscode.TreeItem): ITreeItem {
|
||||
if (!extensionTreeItem) {
|
||||
return null;
|
||||
}
|
||||
const icon = this.getLightIconPath(extensionTreeItem);
|
||||
return {
|
||||
handle: ++this._itemHandlePool,
|
||||
label: extensionTreeItem.label,
|
||||
command: extensionTreeItem.command ? this.commands.toInternal(extensionTreeItem.command) : void 0,
|
||||
contextValue: extensionTreeItem.contextValue,
|
||||
icon,
|
||||
iconDark: this.getDarkIconPath(extensionTreeItem) || icon,
|
||||
collapsibleState: extensionTreeItem.collapsibleState,
|
||||
};
|
||||
}
|
||||
|
||||
private getLightIconPath(extensionTreeItem: vscode.TreeItem) {
|
||||
if (extensionTreeItem.iconPath) {
|
||||
if (typeof extensionTreeItem.iconPath === 'string' || extensionTreeItem.iconPath instanceof URI) {
|
||||
return this.getIconPath(extensionTreeItem.iconPath);
|
||||
}
|
||||
return this.getIconPath(extensionTreeItem.iconPath['light']);
|
||||
}
|
||||
return void 0;
|
||||
}
|
||||
|
||||
private getDarkIconPath(extensionTreeItem: vscode.TreeItem) {
|
||||
if (extensionTreeItem.iconPath && extensionTreeItem.iconPath['dark']) {
|
||||
return this.getIconPath(extensionTreeItem.iconPath['dark']);
|
||||
}
|
||||
return void 0;
|
||||
}
|
||||
|
||||
private getIconPath(iconPath: string | URI): string {
|
||||
if (iconPath instanceof URI) {
|
||||
return iconPath.toString();
|
||||
}
|
||||
return URI.file(iconPath).toString();
|
||||
}
|
||||
|
||||
private clearChildren(extElement: T): void {
|
||||
const children = this.extChildrenElementsMap.get(extElement);
|
||||
if (children) {
|
||||
for (const child of children) {
|
||||
this.clearElement(child);
|
||||
}
|
||||
this.extChildrenElementsMap.delete(extElement);
|
||||
}
|
||||
}
|
||||
|
||||
private clearElement(extElement: T): void {
|
||||
this.clearChildren(extElement);
|
||||
|
||||
const treeItemhandle = this.itemHandlesMap.get(extElement);
|
||||
this.itemHandlesMap.delete(extElement);
|
||||
if (treeItemhandle) {
|
||||
this.extElementsMap.delete(treeItemhandle);
|
||||
}
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.extElementsMap.clear();
|
||||
this.itemHandlesMap.clear();
|
||||
this.extChildrenElementsMap.clear();
|
||||
}
|
||||
}
|
||||
454
src/vs/workbench/api/node/extHostTypeConverters.ts
Normal file
454
src/vs/workbench/api/node/extHostTypeConverters.ts
Normal file
@@ -0,0 +1,454 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import * as modes from 'vs/editor/common/modes';
|
||||
import * as types from './extHostTypes';
|
||||
import { Position as EditorPosition } from 'vs/platform/editor/common/editor';
|
||||
import { IDecorationOptions, EndOfLineSequence } from 'vs/editor/common/editorCommon';
|
||||
import * as vscode from 'vscode';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { ProgressLocation as MainProgressLocation } from 'vs/platform/progress/common/progress';
|
||||
import { SaveReason } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { IPosition } from 'vs/editor/common/core/position';
|
||||
import { IRange } from 'vs/editor/common/core/range';
|
||||
import { ISelection } from 'vs/editor/common/core/selection';
|
||||
import * as htmlContent from 'vs/base/common/htmlContent';
|
||||
|
||||
export interface PositionLike {
|
||||
line: number;
|
||||
character: number;
|
||||
}
|
||||
|
||||
export interface RangeLike {
|
||||
start: PositionLike;
|
||||
end: PositionLike;
|
||||
}
|
||||
|
||||
export interface SelectionLike extends RangeLike {
|
||||
anchor: PositionLike;
|
||||
active: PositionLike;
|
||||
}
|
||||
|
||||
export function toSelection(selection: ISelection): types.Selection {
|
||||
let { selectionStartLineNumber, selectionStartColumn, positionLineNumber, positionColumn } = selection;
|
||||
let start = new types.Position(selectionStartLineNumber - 1, selectionStartColumn - 1);
|
||||
let end = new types.Position(positionLineNumber - 1, positionColumn - 1);
|
||||
return new types.Selection(start, end);
|
||||
}
|
||||
|
||||
export function fromSelection(selection: SelectionLike): ISelection {
|
||||
let { anchor, active } = selection;
|
||||
return {
|
||||
selectionStartLineNumber: anchor.line + 1,
|
||||
selectionStartColumn: anchor.character + 1,
|
||||
positionLineNumber: active.line + 1,
|
||||
positionColumn: active.character + 1
|
||||
};
|
||||
}
|
||||
|
||||
export function fromRange(range: RangeLike): IRange {
|
||||
if (!range) {
|
||||
return undefined;
|
||||
}
|
||||
let { start, end } = range;
|
||||
return {
|
||||
startLineNumber: start.line + 1,
|
||||
startColumn: start.character + 1,
|
||||
endLineNumber: end.line + 1,
|
||||
endColumn: end.character + 1
|
||||
};
|
||||
}
|
||||
|
||||
export function toRange(range: IRange): types.Range {
|
||||
if (!range) {
|
||||
return undefined;
|
||||
}
|
||||
let { startLineNumber, startColumn, endLineNumber, endColumn } = range;
|
||||
return new types.Range(startLineNumber - 1, startColumn - 1, endLineNumber - 1, endColumn - 1);
|
||||
}
|
||||
|
||||
export function toPosition(position: IPosition): types.Position {
|
||||
return new types.Position(position.lineNumber - 1, position.column - 1);
|
||||
}
|
||||
|
||||
export function fromPosition(position: types.Position): IPosition {
|
||||
return { lineNumber: position.line + 1, column: position.character + 1 };
|
||||
}
|
||||
|
||||
export function fromDiagnosticSeverity(value: number): Severity {
|
||||
switch (value) {
|
||||
case types.DiagnosticSeverity.Error:
|
||||
return Severity.Error;
|
||||
case types.DiagnosticSeverity.Warning:
|
||||
return Severity.Warning;
|
||||
case types.DiagnosticSeverity.Information:
|
||||
return Severity.Info;
|
||||
case types.DiagnosticSeverity.Hint:
|
||||
return Severity.Ignore;
|
||||
}
|
||||
return Severity.Error;
|
||||
}
|
||||
|
||||
export function toDiagnosticSeverty(value: Severity): types.DiagnosticSeverity {
|
||||
switch (value) {
|
||||
case Severity.Info:
|
||||
return types.DiagnosticSeverity.Information;
|
||||
case Severity.Warning:
|
||||
return types.DiagnosticSeverity.Warning;
|
||||
case Severity.Error:
|
||||
return types.DiagnosticSeverity.Error;
|
||||
case Severity.Ignore:
|
||||
return types.DiagnosticSeverity.Hint;
|
||||
}
|
||||
return types.DiagnosticSeverity.Error;
|
||||
}
|
||||
|
||||
export function fromViewColumn(column?: vscode.ViewColumn): EditorPosition {
|
||||
let editorColumn = EditorPosition.ONE;
|
||||
if (typeof column !== 'number') {
|
||||
// stick with ONE
|
||||
} else if (column === <number>types.ViewColumn.Two) {
|
||||
editorColumn = EditorPosition.TWO;
|
||||
} else if (column === <number>types.ViewColumn.Three) {
|
||||
editorColumn = EditorPosition.THREE;
|
||||
}
|
||||
return editorColumn;
|
||||
}
|
||||
|
||||
export function toViewColumn(position?: EditorPosition): vscode.ViewColumn {
|
||||
if (typeof position !== 'number') {
|
||||
return undefined;
|
||||
}
|
||||
if (position === EditorPosition.ONE) {
|
||||
return <number>types.ViewColumn.One;
|
||||
} else if (position === EditorPosition.TWO) {
|
||||
return <number>types.ViewColumn.Two;
|
||||
} else if (position === EditorPosition.THREE) {
|
||||
return <number>types.ViewColumn.Three;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function isDecorationOptions(something: any): something is vscode.DecorationOptions {
|
||||
return (typeof something.range !== 'undefined');
|
||||
}
|
||||
|
||||
function isDecorationOptionsArr(something: vscode.Range[] | vscode.DecorationOptions[]): something is vscode.DecorationOptions[] {
|
||||
if (something.length === 0) {
|
||||
return true;
|
||||
}
|
||||
return isDecorationOptions(something[0]) ? true : false;
|
||||
}
|
||||
|
||||
export namespace MarkdownString {
|
||||
|
||||
export function fromMany(markup: (vscode.MarkdownString | vscode.MarkedString)[]): htmlContent.IMarkdownString[] {
|
||||
return markup.map(MarkdownString.from);
|
||||
}
|
||||
|
||||
interface Codeblock {
|
||||
language: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
function isCodeblock(thing: any): thing is Codeblock {
|
||||
return typeof thing === 'object'
|
||||
&& typeof (<Codeblock>thing).language === 'string'
|
||||
&& typeof (<Codeblock>thing).value === 'string';
|
||||
}
|
||||
|
||||
export function from(markup: vscode.MarkdownString | vscode.MarkedString): htmlContent.IMarkdownString {
|
||||
if (isCodeblock(markup)) {
|
||||
const { language, value } = markup;
|
||||
return { value: '```' + language + '\n' + value + '\n```\n' };
|
||||
} else if (htmlContent.isMarkdownString(markup)) {
|
||||
return markup;
|
||||
} else if (typeof markup === 'string') {
|
||||
return { value: <string>markup, isTrusted: true };
|
||||
} else {
|
||||
return { value: '' };
|
||||
}
|
||||
}
|
||||
export function to(value: htmlContent.IMarkdownString): vscode.MarkdownString {
|
||||
const ret = new htmlContent.MarkdownString(value.value);
|
||||
ret.isTrusted = value.isTrusted;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
export function fromRangeOrRangeWithMessage(ranges: vscode.Range[] | vscode.DecorationOptions[]): IDecorationOptions[] {
|
||||
if (isDecorationOptionsArr(ranges)) {
|
||||
return ranges.map(r => {
|
||||
return {
|
||||
range: fromRange(r.range),
|
||||
hoverMessage: Array.isArray(r.hoverMessage) ? MarkdownString.fromMany(r.hoverMessage) : r.hoverMessage && MarkdownString.from(r.hoverMessage),
|
||||
renderOptions: <any> /* URI vs Uri */r.renderOptions
|
||||
};
|
||||
});
|
||||
} else {
|
||||
return ranges.map((r): IDecorationOptions => {
|
||||
return {
|
||||
range: fromRange(r)
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const TextEdit = {
|
||||
|
||||
from(edit: vscode.TextEdit): modes.TextEdit {
|
||||
return <modes.TextEdit>{
|
||||
text: edit.newText,
|
||||
eol: EndOfLine.from(edit.newEol),
|
||||
range: fromRange(edit.range)
|
||||
};
|
||||
},
|
||||
to(edit: modes.TextEdit): vscode.TextEdit {
|
||||
let result = new types.TextEdit(toRange(edit.range), edit.text);
|
||||
result.newEol = EndOfLine.to(edit.eol);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
export namespace SymbolKind {
|
||||
|
||||
const _fromMapping: { [kind: number]: modes.SymbolKind } = Object.create(null);
|
||||
_fromMapping[types.SymbolKind.File] = modes.SymbolKind.File;
|
||||
_fromMapping[types.SymbolKind.Module] = modes.SymbolKind.Module;
|
||||
_fromMapping[types.SymbolKind.Namespace] = modes.SymbolKind.Namespace;
|
||||
_fromMapping[types.SymbolKind.Package] = modes.SymbolKind.Package;
|
||||
_fromMapping[types.SymbolKind.Class] = modes.SymbolKind.Class;
|
||||
_fromMapping[types.SymbolKind.Method] = modes.SymbolKind.Method;
|
||||
_fromMapping[types.SymbolKind.Property] = modes.SymbolKind.Property;
|
||||
_fromMapping[types.SymbolKind.Field] = modes.SymbolKind.Field;
|
||||
_fromMapping[types.SymbolKind.Constructor] = modes.SymbolKind.Constructor;
|
||||
_fromMapping[types.SymbolKind.Enum] = modes.SymbolKind.Enum;
|
||||
_fromMapping[types.SymbolKind.Interface] = modes.SymbolKind.Interface;
|
||||
_fromMapping[types.SymbolKind.Function] = modes.SymbolKind.Function;
|
||||
_fromMapping[types.SymbolKind.Variable] = modes.SymbolKind.Variable;
|
||||
_fromMapping[types.SymbolKind.Constant] = modes.SymbolKind.Constant;
|
||||
_fromMapping[types.SymbolKind.String] = modes.SymbolKind.String;
|
||||
_fromMapping[types.SymbolKind.Number] = modes.SymbolKind.Number;
|
||||
_fromMapping[types.SymbolKind.Boolean] = modes.SymbolKind.Boolean;
|
||||
_fromMapping[types.SymbolKind.Array] = modes.SymbolKind.Array;
|
||||
_fromMapping[types.SymbolKind.Object] = modes.SymbolKind.Object;
|
||||
_fromMapping[types.SymbolKind.Key] = modes.SymbolKind.Key;
|
||||
_fromMapping[types.SymbolKind.Null] = modes.SymbolKind.Null;
|
||||
_fromMapping[types.SymbolKind.EnumMember] = modes.SymbolKind.EnumMember;
|
||||
_fromMapping[types.SymbolKind.Struct] = modes.SymbolKind.Struct;
|
||||
_fromMapping[types.SymbolKind.Event] = modes.SymbolKind.Event;
|
||||
_fromMapping[types.SymbolKind.Operator] = modes.SymbolKind.Operator;
|
||||
_fromMapping[types.SymbolKind.TypeParameter] = modes.SymbolKind.TypeParameter;
|
||||
|
||||
export function from(kind: vscode.SymbolKind): modes.SymbolKind {
|
||||
return _fromMapping[kind] || modes.SymbolKind.Property;
|
||||
}
|
||||
|
||||
export function to(kind: modes.SymbolKind): vscode.SymbolKind {
|
||||
for (let k in _fromMapping) {
|
||||
if (_fromMapping[k] === kind) {
|
||||
return Number(k);
|
||||
}
|
||||
}
|
||||
return types.SymbolKind.Property;
|
||||
}
|
||||
}
|
||||
|
||||
export function fromSymbolInformation(info: vscode.SymbolInformation): modes.SymbolInformation {
|
||||
return <modes.SymbolInformation>{
|
||||
name: info.name,
|
||||
kind: SymbolKind.from(info.kind),
|
||||
containerName: info.containerName,
|
||||
location: location.from(info.location)
|
||||
};
|
||||
}
|
||||
|
||||
export function toSymbolInformation(bearing: modes.SymbolInformation): types.SymbolInformation {
|
||||
return new types.SymbolInformation(
|
||||
bearing.name,
|
||||
SymbolKind.to(bearing.kind),
|
||||
bearing.containerName,
|
||||
location.to(bearing.location)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
export const location = {
|
||||
from(value: vscode.Location): modes.Location {
|
||||
return {
|
||||
range: value.range && fromRange(value.range),
|
||||
uri: <URI>value.uri
|
||||
};
|
||||
},
|
||||
to(value: modes.Location): types.Location {
|
||||
return new types.Location(value.uri, toRange(value.range));
|
||||
}
|
||||
};
|
||||
|
||||
export function fromHover(hover: vscode.Hover): modes.Hover {
|
||||
return <modes.Hover>{
|
||||
range: fromRange(hover.range),
|
||||
contents: MarkdownString.fromMany(hover.contents)
|
||||
};
|
||||
}
|
||||
|
||||
export function toHover(info: modes.Hover): types.Hover {
|
||||
return new types.Hover(info.contents.map(MarkdownString.to), toRange(info.range));
|
||||
}
|
||||
|
||||
export function toDocumentHighlight(occurrence: modes.DocumentHighlight): types.DocumentHighlight {
|
||||
return new types.DocumentHighlight(toRange(occurrence.range), occurrence.kind);
|
||||
}
|
||||
|
||||
export const CompletionItemKind = {
|
||||
|
||||
from(kind: types.CompletionItemKind): modes.SuggestionType {
|
||||
switch (kind) {
|
||||
case types.CompletionItemKind.Method: return 'method';
|
||||
case types.CompletionItemKind.Function: return 'function';
|
||||
case types.CompletionItemKind.Constructor: return 'constructor';
|
||||
case types.CompletionItemKind.Field: return 'field';
|
||||
case types.CompletionItemKind.Variable: return 'variable';
|
||||
case types.CompletionItemKind.Class: return 'class';
|
||||
case types.CompletionItemKind.Interface: return 'interface';
|
||||
case types.CompletionItemKind.Struct: return 'struct';
|
||||
case types.CompletionItemKind.Module: return 'module';
|
||||
case types.CompletionItemKind.Property: return 'property';
|
||||
case types.CompletionItemKind.Unit: return 'unit';
|
||||
case types.CompletionItemKind.Value: return 'value';
|
||||
case types.CompletionItemKind.Constant: return 'constant';
|
||||
case types.CompletionItemKind.Enum: return 'enum';
|
||||
case types.CompletionItemKind.EnumMember: return 'enum-member';
|
||||
case types.CompletionItemKind.Keyword: return 'keyword';
|
||||
case types.CompletionItemKind.Snippet: return 'snippet';
|
||||
case types.CompletionItemKind.Text: return 'text';
|
||||
case types.CompletionItemKind.Color: return 'color';
|
||||
case types.CompletionItemKind.File: return 'file';
|
||||
case types.CompletionItemKind.Reference: return 'reference';
|
||||
case types.CompletionItemKind.Folder: return 'folder';
|
||||
case types.CompletionItemKind.Event: return 'event';
|
||||
case types.CompletionItemKind.Operator: return 'operator';
|
||||
case types.CompletionItemKind.TypeParameter: return 'type-parameter';
|
||||
}
|
||||
return 'property';
|
||||
},
|
||||
|
||||
to(type: modes.SuggestionType): types.CompletionItemKind {
|
||||
if (!type) {
|
||||
return types.CompletionItemKind.Property;
|
||||
} else {
|
||||
return types.CompletionItemKind[type.charAt(0).toUpperCase() + type.substr(1)];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export namespace Suggest {
|
||||
|
||||
export function to(position: types.Position, suggestion: modes.ISuggestion): types.CompletionItem {
|
||||
const result = new types.CompletionItem(suggestion.label);
|
||||
result.insertText = suggestion.insertText;
|
||||
result.kind = CompletionItemKind.to(suggestion.type);
|
||||
result.detail = suggestion.detail;
|
||||
result.documentation = suggestion.documentation;
|
||||
result.sortText = suggestion.sortText;
|
||||
result.filterText = suggestion.filterText;
|
||||
|
||||
// 'overwrite[Before|After]'-logic
|
||||
let overwriteBefore = (typeof suggestion.overwriteBefore === 'number') ? suggestion.overwriteBefore : 0;
|
||||
let startPosition = new types.Position(position.line, Math.max(0, position.character - overwriteBefore));
|
||||
let endPosition = position;
|
||||
if (typeof suggestion.overwriteAfter === 'number') {
|
||||
endPosition = new types.Position(position.line, position.character + suggestion.overwriteAfter);
|
||||
}
|
||||
result.range = new types.Range(startPosition, endPosition);
|
||||
|
||||
// 'inserText'-logic
|
||||
if (suggestion.snippetType === 'textmate') {
|
||||
result.insertText = new types.SnippetString(suggestion.insertText);
|
||||
} else {
|
||||
result.insertText = suggestion.insertText;
|
||||
result.textEdit = new types.TextEdit(result.range, result.insertText);
|
||||
}
|
||||
|
||||
// TODO additionalEdits, command
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
export namespace SignatureHelp {
|
||||
|
||||
export function from(signatureHelp: types.SignatureHelp): modes.SignatureHelp {
|
||||
return signatureHelp;
|
||||
}
|
||||
|
||||
export function to(hints: modes.SignatureHelp): types.SignatureHelp {
|
||||
return hints;
|
||||
}
|
||||
}
|
||||
|
||||
export namespace DocumentLink {
|
||||
|
||||
export function from(link: vscode.DocumentLink): modes.ILink {
|
||||
return {
|
||||
range: fromRange(link.range),
|
||||
url: link.target && link.target.toString()
|
||||
};
|
||||
}
|
||||
|
||||
export function to(link: modes.ILink): vscode.DocumentLink {
|
||||
return new types.DocumentLink(toRange(link.range), link.url && URI.parse(link.url));
|
||||
}
|
||||
}
|
||||
|
||||
export namespace TextDocumentSaveReason {
|
||||
|
||||
export function to(reason: SaveReason): vscode.TextDocumentSaveReason {
|
||||
switch (reason) {
|
||||
case SaveReason.AUTO:
|
||||
return types.TextDocumentSaveReason.AfterDelay;
|
||||
case SaveReason.EXPLICIT:
|
||||
return types.TextDocumentSaveReason.Manual;
|
||||
case SaveReason.FOCUS_CHANGE:
|
||||
case SaveReason.WINDOW_CHANGE:
|
||||
return types.TextDocumentSaveReason.FocusOut;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export namespace EndOfLine {
|
||||
|
||||
export function from(eol: vscode.EndOfLine): EndOfLineSequence {
|
||||
if (eol === types.EndOfLine.CRLF) {
|
||||
return EndOfLineSequence.CRLF;
|
||||
} else if (eol === types.EndOfLine.LF) {
|
||||
return EndOfLineSequence.LF;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function to(eol: EndOfLineSequence): vscode.EndOfLine {
|
||||
if (eol === EndOfLineSequence.CRLF) {
|
||||
return types.EndOfLine.CRLF;
|
||||
} else if (eol === EndOfLineSequence.LF) {
|
||||
return types.EndOfLine.LF;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export namespace ProgressLocation {
|
||||
export function from(loc: vscode.ProgressLocation): MainProgressLocation {
|
||||
switch (loc) {
|
||||
case types.ProgressLocation.SourceControl: return MainProgressLocation.Scm;
|
||||
case types.ProgressLocation.Window: return MainProgressLocation.Window;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
1385
src/vs/workbench/api/node/extHostTypes.ts
Normal file
1385
src/vs/workbench/api/node/extHostTypes.ts
Normal file
File diff suppressed because it is too large
Load Diff
39
src/vs/workbench/api/node/extHostWindow.ts
Normal file
39
src/vs/workbench/api/node/extHostWindow.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { IThreadService } from 'vs/workbench/services/thread/common/threadService';
|
||||
import { ExtHostWindowShape, MainContext, MainThreadWindowShape } from './extHost.protocol';
|
||||
import { WindowState } from 'vscode';
|
||||
|
||||
export class ExtHostWindow implements ExtHostWindowShape {
|
||||
|
||||
private static InitialState: WindowState = {
|
||||
focused: true
|
||||
};
|
||||
|
||||
private _proxy: MainThreadWindowShape;
|
||||
|
||||
private _onDidChangeWindowState = new Emitter<WindowState>();
|
||||
readonly onDidChangeWindowState: Event<WindowState> = this._onDidChangeWindowState.event;
|
||||
|
||||
private _state = ExtHostWindow.InitialState;
|
||||
get state(): WindowState { return this._state; }
|
||||
|
||||
constructor(threadService: IThreadService) {
|
||||
this._proxy = threadService.get(MainContext.MainThreadWindow);
|
||||
this._proxy.$getWindowVisibility().then(isFocused => this.$onDidChangeWindowFocus(isFocused));
|
||||
}
|
||||
|
||||
$onDidChangeWindowFocus(focused: boolean): void {
|
||||
if (focused === this._state.focused) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._state = { ...this._state, focused };
|
||||
this._onDidChangeWindowState.fire(this._state);
|
||||
}
|
||||
}
|
||||
259
src/vs/workbench/api/node/extHostWorkspace.ts
Normal file
259
src/vs/workbench/api/node/extHostWorkspace.ts
Normal file
@@ -0,0 +1,259 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import URI from 'vs/base/common/uri';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { normalize } from 'vs/base/common/paths';
|
||||
import { delta } from 'vs/base/common/arrays';
|
||||
import { relative, basename } from 'path';
|
||||
import { Workspace } from 'vs/platform/workspace/common/workspace';
|
||||
import { IResourceEdit } from 'vs/editor/common/services/bulkEdit';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { fromRange, EndOfLine } from 'vs/workbench/api/node/extHostTypeConverters';
|
||||
import { IWorkspaceData, ExtHostWorkspaceShape, MainContext, MainThreadWorkspaceShape, IMainContext } from './extHost.protocol';
|
||||
import * as vscode from 'vscode';
|
||||
import { compare } from 'vs/base/common/strings';
|
||||
import { asWinJsPromise } from 'vs/base/common/async';
|
||||
import { Disposable } from 'vs/workbench/api/node/extHostTypes';
|
||||
import { TrieMap } from 'vs/base/common/map';
|
||||
import { CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { Progress } from 'vs/platform/progress/common/progress';
|
||||
|
||||
class Workspace2 extends Workspace {
|
||||
|
||||
static fromData(data: IWorkspaceData) {
|
||||
return data ? new Workspace2(data) : null;
|
||||
}
|
||||
|
||||
private readonly _folder: vscode.WorkspaceFolder[] = [];
|
||||
private readonly _structure = new TrieMap<vscode.WorkspaceFolder>(s => s.split('/'));
|
||||
|
||||
private constructor(data: IWorkspaceData) {
|
||||
super(data.id, data.name, data.roots);
|
||||
|
||||
// setup the workspace folder data structure
|
||||
this.roots.forEach((uri, index) => {
|
||||
const folder = {
|
||||
name: basename(uri.fsPath),
|
||||
uri,
|
||||
index
|
||||
};
|
||||
this._folder.push(folder);
|
||||
this._structure.insert(folder.uri.toString(), folder);
|
||||
});
|
||||
}
|
||||
|
||||
get folders(): vscode.WorkspaceFolder[] {
|
||||
return this._folder.slice(0);
|
||||
}
|
||||
|
||||
getWorkspaceFolder(uri: URI): vscode.WorkspaceFolder {
|
||||
let str = uri.toString();
|
||||
let folder = this._structure.lookUp(str);
|
||||
if (folder) {
|
||||
// `uri` is a workspace folder so we
|
||||
let parts = str.split('/');
|
||||
while (parts.length) {
|
||||
if (parts.pop()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
str = parts.join('/');
|
||||
}
|
||||
return this._structure.findSubstr(str);
|
||||
}
|
||||
}
|
||||
|
||||
export class ExtHostWorkspace implements ExtHostWorkspaceShape {
|
||||
|
||||
private static _requestIdPool = 0;
|
||||
|
||||
private readonly _onDidChangeWorkspace = new Emitter<vscode.WorkspaceFoldersChangeEvent>();
|
||||
private readonly _proxy: MainThreadWorkspaceShape;
|
||||
private _workspace: Workspace2;
|
||||
|
||||
readonly onDidChangeWorkspace: Event<vscode.WorkspaceFoldersChangeEvent> = this._onDidChangeWorkspace.event;
|
||||
|
||||
constructor(mainContext: IMainContext, data: IWorkspaceData) {
|
||||
this._proxy = mainContext.get(MainContext.MainThreadWorkspace);
|
||||
this._workspace = Workspace2.fromData(data);
|
||||
}
|
||||
|
||||
// --- workspace ---
|
||||
|
||||
get workspace(): Workspace {
|
||||
return this._workspace;
|
||||
}
|
||||
|
||||
getWorkspaceFolders(): vscode.WorkspaceFolder[] {
|
||||
if (!this._workspace) {
|
||||
return undefined;
|
||||
} else {
|
||||
return this._workspace.folders.slice(0);
|
||||
}
|
||||
}
|
||||
|
||||
getWorkspaceFolder(uri: vscode.Uri): vscode.WorkspaceFolder {
|
||||
if (!this._workspace) {
|
||||
return undefined;
|
||||
}
|
||||
return this._workspace.getWorkspaceFolder(<URI>uri);
|
||||
}
|
||||
|
||||
getPath(): string {
|
||||
// this is legacy from the days before having
|
||||
// multi-root and we keep it only alive if there
|
||||
// is just one workspace folder.
|
||||
if (!this._workspace) {
|
||||
return undefined;
|
||||
}
|
||||
const { roots } = this._workspace;
|
||||
if (roots.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
return roots[0].fsPath;
|
||||
}
|
||||
|
||||
getRelativePath(pathOrUri: string | vscode.Uri, includeWorkspace?: boolean): string {
|
||||
|
||||
let path: string;
|
||||
if (typeof pathOrUri === 'string') {
|
||||
path = pathOrUri;
|
||||
} else if (typeof pathOrUri !== 'undefined') {
|
||||
path = pathOrUri.fsPath;
|
||||
}
|
||||
|
||||
if (!path) {
|
||||
return path;
|
||||
}
|
||||
|
||||
const folder = this.getWorkspaceFolder(typeof pathOrUri === 'string'
|
||||
? URI.file(pathOrUri)
|
||||
: pathOrUri
|
||||
);
|
||||
|
||||
if (!folder) {
|
||||
return normalize(path);
|
||||
}
|
||||
|
||||
if (typeof includeWorkspace === 'undefined') {
|
||||
includeWorkspace = this.workspace.roots.length > 1;
|
||||
}
|
||||
|
||||
let result = relative(folder.uri.fsPath, path);
|
||||
if (includeWorkspace) {
|
||||
result = `${folder.name}/${result}`;
|
||||
}
|
||||
return normalize(result);
|
||||
}
|
||||
|
||||
$acceptWorkspaceData(data: IWorkspaceData): void {
|
||||
|
||||
// keep old workspace folder, build new workspace, and
|
||||
// capture new workspace folders. Compute delta between
|
||||
// them send that as event
|
||||
const oldRoots = this._workspace ? this._workspace.folders.sort(ExtHostWorkspace._compareWorkspaceFolder) : [];
|
||||
|
||||
this._workspace = Workspace2.fromData(data);
|
||||
const newRoots = this._workspace ? this._workspace.folders.sort(ExtHostWorkspace._compareWorkspaceFolder) : [];
|
||||
|
||||
const { added, removed } = delta(oldRoots, newRoots, ExtHostWorkspace._compareWorkspaceFolder);
|
||||
|
||||
// {{SQL CARBON EDIT}} - fix build break Argument of type 'Readonly<...
|
||||
this._onDidChangeWorkspace.fire({
|
||||
added: added,
|
||||
removed: removed
|
||||
});
|
||||
}
|
||||
|
||||
private static _compareWorkspaceFolder(a: vscode.WorkspaceFolder, b: vscode.WorkspaceFolder): number {
|
||||
return compare(a.uri.toString(), b.uri.toString());
|
||||
}
|
||||
|
||||
// --- search ---
|
||||
|
||||
findFiles(include: string, exclude: string, maxResults?: number, token?: vscode.CancellationToken): Thenable<vscode.Uri[]> {
|
||||
const requestId = ExtHostWorkspace._requestIdPool++;
|
||||
const result = this._proxy.$startSearch(include, exclude, maxResults, requestId);
|
||||
if (token) {
|
||||
token.onCancellationRequested(() => this._proxy.$cancelSearch(requestId));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
saveAll(includeUntitled?: boolean): Thenable<boolean> {
|
||||
return this._proxy.$saveAll(includeUntitled);
|
||||
}
|
||||
|
||||
appyEdit(edit: vscode.WorkspaceEdit): TPromise<boolean> {
|
||||
|
||||
let resourceEdits: IResourceEdit[] = [];
|
||||
|
||||
let entries = edit.entries();
|
||||
for (let entry of entries) {
|
||||
let [uri, edits] = entry;
|
||||
|
||||
for (let edit of edits) {
|
||||
resourceEdits.push({
|
||||
resource: <URI>uri,
|
||||
newText: edit.newText,
|
||||
newEol: EndOfLine.from(edit.newEol),
|
||||
range: edit.range && fromRange(edit.range)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return this._proxy.$applyWorkspaceEdit(resourceEdits);
|
||||
}
|
||||
|
||||
// --- EXPERIMENT: workspace resolver
|
||||
|
||||
|
||||
private _handlePool = 0;
|
||||
private readonly _fsProvider = new Map<number, vscode.FileSystemProvider>();
|
||||
private readonly _searchSession = new Map<number, CancellationTokenSource>();
|
||||
|
||||
registerFileSystemProvider(authority: string, provider: vscode.FileSystemProvider): vscode.Disposable {
|
||||
const handle = ++this._handlePool;
|
||||
this._fsProvider.set(handle, provider);
|
||||
const reg = provider.onDidChange(e => this._proxy.$onFileSystemChange(handle, <URI>e));
|
||||
this._proxy.$registerFileSystemProvider(handle, authority);
|
||||
return new Disposable(() => {
|
||||
this._fsProvider.delete(handle);
|
||||
reg.dispose();
|
||||
});
|
||||
}
|
||||
|
||||
$resolveFile(handle: number, resource: URI): TPromise<string> {
|
||||
const provider = this._fsProvider.get(handle);
|
||||
return asWinJsPromise(token => provider.resolveContents(resource));
|
||||
}
|
||||
|
||||
$storeFile(handle: number, resource: URI, content: string): TPromise<any> {
|
||||
const provider = this._fsProvider.get(handle);
|
||||
return asWinJsPromise(token => provider.writeContents(resource, content));
|
||||
}
|
||||
|
||||
$startSearch(handle: number, session: number, query: string): void {
|
||||
const provider = this._fsProvider.get(handle);
|
||||
const source = new CancellationTokenSource();
|
||||
const progress = new Progress<any>(chunk => this._proxy.$updateSearchSession(session, chunk));
|
||||
|
||||
this._searchSession.set(session, source);
|
||||
TPromise.wrap(provider.findFiles(query, progress, source.token)).then(() => {
|
||||
this._proxy.$finishSearchSession(session);
|
||||
}, err => {
|
||||
this._proxy.$finishSearchSession(session, err);
|
||||
});
|
||||
}
|
||||
|
||||
$cancelSearch(handle: number, session: number): void {
|
||||
if (this._searchSession.has(session)) {
|
||||
this._searchSession.get(session).cancel();
|
||||
this._searchSession.delete(session);
|
||||
}
|
||||
}
|
||||
}
|
||||
375
src/vs/workbench/browser/actions.ts
Normal file
375
src/vs/workbench/browser/actions.ts
Normal file
@@ -0,0 +1,375 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import types = require('vs/base/common/types');
|
||||
import { Action, IAction } from 'vs/base/common/actions';
|
||||
import { BaseActionItem, Separator } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { ITree, IActionProvider } from 'vs/base/parts/tree/browser/tree';
|
||||
import { IInstantiationService, IConstructorSignature0 } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
||||
/**
|
||||
* The action bar contributor allows to add actions to an actionbar in a given context.
|
||||
*/
|
||||
export class ActionBarContributor {
|
||||
|
||||
/**
|
||||
* Returns true if this contributor has actions for the given context.
|
||||
*/
|
||||
public hasActions(context: any): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of primary actions in the given context.
|
||||
*/
|
||||
public getActions(context: any): IAction[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this contributor has secondary actions for the given context.
|
||||
*/
|
||||
public hasSecondaryActions(context: any): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of secondary actions in the given context.
|
||||
*/
|
||||
public getSecondaryActions(context: any): IAction[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Can return a specific IActionItem to render the given action.
|
||||
*/
|
||||
public getActionItem(context: any, action: Action): BaseActionItem {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Some predefined scopes to contribute actions to
|
||||
*/
|
||||
export const Scope = {
|
||||
|
||||
/**
|
||||
* Actions inside the global activity bar (DEPRECATED)
|
||||
*/
|
||||
GLOBAL: 'global',
|
||||
|
||||
/**
|
||||
* Actions inside viewlets.
|
||||
*/
|
||||
VIEWLET: 'viewlet',
|
||||
|
||||
/**
|
||||
* Actions inside panels.
|
||||
*/
|
||||
PANEL: 'panel',
|
||||
|
||||
/**
|
||||
* Actions inside editors.
|
||||
*/
|
||||
EDITOR: 'editor',
|
||||
|
||||
/**
|
||||
* Actions inside tree widgets.
|
||||
*/
|
||||
VIEWER: 'viewer'
|
||||
};
|
||||
|
||||
/**
|
||||
* The ContributableActionProvider leverages the actionbar contribution model to find actions.
|
||||
*/
|
||||
export class ContributableActionProvider implements IActionProvider {
|
||||
private registry: IActionBarRegistry;
|
||||
|
||||
constructor() {
|
||||
this.registry = Registry.as<IActionBarRegistry>(Extensions.Actionbar);
|
||||
}
|
||||
|
||||
private toContext(tree: ITree, element: any): any {
|
||||
return {
|
||||
viewer: tree,
|
||||
element: element
|
||||
};
|
||||
}
|
||||
|
||||
public hasActions(tree: ITree, element: any): boolean {
|
||||
let context = this.toContext(tree, element);
|
||||
|
||||
let contributors = this.registry.getActionBarContributors(Scope.VIEWER);
|
||||
for (let i = 0; i < contributors.length; i++) {
|
||||
let contributor = contributors[i];
|
||||
if (contributor.hasActions(context)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public getActions(tree: ITree, element: any): TPromise<IAction[]> {
|
||||
let actions: IAction[] = [];
|
||||
let context = this.toContext(tree, element);
|
||||
|
||||
// Collect Actions
|
||||
let contributors = this.registry.getActionBarContributors(Scope.VIEWER);
|
||||
for (let i = 0; i < contributors.length; i++) {
|
||||
let contributor = contributors[i];
|
||||
if (contributor.hasActions(context)) {
|
||||
actions.push(...contributor.getActions(context));
|
||||
}
|
||||
}
|
||||
|
||||
return TPromise.as(prepareActions(actions));
|
||||
}
|
||||
|
||||
public hasSecondaryActions(tree: ITree, element: any): boolean {
|
||||
let context = this.toContext(tree, element);
|
||||
|
||||
let contributors = this.registry.getActionBarContributors(Scope.VIEWER);
|
||||
for (let i = 0; i < contributors.length; i++) {
|
||||
let contributor = contributors[i];
|
||||
if (contributor.hasSecondaryActions(context)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public getSecondaryActions(tree: ITree, element: any): TPromise<IAction[]> {
|
||||
let actions: IAction[] = [];
|
||||
let context = this.toContext(tree, element);
|
||||
|
||||
// Collect Actions
|
||||
let contributors = this.registry.getActionBarContributors(Scope.VIEWER);
|
||||
for (let i = 0; i < contributors.length; i++) {
|
||||
let contributor = contributors[i];
|
||||
if (contributor.hasSecondaryActions(context)) {
|
||||
actions.push(...contributor.getSecondaryActions(context));
|
||||
}
|
||||
}
|
||||
|
||||
return TPromise.as(prepareActions(actions));
|
||||
}
|
||||
|
||||
public getActionItem(tree: ITree, element: any, action: Action): BaseActionItem {
|
||||
let contributors = this.registry.getActionBarContributors(Scope.VIEWER);
|
||||
let context = this.toContext(tree, element);
|
||||
|
||||
for (let i = contributors.length - 1; i >= 0; i--) {
|
||||
let contributor = contributors[i];
|
||||
|
||||
let itemProvider = contributor.getActionItem(context, action);
|
||||
if (itemProvider) {
|
||||
return itemProvider;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function used in parts to massage actions before showing in action areas
|
||||
export function prepareActions(actions: IAction[]): IAction[] {
|
||||
if (!actions.length) {
|
||||
return actions;
|
||||
}
|
||||
|
||||
// Patch order if not provided
|
||||
for (let l = 0; l < actions.length; l++) {
|
||||
let a = <any>actions[l];
|
||||
if (types.isUndefinedOrNull(a.order)) {
|
||||
a.order = l;
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by order
|
||||
actions = actions.sort((first: Action, second: Action) => {
|
||||
let firstOrder = first.order;
|
||||
let secondOrder = second.order;
|
||||
if (firstOrder < secondOrder) {
|
||||
return -1;
|
||||
} else if (firstOrder > secondOrder) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
|
||||
// Clean up leading separators
|
||||
let firstIndexOfAction = -1;
|
||||
for (let i = 0; i < actions.length; i++) {
|
||||
if (actions[i].id === Separator.ID) {
|
||||
continue;
|
||||
}
|
||||
|
||||
firstIndexOfAction = i;
|
||||
break;
|
||||
}
|
||||
|
||||
if (firstIndexOfAction === -1) {
|
||||
return [];
|
||||
}
|
||||
|
||||
actions = actions.slice(firstIndexOfAction);
|
||||
|
||||
// Clean up trailing separators
|
||||
for (let h = actions.length - 1; h >= 0; h--) {
|
||||
let isSeparator = actions[h].id === Separator.ID;
|
||||
if (isSeparator) {
|
||||
actions.splice(h, 1);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up separator duplicates
|
||||
let foundAction = false;
|
||||
for (let k = actions.length - 1; k >= 0; k--) {
|
||||
let isSeparator = actions[k].id === Separator.ID;
|
||||
if (isSeparator && !foundAction) {
|
||||
actions.splice(k, 1);
|
||||
} else if (!isSeparator) {
|
||||
foundAction = true;
|
||||
} else if (isSeparator) {
|
||||
foundAction = false;
|
||||
}
|
||||
}
|
||||
|
||||
return actions;
|
||||
}
|
||||
|
||||
export const Extensions = {
|
||||
Actionbar: 'workbench.contributions.actionbar'
|
||||
};
|
||||
|
||||
export interface IActionBarRegistry {
|
||||
|
||||
/**
|
||||
* Goes through all action bar contributors and asks them for contributed actions for
|
||||
* the provided scope and context. Supports primary actions.
|
||||
*/
|
||||
getActionBarActionsForContext(scope: string, context: any): IAction[];
|
||||
|
||||
/**
|
||||
* Goes through all action bar contributors and asks them for contributed actions for
|
||||
* the provided scope and context. Supports secondary actions.
|
||||
*/
|
||||
getSecondaryActionBarActionsForContext(scope: string, context: any): IAction[];
|
||||
|
||||
/**
|
||||
* Goes through all action bar contributors and asks them for contributed action item for
|
||||
* the provided scope and context.
|
||||
*/
|
||||
getActionItemForContext(scope: string, context: any, action: Action): BaseActionItem;
|
||||
|
||||
/**
|
||||
* Registers an Actionbar contributor. It will be called to contribute actions to all the action bars
|
||||
* that are used in the Workbench in the given scope.
|
||||
*/
|
||||
registerActionBarContributor(scope: string, ctor: IConstructorSignature0<ActionBarContributor>): void;
|
||||
|
||||
/**
|
||||
* Returns an array of registered action bar contributors known to the workbench for the given scope.
|
||||
*/
|
||||
getActionBarContributors(scope: string): ActionBarContributor[];
|
||||
|
||||
setInstantiationService(service: IInstantiationService): void;
|
||||
}
|
||||
|
||||
class ActionBarRegistry implements IActionBarRegistry {
|
||||
private actionBarContributorConstructors: { scope: string; ctor: IConstructorSignature0<ActionBarContributor>; }[] = [];
|
||||
private actionBarContributorInstances: { [scope: string]: ActionBarContributor[] } = Object.create(null);
|
||||
private instantiationService: IInstantiationService;
|
||||
|
||||
public setInstantiationService(service: IInstantiationService): void {
|
||||
this.instantiationService = service;
|
||||
|
||||
while (this.actionBarContributorConstructors.length > 0) {
|
||||
let entry = this.actionBarContributorConstructors.shift();
|
||||
this.createActionBarContributor(entry.scope, entry.ctor);
|
||||
}
|
||||
}
|
||||
|
||||
private createActionBarContributor(scope: string, ctor: IConstructorSignature0<ActionBarContributor>): void {
|
||||
const instance = this.instantiationService.createInstance(ctor);
|
||||
let target = this.actionBarContributorInstances[scope];
|
||||
if (!target) {
|
||||
target = this.actionBarContributorInstances[scope] = [];
|
||||
}
|
||||
target.push(instance);
|
||||
}
|
||||
|
||||
private getContributors(scope: string): ActionBarContributor[] {
|
||||
return this.actionBarContributorInstances[scope] || [];
|
||||
}
|
||||
|
||||
public getActionBarActionsForContext(scope: string, context: any): IAction[] {
|
||||
let actions: IAction[] = [];
|
||||
|
||||
// Go through contributors for scope
|
||||
this.getContributors(scope).forEach((contributor: ActionBarContributor) => {
|
||||
|
||||
// Primary Actions
|
||||
if (contributor.hasActions(context)) {
|
||||
actions.push(...contributor.getActions(context));
|
||||
}
|
||||
});
|
||||
|
||||
return actions;
|
||||
}
|
||||
|
||||
public getSecondaryActionBarActionsForContext(scope: string, context: any): IAction[] {
|
||||
let actions: IAction[] = [];
|
||||
|
||||
// Go through contributors
|
||||
this.getContributors(scope).forEach((contributor: ActionBarContributor) => {
|
||||
|
||||
// Secondary Actions
|
||||
if (contributor.hasSecondaryActions(context)) {
|
||||
actions.push(...contributor.getSecondaryActions(context));
|
||||
}
|
||||
});
|
||||
|
||||
return actions;
|
||||
}
|
||||
|
||||
public getActionItemForContext(scope: string, context: any, action: Action): BaseActionItem {
|
||||
let contributors = this.getContributors(scope);
|
||||
for (let i = 0; i < contributors.length; i++) {
|
||||
let contributor = contributors[i];
|
||||
let item = contributor.getActionItem(context, action);
|
||||
if (item) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public registerActionBarContributor(scope: string, ctor: IConstructorSignature0<ActionBarContributor>): void {
|
||||
if (!this.instantiationService) {
|
||||
this.actionBarContributorConstructors.push({
|
||||
scope: scope,
|
||||
ctor: ctor
|
||||
});
|
||||
} else {
|
||||
this.createActionBarContributor(scope, ctor);
|
||||
}
|
||||
}
|
||||
|
||||
public getActionBarContributors(scope: string): ActionBarContributor[] {
|
||||
return this.getContributors(scope).slice(0);
|
||||
}
|
||||
}
|
||||
|
||||
Registry.add(Extensions.Actionbar, new ActionBarRegistry());
|
||||
93
src/vs/workbench/browser/actions/configureLocale.ts
Normal file
93
src/vs/workbench/browser/actions/configureLocale.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import nls = require('vs/nls');
|
||||
import * as Path from 'vs/base/common/paths';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import * as Labels from 'vs/base/common/labels';
|
||||
import * as Platform from 'vs/base/common/platform';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actionRegistry';
|
||||
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { IEditor } from 'vs/platform/editor/common/editor';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
|
||||
|
||||
import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry';
|
||||
import { IJSONSchema } from 'vs/base/common/jsonSchema';
|
||||
|
||||
class ConfigureLocaleAction extends Action {
|
||||
public static ID = 'workbench.action.configureLocale';
|
||||
public static LABEL = nls.localize('configureLocale', "Configure Language");
|
||||
|
||||
private static DEFAULT_CONTENT: string = [
|
||||
'{',
|
||||
`\t// ${nls.localize('displayLanguage', 'Defines VSCode\'s display language.')}`,
|
||||
`\t// ${nls.localize('doc', 'See {0} for a list of supported languages.', 'https://go.microsoft.com/fwlink/?LinkId=761051')}`,
|
||||
`\t// ${nls.localize('restart', 'Changing the value requires restarting VSCode.')}`,
|
||||
`\t"locale":"${Platform.language}"`,
|
||||
'}'
|
||||
].join('\n');
|
||||
|
||||
constructor(id, label,
|
||||
@IFileService private fileService: IFileService,
|
||||
@IWorkspaceContextService private contextService: IWorkspaceContextService,
|
||||
@IEnvironmentService private environmentService: IEnvironmentService,
|
||||
@IWorkbenchEditorService private editorService: IWorkbenchEditorService
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
public run(event?: any): TPromise<IEditor> {
|
||||
const file = URI.file(Path.join(this.environmentService.appSettingsHome, 'locale.json'));
|
||||
return this.fileService.resolveFile(file).then(null, (error) => {
|
||||
return this.fileService.createFile(file, ConfigureLocaleAction.DEFAULT_CONTENT);
|
||||
}).then((stat) => {
|
||||
if (!stat) {
|
||||
return undefined;
|
||||
}
|
||||
return this.editorService.openEditor({
|
||||
resource: stat.resource,
|
||||
options: {
|
||||
forceOpen: true
|
||||
}
|
||||
});
|
||||
}, (error) => {
|
||||
throw new Error(nls.localize('fail.createSettings', "Unable to create '{0}' ({1}).", Labels.getPathLabel(file, this.contextService), error));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const registry = Registry.as<IWorkbenchActionRegistry>(Extensions.WorkbenchActions);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(ConfigureLocaleAction, ConfigureLocaleAction.ID, ConfigureLocaleAction.LABEL), 'Configure Language');
|
||||
|
||||
const schemaId = 'vscode://schemas/locale';
|
||||
// Keep en-US since we generated files with that content.
|
||||
const schema: IJSONSchema =
|
||||
{
|
||||
id: schemaId,
|
||||
description: 'Locale Definition file',
|
||||
type: 'object',
|
||||
default: {
|
||||
'locale': 'en'
|
||||
},
|
||||
required: ['locale'],
|
||||
properties: {
|
||||
locale: {
|
||||
type: 'string',
|
||||
enum: ['de', 'en', 'en-US', 'es', 'fr', 'it', 'ja', 'ko', 'ru', 'zh-CN', 'zh-TW'],
|
||||
description: nls.localize('JsonSchema.locale', 'The UI Language to use.')
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const jsonRegistry = Registry.as<IJSONContributionRegistry>(JSONExtensions.JSONContribution);
|
||||
jsonRegistry.registerSchema(schemaId, schema);
|
||||
13
src/vs/workbench/browser/actions/media/actions.css
Normal file
13
src/vs/workbench/browser/actions/media/actions.css
Normal file
@@ -0,0 +1,13 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.vs .monaco-workbench .toggle-editor-layout {
|
||||
background-image: url('editor-layout.svg');
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench .toggle-editor-layout,
|
||||
.hc-black .monaco-workbench .toggle-editor-layout {
|
||||
background-image: url('editor-layout-inverse.svg') !important;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><style>.icon-canvas-transparent{opacity:0;fill:#2d2d30}.icon-vs-out{fill:#2d2d30}.icon-vs-bg{fill:#c5c5c5}.icon-vs-fg{fill:#2b282e}</style><path class="icon-canvas-transparent" d="M16 16H0V0h16v16z" id="canvas"/><path class="icon-vs-out" d="M0 16V0h11v6h5v10H0z" id="outline" style="display: none;"/><path class="icon-vs-fg" d="M4 14H2V4h7v3H4v7zm10 0H5v-4h9v4z" id="iconFg" style="display: none;"/><path class="icon-vs-bg" d="M10 7V1H1v14h14V7h-5zm-6 7H2V4h7v3H4v7zm10 0H5v-4h9v4z" id="iconBg"/></svg>
|
||||
|
After Width: | Height: | Size: 562 B |
1
src/vs/workbench/browser/actions/media/editor-layout.svg
Normal file
1
src/vs/workbench/browser/actions/media/editor-layout.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><style>.icon-canvas-transparent{opacity:0;fill:#f6f6f6}.icon-vs-out{fill:#f6f6f6}.icon-vs-bg{fill:#424242}.icon-vs-fg{fill:#f0eff1}</style><path class="icon-canvas-transparent" d="M16 16H0V0h16v16z" id="canvas"/><path class="icon-vs-out" d="M0 16V0h11v6h5v10H0z" id="outline" style="display: none;"/><path class="icon-vs-fg" d="M4 14H2V4h7v3H4v7zm10 0H5v-4h9v4z" id="iconFg" style="display: none;"/><path class="icon-vs-bg" d="M10 7V1H1v14h14V7h-5zm-6 7H2V4h7v3H4v7zm10 0H5v-4h9v4z" id="iconBg"/></svg>
|
||||
|
After Width: | Height: | Size: 562 B |
@@ -0,0 +1,45 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import nls = require('vs/nls');
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
|
||||
import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actionRegistry';
|
||||
import { IConfigurationEditingService, ConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditing';
|
||||
import { IPartService, Parts } from 'vs/workbench/services/part/common/partService';
|
||||
|
||||
export class ToggleActivityBarVisibilityAction extends Action {
|
||||
|
||||
public static ID = 'workbench.action.toggleActivityBarVisibility';
|
||||
public static LABEL = nls.localize('toggleActivityBar', "Toggle Activity Bar Visibility");
|
||||
|
||||
private static activityBarVisibleKey = 'workbench.activityBar.visible';
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IPartService private partService: IPartService,
|
||||
@IConfigurationEditingService private configurationEditingService: IConfigurationEditingService
|
||||
) {
|
||||
super(id, label);
|
||||
|
||||
this.enabled = !!this.partService;
|
||||
}
|
||||
|
||||
public run(): TPromise<any> {
|
||||
const visibility = this.partService.isVisible(Parts.ACTIVITYBAR_PART);
|
||||
const newVisibilityValue = !visibility;
|
||||
|
||||
this.configurationEditingService.writeConfiguration(ConfigurationTarget.USER, { key: ToggleActivityBarVisibilityAction.activityBarVisibleKey, value: newVisibilityValue });
|
||||
|
||||
return TPromise.as(null);
|
||||
}
|
||||
}
|
||||
|
||||
const registry = Registry.as<IWorkbenchActionRegistry>(Extensions.WorkbenchActions);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleActivityBarVisibilityAction, ToggleActivityBarVisibilityAction.ID, ToggleActivityBarVisibilityAction.LABEL), 'View: Toggle Activity Bar Visibility', nls.localize('view', "View"));
|
||||
84
src/vs/workbench/browser/actions/toggleEditorLayout.ts
Normal file
84
src/vs/workbench/browser/actions/toggleEditorLayout.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import 'vs/css!./media/actions';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import nls = require('vs/nls');
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
|
||||
import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actionRegistry';
|
||||
import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { IEditorGroupService, GroupOrientation } from 'vs/workbench/services/group/common/groupService';
|
||||
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
||||
export class ToggleEditorLayoutAction extends Action {
|
||||
|
||||
public static ID = 'workbench.action.toggleEditorGroupLayout';
|
||||
public static LABEL = nls.localize('toggleEditorGroupLayout', "Toggle Editor Group Vertical/Horizontal Layout");
|
||||
|
||||
private toDispose: IDisposable[];
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IEditorGroupService private editorGroupService: IEditorGroupService
|
||||
) {
|
||||
super(id, label);
|
||||
|
||||
this.toDispose = [];
|
||||
|
||||
this.class = 'toggle-editor-layout';
|
||||
this.updateEnablement();
|
||||
this.updateLabel();
|
||||
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
this.toDispose.push(this.editorGroupService.onEditorsChanged(() => this.updateEnablement()));
|
||||
this.toDispose.push(this.editorGroupService.onGroupOrientationChanged(() => this.updateLabel()));
|
||||
}
|
||||
|
||||
private updateLabel(): void {
|
||||
const editorGroupLayoutVertical = (this.editorGroupService.getGroupOrientation() !== 'horizontal');
|
||||
this.label = editorGroupLayoutVertical ? nls.localize('horizontalLayout', "Horizontal Editor Group Layout") : nls.localize('verticalLayout', "Vertical Editor Group Layout");
|
||||
}
|
||||
|
||||
private updateEnablement(): void {
|
||||
this.enabled = this.editorGroupService.getStacksModel().groups.length > 0;
|
||||
}
|
||||
|
||||
public run(): TPromise<any> {
|
||||
const groupOrientiation = this.editorGroupService.getGroupOrientation();
|
||||
const newGroupOrientation: GroupOrientation = (groupOrientiation === 'vertical') ? 'horizontal' : 'vertical';
|
||||
|
||||
this.editorGroupService.setGroupOrientation(newGroupOrientation);
|
||||
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this.toDispose = dispose(this.toDispose);
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
CommandsRegistry.registerCommand('_workbench.editor.setGroupOrientation', function (accessor: ServicesAccessor, args: [GroupOrientation]) {
|
||||
const editorGroupService = accessor.get(IEditorGroupService);
|
||||
const [orientation] = args;
|
||||
|
||||
editorGroupService.setGroupOrientation(orientation);
|
||||
|
||||
return TPromise.as<void>(null);
|
||||
});
|
||||
|
||||
const registry = Registry.as<IWorkbenchActionRegistry>(Extensions.WorkbenchActions);
|
||||
const group = nls.localize('view', "View");
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleEditorLayoutAction, ToggleEditorLayoutAction.ID, ToggleEditorLayoutAction.LABEL, { primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_1, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_1 } }), 'View: Toggle Editor Group Vertical/Horizontal Layout', group);
|
||||
45
src/vs/workbench/browser/actions/toggleSidebarPosition.ts
Normal file
45
src/vs/workbench/browser/actions/toggleSidebarPosition.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import nls = require('vs/nls');
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
|
||||
import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actionRegistry';
|
||||
import { IConfigurationEditingService, ConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditing';
|
||||
import { IPartService, Position } from 'vs/workbench/services/part/common/partService';
|
||||
|
||||
export class ToggleSidebarPositionAction extends Action {
|
||||
|
||||
public static ID = 'workbench.action.toggleSidebarPosition';
|
||||
public static LABEL = nls.localize('toggleLocation', "Toggle Side Bar Location");
|
||||
|
||||
private static sidebarPositionConfigurationKey = 'workbench.sideBar.location';
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IPartService private partService: IPartService,
|
||||
@IConfigurationEditingService private configurationEditingService: IConfigurationEditingService
|
||||
) {
|
||||
super(id, label);
|
||||
|
||||
this.enabled = !!this.partService && !!this.configurationEditingService;
|
||||
}
|
||||
|
||||
public run(): TPromise<any> {
|
||||
const position = this.partService.getSideBarPosition();
|
||||
const newPositionValue = (position === Position.LEFT) ? 'right' : 'left';
|
||||
|
||||
this.configurationEditingService.writeConfiguration(ConfigurationTarget.USER, { key: ToggleSidebarPositionAction.sidebarPositionConfigurationKey, value: newPositionValue });
|
||||
|
||||
return TPromise.as(null);
|
||||
}
|
||||
}
|
||||
|
||||
const registry = Registry.as<IWorkbenchActionRegistry>(Extensions.WorkbenchActions);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleSidebarPositionAction, ToggleSidebarPositionAction.ID, ToggleSidebarPositionAction.LABEL), 'View: Toggle Side Bar Location', nls.localize('view', "View"));
|
||||
38
src/vs/workbench/browser/actions/toggleSidebarVisibility.ts
Normal file
38
src/vs/workbench/browser/actions/toggleSidebarVisibility.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import nls = require('vs/nls');
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
|
||||
import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actionRegistry';
|
||||
import { IPartService, Parts } from 'vs/workbench/services/part/common/partService';
|
||||
import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
|
||||
|
||||
export class ToggleSidebarVisibilityAction extends Action {
|
||||
|
||||
public static ID = 'workbench.action.toggleSidebarVisibility';
|
||||
public static LABEL = nls.localize('toggleSidebar', "Toggle Side Bar Visibility");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IPartService private partService: IPartService
|
||||
) {
|
||||
super(id, label);
|
||||
|
||||
this.enabled = !!this.partService;
|
||||
}
|
||||
|
||||
public run(): TPromise<any> {
|
||||
const hideSidebar = this.partService.isVisible(Parts.SIDEBAR_PART);
|
||||
return this.partService.setSideBarHidden(hideSidebar);
|
||||
}
|
||||
}
|
||||
|
||||
const registry = Registry.as<IWorkbenchActionRegistry>(Extensions.WorkbenchActions);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleSidebarVisibilityAction, ToggleSidebarVisibilityAction.ID, ToggleSidebarVisibilityAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_B }), 'View: Toggle Side Bar Visibility', nls.localize('view', "View"));
|
||||
@@ -0,0 +1,45 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import nls = require('vs/nls');
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
|
||||
import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actionRegistry';
|
||||
import { IConfigurationEditingService, ConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditing';
|
||||
import { IPartService, Parts } from 'vs/workbench/services/part/common/partService';
|
||||
|
||||
export class ToggleStatusbarVisibilityAction extends Action {
|
||||
|
||||
public static ID = 'workbench.action.toggleStatusbarVisibility';
|
||||
public static LABEL = nls.localize('toggleStatusbar', "Toggle Status Bar Visibility");
|
||||
|
||||
private static statusbarVisibleKey = 'workbench.statusBar.visible';
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IPartService private partService: IPartService,
|
||||
@IConfigurationEditingService private configurationEditingService: IConfigurationEditingService
|
||||
) {
|
||||
super(id, label);
|
||||
|
||||
this.enabled = !!this.partService;
|
||||
}
|
||||
|
||||
public run(): TPromise<any> {
|
||||
const visibility = this.partService.isVisible(Parts.STATUSBAR_PART);
|
||||
const newVisibilityValue = !visibility;
|
||||
|
||||
this.configurationEditingService.writeConfiguration(ConfigurationTarget.USER, { key: ToggleStatusbarVisibilityAction.statusbarVisibleKey, value: newVisibilityValue });
|
||||
|
||||
return TPromise.as(null);
|
||||
}
|
||||
}
|
||||
|
||||
const registry = Registry.as<IWorkbenchActionRegistry>(Extensions.WorkbenchActions);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleStatusbarVisibilityAction, ToggleStatusbarVisibilityAction.ID, ToggleStatusbarVisibilityAction.LABEL), 'View: Toggle Status Bar Visibility', nls.localize('view', "View"));
|
||||
36
src/vs/workbench/browser/actions/toggleZenMode.ts
Normal file
36
src/vs/workbench/browser/actions/toggleZenMode.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import nls = require('vs/nls');
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { KeyCode, KeyMod, KeyChord } from 'vs/base/common/keyCodes';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
|
||||
import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actionRegistry';
|
||||
import { IPartService } from 'vs/workbench/services/part/common/partService';
|
||||
|
||||
class ToggleZenMode extends Action {
|
||||
|
||||
public static ID = 'workbench.action.toggleZenMode';
|
||||
public static LABEL = nls.localize('toggleZenMode', "Toggle Zen Mode");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IPartService private partService: IPartService
|
||||
) {
|
||||
super(id, label);
|
||||
this.enabled = !!this.partService;
|
||||
}
|
||||
|
||||
public run(): TPromise<any> {
|
||||
this.partService.toggleZenMode();
|
||||
return TPromise.as(null);
|
||||
}
|
||||
}
|
||||
|
||||
const registry = Registry.as<IWorkbenchActionRegistry>(Extensions.WorkbenchActions);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleZenMode, ToggleZenMode.ID, ToggleZenMode.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_Z) }), 'View: Toggle Zen Mode', nls.localize('view', "View"));
|
||||
290
src/vs/workbench/browser/actions/workspaceActions.ts
Normal file
290
src/vs/workbench/browser/actions/workspaceActions.ts
Normal file
@@ -0,0 +1,290 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import nls = require('vs/nls');
|
||||
import { distinct } from 'vs/base/common/arrays';
|
||||
import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows';
|
||||
import { ITelemetryData } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IWorkspacesService, WORKSPACE_FILTER } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { IMessageService, Severity } from 'vs/platform/message/common/message';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { isLinux } from 'vs/base/common/platform';
|
||||
import { dirname } from 'vs/base/common/paths';
|
||||
import { mnemonicButtonLabel } from 'vs/base/common/labels';
|
||||
import { isParent } from 'vs/platform/files/common/files';
|
||||
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
|
||||
export class OpenFolderAction extends Action {
|
||||
|
||||
static ID = 'workbench.action.files.openFolder';
|
||||
static LABEL = nls.localize('openFolder', "Open Folder...");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IWindowService private windowService: IWindowService
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(event?: any, data?: ITelemetryData): TPromise<any> {
|
||||
return this.windowService.pickFolderAndOpen({ telemetryExtraData: data });
|
||||
}
|
||||
}
|
||||
|
||||
export class OpenFileFolderAction extends Action {
|
||||
|
||||
static ID = 'workbench.action.files.openFileFolder';
|
||||
static LABEL = nls.localize('openFileFolder', "Open...");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IWindowService private windowService: IWindowService
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(event?: any, data?: ITelemetryData): TPromise<any> {
|
||||
return this.windowService.pickFileFolderAndOpen({ telemetryExtraData: data });
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class BaseWorkspacesAction extends Action {
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
protected windowService: IWindowService,
|
||||
protected environmentService: IEnvironmentService,
|
||||
protected contextService: IWorkspaceContextService
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
protected pickFolders(buttonLabel: string, title: string): string[] {
|
||||
const workspace = this.contextService.getWorkspace();
|
||||
let defaultPath: string;
|
||||
if (workspace && workspace.roots.length > 0) {
|
||||
defaultPath = dirname(workspace.roots[0].fsPath); // pick the parent of the first root by default
|
||||
}
|
||||
|
||||
return this.windowService.showOpenDialog({
|
||||
buttonLabel,
|
||||
title,
|
||||
properties: ['multiSelections', 'openDirectory', 'createDirectory'],
|
||||
defaultPath
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class AddRootFolderAction extends BaseWorkspacesAction {
|
||||
|
||||
static ID = 'workbench.action.addRootFolder';
|
||||
static LABEL = nls.localize('addFolderToWorkspace', "Add Folder to Workspace...");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IWindowService windowService: IWindowService,
|
||||
@IWorkspaceContextService contextService: IWorkspaceContextService,
|
||||
@IEnvironmentService environmentService: IEnvironmentService,
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@IWorkspaceEditingService private workspaceEditingService: IWorkspaceEditingService,
|
||||
@IViewletService private viewletService: IViewletService
|
||||
) {
|
||||
super(id, label, windowService, environmentService, contextService);
|
||||
}
|
||||
|
||||
public run(): TPromise<any> {
|
||||
if (!this.contextService.hasWorkspace()) {
|
||||
return this.instantiationService.createInstance(NewWorkspaceAction, NewWorkspaceAction.ID, NewWorkspaceAction.LABEL, []).run();
|
||||
}
|
||||
|
||||
if (this.contextService.hasFolderWorkspace()) {
|
||||
return this.instantiationService.createInstance(NewWorkspaceAction, NewWorkspaceAction.ID, NewWorkspaceAction.LABEL, this.contextService.getWorkspace().roots).run();
|
||||
}
|
||||
|
||||
const folders = super.pickFolders(mnemonicButtonLabel(nls.localize({ key: 'add', comment: ['&& denotes a mnemonic'] }, "&&Add")), nls.localize('addFolderToWorkspaceTitle', "Add Folder to Workspace"));
|
||||
if (!folders || !folders.length) {
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
return this.workspaceEditingService.addRoots(folders.map(folder => URI.file(folder))).then(() => {
|
||||
return this.viewletService.openViewlet(this.viewletService.getDefaultViewletId(), true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class NewWorkspaceAction extends BaseWorkspacesAction {
|
||||
|
||||
static ID = 'workbench.action.newWorkspace';
|
||||
static LABEL = nls.localize('newWorkspace', "New Workspace...");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
private presetRoots: URI[],
|
||||
@IWindowService windowService: IWindowService,
|
||||
@IWorkspaceContextService contextService: IWorkspaceContextService,
|
||||
@IEnvironmentService environmentService: IEnvironmentService,
|
||||
@IWorkspacesService protected workspacesService: IWorkspacesService,
|
||||
@IWindowsService protected windowsService: IWindowsService,
|
||||
) {
|
||||
super(id, label, windowService, environmentService, contextService);
|
||||
}
|
||||
|
||||
public run(): TPromise<any> {
|
||||
const folders = super.pickFolders(mnemonicButtonLabel(nls.localize({ key: 'select', comment: ['&& denotes a mnemonic'] }, "&&Select")), nls.localize('selectWorkspace', "Select Folders for Workspace"));
|
||||
if (folders && folders.length) {
|
||||
return this.createWorkspace([...this.presetRoots, ...folders.map(folder => URI.file(folder))]);
|
||||
}
|
||||
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
private createWorkspace(folders: URI[]): TPromise<void> {
|
||||
const workspaceFolders = distinct(folders.map(folder => folder.fsPath), folder => isLinux ? folder : folder.toLowerCase());
|
||||
|
||||
return this.windowService.createAndOpenWorkspace(workspaceFolders);
|
||||
}
|
||||
}
|
||||
|
||||
export class RemoveRootFolderAction extends Action {
|
||||
|
||||
static ID = 'workbench.action.removeRootFolder';
|
||||
static LABEL = nls.localize('removeFolderFromWorkspace', "Remove Folder from Workspace");
|
||||
|
||||
constructor(
|
||||
private rootUri: URI,
|
||||
id: string,
|
||||
label: string,
|
||||
@IWorkspaceEditingService private workspaceEditingService: IWorkspaceEditingService
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
public run(): TPromise<any> {
|
||||
return this.workspaceEditingService.removeRoots([this.rootUri]);
|
||||
}
|
||||
}
|
||||
|
||||
export class SaveWorkspaceAsAction extends BaseWorkspacesAction {
|
||||
|
||||
static ID = 'workbench.action.saveWorkspaceAs';
|
||||
static LABEL = nls.localize('saveWorkspaceAsAction', "Save Workspace As...");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IWindowService windowService: IWindowService,
|
||||
@IEnvironmentService environmentService: IEnvironmentService,
|
||||
@IWorkspaceContextService contextService: IWorkspaceContextService,
|
||||
@IWorkspacesService protected workspacesService: IWorkspacesService,
|
||||
@IWindowsService private windowsService: IWindowsService,
|
||||
@IMessageService private messageService: IMessageService
|
||||
) {
|
||||
super(id, label, windowService, environmentService, contextService);
|
||||
}
|
||||
|
||||
public run(): TPromise<any> {
|
||||
if (!this.contextService.hasWorkspace()) {
|
||||
this.messageService.show(Severity.Info, nls.localize('saveEmptyWorkspaceNotSupported', "Please open a workspace first to save."));
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
const configPath = this.getNewWorkspaceConfigPath();
|
||||
if (configPath) {
|
||||
if (this.contextService.hasFolderWorkspace()) {
|
||||
return this.saveFolderWorkspace(configPath);
|
||||
}
|
||||
|
||||
if (this.contextService.hasMultiFolderWorkspace()) {
|
||||
return this.saveWorkspace(configPath);
|
||||
}
|
||||
}
|
||||
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
private saveWorkspace(configPath: string): TPromise<void> {
|
||||
return this.windowService.saveAndOpenWorkspace(configPath);
|
||||
}
|
||||
|
||||
private saveFolderWorkspace(configPath: string): TPromise<void> {
|
||||
const workspaceFolders = this.contextService.getWorkspace().roots.map(root => root.fsPath);
|
||||
|
||||
return this.windowService.createAndOpenWorkspace(workspaceFolders, configPath);
|
||||
}
|
||||
|
||||
private getNewWorkspaceConfigPath(): string {
|
||||
const workspace = this.contextService.getWorkspace();
|
||||
let defaultPath: string;
|
||||
if (this.contextService.hasMultiFolderWorkspace() && !this.isUntitledWorkspace(workspace.configuration.fsPath)) {
|
||||
defaultPath = workspace.configuration.fsPath;
|
||||
} else if (workspace && workspace.roots.length > 0) {
|
||||
defaultPath = dirname(workspace.roots[0].fsPath); // pick the parent of the first root by default
|
||||
}
|
||||
|
||||
return this.windowService.showSaveDialog({
|
||||
buttonLabel: mnemonicButtonLabel(nls.localize({ key: 'save', comment: ['&& denotes a mnemonic'] }, "&&Save")),
|
||||
title: nls.localize('saveWorkspace', "Save Workspace"),
|
||||
filters: WORKSPACE_FILTER,
|
||||
defaultPath
|
||||
});
|
||||
}
|
||||
|
||||
private isUntitledWorkspace(path: string): boolean {
|
||||
return isParent(path, this.environmentService.workspacesHome, !isLinux /* ignore case */);
|
||||
}
|
||||
}
|
||||
|
||||
export class OpenWorkspaceAction extends Action {
|
||||
|
||||
static ID = 'workbench.action.openWorkspace';
|
||||
static LABEL = nls.localize('openWorkspaceAction', "Open Workspace...");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IWindowService private windowService: IWindowService,
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
public run(): TPromise<any> {
|
||||
return this.windowService.openWorkspace();
|
||||
}
|
||||
}
|
||||
|
||||
export class OpenWorkspaceConfigFileAction extends Action {
|
||||
|
||||
public static ID = 'workbench.action.openWorkspaceConfigFile';
|
||||
public static LABEL = nls.localize('openWorkspaceConfigFile', "Open Workspace Configuration File");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IWorkspaceContextService private workspaceContextService: IWorkspaceContextService,
|
||||
@IWorkbenchEditorService private editorService: IWorkbenchEditorService
|
||||
) {
|
||||
super(id, label);
|
||||
|
||||
this.enabled = this.workspaceContextService.hasMultiFolderWorkspace();
|
||||
}
|
||||
|
||||
public run(): TPromise<any> {
|
||||
return this.editorService.openEditor({ resource: this.workspaceContextService.getWorkspace().configuration });
|
||||
}
|
||||
}
|
||||
42
src/vs/workbench/browser/activity.ts
Normal file
42
src/vs/workbench/browser/activity.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IAction } from 'vs/base/common/actions';
|
||||
import { IConstructorSignature0 } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
||||
export interface IActivity {
|
||||
id: string;
|
||||
name: string;
|
||||
cssClass: string;
|
||||
}
|
||||
|
||||
export interface IGlobalActivity extends IActivity {
|
||||
getActions(): IAction[];
|
||||
}
|
||||
|
||||
export const GlobalActivityExtensions = 'workbench.contributions.globalActivities';
|
||||
|
||||
export interface IGlobalActivityRegistry {
|
||||
registerActivity(descriptor: IConstructorSignature0<IGlobalActivity>): void;
|
||||
getActivities(): IConstructorSignature0<IGlobalActivity>[];
|
||||
}
|
||||
|
||||
export class GlobalActivityRegistry implements IGlobalActivityRegistry {
|
||||
|
||||
private activityDescriptors = new Set<IConstructorSignature0<IGlobalActivity>>();
|
||||
|
||||
registerActivity(descriptor: IConstructorSignature0<IGlobalActivity>): void {
|
||||
this.activityDescriptors.add(descriptor);
|
||||
}
|
||||
|
||||
getActivities(): IConstructorSignature0<IGlobalActivity>[] {
|
||||
const result: IConstructorSignature0<IGlobalActivity>[] = [];
|
||||
this.activityDescriptors.forEach(d => result.push(d));
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
Registry.add(GlobalActivityExtensions, new GlobalActivityRegistry());
|
||||
273
src/vs/workbench/browser/composite.ts
Normal file
273
src/vs/workbench/browser/composite.ts
Normal file
@@ -0,0 +1,273 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { Dimension, Builder } from 'vs/base/browser/builder';
|
||||
import { IAction, IActionRunner, ActionRunner } from 'vs/base/common/actions';
|
||||
import { IActionItem } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { Component } from 'vs/workbench/common/component';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { AsyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
import { IComposite } from 'vs/workbench/common/composite';
|
||||
import { IEditorControl } from 'vs/platform/editor/common/editor';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
|
||||
/**
|
||||
* Composites are layed out in the sidebar and panel part of the workbench. At a time only one composite
|
||||
* can be open in the sidebar, and only one composite can be open in the panel.
|
||||
* Each composite has a minimized representation that is good enough to provide some
|
||||
* information about the state of the composite data.
|
||||
* The workbench will keep a composite alive after it has been created and show/hide it based on
|
||||
* user interaction. The lifecycle of a composite goes in the order create(), setVisible(true|false),
|
||||
* layout(), focus(), dispose(). During use of the workbench, a composite will often receive a setVisible,
|
||||
* layout and focus call, but only one create and dispose call.
|
||||
*/
|
||||
export abstract class Composite extends Component implements IComposite {
|
||||
private _telemetryData: any = {};
|
||||
private visible: boolean;
|
||||
private parent: Builder;
|
||||
private _onTitleAreaUpdate: Emitter<void>;
|
||||
|
||||
protected actionRunner: IActionRunner;
|
||||
|
||||
/**
|
||||
* Create a new composite with the given ID and context.
|
||||
*/
|
||||
constructor(
|
||||
id: string,
|
||||
private _telemetryService: ITelemetryService,
|
||||
themeService: IThemeService
|
||||
) {
|
||||
super(id, themeService);
|
||||
|
||||
this.visible = false;
|
||||
this._onTitleAreaUpdate = new Emitter<void>();
|
||||
}
|
||||
|
||||
public getTitle(): string {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected get telemetryService(): ITelemetryService {
|
||||
return this._telemetryService;
|
||||
}
|
||||
|
||||
public get onTitleAreaUpdate(): Event<void> {
|
||||
return this._onTitleAreaUpdate.event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: Clients should not call this method, the workbench calls this
|
||||
* method. Calling it otherwise may result in unexpected behavior.
|
||||
*
|
||||
* Called to create this composite on the provided builder. This method is only
|
||||
* called once during the lifetime of the workbench.
|
||||
* Note that DOM-dependent calculations should be performed from the setVisible()
|
||||
* call. Only then the composite will be part of the DOM.
|
||||
*/
|
||||
public create(parent: Builder): TPromise<void> {
|
||||
this.parent = parent;
|
||||
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
public updateStyles(): void {
|
||||
super.updateStyles();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the container this composite is being build in.
|
||||
*/
|
||||
public getContainer(): Builder {
|
||||
return this.parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: Clients should not call this method, the workbench calls this
|
||||
* method. Calling it otherwise may result in unexpected behavior.
|
||||
*
|
||||
* Called to indicate that the composite has become visible or hidden. This method
|
||||
* is called more than once during workbench lifecycle depending on the user interaction.
|
||||
* The composite will be on-DOM if visible is set to true and off-DOM otherwise.
|
||||
*
|
||||
* The returned promise is complete when the composite is visible. As such it is valid
|
||||
* to do a long running operation from this call. Typically this operation should be
|
||||
* fast though because setVisible might be called many times during a session.
|
||||
*/
|
||||
public setVisible(visible: boolean): TPromise<void> {
|
||||
this.visible = visible;
|
||||
|
||||
// Reset telemetry data when composite becomes visible
|
||||
if (visible) {
|
||||
this._telemetryData = {};
|
||||
this._telemetryData.startTime = new Date();
|
||||
|
||||
// Only submit telemetry data when not running from an integration test
|
||||
if (this._telemetryService && this._telemetryService.publicLog) {
|
||||
const eventName: string = 'compositeOpen';
|
||||
this._telemetryService.publicLog(eventName, { composite: this.getId() });
|
||||
}
|
||||
}
|
||||
|
||||
// Send telemetry data when composite hides
|
||||
else {
|
||||
this._telemetryData.timeSpent = (Date.now() - this._telemetryData.startTime) / 1000;
|
||||
delete this._telemetryData.startTime;
|
||||
|
||||
// Only submit telemetry data when not running from an integration test
|
||||
if (this._telemetryService && this._telemetryService.publicLog) {
|
||||
const eventName: string = 'compositeShown';
|
||||
this._telemetryData.composite = this.getId();
|
||||
this._telemetryService.publicLog(eventName, this._telemetryData);
|
||||
}
|
||||
}
|
||||
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when this composite should receive keyboard focus.
|
||||
*/
|
||||
public focus(): void {
|
||||
// Subclasses can implement
|
||||
}
|
||||
|
||||
/**
|
||||
* Layout the contents of this composite using the provided dimensions.
|
||||
*/
|
||||
public abstract layout(dimension: Dimension): void;
|
||||
|
||||
/**
|
||||
* Returns an array of actions to show in the action bar of the composite.
|
||||
*/
|
||||
public getActions(): IAction[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of actions to show in the action bar of the composite
|
||||
* in a less prominent way then action from getActions.
|
||||
*/
|
||||
public getSecondaryActions(): IAction[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of actions to show in the context menu of the composite
|
||||
*/
|
||||
public getContextMenuActions(): IAction[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* For any of the actions returned by this composite, provide an IActionItem in
|
||||
* cases where the implementor of the composite wants to override the presentation
|
||||
* of an action. Returns null to indicate that the action is not rendered through
|
||||
* an action item.
|
||||
*/
|
||||
public getActionItem(action: IAction): IActionItem {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the instance of IActionRunner to use with this composite for the
|
||||
* composite tool bar.
|
||||
*/
|
||||
public getActionRunner(): IActionRunner {
|
||||
if (!this.actionRunner) {
|
||||
this.actionRunner = new ActionRunner();
|
||||
}
|
||||
|
||||
return this.actionRunner;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for composite implementors to indicate to the composite container that the title or the actions
|
||||
* of the composite have changed. Calling this method will cause the container to ask for title (getTitle())
|
||||
* and actions (getActions(), getSecondaryActions()) if the composite is visible or the next time the composite
|
||||
* gets visible.
|
||||
*/
|
||||
protected updateTitleArea(): void {
|
||||
this._onTitleAreaUpdate.fire();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this composite is currently visible and false otherwise.
|
||||
*/
|
||||
public isVisible(): boolean {
|
||||
return this.visible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the underlying composite control or null if it is not accessible.
|
||||
*/
|
||||
public getControl(): IEditorControl {
|
||||
return null;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._onTitleAreaUpdate.dispose();
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A composite descriptor is a leightweight descriptor of a composite in the workbench.
|
||||
*/
|
||||
export abstract class CompositeDescriptor<T extends Composite> extends AsyncDescriptor<T> {
|
||||
public id: string;
|
||||
public name: string;
|
||||
public cssClass: string;
|
||||
public order: number;
|
||||
|
||||
constructor(moduleId: string, ctorName: string, id: string, name: string, cssClass?: string, order?: number) {
|
||||
super(moduleId, ctorName);
|
||||
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.cssClass = cssClass;
|
||||
this.order = order;
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class CompositeRegistry<T extends Composite> {
|
||||
private composites: CompositeDescriptor<T>[];
|
||||
|
||||
constructor() {
|
||||
this.composites = [];
|
||||
}
|
||||
|
||||
protected registerComposite(descriptor: CompositeDescriptor<T>): void {
|
||||
if (this.compositeById(descriptor.id) !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.composites.push(descriptor);
|
||||
}
|
||||
|
||||
public getComposite(id: string): CompositeDescriptor<T> {
|
||||
return this.compositeById(id);
|
||||
}
|
||||
|
||||
protected getComposites(): CompositeDescriptor<T>[] {
|
||||
return this.composites.slice(0);
|
||||
}
|
||||
|
||||
protected setComposites(compositesToSet: CompositeDescriptor<T>[]): void {
|
||||
this.composites = compositesToSet;
|
||||
}
|
||||
|
||||
private compositeById(id: string): CompositeDescriptor<T> {
|
||||
for (let i = 0; i < this.composites.length; i++) {
|
||||
if (this.composites[i].id === id) {
|
||||
return this.composites[i];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
262
src/vs/workbench/browser/labels.ts
Normal file
262
src/vs/workbench/browser/labels.ts
Normal file
@@ -0,0 +1,262 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import uri from 'vs/base/common/uri';
|
||||
import paths = require('vs/base/common/paths');
|
||||
import { IconLabel, IIconLabelOptions, IIconLabelCreationOptions } from 'vs/base/browser/ui/iconLabel/iconLabel';
|
||||
import { IExtensionService } from 'vs/platform/extensions/common/extensions';
|
||||
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
import { IEditorInput } from 'vs/platform/editor/common/editor';
|
||||
import { toResource } from 'vs/workbench/common/editor';
|
||||
import { getPathLabel, IRootProvider } from 'vs/base/common/labels';
|
||||
import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { FileKind } from 'vs/platform/files/common/files';
|
||||
|
||||
export interface IEditorLabel {
|
||||
name: string;
|
||||
description?: string;
|
||||
resource?: uri;
|
||||
}
|
||||
|
||||
export interface IResourceLabelOptions extends IIconLabelOptions {
|
||||
fileKind?: FileKind;
|
||||
}
|
||||
|
||||
export class ResourceLabel extends IconLabel {
|
||||
private toDispose: IDisposable[];
|
||||
private label: IEditorLabel;
|
||||
private options: IResourceLabelOptions;
|
||||
private computedIconClasses: string[];
|
||||
private lastKnownConfiguredLangId: string;
|
||||
|
||||
constructor(
|
||||
container: HTMLElement,
|
||||
options: IIconLabelCreationOptions,
|
||||
@IExtensionService private extensionService: IExtensionService,
|
||||
@IWorkspaceContextService protected contextService: IWorkspaceContextService,
|
||||
@IConfigurationService private configurationService: IConfigurationService,
|
||||
@IModeService private modeService: IModeService,
|
||||
@IModelService private modelService: IModelService,
|
||||
@IEnvironmentService protected environmentService: IEnvironmentService
|
||||
) {
|
||||
super(container, options);
|
||||
|
||||
this.toDispose = [];
|
||||
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
this.extensionService.onReady().then(() => this.render(true /* clear cache */)); // update when extensions are loaded with potentially new languages
|
||||
this.toDispose.push(this.configurationService.onDidUpdateConfiguration(() => this.render(true /* clear cache */))); // update when file.associations change
|
||||
}
|
||||
|
||||
public setLabel(label: IEditorLabel, options?: IResourceLabelOptions): void {
|
||||
const hasResourceChanged = this.hasResourceChanged(label, options);
|
||||
|
||||
this.label = label;
|
||||
this.options = options;
|
||||
|
||||
this.render(hasResourceChanged);
|
||||
}
|
||||
|
||||
private hasResourceChanged(label: IEditorLabel, options: IResourceLabelOptions): boolean {
|
||||
const newResource = label ? label.resource : void 0;
|
||||
const oldResource = this.label ? this.label.resource : void 0;
|
||||
|
||||
const newFileKind = options ? options.fileKind : void 0;
|
||||
const oldFileKind = this.options ? this.options.fileKind : void 0;
|
||||
|
||||
if (newFileKind !== oldFileKind) {
|
||||
return true; // same resource but different kind (file, folder)
|
||||
}
|
||||
|
||||
if (newResource && oldResource) {
|
||||
return newResource.toString() !== oldResource.toString();
|
||||
}
|
||||
|
||||
if (!newResource && !oldResource) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public clear(): void {
|
||||
this.label = void 0;
|
||||
this.options = void 0;
|
||||
this.lastKnownConfiguredLangId = void 0;
|
||||
this.computedIconClasses = void 0;
|
||||
|
||||
this.setValue();
|
||||
}
|
||||
|
||||
private render(clearIconCache: boolean): void {
|
||||
if (this.label) {
|
||||
const configuredLangId = getConfiguredLangId(this.modelService, this.label.resource);
|
||||
if (this.lastKnownConfiguredLangId !== configuredLangId) {
|
||||
clearIconCache = true;
|
||||
this.lastKnownConfiguredLangId = configuredLangId;
|
||||
}
|
||||
}
|
||||
|
||||
if (clearIconCache) {
|
||||
this.computedIconClasses = void 0;
|
||||
}
|
||||
|
||||
if (!this.label) {
|
||||
return;
|
||||
}
|
||||
|
||||
const resource = this.label.resource;
|
||||
|
||||
let title = '';
|
||||
if (this.options && typeof this.options.title === 'string') {
|
||||
title = this.options.title;
|
||||
} else if (resource) {
|
||||
title = getPathLabel(resource.fsPath, void 0, this.environmentService);
|
||||
}
|
||||
|
||||
if (!this.computedIconClasses) {
|
||||
this.computedIconClasses = getIconClasses(this.modelService, this.modeService, resource, this.options && this.options.fileKind);
|
||||
}
|
||||
|
||||
let extraClasses = this.computedIconClasses.slice(0);
|
||||
if (this.options && this.options.extraClasses) {
|
||||
extraClasses.push(...this.options.extraClasses);
|
||||
}
|
||||
|
||||
const italic = this.options && this.options.italic;
|
||||
const matches = this.options && this.options.matches;
|
||||
|
||||
this.setValue(this.label.name, this.label.description, { title, extraClasses, italic, matches });
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
super.dispose();
|
||||
|
||||
this.toDispose = dispose(this.toDispose);
|
||||
this.label = void 0;
|
||||
this.options = void 0;
|
||||
this.lastKnownConfiguredLangId = void 0;
|
||||
this.computedIconClasses = void 0;
|
||||
}
|
||||
}
|
||||
|
||||
export class EditorLabel extends ResourceLabel {
|
||||
|
||||
public setEditor(editor: IEditorInput, options?: IResourceLabelOptions): void {
|
||||
this.setLabel({
|
||||
resource: toResource(editor, { supportSideBySide: true }),
|
||||
name: editor.getName(),
|
||||
description: editor.getDescription()
|
||||
}, options);
|
||||
}
|
||||
}
|
||||
|
||||
export interface IFileLabelOptions extends IResourceLabelOptions {
|
||||
hideLabel?: boolean;
|
||||
hidePath?: boolean;
|
||||
root?: uri;
|
||||
}
|
||||
|
||||
export class FileLabel extends ResourceLabel {
|
||||
|
||||
constructor(
|
||||
container: HTMLElement,
|
||||
options: IIconLabelCreationOptions,
|
||||
@IExtensionService extensionService: IExtensionService,
|
||||
@IWorkspaceContextService contextService: IWorkspaceContextService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IModeService modeService: IModeService,
|
||||
@IModelService modelService: IModelService,
|
||||
@IEnvironmentService environmentService: IEnvironmentService,
|
||||
@IUntitledEditorService private untitledEditorService: IUntitledEditorService
|
||||
) {
|
||||
super(container, options, extensionService, contextService, configurationService, modeService, modelService, environmentService);
|
||||
}
|
||||
|
||||
public setFile(resource: uri, options: IFileLabelOptions = Object.create(null)): void {
|
||||
const hidePath = options.hidePath || (resource.scheme === Schemas.untitled && !this.untitledEditorService.hasAssociatedFilePath(resource));
|
||||
const rootProvider: IRootProvider = options.root ? {
|
||||
getRoot(): uri { return options.root; },
|
||||
getWorkspace(): { roots: uri[]; } { return { roots: [options.root] }; },
|
||||
} : this.contextService;
|
||||
this.setLabel({
|
||||
resource,
|
||||
name: !options.hideLabel ? paths.basename(resource.fsPath) : void 0,
|
||||
description: !hidePath ? getPathLabel(paths.dirname(resource.fsPath), rootProvider, this.environmentService) : void 0
|
||||
}, options);
|
||||
}
|
||||
}
|
||||
|
||||
export function getIconClasses(modelService: IModelService, modeService: IModeService, resource: uri, fileKind?: FileKind): string[] {
|
||||
|
||||
// we always set these base classes even if we do not have a path
|
||||
const classes = fileKind === FileKind.ROOT_FOLDER ? ['rootfolder-icon'] : fileKind === FileKind.FOLDER ? ['folder-icon'] : ['file-icon'];
|
||||
|
||||
let path: string;
|
||||
if (resource) {
|
||||
path = resource.fsPath;
|
||||
}
|
||||
|
||||
if (path) {
|
||||
const basename = cssEscape(paths.basename(path).toLowerCase());
|
||||
|
||||
// Folders
|
||||
if (fileKind === FileKind.FOLDER) {
|
||||
classes.push(`${basename}-name-folder-icon`);
|
||||
}
|
||||
|
||||
// Files
|
||||
else {
|
||||
|
||||
// Name
|
||||
classes.push(`${basename}-name-file-icon`);
|
||||
|
||||
// Extension(s)
|
||||
const dotSegments = basename.split('.');
|
||||
for (let i = 1; i < dotSegments.length; i++) {
|
||||
classes.push(`${dotSegments.slice(i).join('.')}-ext-file-icon`); // add each combination of all found extensions if more than one
|
||||
}
|
||||
|
||||
// Configured Language
|
||||
let configuredLangId = getConfiguredLangId(modelService, resource);
|
||||
configuredLangId = configuredLangId || modeService.getModeIdByFilenameOrFirstLine(path);
|
||||
if (configuredLangId) {
|
||||
classes.push(`${cssEscape(configuredLangId)}-lang-file-icon`);
|
||||
}
|
||||
}
|
||||
}
|
||||
return classes;
|
||||
}
|
||||
|
||||
function getConfiguredLangId(modelService: IModelService, resource: uri): string {
|
||||
let configuredLangId: string;
|
||||
if (resource) {
|
||||
const model = modelService.getModel(resource);
|
||||
if (model) {
|
||||
const modeId = model.getLanguageIdentifier().language;
|
||||
if (modeId && modeId !== PLAINTEXT_MODE_ID) {
|
||||
configuredLangId = modeId; // only take if the mode is specific (aka no just plain text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return configuredLangId;
|
||||
}
|
||||
|
||||
function cssEscape(val: string): string {
|
||||
return val.replace(/\s/g, '\\$&'); // make sure to not introduce CSS classes from files that contain whitespace
|
||||
}
|
||||
595
src/vs/workbench/browser/layout.ts
Normal file
595
src/vs/workbench/browser/layout.ts
Normal file
@@ -0,0 +1,595 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { Dimension, Builder } from 'vs/base/browser/builder';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import * as errors from 'vs/base/common/errors';
|
||||
import { Part } from 'vs/workbench/browser/part';
|
||||
import { QuickOpenController } from 'vs/workbench/browser/parts/quickopen/quickOpenController';
|
||||
import { Sash, ISashEvent, IVerticalSashLayoutProvider, IHorizontalSashLayoutProvider, Orientation } from 'vs/base/browser/ui/sash/sash';
|
||||
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IPartService, Position, ILayoutOptions, Parts } from 'vs/workbench/services/part/common/partService';
|
||||
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
|
||||
import { getZoomFactor } from 'vs/base/browser/browser';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
|
||||
const MIN_SIDEBAR_PART_WIDTH = 170;
|
||||
const MIN_EDITOR_PART_HEIGHT = 70;
|
||||
const MIN_EDITOR_PART_WIDTH = 220;
|
||||
const MIN_PANEL_PART_HEIGHT = 77;
|
||||
const DEFAULT_PANEL_HEIGHT_COEFFICIENT = 0.4;
|
||||
const HIDE_SIDEBAR_WIDTH_THRESHOLD = 50;
|
||||
const HIDE_PANEL_HEIGHT_THRESHOLD = 50;
|
||||
const TITLE_BAR_HEIGHT = 22;
|
||||
const STATUS_BAR_HEIGHT = 22;
|
||||
const ACTIVITY_BAR_WIDTH = 50;
|
||||
|
||||
interface PartLayoutInfo {
|
||||
titlebar: { height: number; };
|
||||
activitybar: { width: number; };
|
||||
sidebar: { minWidth: number; };
|
||||
panel: { minHeight: number; };
|
||||
editor: { minWidth: number; minHeight: number; };
|
||||
statusbar: { height: number; };
|
||||
}
|
||||
|
||||
/**
|
||||
* The workbench layout is responsible to lay out all parts that make the Workbench.
|
||||
*/
|
||||
export class WorkbenchLayout implements IVerticalSashLayoutProvider, IHorizontalSashLayoutProvider {
|
||||
|
||||
private static sashXWidthSettingsKey = 'workbench.sidebar.width';
|
||||
private static sashYHeightSettingsKey = 'workbench.panel.height';
|
||||
|
||||
private parent: Builder;
|
||||
private workbenchContainer: Builder;
|
||||
private titlebar: Part;
|
||||
private activitybar: Part;
|
||||
private editor: Part;
|
||||
private sidebar: Part;
|
||||
private panel: Part;
|
||||
private statusbar: Part;
|
||||
private quickopen: QuickOpenController;
|
||||
private toUnbind: IDisposable[];
|
||||
private partLayoutInfo: PartLayoutInfo;
|
||||
private workbenchSize: Dimension;
|
||||
private sashX: Sash;
|
||||
private sashY: Sash;
|
||||
private startSidebarWidth: number;
|
||||
private sidebarWidth: number;
|
||||
private sidebarHeight: number;
|
||||
private titlebarHeight: number;
|
||||
private activitybarWidth: number;
|
||||
private statusbarHeight: number;
|
||||
private startPanelHeight: number;
|
||||
private panelHeight: number;
|
||||
private panelHeightBeforeMaximized: number;
|
||||
private panelMaximized: boolean;
|
||||
private panelWidth: number;
|
||||
private layoutEditorGroupsVertically: boolean;
|
||||
|
||||
// Take parts as an object bag since instatation service does not have typings for constructors with 9+ arguments
|
||||
constructor(
|
||||
parent: Builder,
|
||||
workbenchContainer: Builder,
|
||||
parts: {
|
||||
titlebar: Part,
|
||||
activitybar: Part,
|
||||
editor: Part,
|
||||
sidebar: Part,
|
||||
panel: Part,
|
||||
statusbar: Part
|
||||
},
|
||||
quickopen: QuickOpenController,
|
||||
@IStorageService private storageService: IStorageService,
|
||||
@IContextViewService private contextViewService: IContextViewService,
|
||||
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
|
||||
@IEditorGroupService private editorGroupService: IEditorGroupService,
|
||||
@IPartService private partService: IPartService,
|
||||
@IViewletService private viewletService: IViewletService,
|
||||
@IThemeService themeService: IThemeService
|
||||
) {
|
||||
this.parent = parent;
|
||||
this.workbenchContainer = workbenchContainer;
|
||||
this.titlebar = parts.titlebar;
|
||||
this.activitybar = parts.activitybar;
|
||||
this.editor = parts.editor;
|
||||
this.sidebar = parts.sidebar;
|
||||
this.panel = parts.panel;
|
||||
this.statusbar = parts.statusbar;
|
||||
this.quickopen = quickopen;
|
||||
this.toUnbind = [];
|
||||
this.partLayoutInfo = this.getPartLayoutInfo();
|
||||
this.panelHeightBeforeMaximized = 0;
|
||||
this.panelMaximized = false;
|
||||
|
||||
this.sashX = new Sash(this.workbenchContainer.getHTMLElement(), this, {
|
||||
baseSize: 5
|
||||
});
|
||||
|
||||
this.sashY = new Sash(this.workbenchContainer.getHTMLElement(), this, {
|
||||
baseSize: 4,
|
||||
orientation: Orientation.HORIZONTAL
|
||||
});
|
||||
|
||||
this.sidebarWidth = this.storageService.getInteger(WorkbenchLayout.sashXWidthSettingsKey, StorageScope.GLOBAL, -1);
|
||||
this.panelHeight = this.storageService.getInteger(WorkbenchLayout.sashYHeightSettingsKey, StorageScope.GLOBAL, 0);
|
||||
|
||||
this.layoutEditorGroupsVertically = (this.editorGroupService.getGroupOrientation() !== 'horizontal');
|
||||
|
||||
this.toUnbind.push(themeService.onThemeChange(_ => this.layout()));
|
||||
this.toUnbind.push(editorGroupService.onEditorsChanged(() => this.onEditorsChanged()));
|
||||
this.toUnbind.push(editorGroupService.onGroupOrientationChanged(e => this.onGroupOrientationChanged()));
|
||||
|
||||
this.registerSashListeners();
|
||||
}
|
||||
|
||||
private getPartLayoutInfo(): PartLayoutInfo {
|
||||
return {
|
||||
titlebar: {
|
||||
height: TITLE_BAR_HEIGHT
|
||||
},
|
||||
activitybar: {
|
||||
width: ACTIVITY_BAR_WIDTH
|
||||
},
|
||||
sidebar: {
|
||||
minWidth: MIN_SIDEBAR_PART_WIDTH
|
||||
},
|
||||
panel: {
|
||||
minHeight: MIN_PANEL_PART_HEIGHT
|
||||
},
|
||||
editor: {
|
||||
minWidth: MIN_EDITOR_PART_WIDTH,
|
||||
minHeight: MIN_EDITOR_PART_HEIGHT
|
||||
},
|
||||
statusbar: {
|
||||
height: STATUS_BAR_HEIGHT
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private registerSashListeners(): void {
|
||||
let startX: number = 0;
|
||||
let startY: number = 0;
|
||||
|
||||
this.sashX.addListener('start', (e: ISashEvent) => {
|
||||
this.startSidebarWidth = this.sidebarWidth;
|
||||
startX = e.startX;
|
||||
});
|
||||
|
||||
this.sashY.addListener('start', (e: ISashEvent) => {
|
||||
this.startPanelHeight = this.panelHeight;
|
||||
startY = e.startY;
|
||||
});
|
||||
|
||||
this.sashX.addListener('change', (e: ISashEvent) => {
|
||||
let doLayout = false;
|
||||
let sidebarPosition = this.partService.getSideBarPosition();
|
||||
let isSidebarVisible = this.partService.isVisible(Parts.SIDEBAR_PART);
|
||||
let newSashWidth = (sidebarPosition === Position.LEFT) ? this.startSidebarWidth + e.currentX - startX : this.startSidebarWidth - e.currentX + startX;
|
||||
let promise = TPromise.as<void>(null);
|
||||
|
||||
// Sidebar visible
|
||||
if (isSidebarVisible) {
|
||||
|
||||
// Automatically hide side bar when a certain threshold is met
|
||||
if (newSashWidth + HIDE_SIDEBAR_WIDTH_THRESHOLD < this.partLayoutInfo.sidebar.minWidth) {
|
||||
let dragCompensation = MIN_SIDEBAR_PART_WIDTH - HIDE_SIDEBAR_WIDTH_THRESHOLD;
|
||||
promise = this.partService.setSideBarHidden(true);
|
||||
startX = (sidebarPosition === Position.LEFT) ? Math.max(this.activitybarWidth, e.currentX - dragCompensation) : Math.min(e.currentX + dragCompensation, this.workbenchSize.width - this.activitybarWidth);
|
||||
this.sidebarWidth = this.startSidebarWidth; // when restoring sidebar, restore to the sidebar width we started from
|
||||
}
|
||||
|
||||
// Otherwise size the sidebar accordingly
|
||||
else {
|
||||
this.sidebarWidth = Math.max(this.partLayoutInfo.sidebar.minWidth, newSashWidth); // Sidebar can not become smaller than MIN_PART_WIDTH
|
||||
doLayout = newSashWidth >= this.partLayoutInfo.sidebar.minWidth;
|
||||
}
|
||||
}
|
||||
|
||||
// Sidebar hidden
|
||||
else {
|
||||
if ((sidebarPosition === Position.LEFT && e.currentX - startX >= this.partLayoutInfo.sidebar.minWidth) ||
|
||||
(sidebarPosition === Position.RIGHT && startX - e.currentX >= this.partLayoutInfo.sidebar.minWidth)) {
|
||||
this.startSidebarWidth = this.partLayoutInfo.sidebar.minWidth - (sidebarPosition === Position.LEFT ? e.currentX - startX : startX - e.currentX);
|
||||
this.sidebarWidth = this.partLayoutInfo.sidebar.minWidth;
|
||||
promise = this.partService.setSideBarHidden(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (doLayout) {
|
||||
promise.done(() => this.layout(), errors.onUnexpectedError);
|
||||
}
|
||||
});
|
||||
|
||||
this.sashY.addListener('change', (e: ISashEvent) => {
|
||||
let doLayout = false;
|
||||
let isPanelVisible = this.partService.isVisible(Parts.PANEL_PART);
|
||||
let newSashHeight = this.startPanelHeight - (e.currentY - startY);
|
||||
let promise = TPromise.as<void>(null);
|
||||
|
||||
// Panel visible
|
||||
if (isPanelVisible) {
|
||||
|
||||
// Automatically hide panel when a certain threshold is met
|
||||
if (newSashHeight + HIDE_PANEL_HEIGHT_THRESHOLD < this.partLayoutInfo.panel.minHeight) {
|
||||
let dragCompensation = MIN_PANEL_PART_HEIGHT - HIDE_PANEL_HEIGHT_THRESHOLD;
|
||||
promise = this.partService.setPanelHidden(true);
|
||||
startY = Math.min(this.sidebarHeight - this.statusbarHeight - this.titlebarHeight, e.currentY + dragCompensation);
|
||||
this.panelHeight = this.startPanelHeight; // when restoring panel, restore to the panel height we started from
|
||||
}
|
||||
|
||||
// Otherwise size the panel accordingly
|
||||
else {
|
||||
this.panelHeight = Math.max(this.partLayoutInfo.panel.minHeight, newSashHeight); // Panel can not become smaller than MIN_PART_HEIGHT
|
||||
doLayout = newSashHeight >= this.partLayoutInfo.panel.minHeight;
|
||||
}
|
||||
}
|
||||
|
||||
// Panel hidden
|
||||
else {
|
||||
if (startY - e.currentY >= this.partLayoutInfo.panel.minHeight) {
|
||||
this.startPanelHeight = 0;
|
||||
this.panelHeight = this.partLayoutInfo.panel.minHeight;
|
||||
promise = this.partService.setPanelHidden(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (doLayout) {
|
||||
promise.done(() => this.layout(), errors.onUnexpectedError);
|
||||
}
|
||||
});
|
||||
|
||||
this.sashX.addListener('end', () => {
|
||||
this.storageService.store(WorkbenchLayout.sashXWidthSettingsKey, this.sidebarWidth, StorageScope.GLOBAL);
|
||||
});
|
||||
|
||||
this.sashY.addListener('end', () => {
|
||||
this.storageService.store(WorkbenchLayout.sashYHeightSettingsKey, this.panelHeight, StorageScope.GLOBAL);
|
||||
});
|
||||
|
||||
this.sashY.addListener('reset', () => {
|
||||
this.panelHeight = this.sidebarHeight * DEFAULT_PANEL_HEIGHT_COEFFICIENT;
|
||||
this.storageService.store(WorkbenchLayout.sashYHeightSettingsKey, this.panelHeight, StorageScope.GLOBAL);
|
||||
this.partService.setPanelHidden(false).done(() => this.layout(), errors.onUnexpectedError);
|
||||
});
|
||||
|
||||
this.sashX.addListener('reset', () => {
|
||||
let activeViewlet = this.viewletService.getActiveViewlet();
|
||||
let optimalWidth = activeViewlet && activeViewlet.getOptimalWidth();
|
||||
this.sidebarWidth = Math.max(MIN_SIDEBAR_PART_WIDTH, optimalWidth || 0);
|
||||
this.storageService.store(WorkbenchLayout.sashXWidthSettingsKey, this.sidebarWidth, StorageScope.GLOBAL);
|
||||
this.partService.setSideBarHidden(false).done(() => this.layout(), errors.onUnexpectedError);
|
||||
});
|
||||
}
|
||||
|
||||
private onEditorsChanged(): void {
|
||||
|
||||
// Make sure that we layout properly in case we detect that the sidebar or panel is large enought to cause
|
||||
// multiple opened editors to go below minimal size. The fix is to trigger a layout for any editor
|
||||
// input change that falls into this category.
|
||||
if (this.workbenchSize && (this.sidebarWidth || this.panelHeight)) {
|
||||
let visibleEditors = this.editorService.getVisibleEditors().length;
|
||||
if (visibleEditors > 1) {
|
||||
const sidebarOverflow = this.layoutEditorGroupsVertically && (this.workbenchSize.width - this.sidebarWidth < visibleEditors * MIN_EDITOR_PART_WIDTH);
|
||||
const panelOverflow = !this.layoutEditorGroupsVertically && (this.workbenchSize.height - this.panelHeight < visibleEditors * MIN_EDITOR_PART_HEIGHT);
|
||||
|
||||
if (sidebarOverflow || panelOverflow) {
|
||||
this.layout();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private onGroupOrientationChanged(): void {
|
||||
const newLayoutEditorGroupsVertically = (this.editorGroupService.getGroupOrientation() !== 'horizontal');
|
||||
|
||||
const doLayout = this.layoutEditorGroupsVertically !== newLayoutEditorGroupsVertically;
|
||||
this.layoutEditorGroupsVertically = newLayoutEditorGroupsVertically;
|
||||
|
||||
if (doLayout) {
|
||||
this.layout();
|
||||
}
|
||||
}
|
||||
|
||||
public layout(options?: ILayoutOptions): void {
|
||||
this.workbenchSize = this.parent.getClientArea();
|
||||
|
||||
const isActivityBarHidden = !this.partService.isVisible(Parts.ACTIVITYBAR_PART);
|
||||
const isTitlebarHidden = !this.partService.isVisible(Parts.TITLEBAR_PART);
|
||||
const isPanelHidden = !this.partService.isVisible(Parts.PANEL_PART);
|
||||
const isStatusbarHidden = !this.partService.isVisible(Parts.STATUSBAR_PART);
|
||||
const isSidebarHidden = !this.partService.isVisible(Parts.SIDEBAR_PART);
|
||||
const sidebarPosition = this.partService.getSideBarPosition();
|
||||
|
||||
// Sidebar
|
||||
let sidebarWidth: number;
|
||||
if (isSidebarHidden) {
|
||||
sidebarWidth = 0;
|
||||
} else if (this.sidebarWidth !== -1) {
|
||||
sidebarWidth = Math.max(this.partLayoutInfo.sidebar.minWidth, this.sidebarWidth);
|
||||
} else {
|
||||
sidebarWidth = this.workbenchSize.width / 5;
|
||||
this.sidebarWidth = sidebarWidth;
|
||||
}
|
||||
|
||||
this.statusbarHeight = isStatusbarHidden ? 0 : this.partLayoutInfo.statusbar.height;
|
||||
this.titlebarHeight = isTitlebarHidden ? 0 : this.partLayoutInfo.titlebar.height / getZoomFactor(); // adjust for zoom prevention
|
||||
|
||||
const previousMaxPanelHeight = this.sidebarHeight - MIN_EDITOR_PART_HEIGHT;
|
||||
this.sidebarHeight = this.workbenchSize.height - this.statusbarHeight - this.titlebarHeight;
|
||||
let sidebarSize = new Dimension(sidebarWidth, this.sidebarHeight);
|
||||
|
||||
// Activity Bar
|
||||
this.activitybarWidth = isActivityBarHidden ? 0 : this.partLayoutInfo.activitybar.width;
|
||||
let activityBarSize = new Dimension(this.activitybarWidth, sidebarSize.height);
|
||||
|
||||
// Panel part
|
||||
let panelHeight: number;
|
||||
const editorCountForHeight = this.editorGroupService.getGroupOrientation() === 'horizontal' ? this.editorGroupService.getStacksModel().groups.length : 1;
|
||||
const maxPanelHeight = sidebarSize.height - editorCountForHeight * MIN_EDITOR_PART_HEIGHT;
|
||||
if (isPanelHidden) {
|
||||
panelHeight = 0;
|
||||
} else if (this.panelHeight === previousMaxPanelHeight) {
|
||||
panelHeight = maxPanelHeight;
|
||||
} else if (this.panelHeight > 0) {
|
||||
panelHeight = Math.min(maxPanelHeight, Math.max(this.partLayoutInfo.panel.minHeight, this.panelHeight));
|
||||
} else {
|
||||
panelHeight = sidebarSize.height * DEFAULT_PANEL_HEIGHT_COEFFICIENT;
|
||||
}
|
||||
if (options && options.toggleMaximizedPanel) {
|
||||
panelHeight = this.panelMaximized ? Math.max(this.partLayoutInfo.panel.minHeight, Math.min(this.panelHeightBeforeMaximized, maxPanelHeight)) : maxPanelHeight;
|
||||
}
|
||||
this.panelMaximized = panelHeight === maxPanelHeight;
|
||||
if (panelHeight / maxPanelHeight < 0.7) {
|
||||
// Remember the previous height only if the panel size is not too large.
|
||||
// To get a nice minimize effect even if a user dragged the panel sash to maximum.
|
||||
this.panelHeightBeforeMaximized = panelHeight;
|
||||
}
|
||||
const panelDimension = new Dimension(this.workbenchSize.width - sidebarSize.width - activityBarSize.width, panelHeight);
|
||||
this.panelWidth = panelDimension.width;
|
||||
|
||||
// Editor
|
||||
let editorSize = {
|
||||
width: 0,
|
||||
height: 0,
|
||||
remainderLeft: 0,
|
||||
remainderRight: 0
|
||||
};
|
||||
|
||||
editorSize.width = panelDimension.width;
|
||||
editorSize.height = sidebarSize.height - panelDimension.height;
|
||||
|
||||
// Sidebar hidden
|
||||
if (isSidebarHidden) {
|
||||
editorSize.width = this.workbenchSize.width - activityBarSize.width;
|
||||
|
||||
if (sidebarPosition === Position.LEFT) {
|
||||
editorSize.remainderLeft = Math.round((this.workbenchSize.width - editorSize.width + activityBarSize.width) / 2);
|
||||
editorSize.remainderRight = this.workbenchSize.width - editorSize.width - editorSize.remainderLeft;
|
||||
} else {
|
||||
editorSize.remainderRight = Math.round((this.workbenchSize.width - editorSize.width + activityBarSize.width) / 2);
|
||||
editorSize.remainderLeft = this.workbenchSize.width - editorSize.width - editorSize.remainderRight;
|
||||
}
|
||||
}
|
||||
|
||||
// Assert Sidebar and Editor Size to not overflow
|
||||
let editorMinWidth = this.partLayoutInfo.editor.minWidth;
|
||||
let editorMinHeight = this.partLayoutInfo.editor.minHeight;
|
||||
let visibleEditorCount = this.editorService.getVisibleEditors().length;
|
||||
if (visibleEditorCount > 1) {
|
||||
if (this.layoutEditorGroupsVertically) {
|
||||
editorMinWidth *= visibleEditorCount; // when editors layout vertically, multiply the min editor width by number of visible editors
|
||||
} else {
|
||||
editorMinHeight *= visibleEditorCount; // when editors layout horizontally, multiply the min editor height by number of visible editors
|
||||
}
|
||||
}
|
||||
|
||||
if (editorSize.width < editorMinWidth) {
|
||||
let diff = editorMinWidth - editorSize.width;
|
||||
editorSize.width = editorMinWidth;
|
||||
panelDimension.width = editorMinWidth;
|
||||
sidebarSize.width -= diff;
|
||||
sidebarSize.width = Math.max(MIN_SIDEBAR_PART_WIDTH, sidebarSize.width);
|
||||
}
|
||||
|
||||
if (editorSize.height < editorMinHeight) {
|
||||
let diff = editorMinHeight - editorSize.height;
|
||||
editorSize.height = editorMinHeight;
|
||||
panelDimension.height -= diff;
|
||||
panelDimension.height = Math.max(MIN_PANEL_PART_HEIGHT, panelDimension.height);
|
||||
}
|
||||
|
||||
if (!isSidebarHidden) {
|
||||
this.sidebarWidth = sidebarSize.width;
|
||||
this.storageService.store(WorkbenchLayout.sashXWidthSettingsKey, this.sidebarWidth, StorageScope.GLOBAL);
|
||||
}
|
||||
|
||||
if (!isPanelHidden) {
|
||||
this.panelHeight = panelDimension.height;
|
||||
this.storageService.store(WorkbenchLayout.sashYHeightSettingsKey, this.panelHeight, StorageScope.GLOBAL);
|
||||
}
|
||||
|
||||
// Workbench
|
||||
this.workbenchContainer
|
||||
.position(0, 0, 0, 0, 'relative')
|
||||
.size(this.workbenchSize.width, this.workbenchSize.height);
|
||||
|
||||
// Bug on Chrome: Sometimes Chrome wants to scroll the workbench container on layout changes. The fix is to reset scrolling in this case.
|
||||
const workbenchContainer = this.workbenchContainer.getHTMLElement();
|
||||
if (workbenchContainer.scrollTop > 0) {
|
||||
workbenchContainer.scrollTop = 0;
|
||||
}
|
||||
if (workbenchContainer.scrollLeft > 0) {
|
||||
workbenchContainer.scrollLeft = 0;
|
||||
}
|
||||
|
||||
// Title Part
|
||||
if (isTitlebarHidden) {
|
||||
this.titlebar.getContainer().hide();
|
||||
} else {
|
||||
this.titlebar.getContainer().show();
|
||||
}
|
||||
|
||||
// Editor Part and Panel part
|
||||
this.editor.getContainer().size(editorSize.width, editorSize.height);
|
||||
this.panel.getContainer().size(panelDimension.width, panelDimension.height);
|
||||
|
||||
const editorBottom = this.statusbarHeight + panelDimension.height;
|
||||
if (isSidebarHidden) {
|
||||
this.editor.getContainer().position(this.titlebarHeight, editorSize.remainderRight, editorBottom, editorSize.remainderLeft);
|
||||
this.panel.getContainer().position(editorSize.height + this.titlebarHeight, editorSize.remainderRight, this.statusbarHeight, editorSize.remainderLeft);
|
||||
} else if (sidebarPosition === Position.LEFT) {
|
||||
this.editor.getContainer().position(this.titlebarHeight, 0, editorBottom, sidebarSize.width + activityBarSize.width);
|
||||
this.panel.getContainer().position(editorSize.height + this.titlebarHeight, 0, this.statusbarHeight, sidebarSize.width + activityBarSize.width);
|
||||
} else {
|
||||
this.editor.getContainer().position(this.titlebarHeight, sidebarSize.width, editorBottom, 0);
|
||||
this.panel.getContainer().position(editorSize.height + this.titlebarHeight, sidebarSize.width, this.statusbarHeight, 0);
|
||||
}
|
||||
|
||||
// Activity Bar Part
|
||||
this.activitybar.getContainer().size(null, activityBarSize.height);
|
||||
if (sidebarPosition === Position.LEFT) {
|
||||
this.activitybar.getContainer().getHTMLElement().style.right = '';
|
||||
this.activitybar.getContainer().position(this.titlebarHeight, null, 0, 0);
|
||||
} else {
|
||||
this.activitybar.getContainer().getHTMLElement().style.left = '';
|
||||
this.activitybar.getContainer().position(this.titlebarHeight, 0, 0, null);
|
||||
}
|
||||
if (isActivityBarHidden) {
|
||||
this.activitybar.getContainer().hide();
|
||||
} else {
|
||||
this.activitybar.getContainer().show();
|
||||
}
|
||||
|
||||
// Sidebar Part
|
||||
this.sidebar.getContainer().size(sidebarSize.width, sidebarSize.height);
|
||||
|
||||
if (sidebarPosition === Position.LEFT) {
|
||||
this.sidebar.getContainer().position(this.titlebarHeight, editorSize.width, 0, activityBarSize.width);
|
||||
} else {
|
||||
this.sidebar.getContainer().position(this.titlebarHeight, null, 0, editorSize.width);
|
||||
}
|
||||
|
||||
// Statusbar Part
|
||||
this.statusbar.getContainer().position(this.workbenchSize.height - this.statusbarHeight);
|
||||
if (isStatusbarHidden) {
|
||||
this.statusbar.getContainer().hide();
|
||||
} else {
|
||||
this.statusbar.getContainer().show();
|
||||
}
|
||||
|
||||
// Quick open
|
||||
this.quickopen.layout(this.workbenchSize);
|
||||
|
||||
// Sashes
|
||||
this.sashX.layout();
|
||||
this.sashY.layout();
|
||||
|
||||
// Propagate to Part Layouts
|
||||
this.titlebar.layout(new Dimension(this.workbenchSize.width, this.titlebarHeight));
|
||||
this.editor.layout(new Dimension(editorSize.width, editorSize.height));
|
||||
this.sidebar.layout(sidebarSize);
|
||||
this.panel.layout(panelDimension);
|
||||
this.activitybar.layout(activityBarSize);
|
||||
|
||||
// Propagate to Context View
|
||||
this.contextViewService.layout();
|
||||
}
|
||||
|
||||
public getVerticalSashTop(sash: Sash): number {
|
||||
return this.titlebarHeight;
|
||||
}
|
||||
|
||||
public getVerticalSashLeft(sash: Sash): number {
|
||||
let isSidebarVisible = this.partService.isVisible(Parts.SIDEBAR_PART);
|
||||
let sidebarPosition = this.partService.getSideBarPosition();
|
||||
|
||||
if (sidebarPosition === Position.LEFT) {
|
||||
return isSidebarVisible ? this.sidebarWidth + this.activitybarWidth : this.activitybarWidth;
|
||||
}
|
||||
|
||||
return isSidebarVisible ? this.workbenchSize.width - this.sidebarWidth - this.activitybarWidth : this.workbenchSize.width - this.activitybarWidth;
|
||||
}
|
||||
|
||||
public getVerticalSashHeight(sash: Sash): number {
|
||||
return this.sidebarHeight;
|
||||
}
|
||||
|
||||
public getHorizontalSashTop(sash: Sash): number {
|
||||
// Horizontal sash should be a bit lower than the editor area, thus add 2px #5524
|
||||
return 2 + (this.partService.isVisible(Parts.PANEL_PART) ? this.sidebarHeight - this.panelHeight + this.titlebarHeight : this.sidebarHeight + this.titlebarHeight);
|
||||
}
|
||||
|
||||
public getHorizontalSashLeft(sash: Sash): number {
|
||||
return this.partService.getSideBarPosition() === Position.LEFT ? this.getVerticalSashLeft(sash) : 0;
|
||||
}
|
||||
|
||||
public getHorizontalSashWidth(sash: Sash): number {
|
||||
return this.panelWidth;
|
||||
}
|
||||
|
||||
public isPanelMaximized(): boolean {
|
||||
return this.panelMaximized;
|
||||
}
|
||||
|
||||
// change part size along the main axis
|
||||
public resizePart(part: Parts, sizeChange: number): void {
|
||||
const visibleEditors = this.editorService.getVisibleEditors().length;
|
||||
const sizeChangePxWidth = this.workbenchSize.width * (sizeChange / 100);
|
||||
const sizeChangePxHeight = this.workbenchSize.height * (sizeChange / 100);
|
||||
|
||||
let doLayout = false;
|
||||
let newSashSize: number = 0;
|
||||
|
||||
switch (part) {
|
||||
case Parts.SIDEBAR_PART:
|
||||
newSashSize = this.sidebarWidth + sizeChangePxWidth;
|
||||
this.sidebarWidth = Math.max(this.partLayoutInfo.sidebar.minWidth, newSashSize); // Sidebar can not become smaller than MIN_PART_WIDTH
|
||||
|
||||
if (this.layoutEditorGroupsVertically && (this.workbenchSize.width - this.sidebarWidth < visibleEditors * MIN_EDITOR_PART_WIDTH)) {
|
||||
this.sidebarWidth = (this.workbenchSize.width - visibleEditors * MIN_EDITOR_PART_WIDTH);
|
||||
}
|
||||
|
||||
doLayout = true;
|
||||
break;
|
||||
case Parts.PANEL_PART:
|
||||
newSashSize = this.panelHeight + sizeChangePxHeight;
|
||||
this.panelHeight = Math.max(this.partLayoutInfo.panel.minHeight, newSashSize);
|
||||
doLayout = true;
|
||||
break;
|
||||
case Parts.EDITOR_PART:
|
||||
// If we have one editor we can cheat and resize sidebar with the negative delta
|
||||
const visibleEditorCount = this.editorService.getVisibleEditors().length;
|
||||
|
||||
if (visibleEditorCount === 1) {
|
||||
this.sidebarWidth = this.sidebarWidth - sizeChangePxWidth;
|
||||
doLayout = true;
|
||||
} else {
|
||||
const stacks = this.editorGroupService.getStacksModel();
|
||||
const activeGroup = stacks.positionOfGroup(stacks.activeGroup);
|
||||
|
||||
this.editorGroupService.resizeGroup(activeGroup, sizeChangePxWidth);
|
||||
doLayout = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (doLayout) {
|
||||
this.layout();
|
||||
}
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
if (this.toUnbind) {
|
||||
dispose(this.toUnbind);
|
||||
this.toUnbind = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
81
src/vs/workbench/browser/media/part.css
Normal file
81
src/vs/workbench/browser/media/part.css
Normal file
@@ -0,0 +1,81 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-workbench > .part > .title {
|
||||
display: none; /* Parts have to opt in to show title area */
|
||||
}
|
||||
|
||||
/* title styles are defined for two classes because the editor puts the title into the content */
|
||||
|
||||
.monaco-workbench > .part > .title,
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title {
|
||||
height: 35px;
|
||||
display: flex;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part > .title {
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part > .title > .title-label {
|
||||
line-height: 35px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part > .title > .title-label {
|
||||
padding-left: 12px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part > .title > .title-label span {
|
||||
font-size: 11px;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part > .title > .title-label a {
|
||||
text-decoration: none;
|
||||
font-size: 13px;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part > .title > .title-actions {
|
||||
height: 35px;
|
||||
flex: 1;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part > .title > .title-actions .action-label {
|
||||
display: block;
|
||||
height: 35px;
|
||||
line-height: 35px;
|
||||
min-width: 28px;
|
||||
background-size: 16px;
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part > .title > .title-actions .action-label .label {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part > .content {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part > .content .progress-container {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 33px; /* at the bottom of the 35px height title container */
|
||||
z-index: 5; /* on top of things */
|
||||
height: 2px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part > .content .progress-container .progress-bit {
|
||||
height: 2px;
|
||||
}
|
||||
116
src/vs/workbench/browser/panel.ts
Normal file
116
src/vs/workbench/browser/panel.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IPanel } from 'vs/workbench/common/panel';
|
||||
import { Composite, CompositeDescriptor, CompositeRegistry } from 'vs/workbench/browser/composite';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
|
||||
import { IPartService } from 'vs/workbench/services/part/common/partService';
|
||||
|
||||
export abstract class Panel extends Composite implements IPanel { }
|
||||
|
||||
/**
|
||||
* A panel descriptor is a leightweight descriptor of a panel in the workbench.
|
||||
*/
|
||||
export class PanelDescriptor extends CompositeDescriptor<Panel> {
|
||||
|
||||
constructor(moduleId: string, ctorName: string, id: string, name: string, cssClass?: string, order?: number, private _commandId?: string) {
|
||||
super(moduleId, ctorName, id, name, cssClass, order);
|
||||
}
|
||||
|
||||
public get commandId(): string {
|
||||
return this._commandId;
|
||||
}
|
||||
}
|
||||
|
||||
export class PanelRegistry extends CompositeRegistry<Panel> {
|
||||
private defaultPanelId: string;
|
||||
|
||||
/**
|
||||
* Registers a panel to the platform.
|
||||
*/
|
||||
public registerPanel(descriptor: PanelDescriptor): void {
|
||||
super.registerComposite(descriptor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the panel descriptor for the given id or null if none.
|
||||
*/
|
||||
public getPanel(id: string): PanelDescriptor {
|
||||
return this.getComposite(id) as PanelDescriptor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of registered panels known to the platform.
|
||||
*/
|
||||
public getPanels(): PanelDescriptor[] {
|
||||
return this.getComposites() as PanelDescriptor[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the id of the panel that should open on startup by default.
|
||||
*/
|
||||
public setDefaultPanelId(id: string): void {
|
||||
this.defaultPanelId = id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the id of the panel that should open on startup by default.
|
||||
*/
|
||||
public getDefaultPanelId(): string {
|
||||
return this.defaultPanelId;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A reusable action to toggle a panel with a specific id.
|
||||
*/
|
||||
export abstract class TogglePanelAction extends Action {
|
||||
|
||||
private panelId: string;
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
panelId: string,
|
||||
protected panelService: IPanelService,
|
||||
private partService: IPartService,
|
||||
cssClass?: string
|
||||
) {
|
||||
super(id, label, cssClass);
|
||||
this.panelId = panelId;
|
||||
}
|
||||
|
||||
public run(): TPromise<any> {
|
||||
|
||||
if (this.isPanelShowing()) {
|
||||
return this.partService.setPanelHidden(true);
|
||||
}
|
||||
|
||||
return this.panelService.openPanel(this.panelId, true);
|
||||
}
|
||||
|
||||
private isPanelShowing(): boolean {
|
||||
const panel = this.panelService.getActivePanel();
|
||||
|
||||
return panel && panel.getId() === this.panelId;
|
||||
}
|
||||
|
||||
protected isPanelFocused(): boolean {
|
||||
const activePanel = this.panelService.getActivePanel();
|
||||
const activeElement = document.activeElement;
|
||||
|
||||
return activePanel && activeElement && DOM.isAncestor(activeElement, (<Panel>activePanel).getContainer().getHTMLElement());
|
||||
}
|
||||
}
|
||||
|
||||
export const Extensions = {
|
||||
Panels: 'workbench.contributions.panels'
|
||||
};
|
||||
|
||||
Registry.add(Extensions.Panels, new PanelRegistry());
|
||||
148
src/vs/workbench/browser/part.ts
Normal file
148
src/vs/workbench/browser/part.ts
Normal file
@@ -0,0 +1,148 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import 'vs/css!./media/part';
|
||||
import { Dimension, Builder } from 'vs/base/browser/builder';
|
||||
import { Component } from 'vs/workbench/common/component';
|
||||
import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService';
|
||||
|
||||
export interface IPartOptions {
|
||||
hasTitle?: boolean;
|
||||
borderWidth?: () => number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parts are layed out in the workbench and have their own layout that arranges an optional title
|
||||
* and mandatory content area to show content.
|
||||
*/
|
||||
export abstract class Part extends Component {
|
||||
private parent: Builder;
|
||||
private titleArea: Builder;
|
||||
private contentArea: Builder;
|
||||
private partLayout: PartLayout;
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
private options: IPartOptions,
|
||||
themeService: IThemeService
|
||||
) {
|
||||
super(id, themeService);
|
||||
}
|
||||
|
||||
protected onThemeChange(theme: ITheme): void {
|
||||
|
||||
// only call if our create() method has been called
|
||||
if (this.parent) {
|
||||
super.onThemeChange(theme);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: Clients should not call this method, the workbench calls this
|
||||
* method. Calling it otherwise may result in unexpected behavior.
|
||||
*
|
||||
* Called to create title and content area of the part.
|
||||
*/
|
||||
public create(parent: Builder): void {
|
||||
this.parent = parent;
|
||||
this.titleArea = this.createTitleArea(parent);
|
||||
this.contentArea = this.createContentArea(parent);
|
||||
|
||||
this.partLayout = new PartLayout(this.parent, this.options, this.titleArea, this.contentArea);
|
||||
|
||||
this.updateStyles();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the overall part container.
|
||||
*/
|
||||
public getContainer(): Builder {
|
||||
return this.parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclasses override to provide a title area implementation.
|
||||
*/
|
||||
protected createTitleArea(parent: Builder): Builder {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the title area container.
|
||||
*/
|
||||
protected getTitleArea(): Builder {
|
||||
return this.titleArea;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclasses override to provide a content area implementation.
|
||||
*/
|
||||
protected createContentArea(parent: Builder): Builder {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the content area container.
|
||||
*/
|
||||
protected getContentArea(): Builder {
|
||||
return this.contentArea;
|
||||
}
|
||||
|
||||
/**
|
||||
* Layout title and content area in the given dimension.
|
||||
*/
|
||||
public layout(dimension: Dimension): Dimension[] {
|
||||
return this.partLayout.layout(dimension);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the part layout implementation.
|
||||
*/
|
||||
public getLayout(): PartLayout {
|
||||
return this.partLayout;
|
||||
}
|
||||
}
|
||||
|
||||
const TITLE_HEIGHT = 35;
|
||||
|
||||
export class PartLayout {
|
||||
|
||||
constructor(private container: Builder, private options: IPartOptions, private titleArea: Builder, private contentArea: Builder) {
|
||||
}
|
||||
|
||||
public layout(dimension: Dimension): Dimension[] {
|
||||
const { width, height } = dimension;
|
||||
|
||||
// Return the applied sizes to title and content
|
||||
const sizes: Dimension[] = [];
|
||||
|
||||
// Title Size: Width (Fill), Height (Variable)
|
||||
let titleSize: Dimension;
|
||||
if (this.options && this.options.hasTitle) {
|
||||
titleSize = new Dimension(width, Math.min(height, TITLE_HEIGHT));
|
||||
} else {
|
||||
titleSize = new Dimension(0, 0);
|
||||
}
|
||||
|
||||
// Content Size: Width (Fill), Height (Variable)
|
||||
const contentSize = new Dimension(width, height - titleSize.height);
|
||||
|
||||
if (this.options && typeof this.options.borderWidth === 'function') {
|
||||
contentSize.width -= this.options.borderWidth(); // adjust for border size
|
||||
}
|
||||
|
||||
sizes.push(titleSize);
|
||||
sizes.push(contentSize);
|
||||
|
||||
// Content
|
||||
if (this.contentArea) {
|
||||
this.contentArea.size(contentSize.width, contentSize.height);
|
||||
}
|
||||
|
||||
return sizes;
|
||||
}
|
||||
}
|
||||
757
src/vs/workbench/browser/parts/activitybar/activitybarActions.ts
Normal file
757
src/vs/workbench/browser/parts/activitybar/activitybarActions.ts
Normal file
@@ -0,0 +1,757 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import 'vs/css!./media/activityaction';
|
||||
import nls = require('vs/nls');
|
||||
import DOM = require('vs/base/browser/dom');
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { Builder, $ } from 'vs/base/browser/builder';
|
||||
import { DelayedDragHandler } from 'vs/base/browser/dnd';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { BaseActionItem, Separator, IBaseActionItemOptions } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { IActivityBarService, ProgressBadge, TextBadge, NumberBadge, IconBadge, IBadge } from 'vs/workbench/services/activity/common/activityBarService';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { ViewletDescriptor } from 'vs/workbench/browser/viewlet';
|
||||
import { IActivity, IGlobalActivity } from 'vs/workbench/browser/activity';
|
||||
import { dispose } from 'vs/base/common/lifecycle';
|
||||
import { IViewletService, } from 'vs/workbench/services/viewlet/browser/viewlet';
|
||||
import { IPartService, Parts } from 'vs/workbench/services/part/common/partService';
|
||||
import { IThemeService, ITheme, registerThemingParticipant, ICssStyleCollector } from 'vs/platform/theme/common/themeService';
|
||||
import { ACTIVITY_BAR_BADGE_FOREGROUND, ACTIVITY_BAR_BADGE_BACKGROUND, ACTIVITY_BAR_DRAG_AND_DROP_BACKGROUND, ACTIVITY_BAR_FOREGROUND } from 'vs/workbench/common/theme';
|
||||
import { contrastBorder, activeContrastBorder, focusBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
|
||||
export interface IViewletActivity {
|
||||
badge: IBadge;
|
||||
clazz: string;
|
||||
}
|
||||
|
||||
export class ActivityAction extends Action {
|
||||
private badge: IBadge;
|
||||
private _onDidChangeBadge = new Emitter<this>();
|
||||
|
||||
constructor(private _activity: IActivity) {
|
||||
super(_activity.id, _activity.name, _activity.cssClass);
|
||||
|
||||
this.badge = null;
|
||||
}
|
||||
|
||||
public get activity(): IActivity {
|
||||
return this._activity;
|
||||
}
|
||||
|
||||
public get onDidChangeBadge(): Event<this> {
|
||||
return this._onDidChangeBadge.event;
|
||||
}
|
||||
|
||||
public activate(): void {
|
||||
if (!this.checked) {
|
||||
this._setChecked(true);
|
||||
}
|
||||
}
|
||||
|
||||
public deactivate(): void {
|
||||
if (this.checked) {
|
||||
this._setChecked(false);
|
||||
}
|
||||
}
|
||||
|
||||
public getBadge(): IBadge {
|
||||
return this.badge;
|
||||
}
|
||||
|
||||
public setBadge(badge: IBadge): void {
|
||||
this.badge = badge;
|
||||
this._onDidChangeBadge.fire(this);
|
||||
}
|
||||
}
|
||||
|
||||
export class ViewletActivityAction extends ActivityAction {
|
||||
|
||||
private static preventDoubleClickDelay = 300;
|
||||
|
||||
private lastRun: number = 0;
|
||||
|
||||
constructor(
|
||||
private viewlet: ViewletDescriptor,
|
||||
@IViewletService private viewletService: IViewletService,
|
||||
@IPartService private partService: IPartService
|
||||
) {
|
||||
super(viewlet);
|
||||
}
|
||||
|
||||
public run(event): TPromise<any> {
|
||||
if (event instanceof MouseEvent && event.button === 2) {
|
||||
return TPromise.as(false); // do not run on right click
|
||||
}
|
||||
|
||||
// prevent accident trigger on a doubleclick (to help nervous people)
|
||||
const now = Date.now();
|
||||
if (now > this.lastRun /* https://github.com/Microsoft/vscode/issues/25830 */ && now - this.lastRun < ViewletActivityAction.preventDoubleClickDelay) {
|
||||
return TPromise.as(true);
|
||||
}
|
||||
this.lastRun = now;
|
||||
|
||||
const sideBarVisible = this.partService.isVisible(Parts.SIDEBAR_PART);
|
||||
const activeViewlet = this.viewletService.getActiveViewlet();
|
||||
|
||||
// Hide sidebar if selected viewlet already visible
|
||||
if (sideBarVisible && activeViewlet && activeViewlet.getId() === this.viewlet.id) {
|
||||
return this.partService.setSideBarHidden(true);
|
||||
}
|
||||
|
||||
return this.viewletService.openViewlet(this.viewlet.id, true)
|
||||
.then(() => this.activate());
|
||||
}
|
||||
}
|
||||
|
||||
export class ActivityActionItem extends BaseActionItem {
|
||||
protected $container: Builder;
|
||||
protected $label: Builder;
|
||||
protected $badge: Builder;
|
||||
|
||||
private $badgeContent: Builder;
|
||||
private mouseUpTimeout: number;
|
||||
|
||||
constructor(
|
||||
action: ActivityAction,
|
||||
options: IBaseActionItemOptions,
|
||||
@IThemeService protected themeService: IThemeService
|
||||
) {
|
||||
super(null, action, options);
|
||||
|
||||
this.themeService.onThemeChange(this.onThemeChange, this, this._callOnDispose);
|
||||
action.onDidChangeBadge(this.handleBadgeChangeEvenet, this, this._callOnDispose);
|
||||
}
|
||||
|
||||
protected get activity(): IActivity {
|
||||
return (this._action as ActivityAction).activity;
|
||||
}
|
||||
|
||||
protected updateStyles(): void {
|
||||
const theme = this.themeService.getTheme();
|
||||
|
||||
// Label
|
||||
if (this.$label) {
|
||||
const background = theme.getColor(ACTIVITY_BAR_FOREGROUND);
|
||||
|
||||
this.$label.style('background-color', background ? background.toString() : null);
|
||||
}
|
||||
|
||||
// Badge
|
||||
if (this.$badgeContent) {
|
||||
const badgeForeground = theme.getColor(ACTIVITY_BAR_BADGE_FOREGROUND);
|
||||
const badgeBackground = theme.getColor(ACTIVITY_BAR_BADGE_BACKGROUND);
|
||||
const contrastBorderColor = theme.getColor(contrastBorder);
|
||||
|
||||
this.$badgeContent.style('color', badgeForeground ? badgeForeground.toString() : null);
|
||||
this.$badgeContent.style('background-color', badgeBackground ? badgeBackground.toString() : null);
|
||||
|
||||
this.$badgeContent.style('border-style', contrastBorderColor ? 'solid' : null);
|
||||
this.$badgeContent.style('border-width', contrastBorderColor ? '1px' : null);
|
||||
this.$badgeContent.style('border-color', contrastBorderColor ? contrastBorderColor.toString() : null);
|
||||
}
|
||||
}
|
||||
|
||||
public render(container: HTMLElement): void {
|
||||
super.render(container);
|
||||
|
||||
// Make the container tab-able for keyboard navigation
|
||||
this.$container = $(container).attr({
|
||||
tabIndex: '0',
|
||||
role: 'button',
|
||||
title: this.activity.name
|
||||
});
|
||||
|
||||
// Try hard to prevent keyboard only focus feedback when using mouse
|
||||
this.$container.on(DOM.EventType.MOUSE_DOWN, () => {
|
||||
this.$container.addClass('clicked');
|
||||
});
|
||||
|
||||
this.$container.on(DOM.EventType.MOUSE_UP, () => {
|
||||
if (this.mouseUpTimeout) {
|
||||
clearTimeout(this.mouseUpTimeout);
|
||||
}
|
||||
|
||||
this.mouseUpTimeout = setTimeout(() => {
|
||||
this.$container.removeClass('clicked');
|
||||
}, 800); // delayed to prevent focus feedback from showing on mouse up
|
||||
});
|
||||
|
||||
// Label
|
||||
this.$label = $('a.action-label').appendTo(this.builder);
|
||||
if (this.activity.cssClass) {
|
||||
this.$label.addClass(this.activity.cssClass);
|
||||
}
|
||||
|
||||
this.$badge = this.builder.clone().div({ 'class': 'badge' }, (badge: Builder) => {
|
||||
this.$badgeContent = badge.div({ 'class': 'badge-content' });
|
||||
});
|
||||
|
||||
this.$badge.hide();
|
||||
|
||||
this.updateStyles();
|
||||
}
|
||||
|
||||
private onThemeChange(theme: ITheme): void {
|
||||
this.updateStyles();
|
||||
}
|
||||
|
||||
public setBadge(badge: IBadge): void {
|
||||
this.updateBadge(badge);
|
||||
}
|
||||
|
||||
protected updateBadge(badge: IBadge): void {
|
||||
this.$badgeContent.empty();
|
||||
this.$badge.hide();
|
||||
|
||||
if (badge) {
|
||||
|
||||
// Number
|
||||
if (badge instanceof NumberBadge) {
|
||||
if (badge.number) {
|
||||
this.$badgeContent.text(badge.number > 99 ? '99+' : badge.number.toString());
|
||||
this.$badge.show();
|
||||
}
|
||||
}
|
||||
|
||||
// Text
|
||||
else if (badge instanceof TextBadge) {
|
||||
this.$badgeContent.text(badge.text);
|
||||
this.$badge.show();
|
||||
}
|
||||
|
||||
// Text
|
||||
else if (badge instanceof IconBadge) {
|
||||
this.$badge.show();
|
||||
}
|
||||
|
||||
// Progress
|
||||
else if (badge instanceof ProgressBadge) {
|
||||
this.$badge.show();
|
||||
}
|
||||
|
||||
const description = badge.getDescription();
|
||||
this.$label.attr('aria-label', `${this.activity.name} - ${description}`);
|
||||
this.$label.title(description);
|
||||
}
|
||||
}
|
||||
|
||||
private handleBadgeChangeEvenet(): void {
|
||||
const action = this.getAction();
|
||||
if (action instanceof ActivityAction) {
|
||||
this.updateBadge(action.getBadge());
|
||||
}
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
super.dispose();
|
||||
|
||||
if (this.mouseUpTimeout) {
|
||||
clearTimeout(this.mouseUpTimeout);
|
||||
}
|
||||
|
||||
this.$badge.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
export class ViewletActionItem extends ActivityActionItem {
|
||||
|
||||
private static manageExtensionAction: ManageExtensionAction;
|
||||
private static toggleViewletPinnedAction: ToggleViewletPinnedAction;
|
||||
private static draggedViewlet: ViewletDescriptor;
|
||||
|
||||
private _keybinding: string;
|
||||
private cssClass: string;
|
||||
|
||||
constructor(
|
||||
private action: ViewletActivityAction,
|
||||
@IContextMenuService private contextMenuService: IContextMenuService,
|
||||
@IActivityBarService private activityBarService: IActivityBarService,
|
||||
@IKeybindingService private keybindingService: IKeybindingService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IThemeService themeService: IThemeService
|
||||
) {
|
||||
super(action, { draggable: true }, themeService);
|
||||
|
||||
this.cssClass = action.class;
|
||||
this._keybinding = this.getKeybindingLabel(this.viewlet.id);
|
||||
|
||||
if (!ViewletActionItem.manageExtensionAction) {
|
||||
ViewletActionItem.manageExtensionAction = instantiationService.createInstance(ManageExtensionAction);
|
||||
}
|
||||
|
||||
if (!ViewletActionItem.toggleViewletPinnedAction) {
|
||||
ViewletActionItem.toggleViewletPinnedAction = instantiationService.createInstance(ToggleViewletPinnedAction, void 0);
|
||||
}
|
||||
}
|
||||
|
||||
private get viewlet(): ViewletDescriptor {
|
||||
return this.action.activity as ViewletDescriptor;
|
||||
}
|
||||
|
||||
private getKeybindingLabel(id: string): string {
|
||||
const kb = this.keybindingService.lookupKeybinding(id);
|
||||
if (kb) {
|
||||
return kb.getLabel();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public render(container: HTMLElement): void {
|
||||
super.render(container);
|
||||
|
||||
this.$container.on('contextmenu', e => {
|
||||
DOM.EventHelper.stop(e, true);
|
||||
|
||||
this.showContextMenu(container);
|
||||
});
|
||||
|
||||
// Allow to drag
|
||||
this.$container.on(DOM.EventType.DRAG_START, (e: DragEvent) => {
|
||||
e.dataTransfer.effectAllowed = 'move';
|
||||
this.setDraggedViewlet(this.viewlet);
|
||||
|
||||
// Trigger the action even on drag start to prevent clicks from failing that started a drag
|
||||
if (!this.getAction().checked) {
|
||||
this.getAction().run();
|
||||
}
|
||||
});
|
||||
|
||||
// Drag enter
|
||||
let counter = 0; // see https://github.com/Microsoft/vscode/issues/14470
|
||||
this.$container.on(DOM.EventType.DRAG_ENTER, (e: DragEvent) => {
|
||||
const draggedViewlet = ViewletActionItem.getDraggedViewlet();
|
||||
if (draggedViewlet && draggedViewlet.id !== this.viewlet.id) {
|
||||
counter++;
|
||||
this.updateFromDragging(container, true);
|
||||
}
|
||||
});
|
||||
|
||||
// Drag leave
|
||||
this.$container.on(DOM.EventType.DRAG_LEAVE, (e: DragEvent) => {
|
||||
const draggedViewlet = ViewletActionItem.getDraggedViewlet();
|
||||
if (draggedViewlet) {
|
||||
counter--;
|
||||
if (counter === 0) {
|
||||
this.updateFromDragging(container, false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Drag end
|
||||
this.$container.on(DOM.EventType.DRAG_END, (e: DragEvent) => {
|
||||
const draggedViewlet = ViewletActionItem.getDraggedViewlet();
|
||||
if (draggedViewlet) {
|
||||
counter = 0;
|
||||
this.updateFromDragging(container, false);
|
||||
|
||||
ViewletActionItem.clearDraggedViewlet();
|
||||
}
|
||||
});
|
||||
|
||||
// Drop
|
||||
this.$container.on(DOM.EventType.DROP, (e: DragEvent) => {
|
||||
DOM.EventHelper.stop(e, true);
|
||||
|
||||
const draggedViewlet = ViewletActionItem.getDraggedViewlet();
|
||||
if (draggedViewlet && draggedViewlet.id !== this.viewlet.id) {
|
||||
this.updateFromDragging(container, false);
|
||||
ViewletActionItem.clearDraggedViewlet();
|
||||
|
||||
this.activityBarService.move(draggedViewlet.id, this.viewlet.id);
|
||||
}
|
||||
});
|
||||
|
||||
// Keybinding
|
||||
this.keybinding = this._keybinding; // force update
|
||||
|
||||
// Activate on drag over to reveal targets
|
||||
[this.$badge, this.$label].forEach(b => new DelayedDragHandler(b.getHTMLElement(), () => {
|
||||
if (!ViewletActionItem.getDraggedViewlet() && !this.getAction().checked) {
|
||||
this.getAction().run();
|
||||
}
|
||||
}));
|
||||
|
||||
this.updateStyles();
|
||||
}
|
||||
|
||||
private updateFromDragging(element: HTMLElement, isDragging: boolean): void {
|
||||
const theme = this.themeService.getTheme();
|
||||
const dragBackground = theme.getColor(ACTIVITY_BAR_DRAG_AND_DROP_BACKGROUND);
|
||||
|
||||
element.style.backgroundColor = isDragging && dragBackground ? dragBackground.toString() : null;
|
||||
}
|
||||
|
||||
public static getDraggedViewlet(): ViewletDescriptor {
|
||||
return ViewletActionItem.draggedViewlet;
|
||||
}
|
||||
|
||||
private setDraggedViewlet(viewlet: ViewletDescriptor): void {
|
||||
ViewletActionItem.draggedViewlet = viewlet;
|
||||
}
|
||||
|
||||
public static clearDraggedViewlet(): void {
|
||||
ViewletActionItem.draggedViewlet = void 0;
|
||||
}
|
||||
|
||||
private showContextMenu(container: HTMLElement): void {
|
||||
const actions: Action[] = [ViewletActionItem.toggleViewletPinnedAction];
|
||||
if (this.viewlet.extensionId) {
|
||||
actions.push(new Separator());
|
||||
actions.push(ViewletActionItem.manageExtensionAction);
|
||||
}
|
||||
|
||||
const isPinned = this.activityBarService.isPinned(this.viewlet.id);
|
||||
if (isPinned) {
|
||||
ViewletActionItem.toggleViewletPinnedAction.label = nls.localize('removeFromActivityBar', "Remove from Activity Bar");
|
||||
} else {
|
||||
ViewletActionItem.toggleViewletPinnedAction.label = nls.localize('keepInActivityBar', "Keep in Activity Bar");
|
||||
}
|
||||
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => container,
|
||||
getActionsContext: () => this.viewlet,
|
||||
getActions: () => TPromise.as(actions)
|
||||
});
|
||||
}
|
||||
|
||||
public focus(): void {
|
||||
this.$container.domFocus();
|
||||
}
|
||||
|
||||
public set keybinding(keybinding: string) {
|
||||
this._keybinding = keybinding;
|
||||
|
||||
if (!this.$label) {
|
||||
return;
|
||||
}
|
||||
|
||||
let title: string;
|
||||
if (keybinding) {
|
||||
title = nls.localize('titleKeybinding', "{0} ({1})", this.activity.name, keybinding);
|
||||
} else {
|
||||
title = this.activity.name;
|
||||
}
|
||||
|
||||
this.$label.title(title);
|
||||
this.$badge.title(title);
|
||||
this.$container.title(title);
|
||||
}
|
||||
|
||||
protected _updateClass(): void {
|
||||
if (this.cssClass) {
|
||||
this.$badge.removeClass(this.cssClass);
|
||||
}
|
||||
|
||||
this.cssClass = this.getAction().class;
|
||||
this.$badge.addClass(this.cssClass);
|
||||
}
|
||||
|
||||
protected _updateChecked(): void {
|
||||
if (this.getAction().checked) {
|
||||
this.$container.addClass('checked');
|
||||
} else {
|
||||
this.$container.removeClass('checked');
|
||||
}
|
||||
}
|
||||
|
||||
protected _updateEnabled(): void {
|
||||
if (this.getAction().enabled) {
|
||||
this.builder.removeClass('disabled');
|
||||
} else {
|
||||
this.builder.addClass('disabled');
|
||||
}
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
super.dispose();
|
||||
|
||||
ViewletActionItem.clearDraggedViewlet();
|
||||
|
||||
this.$label.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
export class ViewletOverflowActivityAction extends ActivityAction {
|
||||
|
||||
constructor(
|
||||
private showMenu: () => void
|
||||
) {
|
||||
super({
|
||||
id: 'activitybar.additionalViewlets.action',
|
||||
name: nls.localize('additionalViews', "Additional Views"),
|
||||
cssClass: 'toggle-more'
|
||||
});
|
||||
}
|
||||
|
||||
public run(event): TPromise<any> {
|
||||
this.showMenu();
|
||||
|
||||
return TPromise.as(true);
|
||||
}
|
||||
}
|
||||
|
||||
export class ViewletOverflowActivityActionItem extends ActivityActionItem {
|
||||
private name: string;
|
||||
private cssClass: string;
|
||||
private actions: OpenViewletAction[];
|
||||
|
||||
constructor(
|
||||
action: ActivityAction,
|
||||
private getOverflowingViewlets: () => ViewletDescriptor[],
|
||||
private getBadge: (viewlet: ViewletDescriptor) => IBadge,
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@IViewletService private viewletService: IViewletService,
|
||||
@IContextMenuService private contextMenuService: IContextMenuService,
|
||||
@IThemeService themeService: IThemeService
|
||||
) {
|
||||
super(action, null, themeService);
|
||||
|
||||
this.cssClass = action.class;
|
||||
this.name = action.label;
|
||||
}
|
||||
|
||||
public showMenu(): void {
|
||||
if (this.actions) {
|
||||
dispose(this.actions);
|
||||
}
|
||||
|
||||
this.actions = this.getActions();
|
||||
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => this.builder.getHTMLElement(),
|
||||
getActions: () => TPromise.as(this.actions),
|
||||
onHide: () => dispose(this.actions)
|
||||
});
|
||||
}
|
||||
|
||||
private getActions(): OpenViewletAction[] {
|
||||
const activeViewlet = this.viewletService.getActiveViewlet();
|
||||
|
||||
return this.getOverflowingViewlets().map(viewlet => {
|
||||
const action = this.instantiationService.createInstance(OpenViewletAction, viewlet);
|
||||
action.radio = activeViewlet && activeViewlet.getId() === action.id;
|
||||
|
||||
const badge = this.getBadge(action.viewlet);
|
||||
let suffix: string | number;
|
||||
if (badge instanceof NumberBadge) {
|
||||
suffix = badge.number;
|
||||
} else if (badge instanceof TextBadge) {
|
||||
suffix = badge.text;
|
||||
}
|
||||
|
||||
if (suffix) {
|
||||
action.label = nls.localize('numberBadge', "{0} ({1})", action.viewlet.name, suffix);
|
||||
} else {
|
||||
action.label = action.viewlet.name;
|
||||
}
|
||||
|
||||
return action;
|
||||
});
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
super.dispose();
|
||||
|
||||
this.actions = dispose(this.actions);
|
||||
}
|
||||
}
|
||||
|
||||
class ManageExtensionAction extends Action {
|
||||
|
||||
constructor(
|
||||
@ICommandService private commandService: ICommandService
|
||||
) {
|
||||
super('activitybar.manage.extension', nls.localize('manageExtension', "Manage Extension"));
|
||||
}
|
||||
|
||||
public run(viewlet: ViewletDescriptor): TPromise<any> {
|
||||
return this.commandService.executeCommand('_extensions.manage', viewlet.extensionId);
|
||||
}
|
||||
}
|
||||
|
||||
class OpenViewletAction extends Action {
|
||||
|
||||
constructor(
|
||||
private _viewlet: ViewletDescriptor,
|
||||
@IPartService private partService: IPartService,
|
||||
@IViewletService private viewletService: IViewletService
|
||||
) {
|
||||
super(_viewlet.id, _viewlet.name);
|
||||
}
|
||||
|
||||
public get viewlet(): ViewletDescriptor {
|
||||
return this._viewlet;
|
||||
}
|
||||
|
||||
public run(): TPromise<any> {
|
||||
const sideBarVisible = this.partService.isVisible(Parts.SIDEBAR_PART);
|
||||
const activeViewlet = this.viewletService.getActiveViewlet();
|
||||
|
||||
// Hide sidebar if selected viewlet already visible
|
||||
if (sideBarVisible && activeViewlet && activeViewlet.getId() === this.viewlet.id) {
|
||||
return this.partService.setSideBarHidden(true);
|
||||
}
|
||||
|
||||
return this.viewletService.openViewlet(this.viewlet.id, true);
|
||||
}
|
||||
}
|
||||
|
||||
export class ToggleViewletPinnedAction extends Action {
|
||||
|
||||
constructor(
|
||||
private viewlet: ViewletDescriptor,
|
||||
@IActivityBarService private activityBarService: IActivityBarService
|
||||
) {
|
||||
super('activitybar.show.toggleViewletPinned', viewlet ? viewlet.name : nls.localize('toggle', "Toggle View Pinned"));
|
||||
|
||||
this.checked = this.viewlet && this.activityBarService.isPinned(this.viewlet.id);
|
||||
}
|
||||
|
||||
public run(context?: ViewletDescriptor): TPromise<any> {
|
||||
const viewlet = this.viewlet || context;
|
||||
|
||||
if (this.activityBarService.isPinned(viewlet.id)) {
|
||||
this.activityBarService.unpin(viewlet.id);
|
||||
} else {
|
||||
this.activityBarService.pin(viewlet.id);
|
||||
}
|
||||
|
||||
return TPromise.as(true);
|
||||
}
|
||||
}
|
||||
|
||||
export class GlobalActivityAction extends ActivityAction {
|
||||
|
||||
constructor(activity: IGlobalActivity) {
|
||||
super(activity);
|
||||
}
|
||||
}
|
||||
|
||||
export class GlobalActivityActionItem extends ActivityActionItem {
|
||||
|
||||
constructor(
|
||||
action: GlobalActivityAction,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IContextMenuService protected contextMenuService: IContextMenuService
|
||||
) {
|
||||
super(action, { draggable: false }, themeService);
|
||||
}
|
||||
|
||||
public render(container: HTMLElement): void {
|
||||
super.render(container);
|
||||
|
||||
// Context menus are triggered on mouse down so that an item can be picked
|
||||
// and executed with releasing the mouse over it
|
||||
this.$container.on(DOM.EventType.MOUSE_DOWN, (e: MouseEvent) => {
|
||||
DOM.EventHelper.stop(e, true);
|
||||
|
||||
const event = new StandardMouseEvent(e);
|
||||
this.showContextMenu({ x: event.posx, y: event.posy });
|
||||
});
|
||||
|
||||
this.$container.on(DOM.EventType.KEY_UP, (e: KeyboardEvent) => {
|
||||
let event = new StandardKeyboardEvent(e);
|
||||
if (event.equals(KeyCode.Enter) || event.equals(KeyCode.Space)) {
|
||||
DOM.EventHelper.stop(e, true);
|
||||
|
||||
this.showContextMenu(this.$container.getHTMLElement());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private showContextMenu(location: HTMLElement | { x: number, y: number }): void {
|
||||
const globalAction = this._action as GlobalActivityAction;
|
||||
const activity = globalAction.activity as IGlobalActivity;
|
||||
const actions = activity.getActions();
|
||||
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => location,
|
||||
getActions: () => TPromise.as(actions),
|
||||
onHide: () => dispose(actions)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
|
||||
|
||||
// Styling with Outline color (e.g. high contrast theme)
|
||||
const outline = theme.getColor(activeContrastBorder);
|
||||
if (outline) {
|
||||
collector.addRule(`
|
||||
.monaco-workbench > .activitybar > .content .monaco-action-bar .action-item:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 9px;
|
||||
left: 9px;
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.monaco-workbench > .activitybar > .content .monaco-action-bar .action-item.active:before,
|
||||
.monaco-workbench > .activitybar > .content .monaco-action-bar .action-item.active:hover:before,
|
||||
.monaco-workbench > .activitybar > .content .monaco-action-bar .action-item.checked:before,
|
||||
.monaco-workbench > .activitybar > .content .monaco-action-bar .action-item.checked:hover:before {
|
||||
outline: 1px solid;
|
||||
}
|
||||
|
||||
.monaco-workbench > .activitybar > .content .monaco-action-bar .action-item:hover:before {
|
||||
outline: 1px dashed;
|
||||
}
|
||||
|
||||
.monaco-workbench > .activitybar > .content .monaco-action-bar .action-item.active:before,
|
||||
.monaco-workbench > .activitybar > .content .monaco-action-bar .action-item.checked:before,
|
||||
.monaco-workbench > .activitybar > .content .monaco-action-bar .action-item:hover:before {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.monaco-workbench > .activitybar > .content .monaco-action-bar .action-item:focus:before {
|
||||
border-left-color: ${outline};
|
||||
}
|
||||
|
||||
.monaco-workbench > .activitybar > .content .monaco-action-bar .action-item.active:before,
|
||||
.monaco-workbench > .activitybar > .content .monaco-action-bar .action-item.active:hover:before,
|
||||
.monaco-workbench > .activitybar > .content .monaco-action-bar .action-item.checked:before,
|
||||
.monaco-workbench > .activitybar > .content .monaco-action-bar .action-item.checked:hover:before,
|
||||
.monaco-workbench > .activitybar > .content .monaco-action-bar .action-item:hover:before {
|
||||
outline-color: ${outline};
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
// Styling without outline color
|
||||
else {
|
||||
const focusBorderColor = theme.getColor(focusBorder);
|
||||
if (focusBorderColor) {
|
||||
collector.addRule(`
|
||||
.monaco-workbench > .activitybar > .content .monaco-action-bar .action-item.active .action-label,
|
||||
.monaco-workbench > .activitybar > .content .monaco-action-bar .action-item.checked .action-label,
|
||||
.monaco-workbench > .activitybar > .content .monaco-action-bar .action-item:focus .action-label,
|
||||
.monaco-workbench > .activitybar > .content .monaco-action-bar .action-item:hover .action-label {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.monaco-workbench > .activitybar > .content .monaco-action-bar .action-item .action-label {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.monaco-workbench > .activitybar > .content .monaco-action-bar .action-item:focus:before {
|
||||
border-left-color: ${focusBorderColor};
|
||||
}
|
||||
`);
|
||||
}
|
||||
}
|
||||
});
|
||||
562
src/vs/workbench/browser/parts/activitybar/activitybarPart.ts
Normal file
562
src/vs/workbench/browser/parts/activitybar/activitybarPart.ts
Normal file
@@ -0,0 +1,562 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import 'vs/css!./media/activitybarpart';
|
||||
import nls = require('vs/nls');
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import DOM = require('vs/base/browser/dom');
|
||||
import * as arrays from 'vs/base/common/arrays';
|
||||
import { illegalArgument } from 'vs/base/common/errors';
|
||||
import { Builder, $, Dimension } from 'vs/base/browser/builder';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { ActionsOrientation, ActionBar, IActionItem, Separator } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { ViewletDescriptor } from 'vs/workbench/browser/viewlet';
|
||||
import { GlobalActivityExtensions, IGlobalActivityRegistry } from 'vs/workbench/browser/activity';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { Part } from 'vs/workbench/browser/part';
|
||||
import { IViewlet } from 'vs/workbench/common/viewlet';
|
||||
import { ToggleViewletPinnedAction, ViewletActivityAction, ActivityAction, GlobalActivityActionItem, ViewletActionItem, ViewletOverflowActivityAction, ViewletOverflowActivityActionItem, GlobalActivityAction, IViewletActivity } from 'vs/workbench/browser/parts/activitybar/activitybarActions';
|
||||
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
|
||||
import { IActivityBarService, IBadge } from 'vs/workbench/services/activity/common/activityBarService';
|
||||
import { IPartService, Position as SideBarPosition } from 'vs/workbench/services/part/common/partService';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IExtensionService } from 'vs/platform/extensions/common/extensions';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { Scope as MementoScope } from 'vs/workbench/common/memento';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { ToggleActivityBarVisibilityAction } from 'vs/workbench/browser/actions/toggleActivityBarVisibility';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { ACTIVITY_BAR_BACKGROUND, ACTIVITY_BAR_BORDER } from 'vs/workbench/common/theme';
|
||||
import { contrastBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
|
||||
export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
|
||||
private static readonly ACTIVITY_ACTION_HEIGHT = 50;
|
||||
private static readonly PINNED_VIEWLETS = 'workbench.activity.pinnedViewlets';
|
||||
|
||||
public _serviceBrand: any;
|
||||
|
||||
private dimension: Dimension;
|
||||
|
||||
private globalActionBar: ActionBar;
|
||||
private globalActivityIdToActions: { [globalActivityId: string]: GlobalActivityAction; };
|
||||
|
||||
private viewletSwitcherBar: ActionBar;
|
||||
private viewletOverflowAction: ViewletOverflowActivityAction;
|
||||
private viewletOverflowActionItem: ViewletOverflowActivityActionItem;
|
||||
|
||||
private viewletIdToActions: { [viewletId: string]: ActivityAction; };
|
||||
private viewletIdToActionItems: { [viewletId: string]: IActionItem; };
|
||||
private viewletIdToActivityStack: { [viewletId: string]: IViewletActivity[]; };
|
||||
|
||||
private memento: object;
|
||||
private pinnedViewlets: string[];
|
||||
private activeUnpinnedViewlet: ViewletDescriptor;
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
@IViewletService private viewletService: IViewletService,
|
||||
@IExtensionService private extensionService: IExtensionService,
|
||||
@IStorageService private storageService: IStorageService,
|
||||
@IContextMenuService private contextMenuService: IContextMenuService,
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@IPartService private partService: IPartService,
|
||||
@IThemeService themeService: IThemeService
|
||||
) {
|
||||
super(id, { hasTitle: false }, themeService);
|
||||
|
||||
this.globalActivityIdToActions = Object.create(null);
|
||||
|
||||
this.viewletIdToActionItems = Object.create(null);
|
||||
this.viewletIdToActions = Object.create(null);
|
||||
this.viewletIdToActivityStack = Object.create(null);
|
||||
|
||||
this.memento = this.getMemento(this.storageService, MementoScope.GLOBAL);
|
||||
|
||||
const pinnedViewlets = this.memento[ActivitybarPart.PINNED_VIEWLETS] as string[];
|
||||
|
||||
if (pinnedViewlets) {
|
||||
this.pinnedViewlets = pinnedViewlets
|
||||
// TODO@Ben: Migrate git => scm viewlet
|
||||
.map(id => id === 'workbench.view.git' ? 'workbench.view.scm' : id)
|
||||
.filter(arrays.uniqueFilter<string>(str => str));
|
||||
} else {
|
||||
this.pinnedViewlets = this.viewletService.getViewlets().map(v => v.id);
|
||||
}
|
||||
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
|
||||
// Activate viewlet action on opening of a viewlet
|
||||
this.toUnbind.push(this.viewletService.onDidViewletOpen(viewlet => this.onDidViewletOpen(viewlet)));
|
||||
|
||||
// Deactivate viewlet action on close
|
||||
this.toUnbind.push(this.viewletService.onDidViewletClose(viewlet => this.onDidViewletClose(viewlet)));
|
||||
}
|
||||
|
||||
private onDidViewletOpen(viewlet: IViewlet): void {
|
||||
const id = viewlet.getId();
|
||||
|
||||
if (this.viewletIdToActions[id]) {
|
||||
this.viewletIdToActions[id].activate();
|
||||
}
|
||||
|
||||
const activeUnpinnedViewletShouldClose = this.activeUnpinnedViewlet && this.activeUnpinnedViewlet.id !== viewlet.getId();
|
||||
const activeUnpinnedViewletShouldShow = !this.getPinnedViewlets().some(v => v.id === viewlet.getId());
|
||||
if (activeUnpinnedViewletShouldShow || activeUnpinnedViewletShouldClose) {
|
||||
this.updateViewletSwitcher();
|
||||
}
|
||||
}
|
||||
|
||||
private onDidViewletClose(viewlet: IViewlet): void {
|
||||
const id = viewlet.getId();
|
||||
|
||||
if (this.viewletIdToActions[id]) {
|
||||
this.viewletIdToActions[id].deactivate();
|
||||
}
|
||||
}
|
||||
|
||||
public showGlobalActivity(globalActivityId: string, badge: IBadge): IDisposable {
|
||||
if (!badge) {
|
||||
throw illegalArgument('badge');
|
||||
}
|
||||
|
||||
const action = this.globalActivityIdToActions[globalActivityId];
|
||||
if (!action) {
|
||||
throw illegalArgument('globalActivityId');
|
||||
}
|
||||
|
||||
action.setBadge(badge);
|
||||
|
||||
return toDisposable(() => action.setBadge(undefined));
|
||||
}
|
||||
|
||||
public showActivity(viewletId: string, badge: IBadge, clazz?: string): IDisposable {
|
||||
if (!badge) {
|
||||
throw illegalArgument('badge');
|
||||
}
|
||||
|
||||
const activity = <IViewletActivity>{ badge, clazz };
|
||||
const stack = this.viewletIdToActivityStack[viewletId] || (this.viewletIdToActivityStack[viewletId] = []);
|
||||
stack.unshift(activity);
|
||||
|
||||
this.updateActivity(viewletId);
|
||||
|
||||
return {
|
||||
dispose: () => {
|
||||
const stack = this.viewletIdToActivityStack[viewletId];
|
||||
if (!stack) {
|
||||
return;
|
||||
}
|
||||
const idx = stack.indexOf(activity);
|
||||
if (idx < 0) {
|
||||
return;
|
||||
}
|
||||
stack.splice(idx, 1);
|
||||
if (stack.length === 0) {
|
||||
delete this.viewletIdToActivityStack[viewletId];
|
||||
}
|
||||
this.updateActivity(viewletId);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private updateActivity(viewletId: string) {
|
||||
const action = this.viewletIdToActions[viewletId];
|
||||
if (!action) {
|
||||
return;
|
||||
}
|
||||
const stack = this.viewletIdToActivityStack[viewletId];
|
||||
if (!stack || !stack.length) {
|
||||
// reset
|
||||
action.setBadge(undefined);
|
||||
|
||||
} else {
|
||||
// update
|
||||
const [{ badge, clazz }] = stack;
|
||||
action.setBadge(badge);
|
||||
if (clazz) {
|
||||
action.class = clazz;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public createContentArea(parent: Builder): Builder {
|
||||
const $el = $(parent);
|
||||
const $result = $('.content').appendTo($el);
|
||||
|
||||
// Top Actionbar with action items for each viewlet action
|
||||
this.createViewletSwitcher($result.clone());
|
||||
|
||||
// Top Actionbar with action items for each viewlet action
|
||||
this.createGlobalActivityActionBar($result.getHTMLElement());
|
||||
|
||||
// Contextmenu for viewlets
|
||||
$(parent).on('contextmenu', (e: MouseEvent) => {
|
||||
DOM.EventHelper.stop(e, true);
|
||||
|
||||
this.showContextMenu(e);
|
||||
}, this.toUnbind);
|
||||
|
||||
// Allow to drop at the end to move viewlet to the end
|
||||
$(parent).on(DOM.EventType.DROP, (e: DragEvent) => {
|
||||
const draggedViewlet = ViewletActionItem.getDraggedViewlet();
|
||||
if (draggedViewlet) {
|
||||
DOM.EventHelper.stop(e, true);
|
||||
|
||||
ViewletActionItem.clearDraggedViewlet();
|
||||
|
||||
const targetId = this.pinnedViewlets[this.pinnedViewlets.length - 1];
|
||||
if (targetId !== draggedViewlet.id) {
|
||||
this.move(draggedViewlet.id, this.pinnedViewlets[this.pinnedViewlets.length - 1]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public updateStyles(): void {
|
||||
super.updateStyles();
|
||||
|
||||
// Part container
|
||||
const container = this.getContainer();
|
||||
const background = this.getColor(ACTIVITY_BAR_BACKGROUND);
|
||||
container.style('background-color', background);
|
||||
|
||||
const borderColor = this.getColor(ACTIVITY_BAR_BORDER) || this.getColor(contrastBorder);
|
||||
const isPositionLeft = this.partService.getSideBarPosition() === SideBarPosition.LEFT;
|
||||
container.style('box-sizing', borderColor && isPositionLeft ? 'border-box' : null);
|
||||
container.style('border-right-width', borderColor && isPositionLeft ? '1px' : null);
|
||||
container.style('border-right-style', borderColor && isPositionLeft ? 'solid' : null);
|
||||
container.style('border-right-color', isPositionLeft ? borderColor : null);
|
||||
container.style('border-left-width', borderColor && !isPositionLeft ? '1px' : null);
|
||||
container.style('border-left-style', borderColor && !isPositionLeft ? 'solid' : null);
|
||||
container.style('border-left-color', !isPositionLeft ? borderColor : null);
|
||||
}
|
||||
|
||||
private showContextMenu(e: MouseEvent): void {
|
||||
const event = new StandardMouseEvent(e);
|
||||
|
||||
const actions: Action[] = this.viewletService.getViewlets().map(viewlet => this.instantiationService.createInstance(ToggleViewletPinnedAction, viewlet));
|
||||
actions.push(new Separator());
|
||||
actions.push(this.instantiationService.createInstance(ToggleActivityBarVisibilityAction, ToggleActivityBarVisibilityAction.ID, nls.localize('hideActivitBar', "Hide Activity Bar")));
|
||||
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => { return { x: event.posx + 1, y: event.posy }; },
|
||||
getActions: () => TPromise.as(actions),
|
||||
onHide: () => dispose(actions)
|
||||
});
|
||||
}
|
||||
|
||||
private createViewletSwitcher(div: Builder): void {
|
||||
this.viewletSwitcherBar = new ActionBar(div, {
|
||||
actionItemProvider: (action: Action) => action instanceof ViewletOverflowActivityAction ? this.viewletOverflowActionItem : this.viewletIdToActionItems[action.id],
|
||||
orientation: ActionsOrientation.VERTICAL,
|
||||
ariaLabel: nls.localize('activityBarAriaLabel', "Active View Switcher"),
|
||||
animated: false
|
||||
});
|
||||
|
||||
this.updateViewletSwitcher();
|
||||
|
||||
// Update viewlet switcher when external viewlets become ready
|
||||
this.extensionService.onReady().then(() => this.updateViewletSwitcher());
|
||||
}
|
||||
|
||||
private createGlobalActivityActionBar(container: HTMLElement): void {
|
||||
const activityRegistry = Registry.as<IGlobalActivityRegistry>(GlobalActivityExtensions);
|
||||
const descriptors = activityRegistry.getActivities();
|
||||
const actions = descriptors
|
||||
.map(d => this.instantiationService.createInstance(d))
|
||||
.map(a => new GlobalActivityAction(a));
|
||||
|
||||
this.globalActionBar = new ActionBar(container, {
|
||||
actionItemProvider: a => this.instantiationService.createInstance(GlobalActivityActionItem, a),
|
||||
orientation: ActionsOrientation.VERTICAL,
|
||||
ariaLabel: nls.localize('globalActions', "Global Actions"),
|
||||
animated: false
|
||||
});
|
||||
|
||||
actions.forEach(a => {
|
||||
this.globalActivityIdToActions[a.id] = a;
|
||||
this.globalActionBar.push(a);
|
||||
});
|
||||
}
|
||||
|
||||
private updateViewletSwitcher() {
|
||||
if (!this.viewletSwitcherBar) {
|
||||
return; // We have not been rendered yet so there is nothing to update.
|
||||
}
|
||||
|
||||
let viewletsToShow = this.getPinnedViewlets();
|
||||
|
||||
// Always show the active viewlet even if it is marked to be hidden
|
||||
const activeViewlet = this.viewletService.getActiveViewlet();
|
||||
if (activeViewlet && !viewletsToShow.some(viewlet => viewlet.id === activeViewlet.getId())) {
|
||||
this.activeUnpinnedViewlet = this.viewletService.getViewlet(activeViewlet.getId());
|
||||
viewletsToShow.push(this.activeUnpinnedViewlet);
|
||||
} else {
|
||||
this.activeUnpinnedViewlet = void 0;
|
||||
}
|
||||
|
||||
// Ensure we are not showing more viewlets than we have height for
|
||||
let overflows = false;
|
||||
if (this.dimension) {
|
||||
let availableHeight = this.dimension.height;
|
||||
if (this.globalActionBar) {
|
||||
availableHeight -= (this.globalActionBar.items.length * ActivitybarPart.ACTIVITY_ACTION_HEIGHT); // adjust for global actions showing
|
||||
}
|
||||
|
||||
const maxVisible = Math.floor(availableHeight / ActivitybarPart.ACTIVITY_ACTION_HEIGHT);
|
||||
overflows = viewletsToShow.length > maxVisible;
|
||||
|
||||
if (overflows) {
|
||||
viewletsToShow = viewletsToShow.slice(0, maxVisible - 1 /* make room for overflow action */);
|
||||
}
|
||||
}
|
||||
|
||||
const visibleViewlets = Object.keys(this.viewletIdToActions);
|
||||
const visibleViewletsChange = !arrays.equals(viewletsToShow.map(viewlet => viewlet.id), visibleViewlets);
|
||||
|
||||
// Pull out overflow action if there is a viewlet change so that we can add it to the end later
|
||||
if (this.viewletOverflowAction && visibleViewletsChange) {
|
||||
this.viewletSwitcherBar.pull(this.viewletSwitcherBar.length() - 1);
|
||||
|
||||
this.viewletOverflowAction.dispose();
|
||||
this.viewletOverflowAction = null;
|
||||
|
||||
this.viewletOverflowActionItem.dispose();
|
||||
this.viewletOverflowActionItem = null;
|
||||
}
|
||||
|
||||
// Pull out viewlets that overflow or got hidden
|
||||
const viewletIdsToShow = viewletsToShow.map(v => v.id);
|
||||
visibleViewlets.forEach(viewletId => {
|
||||
if (viewletIdsToShow.indexOf(viewletId) === -1) {
|
||||
this.pullViewlet(viewletId);
|
||||
}
|
||||
});
|
||||
|
||||
// Built actions for viewlets to show
|
||||
const newViewletsToShow = viewletsToShow
|
||||
.filter(viewlet => !this.viewletIdToActions[viewlet.id])
|
||||
.map(viewlet => this.toAction(viewlet));
|
||||
|
||||
// Update when we have new viewlets to show
|
||||
if (newViewletsToShow.length) {
|
||||
|
||||
// Add to viewlet switcher
|
||||
this.viewletSwitcherBar.push(newViewletsToShow, { label: true, icon: true });
|
||||
|
||||
// Make sure to activate the active one
|
||||
const activeViewlet = this.viewletService.getActiveViewlet();
|
||||
if (activeViewlet) {
|
||||
const activeViewletEntry = this.viewletIdToActions[activeViewlet.getId()];
|
||||
if (activeViewletEntry) {
|
||||
activeViewletEntry.activate();
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure to restore activity
|
||||
Object.keys(this.viewletIdToActions).forEach(viewletId => {
|
||||
this.updateActivity(viewletId);
|
||||
});
|
||||
}
|
||||
|
||||
// Add overflow action as needed
|
||||
if (visibleViewletsChange && overflows) {
|
||||
this.viewletOverflowAction = this.instantiationService.createInstance(ViewletOverflowActivityAction, () => this.viewletOverflowActionItem.showMenu());
|
||||
this.viewletOverflowActionItem = this.instantiationService.createInstance(ViewletOverflowActivityActionItem, this.viewletOverflowAction, () => this.getOverflowingViewlets(), (viewlet: ViewletDescriptor) => this.viewletIdToActivityStack[viewlet.id] && this.viewletIdToActivityStack[viewlet.id][0].badge);
|
||||
|
||||
this.viewletSwitcherBar.push(this.viewletOverflowAction, { label: true, icon: true });
|
||||
}
|
||||
}
|
||||
|
||||
private getOverflowingViewlets(): ViewletDescriptor[] {
|
||||
const viewlets = this.getPinnedViewlets();
|
||||
if (this.activeUnpinnedViewlet) {
|
||||
viewlets.push(this.activeUnpinnedViewlet);
|
||||
}
|
||||
const visibleViewlets = Object.keys(this.viewletIdToActions);
|
||||
|
||||
return viewlets.filter(viewlet => visibleViewlets.indexOf(viewlet.id) === -1);
|
||||
}
|
||||
|
||||
private getVisibleViewlets(): ViewletDescriptor[] {
|
||||
const viewlets = this.viewletService.getViewlets();
|
||||
const visibleViewlets = Object.keys(this.viewletIdToActions);
|
||||
|
||||
return viewlets.filter(viewlet => visibleViewlets.indexOf(viewlet.id) >= 0);
|
||||
}
|
||||
|
||||
private getPinnedViewlets(): ViewletDescriptor[] {
|
||||
return this.pinnedViewlets.map(viewletId => this.viewletService.getViewlet(viewletId)).filter(v => !!v); // ensure to remove those that might no longer exist
|
||||
}
|
||||
|
||||
private pullViewlet(viewletId: string): void {
|
||||
const index = Object.keys(this.viewletIdToActions).indexOf(viewletId);
|
||||
if (index >= 0) {
|
||||
this.viewletSwitcherBar.pull(index);
|
||||
|
||||
const action = this.viewletIdToActions[viewletId];
|
||||
action.dispose();
|
||||
delete this.viewletIdToActions[viewletId];
|
||||
|
||||
const actionItem = this.viewletIdToActionItems[action.id];
|
||||
actionItem.dispose();
|
||||
delete this.viewletIdToActionItems[action.id];
|
||||
}
|
||||
}
|
||||
|
||||
private toAction(viewlet: ViewletDescriptor): ActivityAction {
|
||||
const action = this.instantiationService.createInstance(ViewletActivityAction, viewlet);
|
||||
|
||||
this.viewletIdToActionItems[action.id] = this.instantiationService.createInstance(ViewletActionItem, action);
|
||||
this.viewletIdToActions[viewlet.id] = action;
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
public getPinned(): string[] {
|
||||
return this.pinnedViewlets;
|
||||
}
|
||||
|
||||
public unpin(viewletId: string): void {
|
||||
if (!this.isPinned(viewletId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const activeViewlet = this.viewletService.getActiveViewlet();
|
||||
const defaultViewletId = this.viewletService.getDefaultViewletId();
|
||||
const visibleViewlets = this.getVisibleViewlets();
|
||||
|
||||
let unpinPromise: TPromise<any>;
|
||||
|
||||
// Case: viewlet is not the active one or the active one is a different one
|
||||
// Solv: we do nothing
|
||||
if (!activeViewlet || activeViewlet.getId() !== viewletId) {
|
||||
unpinPromise = TPromise.as(null);
|
||||
}
|
||||
|
||||
// Case: viewlet is not the default viewlet and default viewlet is still showing
|
||||
// Solv: we open the default viewlet
|
||||
else if (defaultViewletId !== viewletId && this.isPinned(defaultViewletId)) {
|
||||
unpinPromise = this.viewletService.openViewlet(defaultViewletId, true);
|
||||
}
|
||||
|
||||
// Case: we closed the last visible viewlet
|
||||
// Solv: we hide the sidebar
|
||||
else if (visibleViewlets.length === 1) {
|
||||
unpinPromise = this.partService.setSideBarHidden(true);
|
||||
}
|
||||
|
||||
// Case: we closed the default viewlet
|
||||
// Solv: we open the next visible viewlet from top
|
||||
else {
|
||||
unpinPromise = this.viewletService.openViewlet(visibleViewlets.filter(viewlet => viewlet.id !== viewletId)[0].id, true);
|
||||
}
|
||||
|
||||
unpinPromise.then(() => {
|
||||
|
||||
// then remove from pinned and update switcher
|
||||
const index = this.pinnedViewlets.indexOf(viewletId);
|
||||
this.pinnedViewlets.splice(index, 1);
|
||||
|
||||
this.updateViewletSwitcher();
|
||||
});
|
||||
}
|
||||
|
||||
public isPinned(viewletId: string): boolean {
|
||||
return this.pinnedViewlets.indexOf(viewletId) >= 0;
|
||||
}
|
||||
|
||||
public pin(viewletId: string, update = true): void {
|
||||
if (this.isPinned(viewletId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// first open that viewlet
|
||||
this.viewletService.openViewlet(viewletId, true).then(() => {
|
||||
|
||||
// then update
|
||||
this.pinnedViewlets.push(viewletId);
|
||||
this.pinnedViewlets = arrays.distinct(this.pinnedViewlets);
|
||||
|
||||
if (update) {
|
||||
this.updateViewletSwitcher();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public move(viewletId: string, toViewletId: string): void {
|
||||
|
||||
// Make sure a moved viewlet gets pinned
|
||||
if (!this.isPinned(viewletId)) {
|
||||
this.pin(viewletId, false /* defer update, we take care of it */);
|
||||
}
|
||||
|
||||
const fromIndex = this.pinnedViewlets.indexOf(viewletId);
|
||||
const toIndex = this.pinnedViewlets.indexOf(toViewletId);
|
||||
|
||||
this.pinnedViewlets.splice(fromIndex, 1);
|
||||
this.pinnedViewlets.splice(toIndex, 0, viewletId);
|
||||
|
||||
// Clear viewlets that are impacted by the move
|
||||
const visibleViewlets = Object.keys(this.viewletIdToActions);
|
||||
for (let i = Math.min(fromIndex, toIndex); i < visibleViewlets.length; i++) {
|
||||
this.pullViewlet(visibleViewlets[i]);
|
||||
}
|
||||
|
||||
// timeout helps to prevent artifacts from showing up
|
||||
setTimeout(() => {
|
||||
this.updateViewletSwitcher();
|
||||
}, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Layout title, content and status area in the given dimension.
|
||||
*/
|
||||
public layout(dimension: Dimension): Dimension[] {
|
||||
|
||||
// Pass to super
|
||||
const sizes = super.layout(dimension);
|
||||
|
||||
this.dimension = sizes[1];
|
||||
|
||||
// Update switcher to handle overflow issues
|
||||
this.updateViewletSwitcher();
|
||||
|
||||
return sizes;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
if (this.viewletSwitcherBar) {
|
||||
this.viewletSwitcherBar.dispose();
|
||||
this.viewletSwitcherBar = null;
|
||||
}
|
||||
|
||||
if (this.globalActionBar) {
|
||||
this.globalActionBar.dispose();
|
||||
this.globalActionBar = null;
|
||||
}
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
public shutdown(): void {
|
||||
|
||||
// Persist Hidden State
|
||||
this.memento[ActivitybarPart.PINNED_VIEWLETS] = this.pinnedViewlets;
|
||||
|
||||
// Pass to super
|
||||
super.shutdown();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-workbench > .activitybar > .content .monaco-action-bar .action-item {
|
||||
display: block;
|
||||
position: relative;
|
||||
padding: 5px 0;
|
||||
}
|
||||
|
||||
.monaco-workbench > .activitybar > .content .monaco-action-bar .action-label {
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
margin-right: 0;
|
||||
padding: 0 0 0 50px;
|
||||
box-sizing: border-box;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .activitybar > .content .monaco-action-bar .action-item:focus:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 9px;
|
||||
height: 32px;
|
||||
width: 0;
|
||||
border-left: 2px solid;
|
||||
}
|
||||
|
||||
.monaco-workbench > .activitybar > .content .monaco-action-bar .action-item.clicked:focus:before {
|
||||
border-left: none !important; /* no focus feedback when using mouse */
|
||||
}
|
||||
|
||||
.monaco-workbench > .activitybar.left > .content .monaco-action-bar .action-item:focus:before {
|
||||
left: 1px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .activitybar.right > .content .monaco-action-bar .action-item:focus:before {
|
||||
right: 1px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .activitybar > .content .monaco-action-bar .action-label.toggle-more {
|
||||
-webkit-mask: url('ellipsis-global.svg') no-repeat 50% 50%;
|
||||
}
|
||||
|
||||
.monaco-workbench > .activitybar > .content .monaco-action-bar .badge {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
left: 0;
|
||||
overflow: hidden;
|
||||
width: 50px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .activitybar > .content .monaco-action-bar .badge .badge-content {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 8px;
|
||||
font-size: 11px;
|
||||
min-width: 8px;
|
||||
height: 18px;
|
||||
line-height: 18px;
|
||||
padding: 0 5px;
|
||||
border-radius: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Right aligned */
|
||||
|
||||
.monaco-workbench > .activitybar.right > .content .monaco-action-bar .action-label {
|
||||
margin-left: 0;
|
||||
padding: 0 50px 0 0;
|
||||
background-position: calc(100% - 9px) center;
|
||||
}
|
||||
|
||||
.monaco-workbench > .activitybar.right > .content .monaco-action-bar .badge {
|
||||
left: auto;
|
||||
right: 0;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-workbench > .part.activitybar {
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .activitybar > .content {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.monaco-workbench > .activitybar > .content .monaco-action-bar {
|
||||
text-align: left;
|
||||
background-color: inherit;
|
||||
}
|
||||
|
||||
.monaco-workbench > .activitybar [tabindex="0"]:focus {
|
||||
outline: 0 !important; /* activity bar indicates focus custom */
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="32" height="32"><defs><style>.icon-canvas-transparent{fill:#f6f6f6;opacity:0;}.cls-1{fill:#fff;}</style></defs><title>Ellipsis_32x</title><g id="canvas"><path class="icon-canvas-transparent" d="M32,32H0V0H32Z" transform="translate(0 0)"/></g><g id="iconBg"><path class="cls-1" d="M9,16a3,3,0,1,1-3-3A3,3,0,0,1,9,16Zm7-3a3,3,0,1,0,3,3A3,3,0,0,0,16,13Zm10,0a3,3,0,1,0,3,3A3,3,0,0,0,26,13Z" transform="translate(0 0)"/></g></svg>
|
||||
|
After Width: | Height: | Size: 493 B |
581
src/vs/workbench/browser/parts/compositePart.ts
Normal file
581
src/vs/workbench/browser/parts/compositePart.ts
Normal file
@@ -0,0 +1,581 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import 'vs/css!./media/compositepart';
|
||||
import nls = require('vs/nls');
|
||||
import { defaultGenerator } from 'vs/base/common/idGenerator';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { Dimension, Builder, $ } from 'vs/base/browser/builder';
|
||||
import events = require('vs/base/common/events');
|
||||
import strings = require('vs/base/common/strings');
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import types = require('vs/base/common/types');
|
||||
import errors = require('vs/base/common/errors');
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { CONTEXT as ToolBarContext, ToolBar } from 'vs/base/browser/ui/toolbar/toolbar';
|
||||
import { IActionItem, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar';
|
||||
import { IActionBarRegistry, Extensions, prepareActions } from 'vs/workbench/browser/actions';
|
||||
import { Action, IAction } from 'vs/base/common/actions';
|
||||
import { Part, IPartOptions } from 'vs/workbench/browser/part';
|
||||
import { Composite, CompositeRegistry } from 'vs/workbench/browser/composite';
|
||||
import { IComposite } from 'vs/workbench/common/composite';
|
||||
import { WorkbenchProgressService } from 'vs/workbench/services/progress/browser/progressService';
|
||||
import { IPartService } from 'vs/workbench/services/part/common/partService';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
|
||||
import { IMessageService, Severity } from 'vs/platform/message/common/message';
|
||||
import { IProgressService } from 'vs/platform/progress/common/progress';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { attachProgressBarStyler } from 'vs/platform/theme/common/styler';
|
||||
|
||||
export interface ICompositeTitleLabel {
|
||||
|
||||
/**
|
||||
* Asks to update the title for the composite with the given ID.
|
||||
*/
|
||||
updateTitle(id: string, title: string, keybinding?: string): void;
|
||||
|
||||
/**
|
||||
* Called when theming information changes.
|
||||
*/
|
||||
updateStyles(): void;
|
||||
}
|
||||
|
||||
export abstract class CompositePart<T extends Composite> extends Part {
|
||||
private instantiatedCompositeListeners: IDisposable[];
|
||||
private mapCompositeToCompositeContainer: { [compositeId: string]: Builder; };
|
||||
private mapActionsBindingToComposite: { [compositeId: string]: () => void; };
|
||||
private mapProgressServiceToComposite: { [compositeId: string]: IProgressService; };
|
||||
private activeComposite: Composite;
|
||||
private lastActiveCompositeId: string;
|
||||
private instantiatedComposites: Composite[];
|
||||
private titleLabel: ICompositeTitleLabel;
|
||||
private toolBar: ToolBar;
|
||||
private compositeLoaderPromises: { [compositeId: string]: TPromise<Composite>; };
|
||||
private progressBar: ProgressBar;
|
||||
private contentAreaSize: Dimension;
|
||||
private telemetryActionsListener: IDisposable;
|
||||
private currentCompositeOpenToken: string;
|
||||
protected _onDidCompositeOpen = new Emitter<IComposite>();
|
||||
protected _onDidCompositeClose = new Emitter<IComposite>();
|
||||
|
||||
constructor(
|
||||
private messageService: IMessageService,
|
||||
private storageService: IStorageService,
|
||||
private telemetryService: ITelemetryService,
|
||||
private contextMenuService: IContextMenuService,
|
||||
protected partService: IPartService,
|
||||
private keybindingService: IKeybindingService,
|
||||
protected instantiationService: IInstantiationService,
|
||||
themeService: IThemeService,
|
||||
private registry: CompositeRegistry<T>,
|
||||
private activeCompositeSettingsKey: string,
|
||||
private defaultCompositeId: string,
|
||||
private nameForTelemetry: string,
|
||||
private compositeCSSClass: string,
|
||||
private actionContributionScope: string,
|
||||
private titleForegroundColor: string,
|
||||
id: string,
|
||||
options: IPartOptions
|
||||
) {
|
||||
super(id, options, themeService);
|
||||
|
||||
this.instantiatedCompositeListeners = [];
|
||||
this.mapCompositeToCompositeContainer = {};
|
||||
this.mapActionsBindingToComposite = {};
|
||||
this.mapProgressServiceToComposite = {};
|
||||
this.activeComposite = null;
|
||||
this.instantiatedComposites = [];
|
||||
this.compositeLoaderPromises = {};
|
||||
this.lastActiveCompositeId = storageService.get(activeCompositeSettingsKey, StorageScope.WORKSPACE, this.defaultCompositeId);
|
||||
}
|
||||
|
||||
protected openComposite(id: string, focus?: boolean): TPromise<Composite> {
|
||||
// Check if composite already visible and just focus in that case
|
||||
if (this.activeComposite && this.activeComposite.getId() === id) {
|
||||
if (focus) {
|
||||
this.activeComposite.focus();
|
||||
}
|
||||
|
||||
// Fullfill promise with composite that is being opened
|
||||
return TPromise.as(this.activeComposite);
|
||||
}
|
||||
|
||||
// Open
|
||||
return this.doOpenComposite(id, focus);
|
||||
}
|
||||
|
||||
private doOpenComposite(id: string, focus?: boolean): TPromise<Composite> {
|
||||
|
||||
// Use a generated token to avoid race conditions from long running promises
|
||||
let currentCompositeOpenToken = defaultGenerator.nextId();
|
||||
this.currentCompositeOpenToken = currentCompositeOpenToken;
|
||||
|
||||
// Hide current
|
||||
let hidePromise: TPromise<Composite>;
|
||||
if (this.activeComposite) {
|
||||
hidePromise = this.hideActiveComposite();
|
||||
} else {
|
||||
hidePromise = TPromise.as(null);
|
||||
}
|
||||
|
||||
return hidePromise.then(() => {
|
||||
|
||||
// Update Title
|
||||
this.updateTitle(id);
|
||||
|
||||
// Create composite
|
||||
return this.createComposite(id, true).then(composite => {
|
||||
|
||||
// Check if another composite opened meanwhile and return in that case
|
||||
if ((this.currentCompositeOpenToken !== currentCompositeOpenToken) || (this.activeComposite && this.activeComposite.getId() !== composite.getId())) {
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
// Check if composite already visible and just focus in that case
|
||||
if (this.activeComposite && this.activeComposite.getId() === composite.getId()) {
|
||||
if (focus) {
|
||||
composite.focus();
|
||||
}
|
||||
|
||||
// Fullfill promise with composite that is being opened
|
||||
return TPromise.as(composite);
|
||||
}
|
||||
|
||||
// Show Composite and Focus
|
||||
return this.showComposite(composite).then(() => {
|
||||
if (focus) {
|
||||
composite.focus();
|
||||
}
|
||||
|
||||
// Fullfill promise with composite that is being opened
|
||||
return composite;
|
||||
});
|
||||
});
|
||||
}).then(composite => {
|
||||
if (composite) {
|
||||
this._onDidCompositeOpen.fire(composite);
|
||||
}
|
||||
|
||||
return composite;
|
||||
});
|
||||
}
|
||||
|
||||
protected createComposite(id: string, isActive?: boolean): TPromise<Composite> {
|
||||
|
||||
// Check if composite is already created
|
||||
for (let i = 0; i < this.instantiatedComposites.length; i++) {
|
||||
if (this.instantiatedComposites[i].getId() === id) {
|
||||
return TPromise.as(this.instantiatedComposites[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Instantiate composite from registry otherwise
|
||||
let compositeDescriptor = this.registry.getComposite(id);
|
||||
if (compositeDescriptor) {
|
||||
let loaderPromise = this.compositeLoaderPromises[id];
|
||||
if (!loaderPromise) {
|
||||
let progressService = this.instantiationService.createInstance(WorkbenchProgressService, this.progressBar, compositeDescriptor.id, isActive);
|
||||
let compositeInstantiationService = this.instantiationService.createChild(new ServiceCollection([IProgressService, progressService]));
|
||||
|
||||
loaderPromise = compositeInstantiationService.createInstance(compositeDescriptor).then((composite: Composite) => {
|
||||
this.mapProgressServiceToComposite[composite.getId()] = progressService;
|
||||
|
||||
// Remember as Instantiated
|
||||
this.instantiatedComposites.push(composite);
|
||||
|
||||
// Register to title area update events from the composite
|
||||
this.instantiatedCompositeListeners.push(composite.onTitleAreaUpdate(() => this.onTitleAreaUpdate(composite.getId())));
|
||||
|
||||
// Remove from Promises Cache since Loaded
|
||||
delete this.compositeLoaderPromises[id];
|
||||
|
||||
return composite;
|
||||
});
|
||||
|
||||
// Report progress for slow loading composites
|
||||
progressService.showWhile(loaderPromise, this.partService.isCreated() ? 800 : 3200 /* less ugly initial startup */);
|
||||
|
||||
// Add to Promise Cache until Loaded
|
||||
this.compositeLoaderPromises[id] = loaderPromise;
|
||||
}
|
||||
|
||||
return loaderPromise;
|
||||
}
|
||||
|
||||
throw new Error(strings.format('Unable to find composite with id {0}', id));
|
||||
}
|
||||
|
||||
protected showComposite(composite: Composite): TPromise<void> {
|
||||
|
||||
// Remember Composite
|
||||
this.activeComposite = composite;
|
||||
|
||||
// Store in preferences
|
||||
const id = this.activeComposite.getId();
|
||||
if (id !== this.defaultCompositeId) {
|
||||
this.storageService.store(this.activeCompositeSettingsKey, id, StorageScope.WORKSPACE);
|
||||
} else {
|
||||
this.storageService.remove(this.activeCompositeSettingsKey, StorageScope.WORKSPACE);
|
||||
}
|
||||
|
||||
// Remember
|
||||
this.lastActiveCompositeId = this.activeComposite.getId();
|
||||
|
||||
let createCompositePromise: TPromise<void>;
|
||||
|
||||
// Composites created for the first time
|
||||
let compositeContainer = this.mapCompositeToCompositeContainer[composite.getId()];
|
||||
if (!compositeContainer) {
|
||||
|
||||
// Build Container off-DOM
|
||||
compositeContainer = $().div({
|
||||
'class': ['composite', this.compositeCSSClass],
|
||||
id: composite.getId()
|
||||
}, (div: Builder) => {
|
||||
createCompositePromise = composite.create(div).then(() => {
|
||||
composite.updateStyles();
|
||||
});
|
||||
});
|
||||
|
||||
// Remember composite container
|
||||
this.mapCompositeToCompositeContainer[composite.getId()] = compositeContainer;
|
||||
}
|
||||
|
||||
// Composite already exists but is hidden
|
||||
else {
|
||||
createCompositePromise = TPromise.as(null);
|
||||
}
|
||||
|
||||
// Report progress for slow loading composites (but only if we did not create the composites before already)
|
||||
let progressService = this.mapProgressServiceToComposite[composite.getId()];
|
||||
if (progressService && !compositeContainer) {
|
||||
this.mapProgressServiceToComposite[composite.getId()].showWhile(createCompositePromise, this.partService.isCreated() ? 800 : 3200 /* less ugly initial startup */);
|
||||
}
|
||||
|
||||
// Fill Content and Actions
|
||||
return createCompositePromise.then(() => {
|
||||
|
||||
// Make sure that the user meanwhile did not open another composite or closed the part containing the composite
|
||||
if (!this.activeComposite || composite.getId() !== this.activeComposite.getId()) {
|
||||
return void 0;
|
||||
}
|
||||
|
||||
// Take Composite on-DOM and show
|
||||
compositeContainer.build(this.getContentArea());
|
||||
compositeContainer.show();
|
||||
|
||||
// Setup action runner
|
||||
this.toolBar.actionRunner = composite.getActionRunner();
|
||||
|
||||
// Update title with composite title if it differs from descriptor
|
||||
let descriptor = this.registry.getComposite(composite.getId());
|
||||
if (descriptor && descriptor.name !== composite.getTitle()) {
|
||||
this.updateTitle(composite.getId(), composite.getTitle());
|
||||
}
|
||||
|
||||
// Handle Composite Actions
|
||||
let actionsBinding = this.mapActionsBindingToComposite[composite.getId()];
|
||||
if (!actionsBinding) {
|
||||
actionsBinding = this.collectCompositeActions(composite);
|
||||
this.mapActionsBindingToComposite[composite.getId()] = actionsBinding;
|
||||
}
|
||||
actionsBinding();
|
||||
|
||||
if (this.telemetryActionsListener) {
|
||||
this.telemetryActionsListener.dispose();
|
||||
this.telemetryActionsListener = null;
|
||||
}
|
||||
|
||||
// Action Run Handling
|
||||
this.telemetryActionsListener = this.toolBar.actionRunner.addListener(events.EventType.RUN, (e: any) => {
|
||||
|
||||
// Check for Error
|
||||
if (e.error && !errors.isPromiseCanceledError(e.error)) {
|
||||
this.messageService.show(Severity.Error, e.error);
|
||||
}
|
||||
|
||||
// Log in telemetry
|
||||
if (this.telemetryService) {
|
||||
this.telemetryService.publicLog('workbenchActionExecuted', { id: e.action.id, from: this.nameForTelemetry });
|
||||
}
|
||||
});
|
||||
|
||||
// Indicate to composite that it is now visible
|
||||
return composite.setVisible(true).then(() => {
|
||||
|
||||
// Make sure that the user meanwhile did not open another composite or closed the part containing the composite
|
||||
if (!this.activeComposite || composite.getId() !== this.activeComposite.getId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure the composite is layed out
|
||||
if (this.contentAreaSize) {
|
||||
composite.layout(this.contentAreaSize);
|
||||
}
|
||||
});
|
||||
}, (error: any) => this.onError(error));
|
||||
}
|
||||
|
||||
protected onTitleAreaUpdate(compositeId: string): void {
|
||||
|
||||
// Active Composite
|
||||
if (this.activeComposite && this.activeComposite.getId() === compositeId) {
|
||||
|
||||
// Title
|
||||
this.updateTitle(this.activeComposite.getId(), this.activeComposite.getTitle());
|
||||
|
||||
// Actions
|
||||
let actionsBinding = this.collectCompositeActions(this.activeComposite);
|
||||
this.mapActionsBindingToComposite[this.activeComposite.getId()] = actionsBinding;
|
||||
actionsBinding();
|
||||
}
|
||||
|
||||
// Otherwise invalidate actions binding for next time when the composite becomes visible
|
||||
else {
|
||||
delete this.mapActionsBindingToComposite[compositeId];
|
||||
}
|
||||
}
|
||||
|
||||
private updateTitle(compositeId: string, compositeTitle?: string): void {
|
||||
let compositeDescriptor = this.registry.getComposite(compositeId);
|
||||
if (!compositeDescriptor) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!compositeTitle) {
|
||||
compositeTitle = compositeDescriptor.name;
|
||||
}
|
||||
|
||||
const keybinding = this.keybindingService.lookupKeybinding(compositeId);
|
||||
|
||||
this.titleLabel.updateTitle(compositeId, compositeTitle, keybinding ? keybinding.getLabel() : undefined);
|
||||
|
||||
this.toolBar.setAriaLabel(nls.localize('ariaCompositeToolbarLabel', "{0} actions", compositeTitle));
|
||||
}
|
||||
|
||||
private collectCompositeActions(composite: Composite): () => void {
|
||||
|
||||
// From Composite
|
||||
let primaryActions: IAction[] = composite.getActions().slice(0);
|
||||
let secondaryActions: IAction[] = composite.getSecondaryActions().slice(0);
|
||||
|
||||
// From Part
|
||||
primaryActions.push(...this.getActions());
|
||||
secondaryActions.push(...this.getSecondaryActions());
|
||||
|
||||
// From Contributions
|
||||
let actionBarRegistry = Registry.as<IActionBarRegistry>(Extensions.Actionbar);
|
||||
primaryActions.push(...actionBarRegistry.getActionBarActionsForContext(this.actionContributionScope, composite));
|
||||
secondaryActions.push(...actionBarRegistry.getSecondaryActionBarActionsForContext(this.actionContributionScope, composite));
|
||||
|
||||
// Return fn to set into toolbar
|
||||
return this.toolBar.setActions(prepareActions(primaryActions), prepareActions(secondaryActions));
|
||||
}
|
||||
|
||||
protected getActiveComposite(): IComposite {
|
||||
return this.activeComposite;
|
||||
}
|
||||
|
||||
protected getLastActiveCompositetId(): string {
|
||||
return this.lastActiveCompositeId;
|
||||
}
|
||||
|
||||
protected hideActiveComposite(): TPromise<Composite> {
|
||||
if (!this.activeComposite) {
|
||||
return TPromise.as(null); // Nothing to do
|
||||
}
|
||||
|
||||
let composite = this.activeComposite;
|
||||
this.activeComposite = null;
|
||||
|
||||
let compositeContainer = this.mapCompositeToCompositeContainer[composite.getId()];
|
||||
|
||||
// Indicate to Composite
|
||||
return composite.setVisible(false).then(() => {
|
||||
|
||||
// Take Container Off-DOM and hide
|
||||
compositeContainer.offDOM();
|
||||
compositeContainer.hide();
|
||||
|
||||
// Clear any running Progress
|
||||
this.progressBar.stop().getContainer().hide();
|
||||
|
||||
// Empty Actions
|
||||
this.toolBar.setActions([])();
|
||||
this._onDidCompositeClose.fire(composite);
|
||||
|
||||
return composite;
|
||||
});
|
||||
}
|
||||
|
||||
public createTitleArea(parent: Builder): Builder {
|
||||
|
||||
// Title Area Container
|
||||
let titleArea = $(parent).div({
|
||||
'class': ['composite', 'title']
|
||||
});
|
||||
|
||||
$(titleArea).on(DOM.EventType.CONTEXT_MENU, (e: MouseEvent) => this.onTitleAreaContextMenu(new StandardMouseEvent(e)));
|
||||
|
||||
// Left Title Label
|
||||
this.titleLabel = this.createTitleLabel(titleArea);
|
||||
|
||||
// Right Actions Container
|
||||
$(titleArea).div({
|
||||
'class': 'title-actions'
|
||||
}, (div) => {
|
||||
|
||||
// Toolbar
|
||||
this.toolBar = new ToolBar(div.getHTMLElement(), this.contextMenuService, {
|
||||
actionItemProvider: (action: Action) => this.actionItemProvider(action),
|
||||
orientation: ActionsOrientation.HORIZONTAL,
|
||||
getKeyBinding: (action) => this.keybindingService.lookupKeybinding(action.id)
|
||||
});
|
||||
});
|
||||
|
||||
return titleArea;
|
||||
}
|
||||
|
||||
protected createTitleLabel(parent: Builder): ICompositeTitleLabel {
|
||||
let titleLabel: Builder;
|
||||
$(parent).div({
|
||||
'class': 'title-label'
|
||||
}, (div) => {
|
||||
titleLabel = div.span();
|
||||
});
|
||||
|
||||
const $this = this;
|
||||
return {
|
||||
updateTitle: (id, title, keybinding) => {
|
||||
titleLabel.safeInnerHtml(title);
|
||||
titleLabel.title(keybinding ? nls.localize('titleTooltip', "{0} ({1})", title, keybinding) : title);
|
||||
},
|
||||
updateStyles: () => {
|
||||
titleLabel.style('color', $this.getColor($this.titleForegroundColor));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected updateStyles(): void {
|
||||
super.updateStyles();
|
||||
|
||||
// Forward to title label
|
||||
this.titleLabel.updateStyles();
|
||||
}
|
||||
|
||||
private onTitleAreaContextMenu(event: StandardMouseEvent): void {
|
||||
if (this.activeComposite) {
|
||||
const contextMenuActions = this.getTitleAreaContextMenuActions();
|
||||
if (contextMenuActions.length) {
|
||||
let anchor: { x: number, y: number } = { x: event.posx, y: event.posy };
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => anchor,
|
||||
getActions: () => TPromise.as(contextMenuActions),
|
||||
getActionItem: (action: Action) => this.actionItemProvider(action),
|
||||
actionRunner: this.activeComposite.getActionRunner(),
|
||||
getKeyBinding: (action) => this.keybindingService.lookupKeybinding(action.id)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected getTitleAreaContextMenuActions(): IAction[] {
|
||||
return this.activeComposite ? this.activeComposite.getContextMenuActions() : [];
|
||||
}
|
||||
|
||||
private actionItemProvider(action: Action): IActionItem {
|
||||
let actionItem: IActionItem;
|
||||
|
||||
// Check Active Composite
|
||||
if (this.activeComposite) {
|
||||
actionItem = this.activeComposite.getActionItem(action);
|
||||
}
|
||||
|
||||
// Check Registry
|
||||
if (!actionItem) {
|
||||
let actionBarRegistry = Registry.as<IActionBarRegistry>(Extensions.Actionbar);
|
||||
actionItem = actionBarRegistry.getActionItemForContext(this.actionContributionScope, ToolBarContext, action);
|
||||
}
|
||||
|
||||
return actionItem;
|
||||
}
|
||||
|
||||
public createContentArea(parent: Builder): Builder {
|
||||
return $(parent).div({
|
||||
'class': 'content'
|
||||
}, (div: Builder) => {
|
||||
this.progressBar = new ProgressBar(div);
|
||||
this.toUnbind.push(attachProgressBarStyler(this.progressBar, this.themeService));
|
||||
this.progressBar.getContainer().hide();
|
||||
});
|
||||
}
|
||||
|
||||
private onError(error: any): void {
|
||||
this.messageService.show(Severity.Error, types.isString(error) ? new Error(error) : error);
|
||||
}
|
||||
|
||||
public getProgressIndicator(id: string): IProgressService {
|
||||
return this.mapProgressServiceToComposite[id];
|
||||
}
|
||||
|
||||
protected getActions(): IAction[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
protected getSecondaryActions(): IAction[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
public layout(dimension: Dimension): Dimension[] {
|
||||
|
||||
// Pass to super
|
||||
let sizes = super.layout(dimension);
|
||||
|
||||
// Pass Contentsize to composite
|
||||
this.contentAreaSize = sizes[1];
|
||||
if (this.activeComposite) {
|
||||
this.activeComposite.layout(this.contentAreaSize);
|
||||
}
|
||||
|
||||
return sizes;
|
||||
}
|
||||
|
||||
public shutdown(): void {
|
||||
this.instantiatedComposites.forEach(i => i.shutdown());
|
||||
|
||||
super.shutdown();
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this.mapCompositeToCompositeContainer = null;
|
||||
this.mapProgressServiceToComposite = null;
|
||||
this.mapActionsBindingToComposite = null;
|
||||
|
||||
for (let i = 0; i < this.instantiatedComposites.length; i++) {
|
||||
this.instantiatedComposites[i].dispose();
|
||||
}
|
||||
|
||||
this.instantiatedComposites = [];
|
||||
|
||||
this.instantiatedCompositeListeners = dispose(this.instantiatedCompositeListeners);
|
||||
|
||||
this.progressBar.dispose();
|
||||
this.toolBar.dispose();
|
||||
|
||||
// Super Dispose
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
305
src/vs/workbench/browser/parts/editor/baseEditor.ts
Normal file
305
src/vs/workbench/browser/parts/editor/baseEditor.ts
Normal file
@@ -0,0 +1,305 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import types = require('vs/base/common/types');
|
||||
import { Builder } from 'vs/base/browser/builder';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { Panel } from 'vs/workbench/browser/panel';
|
||||
import { EditorInput, EditorOptions, IEditorDescriptor, IEditorInputFactory, IEditorRegistry, Extensions, IFileInputFactory } from 'vs/workbench/common/editor';
|
||||
import { IEditor, Position } from 'vs/platform/editor/common/editor';
|
||||
import { IInstantiationService, IConstructorSignature0 } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { SyncDescriptor, AsyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
|
||||
/**
|
||||
* The base class of editors in the workbench. Editors register themselves for specific editor inputs.
|
||||
* Editors are layed out in the editor part of the workbench. Only one editor can be open at a time.
|
||||
* Each editor has a minimized representation that is good enough to provide some information about the
|
||||
* state of the editor data.
|
||||
* The workbench will keep an editor alive after it has been created and show/hide it based on
|
||||
* user interaction. The lifecycle of a editor goes in the order create(), setVisible(true|false),
|
||||
* layout(), setInput(), focus(), dispose(). During use of the workbench, a editor will often receive a
|
||||
* clearInput, setVisible, layout and focus call, but only one create and dispose call.
|
||||
*
|
||||
* This class is only intended to be subclassed and not instantiated.
|
||||
*/
|
||||
export abstract class BaseEditor extends Panel implements IEditor {
|
||||
protected _input: EditorInput;
|
||||
private _options: EditorOptions;
|
||||
private _position: Position;
|
||||
|
||||
constructor(id: string, telemetryService: ITelemetryService, themeService: IThemeService) {
|
||||
super(id, telemetryService, themeService);
|
||||
}
|
||||
|
||||
public get input(): EditorInput {
|
||||
return this._input;
|
||||
}
|
||||
|
||||
public get options(): EditorOptions {
|
||||
return this._options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: Clients should not call this method, the workbench calls this
|
||||
* method. Calling it otherwise may result in unexpected behavior.
|
||||
*
|
||||
* Sets the given input with the options to the part. An editor has to deal with the
|
||||
* situation that the same input is being set with different options.
|
||||
*/
|
||||
public setInput(input: EditorInput, options?: EditorOptions): TPromise<void> {
|
||||
this._input = input;
|
||||
this._options = options;
|
||||
|
||||
return TPromise.as<void>(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to indicate to the editor that the input should be cleared and resources associated with the
|
||||
* input should be freed.
|
||||
*/
|
||||
public clearInput(): void {
|
||||
this._input = null;
|
||||
this._options = null;
|
||||
}
|
||||
|
||||
public create(parent: Builder): void; // create is sync for editors
|
||||
public create(parent: Builder): TPromise<void>;
|
||||
public create(parent: Builder): TPromise<void> {
|
||||
const res = super.create(parent);
|
||||
|
||||
// Create Editor
|
||||
this.createEditor(parent);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to create the editor in the parent builder.
|
||||
*/
|
||||
protected abstract createEditor(parent: Builder): void;
|
||||
|
||||
/**
|
||||
* Overload this function to allow for passing in a position argument.
|
||||
*/
|
||||
public setVisible(visible: boolean, position?: Position): void; // setVisible is sync for editors
|
||||
public setVisible(visible: boolean, position?: Position): TPromise<void>;
|
||||
public setVisible(visible: boolean, position: Position = null): TPromise<void> {
|
||||
const promise = super.setVisible(visible);
|
||||
|
||||
// Propagate to Editor
|
||||
this.setEditorVisible(visible, position);
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
protected setEditorVisible(visible: boolean, position: Position = null): void {
|
||||
this._position = position;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the position of the editor changes while it is visible.
|
||||
*/
|
||||
public changePosition(position: Position): void {
|
||||
this._position = position;
|
||||
}
|
||||
|
||||
/**
|
||||
* The position this editor is showing in or null if none.
|
||||
*/
|
||||
public get position(): Position {
|
||||
return this._position;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._input = null;
|
||||
this._options = null;
|
||||
|
||||
// Super Dispose
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A lightweight descriptor of an editor. The descriptor is deferred so that heavy editors
|
||||
* can load lazily in the workbench.
|
||||
*/
|
||||
export class EditorDescriptor extends AsyncDescriptor<BaseEditor> implements IEditorDescriptor {
|
||||
private id: string;
|
||||
private name: string;
|
||||
|
||||
constructor(id: string, name: string, moduleId: string, ctorName: string) {
|
||||
super(moduleId, ctorName);
|
||||
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public getId(): string {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public getName(): string {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public describes(obj: any): boolean {
|
||||
return obj instanceof BaseEditor && (<BaseEditor>obj).getId() === this.id;
|
||||
}
|
||||
}
|
||||
|
||||
const INPUT_DESCRIPTORS_PROPERTY = '__$inputDescriptors';
|
||||
|
||||
class EditorRegistry implements IEditorRegistry {
|
||||
private editors: EditorDescriptor[];
|
||||
private instantiationService: IInstantiationService;
|
||||
private fileInputFactory: IFileInputFactory;
|
||||
private editorInputFactoryConstructors: { [editorInputId: string]: IConstructorSignature0<IEditorInputFactory> } = Object.create(null);
|
||||
private editorInputFactoryInstances: { [editorInputId: string]: IEditorInputFactory } = Object.create(null);
|
||||
|
||||
constructor() {
|
||||
this.editors = [];
|
||||
}
|
||||
|
||||
public setInstantiationService(service: IInstantiationService): void {
|
||||
this.instantiationService = service;
|
||||
|
||||
for (let key in this.editorInputFactoryConstructors) {
|
||||
const element = this.editorInputFactoryConstructors[key];
|
||||
this.createEditorInputFactory(key, element);
|
||||
}
|
||||
|
||||
this.editorInputFactoryConstructors = {};
|
||||
}
|
||||
|
||||
private createEditorInputFactory(editorInputId: string, ctor: IConstructorSignature0<IEditorInputFactory>): void {
|
||||
const instance = this.instantiationService.createInstance(ctor);
|
||||
this.editorInputFactoryInstances[editorInputId] = instance;
|
||||
}
|
||||
|
||||
public registerEditor(descriptor: EditorDescriptor, editorInputDescriptor: SyncDescriptor<EditorInput>): void;
|
||||
public registerEditor(descriptor: EditorDescriptor, editorInputDescriptor: SyncDescriptor<EditorInput>[]): void;
|
||||
public registerEditor(descriptor: EditorDescriptor, editorInputDescriptor: any): void {
|
||||
|
||||
// Support both non-array and array parameter
|
||||
let inputDescriptors: SyncDescriptor<EditorInput>[] = [];
|
||||
if (!types.isArray(editorInputDescriptor)) {
|
||||
inputDescriptors.push(editorInputDescriptor);
|
||||
} else {
|
||||
inputDescriptors = editorInputDescriptor;
|
||||
}
|
||||
|
||||
// Register (Support multiple Editors per Input)
|
||||
descriptor[INPUT_DESCRIPTORS_PROPERTY] = inputDescriptors;
|
||||
this.editors.push(descriptor);
|
||||
}
|
||||
|
||||
public getEditor(input: EditorInput): EditorDescriptor {
|
||||
const findEditorDescriptors = (input: EditorInput, byInstanceOf?: boolean): EditorDescriptor[] => {
|
||||
const matchingDescriptors: EditorDescriptor[] = [];
|
||||
|
||||
for (let i = 0; i < this.editors.length; i++) {
|
||||
const editor = this.editors[i];
|
||||
const inputDescriptors = <SyncDescriptor<EditorInput>[]>editor[INPUT_DESCRIPTORS_PROPERTY];
|
||||
for (let j = 0; j < inputDescriptors.length; j++) {
|
||||
const inputClass = inputDescriptors[j].ctor;
|
||||
|
||||
// Direct check on constructor type (ignores prototype chain)
|
||||
if (!byInstanceOf && input.constructor === inputClass) {
|
||||
matchingDescriptors.push(editor);
|
||||
break;
|
||||
}
|
||||
|
||||
// Normal instanceof check
|
||||
else if (byInstanceOf && input instanceof inputClass) {
|
||||
matchingDescriptors.push(editor);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If no descriptors found, continue search using instanceof and prototype chain
|
||||
if (!byInstanceOf && matchingDescriptors.length === 0) {
|
||||
return findEditorDescriptors(input, true);
|
||||
}
|
||||
|
||||
if (byInstanceOf) {
|
||||
return matchingDescriptors;
|
||||
}
|
||||
|
||||
return matchingDescriptors;
|
||||
};
|
||||
|
||||
const descriptors = findEditorDescriptors(input);
|
||||
if (descriptors && descriptors.length > 0) {
|
||||
|
||||
// Ask the input for its preferred Editor
|
||||
const preferredEditorId = input.getPreferredEditorId(descriptors.map(d => d.getId()));
|
||||
if (preferredEditorId) {
|
||||
return this.getEditorById(preferredEditorId);
|
||||
}
|
||||
|
||||
// Otherwise, first come first serve
|
||||
return descriptors[0];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public getEditorById(editorId: string): EditorDescriptor {
|
||||
for (let i = 0; i < this.editors.length; i++) {
|
||||
const editor = this.editors[i];
|
||||
if (editor.getId() === editorId) {
|
||||
return editor;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public getEditors(): EditorDescriptor[] {
|
||||
return this.editors.slice(0);
|
||||
}
|
||||
|
||||
public setEditors(editorsToSet: EditorDescriptor[]): void {
|
||||
this.editors = editorsToSet;
|
||||
}
|
||||
|
||||
public getEditorInputs(): any[] {
|
||||
const inputClasses: any[] = [];
|
||||
for (let i = 0; i < this.editors.length; i++) {
|
||||
const editor = this.editors[i];
|
||||
const editorInputDescriptors = <SyncDescriptor<EditorInput>[]>editor[INPUT_DESCRIPTORS_PROPERTY];
|
||||
inputClasses.push(...editorInputDescriptors.map(descriptor => descriptor.ctor));
|
||||
}
|
||||
|
||||
return inputClasses;
|
||||
}
|
||||
|
||||
public registerFileInputFactory(factory: IFileInputFactory): void {
|
||||
this.fileInputFactory = factory;
|
||||
}
|
||||
|
||||
public getFileInputFactory(): IFileInputFactory {
|
||||
return this.fileInputFactory;
|
||||
}
|
||||
|
||||
public registerEditorInputFactory(editorInputId: string, ctor: IConstructorSignature0<IEditorInputFactory>): void {
|
||||
if (!this.instantiationService) {
|
||||
this.editorInputFactoryConstructors[editorInputId] = ctor;
|
||||
} else {
|
||||
this.createEditorInputFactory(editorInputId, ctor);
|
||||
}
|
||||
}
|
||||
|
||||
public getEditorInputFactory(editorInputId: string): IEditorInputFactory {
|
||||
return this.editorInputFactoryInstances[editorInputId];
|
||||
}
|
||||
}
|
||||
|
||||
Registry.add(Extensions.Editors, new EditorRegistry());
|
||||
41
src/vs/workbench/browser/parts/editor/binaryDiffEditor.ts
Normal file
41
src/vs/workbench/browser/parts/editor/binaryDiffEditor.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import nls = require('vs/nls');
|
||||
import { BINARY_DIFF_EDITOR_ID } from 'vs/workbench/common/editor';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { SideBySideEditor } from 'vs/workbench/browser/parts/editor/sideBySideEditor';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { BaseBinaryResourceEditor } from 'vs/workbench/browser/parts/editor/binaryEditor';
|
||||
|
||||
/**
|
||||
* An implementation of editor for diffing binary files like images or videos.
|
||||
*/
|
||||
export class BinaryResourceDiffEditor extends SideBySideEditor {
|
||||
|
||||
public static ID = BINARY_DIFF_EDITOR_ID;
|
||||
|
||||
constructor(
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IThemeService themeService: IThemeService
|
||||
) {
|
||||
super(telemetryService, instantiationService, themeService);
|
||||
}
|
||||
|
||||
public getMetadata(): string {
|
||||
const master = this.masterEditor;
|
||||
const details = this.detailsEditor;
|
||||
|
||||
if (master instanceof BaseBinaryResourceEditor && details instanceof BaseBinaryResourceEditor) {
|
||||
return nls.localize('metadataDiff', "{0} ↔ {1}", details.getMetadata(), master.getMetadata());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
151
src/vs/workbench/browser/parts/editor/binaryEditor.ts
Normal file
151
src/vs/workbench/browser/parts/editor/binaryEditor.ts
Normal file
@@ -0,0 +1,151 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import nls = require('vs/nls');
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { Dimension, Builder, $ } from 'vs/base/browser/builder';
|
||||
import { ResourceViewer } from 'vs/base/browser/ui/resourceviewer/resourceViewer';
|
||||
import { EditorModel, EditorInput, EditorOptions } from 'vs/workbench/common/editor';
|
||||
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
|
||||
import { BinaryEditorModel } from 'vs/workbench/common/editor/binaryEditorModel';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
|
||||
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { IWindowsService } from 'vs/platform/windows/common/windows';
|
||||
|
||||
/*
|
||||
* This class is only intended to be subclassed and not instantiated.
|
||||
*/
|
||||
export abstract class BaseBinaryResourceEditor extends BaseEditor {
|
||||
private _onMetadataChanged: Emitter<void>;
|
||||
private metadata: string;
|
||||
|
||||
private binaryContainer: Builder;
|
||||
private scrollbar: DomScrollableElement;
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
telemetryService: ITelemetryService,
|
||||
themeService: IThemeService,
|
||||
private windowsService: IWindowsService
|
||||
) {
|
||||
super(id, telemetryService, themeService);
|
||||
|
||||
this._onMetadataChanged = new Emitter<void>();
|
||||
}
|
||||
|
||||
public get onMetadataChanged(): Event<void> {
|
||||
return this._onMetadataChanged.event;
|
||||
}
|
||||
|
||||
public getTitle(): string {
|
||||
return this.input ? this.input.getName() : nls.localize('binaryEditor', "Binary Viewer");
|
||||
}
|
||||
|
||||
protected createEditor(parent: Builder): void {
|
||||
|
||||
// Container for Binary
|
||||
const binaryContainerElement = document.createElement('div');
|
||||
binaryContainerElement.className = 'binary-container';
|
||||
this.binaryContainer = $(binaryContainerElement);
|
||||
this.binaryContainer.style('outline', 'none');
|
||||
this.binaryContainer.tabindex(0); // enable focus support from the editor part (do not remove)
|
||||
|
||||
// Custom Scrollbars
|
||||
this.scrollbar = new DomScrollableElement(binaryContainerElement, { horizontal: ScrollbarVisibility.Auto, vertical: ScrollbarVisibility.Auto });
|
||||
parent.getHTMLElement().appendChild(this.scrollbar.getDomNode());
|
||||
}
|
||||
|
||||
public setInput(input: EditorInput, options?: EditorOptions): TPromise<void> {
|
||||
const oldInput = this.input;
|
||||
super.setInput(input, options);
|
||||
|
||||
// Detect options
|
||||
const forceOpen = options && options.forceOpen;
|
||||
|
||||
// Same Input
|
||||
if (!forceOpen && input.matches(oldInput)) {
|
||||
return TPromise.as<void>(null);
|
||||
}
|
||||
|
||||
// Different Input (Reload)
|
||||
return input.resolve(true).then((resolvedModel: EditorModel) => {
|
||||
|
||||
// Assert Model instance
|
||||
if (!(resolvedModel instanceof BinaryEditorModel)) {
|
||||
return TPromise.wrapError<void>(new Error('Unable to open file as binary'));
|
||||
}
|
||||
|
||||
// Assert that the current input is still the one we expect. This prevents a race condition when loading takes long and another input was set meanwhile
|
||||
if (!this.input || this.input !== input) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Render Input
|
||||
const model = <BinaryEditorModel>resolvedModel;
|
||||
ResourceViewer.show(
|
||||
{ name: model.getName(), resource: model.getResource(), size: model.getSize(), etag: model.getETag() },
|
||||
this.binaryContainer,
|
||||
this.scrollbar,
|
||||
(resource: URI) => {
|
||||
this.windowsService.openExternal(resource.toString()).then(didOpen => {
|
||||
if (!didOpen) {
|
||||
return this.windowsService.showItemInFolder(resource.fsPath);
|
||||
}
|
||||
|
||||
return void 0;
|
||||
});
|
||||
},
|
||||
(meta) => this.handleMetadataChanged(meta));
|
||||
|
||||
return TPromise.as<void>(null);
|
||||
});
|
||||
}
|
||||
|
||||
private handleMetadataChanged(meta: string): void {
|
||||
this.metadata = meta;
|
||||
this._onMetadataChanged.fire();
|
||||
}
|
||||
|
||||
public getMetadata(): string {
|
||||
return this.metadata;
|
||||
}
|
||||
|
||||
public clearInput(): void {
|
||||
|
||||
// Clear Meta
|
||||
this.handleMetadataChanged(null);
|
||||
|
||||
// Empty HTML Container
|
||||
$(this.binaryContainer).empty();
|
||||
|
||||
super.clearInput();
|
||||
}
|
||||
|
||||
public layout(dimension: Dimension): void {
|
||||
|
||||
// Pass on to Binary Container
|
||||
this.binaryContainer.size(dimension.width, dimension.height);
|
||||
this.scrollbar.scanDomNode();
|
||||
}
|
||||
|
||||
public focus(): void {
|
||||
this.binaryContainer.domFocus();
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
|
||||
// Destroy Container
|
||||
this.binaryContainer.destroy();
|
||||
this.scrollbar.dispose();
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
408
src/vs/workbench/browser/parts/editor/editor.contribution.ts
Normal file
408
src/vs/workbench/browser/parts/editor/editor.contribution.ts
Normal file
@@ -0,0 +1,408 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import nls = require('vs/nls');
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { Action, IAction } from 'vs/base/common/actions';
|
||||
import { IEditorQuickOpenEntry, IQuickOpenRegistry, Extensions as QuickOpenExtensions, QuickOpenHandlerDescriptor } from 'vs/workbench/browser/quickopen';
|
||||
import { StatusbarItemDescriptor, StatusbarAlignment, IStatusbarRegistry, Extensions as StatusExtensions } from 'vs/workbench/browser/parts/statusbar/statusbar';
|
||||
import { EditorDescriptor } from 'vs/workbench/browser/parts/editor/baseEditor';
|
||||
import { EditorInput, IEditorRegistry, Extensions as EditorExtensions, IEditorInputFactory, SideBySideEditorInput } from 'vs/workbench/common/editor';
|
||||
import { TextResourceEditor } from 'vs/workbench/browser/parts/editor/textResourceEditor';
|
||||
import { SideBySideEditor } from 'vs/workbench/browser/parts/editor/sideBySideEditor';
|
||||
import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput';
|
||||
import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
|
||||
import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { TextDiffEditor } from 'vs/workbench/browser/parts/editor/textDiffEditor';
|
||||
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { BinaryResourceDiffEditor } from 'vs/workbench/browser/parts/editor/binaryDiffEditor';
|
||||
import { ChangeEncodingAction, ChangeEOLAction, ChangeModeAction, EditorStatus } from 'vs/workbench/browser/parts/editor/editorStatus';
|
||||
import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actionRegistry';
|
||||
import { Scope, IActionBarRegistry, Extensions as ActionBarExtensions, ActionBarContributor } from 'vs/workbench/browser/actions';
|
||||
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
|
||||
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes';
|
||||
import {
|
||||
CloseEditorsInGroupAction, CloseEditorsInOtherGroupsAction, CloseAllEditorsAction, MoveGroupLeftAction, MoveGroupRightAction, SplitEditorAction, JoinTwoGroupsAction, KeepEditorAction, CloseOtherEditorsInGroupAction, OpenToSideAction, RevertAndCloseEditorAction,
|
||||
NavigateBetweenGroupsAction, FocusActiveGroupAction, FocusFirstGroupAction, FocusSecondGroupAction, FocusThirdGroupAction, EvenGroupWidthsAction, MaximizeGroupAction, MinimizeOtherGroupsAction, FocusPreviousGroup, FocusNextGroup, ShowEditorsInGroupOneAction,
|
||||
toEditorQuickOpenEntry, CloseLeftEditorsInGroupAction, CloseRightEditorsInGroupAction, CloseUnmodifiedEditorsInGroupAction, OpenNextEditor, OpenPreviousEditor, NavigateBackwardsAction, NavigateForwardAction, ReopenClosedEditorAction, OpenPreviousRecentlyUsedEditorInGroupAction, NAVIGATE_IN_GROUP_ONE_PREFIX,
|
||||
OpenPreviousEditorFromHistoryAction, ShowAllEditorsAction, NAVIGATE_ALL_EDITORS_GROUP_PREFIX, ClearEditorHistoryAction, ShowEditorsInGroupTwoAction, MoveEditorRightInGroupAction, OpenNextEditorInGroup, OpenPreviousEditorInGroup, OpenNextRecentlyUsedEditorAction, OpenPreviousRecentlyUsedEditorAction,
|
||||
NAVIGATE_IN_GROUP_TWO_PREFIX, ShowEditorsInGroupThreeAction, NAVIGATE_IN_GROUP_THREE_PREFIX, FocusLastEditorInStackAction, OpenNextRecentlyUsedEditorInGroupAction, MoveEditorToPreviousGroupAction, MoveEditorToNextGroupAction, MoveEditorLeftInGroupAction, ClearRecentFilesAction
|
||||
} from 'vs/workbench/browser/parts/editor/editorActions';
|
||||
import * as editorCommands from 'vs/workbench/browser/parts/editor/editorCommands';
|
||||
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { getQuickNavigateHandler, inQuickOpenContext } from 'vs/workbench/browser/parts/quickopen/quickopen';
|
||||
import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
|
||||
// Register String Editor
|
||||
Registry.as<IEditorRegistry>(EditorExtensions.Editors).registerEditor(
|
||||
new EditorDescriptor(
|
||||
TextResourceEditor.ID,
|
||||
nls.localize('textEditor', "Text Editor"),
|
||||
'vs/workbench/browser/parts/editor/textResourceEditor',
|
||||
'TextResourceEditor'
|
||||
),
|
||||
[
|
||||
new SyncDescriptor(UntitledEditorInput),
|
||||
new SyncDescriptor(ResourceEditorInput)
|
||||
]
|
||||
);
|
||||
|
||||
// Register Text Diff Editor
|
||||
Registry.as<IEditorRegistry>(EditorExtensions.Editors).registerEditor(
|
||||
new EditorDescriptor(
|
||||
TextDiffEditor.ID,
|
||||
nls.localize('textDiffEditor', "Text Diff Editor"),
|
||||
'vs/workbench/browser/parts/editor/textDiffEditor',
|
||||
'TextDiffEditor'
|
||||
),
|
||||
[
|
||||
new SyncDescriptor(DiffEditorInput)
|
||||
]
|
||||
);
|
||||
|
||||
// Register Binary Resource Diff Editor
|
||||
Registry.as<IEditorRegistry>(EditorExtensions.Editors).registerEditor(
|
||||
new EditorDescriptor(
|
||||
BinaryResourceDiffEditor.ID,
|
||||
nls.localize('binaryDiffEditor', "Binary Diff Editor"),
|
||||
'vs/workbench/browser/parts/editor/binaryDiffEditor',
|
||||
'BinaryResourceDiffEditor'
|
||||
),
|
||||
[
|
||||
new SyncDescriptor(DiffEditorInput)
|
||||
]
|
||||
);
|
||||
|
||||
Registry.as<IEditorRegistry>(EditorExtensions.Editors).registerEditor(
|
||||
new EditorDescriptor(
|
||||
SideBySideEditor.ID,
|
||||
nls.localize('sideBySideEditor', "Side by Side Editor"),
|
||||
'vs/workbench/browser/parts/editor/sideBySideEditor',
|
||||
'SideBySideEditor'
|
||||
),
|
||||
[
|
||||
new SyncDescriptor(SideBySideEditorInput)
|
||||
]
|
||||
);
|
||||
|
||||
interface ISerializedUntitledEditorInput {
|
||||
resource: string;
|
||||
resourceJSON: object;
|
||||
modeId: string;
|
||||
encoding: string;
|
||||
}
|
||||
|
||||
// Register Editor Input Factory
|
||||
class UntitledEditorInputFactory implements IEditorInputFactory {
|
||||
|
||||
constructor(
|
||||
@ITextFileService private textFileService: ITextFileService
|
||||
) {
|
||||
}
|
||||
|
||||
public serialize(editorInput: EditorInput): string {
|
||||
if (!this.textFileService.isHotExitEnabled) {
|
||||
return null; // never restore untitled unless hot exit is enabled
|
||||
}
|
||||
|
||||
const untitledEditorInput = <UntitledEditorInput>editorInput;
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
if (!untitledEditorInput.getResource) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let resource = untitledEditorInput.getResource();
|
||||
if (untitledEditorInput.hasAssociatedFilePath) {
|
||||
resource = URI.file(resource.fsPath); // untitled with associated file path use the file schema
|
||||
}
|
||||
|
||||
const serialized: ISerializedUntitledEditorInput = {
|
||||
resource: resource.toString(), // Keep for backwards compatibility
|
||||
resourceJSON: resource.toJSON(),
|
||||
modeId: untitledEditorInput.getModeId(),
|
||||
encoding: untitledEditorInput.getEncoding()
|
||||
};
|
||||
|
||||
return JSON.stringify(serialized);
|
||||
}
|
||||
|
||||
public deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): UntitledEditorInput {
|
||||
return instantiationService.invokeFunction<UntitledEditorInput>(accessor => {
|
||||
const deserialized: ISerializedUntitledEditorInput = JSON.parse(serializedEditorInput);
|
||||
const resource = !!deserialized.resourceJSON ? URI.revive(deserialized.resourceJSON) : URI.parse(deserialized.resource);
|
||||
const filePath = resource.scheme === 'file' ? resource.fsPath : void 0;
|
||||
const language = deserialized.modeId;
|
||||
const encoding = deserialized.encoding;
|
||||
|
||||
return accessor.get(IWorkbenchEditorService).createInput({ resource, filePath, language, encoding }) as UntitledEditorInput;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Registry.as<IEditorRegistry>(EditorExtensions.Editors).registerEditorInputFactory(UntitledEditorInput.ID, UntitledEditorInputFactory);
|
||||
|
||||
interface ISerializedSideBySideEditorInput {
|
||||
name: string;
|
||||
description: string;
|
||||
|
||||
detailsSerialized: string;
|
||||
masterSerialized: string;
|
||||
|
||||
detailsTypeId: string;
|
||||
masterTypeId: string;
|
||||
}
|
||||
|
||||
// Register Side by Side Editor Input Factory
|
||||
class SideBySideEditorInputFactory implements IEditorInputFactory {
|
||||
|
||||
public serialize(editorInput: EditorInput): string {
|
||||
const input = <SideBySideEditorInput>editorInput;
|
||||
|
||||
if (input.details && input.master) {
|
||||
const registry = Registry.as<IEditorRegistry>(EditorExtensions.Editors);
|
||||
const detailsInputFactory = registry.getEditorInputFactory(input.details.getTypeId());
|
||||
const masterInputFactory = registry.getEditorInputFactory(input.master.getTypeId());
|
||||
|
||||
if (detailsInputFactory && masterInputFactory) {
|
||||
const detailsSerialized = detailsInputFactory.serialize(input.details);
|
||||
const masterSerialized = masterInputFactory.serialize(input.master);
|
||||
|
||||
if (detailsSerialized && masterSerialized) {
|
||||
return JSON.stringify(<ISerializedSideBySideEditorInput>{
|
||||
name: input.getName(),
|
||||
description: input.getDescription(),
|
||||
detailsSerialized,
|
||||
masterSerialized,
|
||||
detailsTypeId: input.details.getTypeId(),
|
||||
masterTypeId: input.master.getTypeId()
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): EditorInput {
|
||||
const deserialized: ISerializedSideBySideEditorInput = JSON.parse(serializedEditorInput);
|
||||
|
||||
const registry = Registry.as<IEditorRegistry>(EditorExtensions.Editors);
|
||||
const detailsInputFactory = registry.getEditorInputFactory(deserialized.detailsTypeId);
|
||||
const masterInputFactory = registry.getEditorInputFactory(deserialized.masterTypeId);
|
||||
|
||||
if (detailsInputFactory && masterInputFactory) {
|
||||
const detailsInput = detailsInputFactory.deserialize(instantiationService, deserialized.detailsSerialized);
|
||||
const masterInput = masterInputFactory.deserialize(instantiationService, deserialized.masterSerialized);
|
||||
|
||||
if (detailsInput && masterInput) {
|
||||
return new SideBySideEditorInput(deserialized.name, deserialized.description, detailsInput, masterInput);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Registry.as<IEditorRegistry>(EditorExtensions.Editors).registerEditorInputFactory(SideBySideEditorInput.ID, SideBySideEditorInputFactory);
|
||||
|
||||
// Register Editor Status
|
||||
const statusBar = Registry.as<IStatusbarRegistry>(StatusExtensions.Statusbar);
|
||||
statusBar.registerStatusbarItem(new StatusbarItemDescriptor(EditorStatus, StatusbarAlignment.RIGHT, 100 /* High Priority */));
|
||||
|
||||
// Register Status Actions
|
||||
const registry = Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(ChangeModeAction, ChangeModeAction.ID, ChangeModeAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_M) }), 'Change Language Mode');
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(ChangeEOLAction, ChangeEOLAction.ID, ChangeEOLAction.LABEL), 'Change End of Line Sequence');
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(ChangeEncodingAction, ChangeEncodingAction.ID, ChangeEncodingAction.LABEL), 'Change File Encoding');
|
||||
|
||||
export class QuickOpenActionContributor extends ActionBarContributor {
|
||||
private openToSideActionInstance: OpenToSideAction;
|
||||
|
||||
constructor( @IInstantiationService private instantiationService: IInstantiationService) {
|
||||
super();
|
||||
}
|
||||
|
||||
public hasActions(context: any): boolean {
|
||||
const entry = this.getEntry(context);
|
||||
|
||||
return !!entry;
|
||||
}
|
||||
|
||||
public getActions(context: any): IAction[] {
|
||||
const actions: Action[] = [];
|
||||
|
||||
const entry = this.getEntry(context);
|
||||
if (entry) {
|
||||
if (!this.openToSideActionInstance) {
|
||||
this.openToSideActionInstance = this.instantiationService.createInstance(OpenToSideAction);
|
||||
} else {
|
||||
this.openToSideActionInstance.updateClass();
|
||||
}
|
||||
|
||||
actions.push(this.openToSideActionInstance);
|
||||
}
|
||||
|
||||
return actions;
|
||||
}
|
||||
|
||||
private getEntry(context: any): IEditorQuickOpenEntry {
|
||||
if (!context || !context.element) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return toEditorQuickOpenEntry(context.element);
|
||||
}
|
||||
}
|
||||
|
||||
const actionBarRegistry = Registry.as<IActionBarRegistry>(ActionBarExtensions.Actionbar);
|
||||
actionBarRegistry.registerActionBarContributor(Scope.VIEWER, QuickOpenActionContributor);
|
||||
|
||||
const editorPickerContextKey = 'inEditorsPicker';
|
||||
const editorPickerContext = ContextKeyExpr.and(inQuickOpenContext, ContextKeyExpr.has(editorPickerContextKey));
|
||||
|
||||
Registry.as<IQuickOpenRegistry>(QuickOpenExtensions.Quickopen).registerQuickOpenHandler(
|
||||
new QuickOpenHandlerDescriptor(
|
||||
'vs/workbench/browser/parts/editor/editorPicker',
|
||||
'GroupOnePicker',
|
||||
NAVIGATE_IN_GROUP_ONE_PREFIX,
|
||||
editorPickerContextKey,
|
||||
[
|
||||
{
|
||||
prefix: NAVIGATE_IN_GROUP_ONE_PREFIX,
|
||||
needsEditor: false,
|
||||
description: nls.localize('groupOnePicker', "Show Editors in First Group")
|
||||
},
|
||||
{
|
||||
prefix: NAVIGATE_IN_GROUP_TWO_PREFIX,
|
||||
needsEditor: false,
|
||||
description: nls.localize('groupTwoPicker', "Show Editors in Second Group")
|
||||
},
|
||||
{
|
||||
prefix: NAVIGATE_IN_GROUP_THREE_PREFIX,
|
||||
needsEditor: false,
|
||||
description: nls.localize('groupThreePicker', "Show Editors in Third Group")
|
||||
}
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
Registry.as<IQuickOpenRegistry>(QuickOpenExtensions.Quickopen).registerQuickOpenHandler(
|
||||
new QuickOpenHandlerDescriptor(
|
||||
'vs/workbench/browser/parts/editor/editorPicker',
|
||||
'GroupTwoPicker',
|
||||
NAVIGATE_IN_GROUP_TWO_PREFIX,
|
||||
editorPickerContextKey,
|
||||
[]
|
||||
)
|
||||
);
|
||||
|
||||
Registry.as<IQuickOpenRegistry>(QuickOpenExtensions.Quickopen).registerQuickOpenHandler(
|
||||
new QuickOpenHandlerDescriptor(
|
||||
'vs/workbench/browser/parts/editor/editorPicker',
|
||||
'GroupThreePicker',
|
||||
NAVIGATE_IN_GROUP_THREE_PREFIX,
|
||||
editorPickerContextKey,
|
||||
[]
|
||||
)
|
||||
);
|
||||
|
||||
Registry.as<IQuickOpenRegistry>(QuickOpenExtensions.Quickopen).registerQuickOpenHandler(
|
||||
new QuickOpenHandlerDescriptor(
|
||||
'vs/workbench/browser/parts/editor/editorPicker',
|
||||
'AllEditorsPicker',
|
||||
NAVIGATE_ALL_EDITORS_GROUP_PREFIX,
|
||||
editorPickerContextKey,
|
||||
[
|
||||
{
|
||||
prefix: NAVIGATE_ALL_EDITORS_GROUP_PREFIX,
|
||||
needsEditor: false,
|
||||
description: nls.localize('allEditorsPicker', "Show All Opened Editors")
|
||||
}
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
// Register Editor Actions
|
||||
const category = nls.localize('view', "View");
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenNextEditorInGroup, OpenNextEditorInGroup.ID, OpenNextEditorInGroup.LABEL), 'View: Open Next Editor in Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenPreviousEditorInGroup, OpenPreviousEditorInGroup.ID, OpenPreviousEditorInGroup.LABEL), 'View: Open Previous Editor in Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenNextRecentlyUsedEditorAction, OpenNextRecentlyUsedEditorAction.ID, OpenNextRecentlyUsedEditorAction.LABEL), 'View: Open Next Recently Used Editor', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenPreviousRecentlyUsedEditorAction, OpenPreviousRecentlyUsedEditorAction.ID, OpenPreviousRecentlyUsedEditorAction.LABEL), 'View: Open Previous Recently Used Editor', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(ShowAllEditorsAction, ShowAllEditorsAction.ID, ShowAllEditorsAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_P), mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.Tab } }), 'View: Show All Editors', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(ShowEditorsInGroupOneAction, ShowEditorsInGroupOneAction.ID, ShowEditorsInGroupOneAction.LABEL), 'View: Show Editors in First Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(ShowEditorsInGroupTwoAction, ShowEditorsInGroupTwoAction.ID, ShowEditorsInGroupTwoAction.LABEL), 'View: Show Editors in Second Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(ShowEditorsInGroupThreeAction, ShowEditorsInGroupThreeAction.ID, ShowEditorsInGroupThreeAction.LABEL), 'View: Show Editors in Third Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenNextEditor, OpenNextEditor.ID, OpenNextEditor.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.PageDown, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.RightArrow, secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_CLOSE_SQUARE_BRACKET] } }), 'View: Open Next Editor', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenPreviousEditor, OpenPreviousEditor.ID, OpenPreviousEditor.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.PageUp, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.LeftArrow, secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_OPEN_SQUARE_BRACKET] } }), 'View: Open Previous Editor', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(ReopenClosedEditorAction, ReopenClosedEditorAction.ID, ReopenClosedEditorAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_T }), 'View: Reopen Closed Editor', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(ClearRecentFilesAction, ClearRecentFilesAction.ID, ClearRecentFilesAction.LABEL), 'View: Clear Recently Opened', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(KeepEditorAction, KeepEditorAction.ID, KeepEditorAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.Enter) }), 'View: Keep Editor', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(CloseAllEditorsAction, CloseAllEditorsAction.ID, CloseAllEditorsAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_W) }), 'View: Close All Editors', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(CloseLeftEditorsInGroupAction, CloseLeftEditorsInGroupAction.ID, CloseLeftEditorsInGroupAction.LABEL), 'View: Close Editors to the Left', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(CloseRightEditorsInGroupAction, CloseRightEditorsInGroupAction.ID, CloseRightEditorsInGroupAction.LABEL), 'View: Close Editors to the Right', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(CloseUnmodifiedEditorsInGroupAction, CloseUnmodifiedEditorsInGroupAction.ID, CloseUnmodifiedEditorsInGroupAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_U) }), 'View: Close Unmodified Editors in Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(CloseEditorsInGroupAction, CloseEditorsInGroupAction.ID, CloseEditorsInGroupAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_W) }), 'View: Close All Editors in Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(CloseOtherEditorsInGroupAction, CloseOtherEditorsInGroupAction.ID, CloseOtherEditorsInGroupAction.LABEL, { primary: null, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_T } }), 'View: Close Other Editors', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(CloseEditorsInOtherGroupsAction, CloseEditorsInOtherGroupsAction.ID, CloseEditorsInOtherGroupsAction.LABEL), 'View: Close Editors in Other Groups', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(SplitEditorAction, SplitEditorAction.ID, SplitEditorAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.US_BACKSLASH }), 'View: Split Editor', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(JoinTwoGroupsAction, JoinTwoGroupsAction.ID, JoinTwoGroupsAction.LABEL), 'View: Join Editors of Two Groups', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(NavigateBetweenGroupsAction, NavigateBetweenGroupsAction.ID, NavigateBetweenGroupsAction.LABEL), 'View: Navigate Between Editor Groups', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(FocusActiveGroupAction, FocusActiveGroupAction.ID, FocusActiveGroupAction.LABEL), 'View: Focus Active Editor Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(FocusFirstGroupAction, FocusFirstGroupAction.ID, FocusFirstGroupAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_1 }), 'View: Focus First Editor Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(FocusSecondGroupAction, FocusSecondGroupAction.ID, FocusSecondGroupAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_2 }), 'View: Focus Second Editor Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(FocusThirdGroupAction, FocusThirdGroupAction.ID, FocusThirdGroupAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_3 }), 'View: Focus Third Editor Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(FocusLastEditorInStackAction, FocusLastEditorInStackAction.ID, FocusLastEditorInStackAction.LABEL, { primary: KeyMod.Alt | KeyCode.KEY_0, mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_0 } }), 'View: Open Last Editor in Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(EvenGroupWidthsAction, EvenGroupWidthsAction.ID, EvenGroupWidthsAction.LABEL), 'View: Even Editor Group Widths', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(MaximizeGroupAction, MaximizeGroupAction.ID, MaximizeGroupAction.LABEL), 'View: Maximize Editor Group and Hide Sidebar', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(MinimizeOtherGroupsAction, MinimizeOtherGroupsAction.ID, MinimizeOtherGroupsAction.LABEL), 'View: Minimize Other Editor Groups', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(MoveEditorLeftInGroupAction, MoveEditorLeftInGroupAction.ID, MoveEditorLeftInGroupAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.PageUp, mac: { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.LeftArrow) } }), 'View: Move Editor Left', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(MoveEditorRightInGroupAction, MoveEditorRightInGroupAction.ID, MoveEditorRightInGroupAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.PageDown, mac: { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.RightArrow) } }), 'View: Move Editor Right', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(MoveGroupLeftAction, MoveGroupLeftAction.ID, MoveGroupLeftAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.LeftArrow) }), 'View: Move Editor Group Left', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(MoveGroupRightAction, MoveGroupRightAction.ID, MoveGroupRightAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.RightArrow) }), 'View: Move Editor Group Right', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(MoveEditorToPreviousGroupAction, MoveEditorToPreviousGroupAction.ID, MoveEditorToPreviousGroupAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.LeftArrow, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.LeftArrow } }), 'View: Move Editor into Previous Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(MoveEditorToNextGroupAction, MoveEditorToNextGroupAction.ID, MoveEditorToNextGroupAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.RightArrow, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.RightArrow } }), 'View: Move Editor into Next Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(FocusPreviousGroup, FocusPreviousGroup.ID, FocusPreviousGroup.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.LeftArrow) }), 'View: Focus Previous Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(FocusNextGroup, FocusNextGroup.ID, FocusNextGroup.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.RightArrow) }), 'View: Focus Next Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(NavigateForwardAction, NavigateForwardAction.ID, NavigateForwardAction.LABEL, { primary: null, win: { primary: KeyMod.Alt | KeyCode.RightArrow }, mac: { primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.US_MINUS }, linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_MINUS } }), 'Go Forward');
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(NavigateBackwardsAction, NavigateBackwardsAction.ID, NavigateBackwardsAction.LABEL, { primary: null, win: { primary: KeyMod.Alt | KeyCode.LeftArrow }, mac: { primary: KeyMod.WinCtrl | KeyCode.US_MINUS }, linux: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.US_MINUS } }), 'Go Back');
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenPreviousEditorFromHistoryAction, OpenPreviousEditorFromHistoryAction.ID, OpenPreviousEditorFromHistoryAction.LABEL), 'Open Previous Editor from History');
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(ClearEditorHistoryAction, ClearEditorHistoryAction.ID, ClearEditorHistoryAction.LABEL), 'Clear Editor History');
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(RevertAndCloseEditorAction, RevertAndCloseEditorAction.ID, RevertAndCloseEditorAction.LABEL), 'View: Revert and Close Editor', category);
|
||||
|
||||
// Register Editor Picker Actions including quick navigate support
|
||||
const openNextEditorKeybinding = { primary: KeyMod.CtrlCmd | KeyCode.Tab, mac: { primary: KeyMod.WinCtrl | KeyCode.Tab } };
|
||||
const openPreviousEditorKeybinding = { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Tab, mac: { primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.Tab } };
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenNextRecentlyUsedEditorInGroupAction, OpenNextRecentlyUsedEditorInGroupAction.ID, OpenNextRecentlyUsedEditorInGroupAction.LABEL, openNextEditorKeybinding), 'Open Next Recently Used Editor in Group');
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenPreviousRecentlyUsedEditorInGroupAction, OpenPreviousRecentlyUsedEditorInGroupAction.ID, OpenPreviousRecentlyUsedEditorInGroupAction.LABEL, openPreviousEditorKeybinding), 'Open Previous Recently Used Editor in Group');
|
||||
|
||||
const quickOpenNavigateNextInEditorPickerId = 'workbench.action.quickOpenNavigateNextInEditorPicker';
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: quickOpenNavigateNextInEditorPickerId,
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(50),
|
||||
handler: getQuickNavigateHandler(quickOpenNavigateNextInEditorPickerId, true),
|
||||
when: editorPickerContext,
|
||||
primary: openNextEditorKeybinding.primary,
|
||||
mac: openNextEditorKeybinding.mac
|
||||
});
|
||||
|
||||
const quickOpenNavigatePreviousInEditorPickerId = 'workbench.action.quickOpenNavigatePreviousInEditorPicker';
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: quickOpenNavigatePreviousInEditorPickerId,
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(50),
|
||||
handler: getQuickNavigateHandler(quickOpenNavigatePreviousInEditorPickerId, false),
|
||||
when: editorPickerContext,
|
||||
primary: openPreviousEditorKeybinding.primary,
|
||||
mac: openPreviousEditorKeybinding.mac
|
||||
});
|
||||
|
||||
|
||||
// Editor Commands
|
||||
editorCommands.setup();
|
||||
1520
src/vs/workbench/browser/parts/editor/editorActions.ts
Normal file
1520
src/vs/workbench/browser/parts/editor/editorActions.ts
Normal file
File diff suppressed because it is too large
Load Diff
275
src/vs/workbench/browser/parts/editor/editorCommands.ts
Normal file
275
src/vs/workbench/browser/parts/editor/editorCommands.ts
Normal file
@@ -0,0 +1,275 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import * as types from 'vs/base/common/types';
|
||||
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
|
||||
import { ActiveEditorMoveArguments, ActiveEditorMovePositioning, ActiveEditorMovePositioningBy, EditorCommands, TextCompareEditorVisible } from 'vs/workbench/common/editor';
|
||||
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IEditor, Position, POSITIONS } from 'vs/platform/editor/common/editor';
|
||||
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
|
||||
import { TextDiffEditor } from 'vs/workbench/browser/parts/editor/textDiffEditor';
|
||||
import { EditorStacksModel } from 'vs/workbench/common/editor/editorStacksModel';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { IMessageService, Severity, CloseAction } from 'vs/platform/message/common/message';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
|
||||
|
||||
export function setup(): void {
|
||||
registerActiveEditorMoveCommand();
|
||||
registerDiffEditorCommands();
|
||||
registerOpenEditorAtIndexCommands();
|
||||
handleCommandDeprecations();
|
||||
}
|
||||
|
||||
const isActiveEditorMoveArg = function (arg: ActiveEditorMoveArguments): boolean {
|
||||
if (!types.isObject(arg)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const activeEditorMoveArg: ActiveEditorMoveArguments = arg;
|
||||
|
||||
if (!types.isString(activeEditorMoveArg.to)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!types.isUndefined(activeEditorMoveArg.by) && !types.isString(activeEditorMoveArg.by)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!types.isUndefined(activeEditorMoveArg.value) && !types.isNumber(activeEditorMoveArg.value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
function registerActiveEditorMoveCommand(): void {
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: EditorCommands.MoveActiveEditor,
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
|
||||
when: EditorContextKeys.textFocus,
|
||||
primary: null,
|
||||
handler: (accessor, args: any) => moveActiveEditor(args, accessor),
|
||||
description: {
|
||||
description: nls.localize('editorCommand.activeEditorMove.description', "Move the active editor by tabs or groups"),
|
||||
args: [
|
||||
{
|
||||
name: nls.localize('editorCommand.activeEditorMove.arg.name', "Active editor move argument"),
|
||||
description: nls.localize('editorCommand.activeEditorMove.arg.description', `Argument Properties:
|
||||
* 'to': String value providing where to move.
|
||||
* 'by': String value providing the unit for move. By tab or by group.
|
||||
* 'value': Number value providing how many positions or an absolute position to move.
|
||||
`),
|
||||
constraint: isActiveEditorMoveArg
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function moveActiveEditor(args: ActiveEditorMoveArguments = {}, accessor: ServicesAccessor): void {
|
||||
const showTabs = accessor.get(IEditorGroupService).getTabOptions().showTabs;
|
||||
args.to = args.to || ActiveEditorMovePositioning.RIGHT;
|
||||
args.by = showTabs ? args.by || ActiveEditorMovePositioningBy.TAB : ActiveEditorMovePositioningBy.GROUP;
|
||||
args.value = types.isUndefined(args.value) ? 1 : args.value;
|
||||
|
||||
const activeEditor = accessor.get(IWorkbenchEditorService).getActiveEditor();
|
||||
if (activeEditor) {
|
||||
switch (args.by) {
|
||||
case ActiveEditorMovePositioningBy.TAB:
|
||||
return moveActiveTab(args, activeEditor, accessor);
|
||||
case ActiveEditorMovePositioningBy.GROUP:
|
||||
return moveActiveEditorToGroup(args, activeEditor, accessor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function moveActiveTab(args: ActiveEditorMoveArguments, activeEditor: IEditor, accessor: ServicesAccessor): void {
|
||||
const editorGroupsService: IEditorGroupService = accessor.get(IEditorGroupService);
|
||||
const editorGroup = editorGroupsService.getStacksModel().groupAt(activeEditor.position);
|
||||
let index = editorGroup.indexOf(activeEditor.input);
|
||||
switch (args.to) {
|
||||
case ActiveEditorMovePositioning.FIRST:
|
||||
index = 0;
|
||||
break;
|
||||
case ActiveEditorMovePositioning.LAST:
|
||||
index = editorGroup.count - 1;
|
||||
break;
|
||||
case ActiveEditorMovePositioning.LEFT:
|
||||
index = index - args.value;
|
||||
break;
|
||||
case ActiveEditorMovePositioning.RIGHT:
|
||||
index = index + args.value;
|
||||
break;
|
||||
case ActiveEditorMovePositioning.CENTER:
|
||||
index = Math.round(editorGroup.count / 2) - 1;
|
||||
break;
|
||||
case ActiveEditorMovePositioning.POSITION:
|
||||
index = args.value - 1;
|
||||
break;
|
||||
}
|
||||
|
||||
index = index < 0 ? 0 : index >= editorGroup.count ? editorGroup.count - 1 : index;
|
||||
editorGroupsService.moveEditor(activeEditor.input, editorGroup, editorGroup, { index });
|
||||
}
|
||||
|
||||
function moveActiveEditorToGroup(args: ActiveEditorMoveArguments, activeEditor: IEditor, accessor: ServicesAccessor): void {
|
||||
let newPosition = activeEditor.position;
|
||||
switch (args.to) {
|
||||
case ActiveEditorMovePositioning.LEFT:
|
||||
newPosition = newPosition - 1;
|
||||
break;
|
||||
case ActiveEditorMovePositioning.RIGHT:
|
||||
newPosition = newPosition + 1;
|
||||
break;
|
||||
case ActiveEditorMovePositioning.FIRST:
|
||||
newPosition = Position.ONE;
|
||||
break;
|
||||
case ActiveEditorMovePositioning.LAST:
|
||||
newPosition = Position.THREE;
|
||||
break;
|
||||
case ActiveEditorMovePositioning.CENTER:
|
||||
newPosition = Position.TWO;
|
||||
break;
|
||||
case ActiveEditorMovePositioning.POSITION:
|
||||
newPosition = args.value - 1;
|
||||
break;
|
||||
}
|
||||
|
||||
newPosition = POSITIONS.indexOf(newPosition) !== -1 ? newPosition : activeEditor.position;
|
||||
accessor.get(IEditorGroupService).moveEditor(activeEditor.input, activeEditor.position, newPosition);
|
||||
}
|
||||
|
||||
function registerDiffEditorCommands(): void {
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'workbench.action.compareEditor.nextChange',
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
|
||||
when: TextCompareEditorVisible,
|
||||
primary: null,
|
||||
handler: accessor => navigateInDiffEditor(accessor, true)
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'workbench.action.compareEditor.previousChange',
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
|
||||
when: TextCompareEditorVisible,
|
||||
primary: null,
|
||||
handler: accessor => navigateInDiffEditor(accessor, false)
|
||||
});
|
||||
|
||||
function navigateInDiffEditor(accessor: ServicesAccessor, next: boolean): void {
|
||||
let editorService = accessor.get(IWorkbenchEditorService);
|
||||
const candidates = [editorService.getActiveEditor(), ...editorService.getVisibleEditors()].filter(e => e instanceof TextDiffEditor);
|
||||
|
||||
if (candidates.length > 0) {
|
||||
next ? (<TextDiffEditor>candidates[0]).getDiffNavigator().next() : (<TextDiffEditor>candidates[0]).getDiffNavigator().previous();
|
||||
}
|
||||
}
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: '_workbench.printStacksModel',
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(0),
|
||||
handler(accessor: ServicesAccessor) {
|
||||
console.log(`${accessor.get(IEditorGroupService).getStacksModel().toString()}\n\n`);
|
||||
},
|
||||
when: undefined,
|
||||
primary: undefined
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: '_workbench.validateStacksModel',
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(0),
|
||||
handler(accessor: ServicesAccessor) {
|
||||
(<EditorStacksModel>accessor.get(IEditorGroupService).getStacksModel()).validate();
|
||||
},
|
||||
when: undefined,
|
||||
primary: undefined
|
||||
});
|
||||
}
|
||||
|
||||
function handleCommandDeprecations(): void {
|
||||
const mapDeprecatedCommands = {
|
||||
'workbench.action.files.newFile': 'explorer.newFile',
|
||||
'workbench.action.files.newFolder': 'explorer.newFolder'
|
||||
};
|
||||
|
||||
Object.keys(mapDeprecatedCommands).forEach(deprecatedCommandId => {
|
||||
const newCommandId = mapDeprecatedCommands[deprecatedCommandId];
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: deprecatedCommandId,
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(0),
|
||||
handler(accessor: ServicesAccessor) {
|
||||
const messageService = accessor.get(IMessageService);
|
||||
const commandService = accessor.get(ICommandService);
|
||||
|
||||
messageService.show(Severity.Warning, {
|
||||
message: nls.localize('commandDeprecated', "Command **{0}** has been removed. You can use **{1}** instead", deprecatedCommandId, newCommandId),
|
||||
actions: [
|
||||
new Action('openKeybindings', nls.localize('openKeybindings', "Configure Keyboard Shortcuts"), null, true, () => {
|
||||
return commandService.executeCommand('workbench.action.openGlobalKeybindings');
|
||||
}),
|
||||
CloseAction
|
||||
]
|
||||
});
|
||||
},
|
||||
when: undefined,
|
||||
primary: undefined
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function registerOpenEditorAtIndexCommands(): void {
|
||||
|
||||
// Keybindings to focus a specific index in the tab folder if tabs are enabled
|
||||
for (let i = 0; i < 9; i++) {
|
||||
const editorIndex = i;
|
||||
const visibleIndex = i + 1;
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'workbench.action.openEditorAtIndex' + visibleIndex,
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
|
||||
when: void 0,
|
||||
primary: KeyMod.Alt | toKeyCode(visibleIndex),
|
||||
mac: { primary: KeyMod.WinCtrl | toKeyCode(visibleIndex) },
|
||||
handler: accessor => {
|
||||
const editorService = accessor.get(IWorkbenchEditorService);
|
||||
const editorGroupService = accessor.get(IEditorGroupService);
|
||||
|
||||
const active = editorService.getActiveEditor();
|
||||
if (active) {
|
||||
const group = editorGroupService.getStacksModel().groupAt(active.position);
|
||||
const editor = group.getEditor(editorIndex);
|
||||
|
||||
if (editor) {
|
||||
return editorService.openEditor(editor);
|
||||
}
|
||||
}
|
||||
|
||||
return void 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function toKeyCode(index: number): KeyCode {
|
||||
switch (index) {
|
||||
case 0: return KeyCode.KEY_0;
|
||||
case 1: return KeyCode.KEY_1;
|
||||
case 2: return KeyCode.KEY_2;
|
||||
case 3: return KeyCode.KEY_3;
|
||||
case 4: return KeyCode.KEY_4;
|
||||
case 5: return KeyCode.KEY_5;
|
||||
case 6: return KeyCode.KEY_6;
|
||||
case 7: return KeyCode.KEY_7;
|
||||
case 8: return KeyCode.KEY_8;
|
||||
case 9: return KeyCode.KEY_9;
|
||||
}
|
||||
|
||||
return void 0;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user