mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-03 09:35:40 -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 }))
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user