VSCode merge (#4610)

* Merge from vscode e388c734f30757875976c7e326d6cfeee77710de

* fix yarn lcoks

* remove small issue
This commit is contained in:
Anthony Dresser
2019-03-20 10:39:09 -07:00
committed by GitHub
parent 87765e8673
commit c814b92557
310 changed files with 6606 additions and 2129 deletions

View 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.
*--------------------------------------------------------------------------------------------*/
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { MainContext, MainThreadClipboardShape } from '../common/extHost.protocol';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
@extHostNamedCustomer(MainContext.MainThreadClipboard)
export class MainThreadCommands implements MainThreadClipboardShape {
constructor(
_context: any,
@IClipboardService private readonly _clipboardService: IClipboardService,
) { }
dispose(): void {
// nothing
}
$readText(): Promise<string> {
return Promise.resolve(this._clipboardService.readText());
}
$writeText(value: string): Promise<void> {
this._clipboardService.writeText(value);
return Promise.resolve();
}
}

View File

@@ -0,0 +1,105 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ICommandService, CommandsRegistry, ICommandHandlerDescription } from 'vs/platform/commands/common/commands';
import { IDisposable } from 'vs/base/common/lifecycle';
import { ExtHostContext, MainThreadCommandsShape, ExtHostCommandsShape, MainContext, IExtHostContext } from '../common/extHost.protocol';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { revive } from 'vs/base/common/marshalling';
@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,
) {
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostCommands);
this._generateCommandsDocumentationRegistration = CommandsRegistry.registerCommand('_generateCommandsDocumentation', () => this._generateCommandsDocumentation());
}
dispose() {
this._disposables.forEach(value => value.dispose());
this._disposables.clear();
this._generateCommandsDocumentationRegistration.dispose();
}
private _generateCommandsDocumentation(): Promise<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): void {
this._disposables.set(
id,
CommandsRegistry.registerCommand(id, (accessor, ...args) => {
return this._proxy.$executeContributedCommand(id, ...args).then(result => {
return revive(result, 0);
});
})
);
}
$unregisterCommand(id: string): void {
const command = this._disposables.get(id);
if (command) {
command.dispose();
this._disposables.delete(id);
}
}
$executeCommand<T>(id: string, args: any[]): Promise<T | undefined> {
for (let i = 0; i < args.length; i++) {
args[i] = revive(args[i], 0);
}
return this._commandService.executeCommand<T>(id, ...args);
}
$getCommands(): Promise<string[]> {
return Promise.resolve(Object.keys(CommandsRegistry.getCommands()));
}
}
// --- command doc
function _generateMarkdown(description: string | ICommandHandlerDescription): string {
if (typeof description === 'string') {
return description;
} else {
const 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('');
}
}

View File

@@ -0,0 +1,90 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { URI, UriComponents } from 'vs/base/common/uri';
import { IDisposable } from 'vs/base/common/lifecycle';
import { Registry } from 'vs/platform/registry/common/platform';
import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope, getScopes } from 'vs/platform/configuration/common/configurationRegistry';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { MainThreadConfigurationShape, MainContext, ExtHostContext, IExtHostContext, IWorkspaceConfigurationChangeEventData, IConfigurationInitData } from '../common/extHost.protocol';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { ConfigurationTarget, IConfigurationChangeEvent, IConfigurationModel, IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
@extHostNamedCustomer(MainContext.MainThreadConfiguration)
export class MainThreadConfiguration implements MainThreadConfigurationShape {
private readonly _configurationListener: IDisposable;
constructor(
extHostContext: IExtHostContext,
@IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IEnvironmentService private readonly _environmentService: IEnvironmentService,
) {
const proxy = extHostContext.getProxy(ExtHostContext.ExtHostConfiguration);
proxy.$initializeConfiguration(this._getConfigurationData());
this._configurationListener = configurationService.onDidChangeConfiguration(e => {
proxy.$acceptConfigurationChanged(this._getConfigurationData(), this.toConfigurationChangeEventData(e));
});
}
private _getConfigurationData(): IConfigurationInitData {
const configurationData: IConfigurationInitData = { ...(this.configurationService.getConfigurationData()!), configurationScopes: {} };
// Send configurations scopes only in development mode.
if (!this._environmentService.isBuilt || this._environmentService.isExtensionDevelopment) {
configurationData.configurationScopes = getScopes();
}
return configurationData;
}
public dispose(): void {
this._configurationListener.dispose();
}
$updateConfigurationOption(target: ConfigurationTarget | null, key: string, value: any, resourceUriComponenets: UriComponents | undefined): Promise<void> {
const resource = resourceUriComponenets ? URI.revive(resourceUriComponenets) : null;
return this.writeConfiguration(target, key, value, resource);
}
$removeConfigurationOption(target: ConfigurationTarget | null, key: string, resourceUriComponenets: UriComponents | undefined): Promise<void> {
const resource = resourceUriComponenets ? URI.revive(resourceUriComponenets) : null;
return this.writeConfiguration(target, key, undefined, resource);
}
private writeConfiguration(target: ConfigurationTarget | null, key: string, value: any, resource: URI | null): Promise<void> {
target = target !== null && target !== undefined ? target : this.deriveConfigurationTarget(key, resource);
return this.configurationService.updateValue(key, value, { resource }, target, true);
}
private deriveConfigurationTarget(key: string, resource: URI | null): ConfigurationTarget {
if (resource && this._workspaceContextService.getWorkbenchState() === WorkbenchState.WORKSPACE) {
const configurationProperties = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).getConfigurationProperties();
if (configurationProperties[key] && configurationProperties[key].scope === ConfigurationScope.RESOURCE) {
return ConfigurationTarget.WORKSPACE_FOLDER;
}
}
return ConfigurationTarget.WORKSPACE;
}
private toConfigurationChangeEventData(event: IConfigurationChangeEvent): IWorkspaceConfigurationChangeEventData {
return {
changedConfiguration: this.toJSONConfiguration(event.changedConfiguration),
changedConfigurationByResource: event.changedConfigurationByResource.keys().reduce((result, resource) => {
result[resource.toString()] = this.toJSONConfiguration(event.changedConfigurationByResource.get(resource));
return result;
}, Object.create({}))
};
}
private toJSONConfiguration({ contents, keys, overrides }: IConfigurationModel = { contents: {}, keys: [], overrides: [] }): IConfigurationModel {
return {
contents,
keys,
overrides
};
}
}

View File

@@ -0,0 +1,385 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
// {{SQL CARBON EDIT}}
/*
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { URI as uri } from 'vs/base/common/uri';
import { IDebugService, IConfig, IDebugConfigurationProvider, IBreakpoint, IFunctionBreakpoint, IBreakpointData, ITerminalSettings, IDebugAdapter, IDebugAdapterDescriptorFactory, IDebugSession, IDebugAdapterFactory, IDebugAdapterTrackerFactory } from 'vs/workbench/contrib/debug/common/debug';
import {
ExtHostContext, ExtHostDebugServiceShape, MainThreadDebugServiceShape, DebugSessionUUID, MainContext,
IExtHostContext, IBreakpointsDeltaDto, ISourceMultiBreakpointDto, ISourceBreakpointDto, IFunctionBreakpointDto, IDebugSessionDto
} from 'vs/workbench/api/common/extHost.protocol';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import severity from 'vs/base/common/severity';
import { AbstractDebugAdapter } from 'vs/workbench/contrib/debug/common/abstractDebugAdapter';
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { convertToVSCPaths, convertToDAPaths } from 'vs/workbench/contrib/debug/common/debugUtils';
@extHostNamedCustomer(MainContext.MainThreadDebugService)
export class MainThreadDebugService implements MainThreadDebugServiceShape, IDebugAdapterFactory {
private readonly _proxy: ExtHostDebugServiceShape;
private _toDispose: IDisposable[];
private _breakpointEventsActive: boolean;
private readonly _debugAdapters: Map<number, ExtensionHostDebugAdapter>;
private _debugAdaptersHandleCounter = 1;
private readonly _debugConfigurationProviders: Map<number, IDebugConfigurationProvider>;
private readonly _debugAdapterDescriptorFactories: Map<number, IDebugAdapterDescriptorFactory>;
private readonly _debugAdapterTrackerFactories: Map<number, IDebugAdapterTrackerFactory>;
private readonly _sessions: Set<DebugSessionUUID>;
constructor(
extHostContext: IExtHostContext,
@IDebugService private readonly debugService: IDebugService
) {
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostDebugService);
this._toDispose = [];
this._toDispose.push(debugService.onDidNewSession(session => {
this._proxy.$acceptDebugSessionStarted(this.getSessionDto(session));
}));
// Need to start listening early to new session events because a custom event can come while a session is initialising
this._toDispose.push(debugService.onWillNewSession(session => {
this._toDispose.push(session.onDidCustomEvent(event => this._proxy.$acceptDebugSessionCustomEvent(this.getSessionDto(session), event)));
}));
this._toDispose.push(debugService.onDidEndSession(session => {
this._proxy.$acceptDebugSessionTerminated(this.getSessionDto(session));
this._sessions.delete(session.getId());
}));
this._toDispose.push(debugService.getViewModel().onDidFocusSession(session => {
this._proxy.$acceptDebugSessionActiveChanged(this.getSessionDto(session));
}));
this._debugAdapters = new Map();
this._debugConfigurationProviders = new Map();
this._debugAdapterDescriptorFactories = new Map();
this._debugAdapterTrackerFactories = new Map();
this._sessions = new Set();
}
public dispose(): void {
this._toDispose = dispose(this._toDispose);
}
// interface IDebugAdapterProvider
createDebugAdapter(session: IDebugSession): IDebugAdapter {
const handle = this._debugAdaptersHandleCounter++;
const da = new ExtensionHostDebugAdapter(this, handle, this._proxy, session);
this._debugAdapters.set(handle, da);
return da;
}
substituteVariables(folder: IWorkspaceFolder | undefined, config: IConfig): Promise<IConfig> {
return Promise.resolve(this._proxy.$substituteVariables(folder ? folder.uri : undefined, config));
}
runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments, config: ITerminalSettings): Promise<number | undefined> {
return Promise.resolve(this._proxy.$runInTerminal(args, config));
}
// RPC methods (MainThreadDebugServiceShape)
public $registerDebugTypes(debugTypes: string[]) {
this._toDispose.push(this.debugService.getConfigurationManager().registerDebugAdapterFactory(debugTypes, this));
}
public $startBreakpointEvents(): void {
if (!this._breakpointEventsActive) {
this._breakpointEventsActive = true;
// set up a handler to send more
this._toDispose.push(this.debugService.getModel().onDidChangeBreakpoints(e => {
// Ignore session only breakpoint events since they should only reflect in the UI
if (e && !e.sessionOnly) {
const delta: IBreakpointsDeltaDto = {};
if (e.added) {
delta.added = this.convertToDto(e.added);
}
if (e.removed) {
delta.removed = e.removed.map(x => x.getId());
}
if (e.changed) {
delta.changed = this.convertToDto(e.changed);
}
if (delta.added || delta.removed || delta.changed) {
this._proxy.$acceptBreakpointsDelta(delta);
}
}
}));
// send all breakpoints
const bps = this.debugService.getModel().getBreakpoints();
const fbps = this.debugService.getModel().getFunctionBreakpoints();
if (bps.length > 0 || fbps.length > 0) {
this._proxy.$acceptBreakpointsDelta({
added: this.convertToDto(bps).concat(this.convertToDto(fbps))
});
}
}
}
public $registerBreakpoints(DTOs: Array<ISourceMultiBreakpointDto | IFunctionBreakpointDto>): Promise<void> {
for (let dto of DTOs) {
if (dto.type === 'sourceMulti') {
const rawbps = dto.lines.map(l =>
<IBreakpointData>{
id: l.id,
enabled: l.enabled,
lineNumber: l.line + 1,
column: l.character > 0 ? l.character + 1 : undefined, // a column value of 0 results in an omitted column attribute; see #46784
condition: l.condition,
hitCondition: l.hitCondition,
logMessage: l.logMessage
}
);
this.debugService.addBreakpoints(uri.revive(dto.uri), rawbps, 'extension');
} else if (dto.type === 'function') {
this.debugService.addFunctionBreakpoint(dto.functionName, dto.id);
}
}
return Promise.resolve();
}
public $unregisterBreakpoints(breakpointIds: string[], functionBreakpointIds: string[]): Promise<void> {
breakpointIds.forEach(id => this.debugService.removeBreakpoints(id));
functionBreakpointIds.forEach(id => this.debugService.removeFunctionBreakpoints(id));
return Promise.resolve();
}
public $registerDebugConfigurationProvider(debugType: string, hasProvide: boolean, hasResolve: boolean, hasProvideDebugAdapter: boolean, handle: number): Promise<void> {
const provider = <IDebugConfigurationProvider>{
type: debugType
};
if (hasProvide) {
provider.provideDebugConfigurations = (folder) => {
return this._proxy.$provideDebugConfigurations(handle, folder);
};
}
if (hasResolve) {
provider.resolveDebugConfiguration = (folder, config) => {
return this._proxy.$resolveDebugConfiguration(handle, folder, config);
};
}
if (hasProvideDebugAdapter) {
console.info('DebugConfigurationProvider.debugAdapterExecutable is deprecated and will be removed soon; please use DebugAdapterDescriptorFactory.createDebugAdapterDescriptor instead.');
provider.debugAdapterExecutable = (folder) => {
return this._proxy.$legacyDebugAdapterExecutable(handle, folder);
};
}
this._debugConfigurationProviders.set(handle, provider);
this._toDispose.push(this.debugService.getConfigurationManager().registerDebugConfigurationProvider(provider));
return Promise.resolve(undefined);
}
public $unregisterDebugConfigurationProvider(handle: number): void {
const provider = this._debugConfigurationProviders.get(handle);
if (provider) {
this._debugConfigurationProviders.delete(handle);
this.debugService.getConfigurationManager().unregisterDebugConfigurationProvider(provider);
}
}
public $registerDebugAdapterDescriptorFactory(debugType: string, handle: number): Promise<void> {
const provider = <IDebugAdapterDescriptorFactory>{
type: debugType,
createDebugAdapterDescriptor: session => {
return Promise.resolve(this._proxy.$provideDebugAdapter(handle, this.getSessionDto(session)));
}
};
this._debugAdapterDescriptorFactories.set(handle, provider);
this._toDispose.push(this.debugService.getConfigurationManager().registerDebugAdapterDescriptorFactory(provider));
return Promise.resolve(undefined);
}
public $unregisterDebugAdapterDescriptorFactory(handle: number): void {
const provider = this._debugAdapterDescriptorFactories.get(handle);
if (provider) {
this._debugAdapterDescriptorFactories.delete(handle);
this.debugService.getConfigurationManager().unregisterDebugAdapterDescriptorFactory(provider);
}
}
public $registerDebugAdapterTrackerFactory(debugType: string, handle: number) {
const factory = <IDebugAdapterTrackerFactory>{
type: debugType,
};
this._debugAdapterTrackerFactories.set(handle, factory);
this._toDispose.push(this.debugService.getConfigurationManager().registerDebugAdapterTrackerFactory(factory));
return Promise.resolve(undefined);
}
public $unregisterDebugAdapterTrackerFactory(handle: number) {
const factory = this._debugAdapterTrackerFactories.get(handle);
if (factory) {
this._debugAdapterTrackerFactories.delete(handle);
this.debugService.getConfigurationManager().unregisterDebugAdapterTrackerFactory(factory);
}
}
private getSession(sessionId: DebugSessionUUID | undefined): IDebugSession | undefined {
if (sessionId) {
return this.debugService.getModel().getSessions(true).filter(s => s.getId() === sessionId).pop();
}
return undefined;
}
public $startDebugging(_folderUri: uri | undefined, nameOrConfiguration: string | IConfig, parentSessionID: DebugSessionUUID | undefined): Promise<boolean> {
const folderUri = _folderUri ? uri.revive(_folderUri) : undefined;
const launch = this.debugService.getConfigurationManager().getLaunch(folderUri);
return this.debugService.startDebugging(launch, nameOrConfiguration, false, this.getSession(parentSessionID)).then(success => {
return success;
}, err => {
return Promise.reject(new Error(err && err.message ? err.message : 'cannot start debugging'));
});
}
public $customDebugAdapterRequest(sessionId: DebugSessionUUID, request: string, args: any): Promise<any> {
const session = this.debugService.getModel().getSessions(true).filter(s => s.getId() === sessionId).pop();
if (session) {
return session.customRequest(request, args).then(response => {
if (response && response.success) {
return response.body;
} else {
return Promise.reject(new Error(response ? response.message : 'custom request failed'));
}
});
}
return Promise.reject(new Error('debug session not found'));
}
public $appendDebugConsole(value: string): void {
// Use warning as severity to get the orange color for messages coming from the debug extension
const session = this.debugService.getViewModel().focusedSession;
if (session) {
session.appendToRepl(value, severity.Warning);
}
}
public $acceptDAMessage(handle: number, message: DebugProtocol.ProtocolMessage) {
this.getDebugAdapter(handle).acceptMessage(convertToVSCPaths(message, false));
}
public $acceptDAError(handle: number, name: string, message: string, stack: string) {
this.getDebugAdapter(handle).fireError(handle, new Error(`${name}: ${message}\n${stack}`));
}
public $acceptDAExit(handle: number, code: number, signal: string) {
this.getDebugAdapter(handle).fireExit(handle, code, signal);
}
private getDebugAdapter(handle: number): ExtensionHostDebugAdapter {
const adapter = this._debugAdapters.get(handle);
if (!adapter) {
throw new Error('Invalid debug adapter');
}
return adapter;
}
// dto helpers
public $sessionCached(sessionID: string) {
// remember that the EH has cached the session and we do not have to send it again
this._sessions.add(sessionID);
}
getSessionDto(session: undefined): undefined;
getSessionDto(session: IDebugSession): IDebugSessionDto;
getSessionDto(session: IDebugSession | undefined): IDebugSessionDto | undefined;
getSessionDto(session: IDebugSession | undefined): IDebugSessionDto | undefined {
if (session) {
const sessionID = <DebugSessionUUID>session.getId();
if (this._sessions.has(sessionID)) {
return sessionID;
} else {
// this._sessions.add(sessionID); // #69534: see $sessionCached above
return {
id: sessionID,
type: session.configuration.type,
name: session.configuration.name,
folderUri: session.root ? session.root.uri : undefined,
configuration: session.configuration
};
}
}
return undefined;
}
private convertToDto(bps: (ReadonlyArray<IBreakpoint | IFunctionBreakpoint>)): Array<ISourceBreakpointDto | IFunctionBreakpointDto> {
return bps.map(bp => {
if ('name' in bp) {
const fbp = <IFunctionBreakpoint>bp;
return <IFunctionBreakpointDto>{
type: 'function',
id: fbp.getId(),
enabled: fbp.enabled,
condition: fbp.condition,
hitCondition: fbp.hitCondition,
logMessage: fbp.logMessage,
functionName: fbp.name
};
} else {
const sbp = <IBreakpoint>bp;
return <ISourceBreakpointDto>{
type: 'source',
id: sbp.getId(),
enabled: sbp.enabled,
condition: sbp.condition,
hitCondition: sbp.hitCondition,
logMessage: sbp.logMessage,
uri: sbp.uri,
line: sbp.lineNumber > 0 ? sbp.lineNumber - 1 : 0,
character: (typeof sbp.column === 'number' && sbp.column > 0) ? sbp.column - 1 : 0,
};
}
});
}
}
/**
* DebugAdapter that communicates via extension protocol with another debug adapter.
*/
// {{SQL CARBON EDIT}}
/*
class ExtensionHostDebugAdapter extends AbstractDebugAdapter {
constructor(private readonly _ds: MainThreadDebugService, private _handle: number, private _proxy: ExtHostDebugServiceShape, private _session: IDebugSession) {
super();
}
public fireError(handle: number, err: Error) {
this._onError.fire(err);
}
public fireExit(handle: number, code: number, signal: string) {
this._onExit.fire(code);
}
public startSession(): Promise<void> {
return Promise.resolve(this._proxy.$startDASession(this._handle, this._ds.getSessionDto(this._session)));
}
public sendMessage(message: DebugProtocol.ProtocolMessage): void {
this._proxy.$sendDAMessage(this._handle, convertToDAPaths(message, true));
}
public stopSession(): Promise<void> {
return Promise.resolve(this._proxy.$stopDASession(this._handle));
}
}
// {{SQL CARBON EDIT}}
*/

View File

@@ -0,0 +1,126 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { URI, UriComponents } from 'vs/base/common/uri';
import { Emitter } from 'vs/base/common/event';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { ExtHostContext, MainContext, IExtHostContext, MainThreadDecorationsShape, ExtHostDecorationsShape, DecorationData, DecorationRequest } from '../common/extHost.protocol';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { IDecorationsService, IDecorationData } from 'vs/workbench/services/decorations/browser/decorations';
import { values } from 'vs/base/common/collections';
import { CancellationToken } from 'vs/base/common/cancellation';
class DecorationRequestsQueue {
private _idPool = 0;
private _requests: { [id: number]: DecorationRequest } = Object.create(null);
private _resolver: { [id: number]: (data: DecorationData) => any } = Object.create(null);
private _timer: any;
constructor(
private readonly _proxy: ExtHostDecorationsShape
) {
//
}
enqueue(handle: number, uri: URI, token: CancellationToken): Promise<DecorationData> {
const id = ++this._idPool;
const result = new Promise<DecorationData>(resolve => {
this._requests[id] = { id, handle, uri };
this._resolver[id] = resolve;
this._processQueue();
});
token.onCancellationRequested(() => {
delete this._requests[id];
delete this._resolver[id];
});
return result;
}
private _processQueue(): void {
if (typeof this._timer === 'number') {
// already queued
return;
}
this._timer = setTimeout(() => {
// make request
const requests = this._requests;
const resolver = this._resolver;
this._proxy.$provideDecorations(values(requests), CancellationToken.None).then(data => {
for (const id in resolver) {
resolver[id](data[id]);
}
});
// reset
this._requests = [];
this._resolver = [];
this._timer = undefined;
}, 0);
}
}
@extHostNamedCustomer(MainContext.MainThreadDecorations)
export class MainThreadDecorations implements MainThreadDecorationsShape {
private readonly _provider = new Map<number, [Emitter<URI[]>, IDisposable]>();
private readonly _proxy: ExtHostDecorationsShape;
private readonly _requestQueue: DecorationRequestsQueue;
constructor(
context: IExtHostContext,
@IDecorationsService private readonly _decorationsService: IDecorationsService
) {
this._proxy = context.getProxy(ExtHostContext.ExtHostDecorations);
this._requestQueue = new DecorationRequestsQueue(this._proxy);
}
dispose() {
this._provider.forEach(value => dispose(value));
this._provider.clear();
}
$registerDecorationProvider(handle: number, label: string): void {
const emitter = new Emitter<URI[]>();
const registration = this._decorationsService.registerDecorationsProvider({
label,
onDidChange: emitter.event,
provideDecorations: (uri, token) => {
return this._requestQueue.enqueue(handle, uri, token).then(data => {
if (!data) {
return undefined;
}
const [weight, bubble, tooltip, letter, themeColor, source] = data;
return <IDecorationData>{
weight: weight || 0,
bubble: bubble || false,
color: themeColor && themeColor.id,
tooltip,
letter,
source,
};
});
}
});
this._provider.set(handle, [emitter, registration]);
}
$onDidChange(handle: number, resources: UriComponents[]): void {
const provider = this._provider.get(handle);
if (provider) {
const [emitter] = provider;
emitter.fire(resources && resources.map(URI.revive));
}
}
$unregisterDecorationProvider(handle: number): void {
const provider = this._provider.get(handle);
if (provider) {
dispose(provider);
this._provider.delete(handle);
}
}
}

View 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.
*--------------------------------------------------------------------------------------------*/
import { IMarkerService, IMarkerData } from 'vs/platform/markers/common/markers';
import { URI, UriComponents } from 'vs/base/common/uri';
import { MainThreadDiagnosticsShape, MainContext, IExtHostContext } from '../common/extHost.protocol';
import { extHostNamedCustomer } from 'vs/workbench/api/common/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, []));
}
$changeMany(owner: string, entries: [UriComponents, IMarkerData[]][]): void {
for (let entry of entries) {
let [uri, markers] = entry;
if (markers) {
for (const marker of markers) {
if (marker.relatedInformation) {
for (const relatedInformation of marker.relatedInformation) {
relatedInformation.resource = URI.revive(relatedInformation.resource);
}
}
}
}
this._markerService.changeOne(owner, URI.revive(uri), markers);
}
this._activeOwners.add(owner);
}
$clear(owner: string): void {
this._markerService.changeAll(owner, []);
this._activeOwners.delete(owner);
}
}

View File

@@ -0,0 +1,60 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { URI } from 'vs/base/common/uri';
import { MainThreadDiaglogsShape, MainContext, IExtHostContext, MainThreadDialogOpenOptions, MainThreadDialogSaveOptions } from '../common/extHost.protocol';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { forEach } from 'vs/base/common/collections';
import { IFileDialogService, IOpenDialogOptions, ISaveDialogOptions } from 'vs/platform/dialogs/common/dialogs';
@extHostNamedCustomer(MainContext.MainThreadDialogs)
export class MainThreadDialogs implements MainThreadDiaglogsShape {
constructor(
context: IExtHostContext,
@IFileDialogService private readonly _fileDialogService: IFileDialogService,
) {
//
}
dispose(): void {
//
}
$showOpenDialog(options: MainThreadDialogOpenOptions): Promise<URI[] | undefined> {
return Promise.resolve(this._fileDialogService.showOpenDialog(MainThreadDialogs._convertOpenOptions(options)));
}
$showSaveDialog(options: MainThreadDialogSaveOptions): Promise<URI | undefined> {
return Promise.resolve(this._fileDialogService.showSaveDialog(MainThreadDialogs._convertSaveOptions(options)));
}
private static _convertOpenOptions(options: MainThreadDialogOpenOptions): IOpenDialogOptions {
const result: IOpenDialogOptions = {
openLabel: options.openLabel,
canSelectFiles: options.canSelectFiles || (!options.canSelectFiles && !options.canSelectFolders),
canSelectFolders: options.canSelectFolders,
canSelectMany: options.canSelectMany,
defaultUri: URI.revive(options.defaultUri)
};
if (options.filters) {
result.filters = [];
forEach(options.filters, entry => result.filters!.push({ name: entry.key, extensions: entry.value }));
}
return result;
}
private static _convertSaveOptions(options: MainThreadDialogSaveOptions): ISaveDialogOptions {
const result: ISaveDialogOptions = {
defaultUri: URI.revive(options.defaultUri),
saveLabel: options.saveLabel
};
if (options.filters) {
result.filters = [];
forEach(options.filters, entry => result.filters!.push({ name: entry.key, extensions: entry.value }));
}
return result;
}
}

View File

@@ -0,0 +1,96 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { onUnexpectedError } from 'vs/base/common/errors';
import { IDisposable } from 'vs/base/common/lifecycle';
import { URI, UriComponents } from 'vs/base/common/uri';
import { EditOperation } from 'vs/editor/common/core/editOperation';
import { Range } from 'vs/editor/common/core/range';
import { ITextModel } from 'vs/editor/common/model';
import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService';
import { IModelService } from 'vs/editor/common/services/modelService';
import { IModeService } from 'vs/editor/common/services/modeService';
import { ITextModelService } from 'vs/editor/common/services/resolverService';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { ExtHostContext, ExtHostDocumentContentProvidersShape, IExtHostContext, MainContext, MainThreadDocumentContentProvidersShape } from '../common/extHost.protocol';
import { CancellationTokenSource } from 'vs/base/common/cancellation';
@extHostNamedCustomer(MainContext.MainThreadDocumentContentProviders)
export class MainThreadDocumentContentProviders implements MainThreadDocumentContentProvidersShape {
private readonly _resourceContentProvider = new Map<number, IDisposable>();
private readonly _pendingUpdate = new Map<string, CancellationTokenSource>();
private readonly _proxy: ExtHostDocumentContentProvidersShape;
constructor(
extHostContext: IExtHostContext,
@ITextModelService private readonly _textModelResolverService: ITextModelService,
@IModeService private readonly _modeService: IModeService,
@IModelService private readonly _modelService: IModelService,
@IEditorWorkerService private readonly _editorWorkerService: IEditorWorkerService
) {
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostDocumentContentProviders);
}
dispose(): void {
this._resourceContentProvider.forEach(p => p.dispose());
this._pendingUpdate.forEach(source => source.dispose());
}
$registerTextContentProvider(handle: number, scheme: string): void {
const registration = this._textModelResolverService.registerTextModelContentProvider(scheme, {
provideTextContent: (uri: URI): Promise<ITextModel | undefined> => {
return this._proxy.$provideTextDocumentContent(handle, uri).then(value => {
if (typeof value === 'string') {
const firstLineText = value.substr(0, 1 + value.search(/\r?\n/));
const languageSelection = this._modeService.createByFilepathOrFirstLine(uri.fsPath, firstLineText);
return this._modelService.createModel(value, languageSelection, uri);
}
return undefined;
});
}
});
this._resourceContentProvider.set(handle, registration);
}
$unregisterTextContentProvider(handle: number): void {
const registration = this._resourceContentProvider.get(handle);
if (registration) {
registration.dispose();
this._resourceContentProvider.delete(handle);
}
}
$onVirtualDocumentChange(uri: UriComponents, value: string): void {
const model = this._modelService.getModel(URI.revive(uri));
if (!model) {
return;
}
// cancel and dispose an existing update
const pending = this._pendingUpdate.get(model.id);
if (pending) {
pending.cancel();
}
// create and keep update token
const myToken = new CancellationTokenSource();
this._pendingUpdate.set(model.id, myToken);
this._editorWorkerService.computeMoreMinimalEdits(model.uri, [{ text: value, range: model.getFullModelRange() }]).then(edits => {
// remove token
this._pendingUpdate.delete(model.id);
if (myToken.token.isCancellationRequested) {
// ignore this
return;
}
if (edits && edits.length > 0) {
// use the evil-edit as these models show in readonly-editor only
model.applyEdits(edits.map(edit => EditOperation.replace(Range.lift(edit.range), edit.text)));
}
}).catch(onUnexpectedError);
}
}

View File

@@ -0,0 +1,27 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { SerializedError, onUnexpectedError } from 'vs/base/common/errors';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { MainContext, MainThreadErrorsShape } from 'vs/workbench/api/common/extHost.protocol';
@extHostNamedCustomer(MainContext.MainThreadErrors)
export class MainThreadErrors implements MainThreadErrorsShape {
dispose(): void {
//
}
$onUnexpectedError(err: any | SerializedError): void {
if (err && err.$isError) {
const { name, message, stack } = err;
err = new Error();
err.message = message;
err.name = name;
err.stack = stack;
}
onUnexpectedError(err);
}
}

View File

@@ -0,0 +1,164 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Emitter, Event } from 'vs/base/common/event';
import { IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { FileWriteOptions, FileSystemProviderCapabilities, IFileChange, IFileService, IFileSystemProvider, IStat, IWatchOptions, FileType, FileOverwriteOptions, FileDeleteOptions, FileOpenOptions } from 'vs/platform/files/common/files';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { ExtHostContext, ExtHostFileSystemShape, IExtHostContext, IFileChangeDto, MainContext, MainThreadFileSystemShape } from '../common/extHost.protocol';
import { ResourceLabelFormatter, ILabelService } from 'vs/platform/label/common/label';
@extHostNamedCustomer(MainContext.MainThreadFileSystem)
export class MainThreadFileSystem implements MainThreadFileSystemShape {
private readonly _proxy: ExtHostFileSystemShape;
private readonly _fileProvider = new Map<number, RemoteFileSystemProvider>();
private readonly _resourceLabelFormatters = new Map<number, IDisposable>();
constructor(
extHostContext: IExtHostContext,
@IFileService private readonly _fileService: IFileService,
@ILabelService private readonly _labelService: ILabelService
) {
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostFileSystem);
}
dispose(): void {
this._fileProvider.forEach(value => value.dispose());
this._fileProvider.clear();
}
$registerFileSystemProvider(handle: number, scheme: string, capabilities: FileSystemProviderCapabilities): void {
this._fileProvider.set(handle, new RemoteFileSystemProvider(this._fileService, scheme, capabilities, handle, this._proxy));
}
$unregisterProvider(handle: number): void {
dispose(this._fileProvider.get(handle));
this._fileProvider.delete(handle);
}
$registerResourceLabelFormatter(handle: number, formatter: ResourceLabelFormatter): void {
// Dynamicily registered formatters should have priority over those contributed via package.json
formatter.priority = true;
const disposable = this._labelService.registerFormatter(formatter);
this._resourceLabelFormatters.set(handle, disposable);
}
$unregisterResourceLabelFormatter(handle: number): void {
dispose(this._resourceLabelFormatters.get(handle));
this._resourceLabelFormatters.delete(handle);
}
$onFileSystemChange(handle: number, changes: IFileChangeDto[]): void {
const fileProvider = this._fileProvider.get(handle);
if (!fileProvider) {
throw new Error('Unknown file provider');
}
fileProvider.$onFileSystemChange(changes);
}
}
class RemoteFileSystemProvider implements IFileSystemProvider {
private readonly _onDidChange = new Emitter<IFileChange[]>();
private readonly _registration: IDisposable;
readonly onDidChangeFile: Event<IFileChange[]> = this._onDidChange.event;
readonly capabilities: FileSystemProviderCapabilities;
readonly onDidChangeCapabilities: Event<void> = Event.None;
constructor(
fileService: IFileService,
scheme: string,
capabilities: FileSystemProviderCapabilities,
private readonly _handle: number,
private readonly _proxy: ExtHostFileSystemShape
) {
this.capabilities = capabilities;
this._registration = fileService.registerProvider(scheme, this);
}
dispose(): void {
this._registration.dispose();
this._onDidChange.dispose();
}
watch(resource: URI, opts: IWatchOptions) {
const session = Math.random();
this._proxy.$watch(this._handle, session, resource, opts);
return toDisposable(() => {
this._proxy.$unwatch(this._handle, session);
});
}
$onFileSystemChange(changes: IFileChangeDto[]): void {
this._onDidChange.fire(changes.map(RemoteFileSystemProvider._createFileChange));
}
private static _createFileChange(dto: IFileChangeDto): IFileChange {
return { resource: URI.revive(dto.resource), type: dto.type };
}
// --- forwarding calls
private static _asBuffer(data: Uint8Array): Buffer {
return Buffer.isBuffer(data) ? data : Buffer.from(data.buffer, data.byteOffset, data.byteLength);
}
stat(resource: URI): Promise<IStat> {
return this._proxy.$stat(this._handle, resource).then(undefined, err => {
throw err;
});
}
readFile(resource: URI): Promise<Uint8Array> {
return this._proxy.$readFile(this._handle, resource);
}
writeFile(resource: URI, content: Uint8Array, opts: FileWriteOptions): Promise<void> {
return this._proxy.$writeFile(this._handle, resource, RemoteFileSystemProvider._asBuffer(content), opts);
}
delete(resource: URI, opts: FileDeleteOptions): Promise<void> {
return this._proxy.$delete(this._handle, resource, opts);
}
mkdir(resource: URI): Promise<void> {
return this._proxy.$mkdir(this._handle, resource);
}
readdir(resource: URI): Promise<[string, FileType][]> {
return this._proxy.$readdir(this._handle, resource);
}
rename(resource: URI, target: URI, opts: FileOverwriteOptions): Promise<void> {
return this._proxy.$rename(this._handle, resource, target, opts);
}
copy(resource: URI, target: URI, opts: FileOverwriteOptions): Promise<void> {
return this._proxy.$copy(this._handle, resource, target, opts);
}
open(resource: URI, opts: FileOpenOptions): Promise<number> {
return this._proxy.$open(this._handle, resource, opts);
}
close(fd: number): Promise<void> {
return this._proxy.$close(this._handle, fd);
}
read(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise<number> {
return this._proxy.$read(this._handle, fd, pos, length).then(readData => {
data.set(readData, offset);
return readData.byteLength;
});
}
write(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise<number> {
return this._proxy.$write(this._handle, fd, pos, Buffer.from(data, offset, length));
}
}

View File

@@ -0,0 +1,68 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { FileChangeType, IFileService, FileOperation } from 'vs/platform/files/common/files';
import { extHostCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { ExtHostContext, FileSystemEvents, IExtHostContext } from '../common/extHost.protocol';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
@extHostCustomer
export class MainThreadFileSystemEventService {
private readonly _listener = new Array<IDisposable>();
constructor(
extHostContext: IExtHostContext,
@IFileService fileService: IFileService,
@ITextFileService textfileService: ITextFileService,
) {
const proxy = extHostContext.getProxy(ExtHostContext.ExtHostFileSystemEventService);
// file system events - (changes the editor and other make)
const events: FileSystemEvents = {
created: [],
changed: [],
deleted: []
};
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;
}, undefined, this._listener);
// file operation events - (changes the editor makes)
fileService.onAfterOperation(e => {
if (e.operation === FileOperation.MOVE) {
proxy.$onFileRename(e.resource, e.target!.resource);
}
}, undefined, this._listener);
textfileService.onWillMove(e => {
const promise = proxy.$onWillRename(e.oldResource, e.newResource);
e.waitUntil(promise);
}, undefined, this._listener);
}
dispose(): void {
dispose(this._listener);
}
}

View File

@@ -0,0 +1,43 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { URI, UriComponents } from 'vs/base/common/uri';
import { IModeService } from 'vs/editor/common/services/modeService';
import { IModelService } from 'vs/editor/common/services/modelService';
import { MainThreadLanguagesShape, MainContext, IExtHostContext } from '../common/extHost.protocol';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
@extHostNamedCustomer(MainContext.MainThreadLanguages)
export class MainThreadLanguages implements MainThreadLanguagesShape {
constructor(
_extHostContext: IExtHostContext,
@IModeService private readonly _modeService: IModeService,
@IModelService private readonly _modelService: IModelService
) {
}
dispose(): void {
// nothing
}
$getLanguages(): Promise<string[]> {
return Promise.resolve(this._modeService.getRegisteredModes());
}
$changeLanguage(resource: UriComponents, languageId: string): Promise<void> {
const uri = URI.revive(resource);
const model = this._modelService.getModel(uri);
if (!model) {
return Promise.reject(new Error('Invalid uri'));
}
const languageIdentifier = this._modeService.getLanguageIdentifier(languageId);
if (!languageIdentifier || languageIdentifier.language !== languageId) {
return Promise.reject(new Error(`Unknown language id: ${languageId}`));
}
this._modelService.setMode(model, this._modeService.create(languageId));
return Promise.resolve(undefined);
}
}

View File

@@ -0,0 +1,22 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { extHostCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { ILogService } from 'vs/platform/log/common/log';
import { Disposable } from 'vs/base/common/lifecycle';
import { IExtHostContext, ExtHostContext } from 'vs/workbench/api/common/extHost.protocol';
@extHostCustomer
export class MainThreadLogService extends Disposable {
constructor(
extHostContext: IExtHostContext,
@ILogService logService: ILogService,
) {
super();
this._register(logService.onDidChangeLogLevel(level => extHostContext.getProxy(ExtHostContext.ExtHostLogService).$setLevel(level)));
}
}

View File

@@ -0,0 +1,123 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import Severity from 'vs/base/common/severity';
import { Action, IAction } from 'vs/base/common/actions';
import { MainThreadMessageServiceShape, MainContext, IExtHostContext, MainThreadMessageOptions } from '../common/extHost.protocol';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { Event } from 'vs/base/common/event';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { dispose } from 'vs/base/common/lifecycle';
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
@extHostNamedCustomer(MainContext.MainThreadMessageService)
export class MainThreadMessageService implements MainThreadMessageServiceShape {
constructor(
extHostContext: IExtHostContext,
@INotificationService private readonly _notificationService: INotificationService,
@ICommandService private readonly _commandService: ICommandService,
@IDialogService private readonly _dialogService: IDialogService
) {
//
}
dispose(): void {
//
}
$showMessage(severity: Severity, message: string, options: MainThreadMessageOptions, commands: { title: string; isCloseAffordance: boolean; handle: number; }[]): Promise<number | undefined> {
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 | undefined): Promise<number> {
return new Promise<number>(resolve => {
const primaryActions: MessageItemAction[] = [];
class MessageItemAction extends Action {
constructor(id: string, label: string, handle: number) {
super(id, label, undefined, true, () => {
resolve(handle);
return Promise.resolve();
});
}
}
class ManageExtensionAction extends Action {
constructor(id: ExtensionIdentifier, label: string, commandService: ICommandService) {
super(id.value, label, undefined, true, () => {
return commandService.executeCommand('_extensions.manage', id.value);
});
}
}
commands.forEach(command => {
primaryActions.push(new MessageItemAction('_extension_message_handle_' + command.handle, command.title, command.handle));
});
let source: string | undefined;
if (extension) {
source = nls.localize('extensionSource', "{0} (Extension)", extension.displayName || extension.name);
}
if (!source) {
source = nls.localize('defaultSource', "Extension");
}
const secondaryActions: IAction[] = [];
if (extension && !extension.isUnderDevelopment) {
secondaryActions.push(new ManageExtensionAction(extension.identifier, nls.localize('manageExtension', "Manage Extension"), this._commandService));
}
const messageHandle = this._notificationService.notify({
severity,
message,
actions: { primary: primaryActions, secondary: secondaryActions },
source
});
// if promise has not been resolved yet, now is the time to ensure a return value
// otherwise if already resolved it means the user clicked one of the buttons
Event.once(messageHandle.onDidClose)(() => {
dispose(...primaryActions, ...secondaryActions);
resolve(undefined);
});
});
}
private _showModalMessage(severity: Severity, message: string, commands: { title: string; isCloseAffordance: boolean; handle: number; }[]): Promise<number | undefined> {
let cancelId: number | undefined = undefined;
const buttons = commands.map((command, index) => {
if (command.isCloseAffordance === true) {
cancelId = index;
}
return command.title;
});
if (cancelId === undefined) {
if (buttons.length > 0) {
buttons.push(nls.localize('cancel', "Cancel"));
} else {
buttons.push(nls.localize('ok', "OK"));
}
cancelId = buttons.length - 1;
}
return this._dialogService.show(severity, message, buttons, { cancelId })
.then(result => result === commands.length ? undefined : commands[result].handle);
}
}

View File

@@ -0,0 +1,110 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Registry } from 'vs/platform/registry/common/platform';
import { IOutputService, IOutputChannel, OUTPUT_PANEL_ID, Extensions, IOutputChannelRegistry } from 'vs/workbench/contrib/output/common/output';
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
import { MainThreadOutputServiceShape, MainContext, IExtHostContext, ExtHostOutputServiceShape, ExtHostContext } from '../common/extHost.protocol';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { UriComponents, URI } from 'vs/base/common/uri';
import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
import { Event } from 'vs/base/common/event';
@extHostNamedCustomer(MainContext.MainThreadOutputService)
export class MainThreadOutputService extends Disposable implements MainThreadOutputServiceShape {
private static _idPool = 1;
private readonly _proxy: ExtHostOutputServiceShape;
private readonly _outputService: IOutputService;
private readonly _layoutService: IWorkbenchLayoutService;
private readonly _panelService: IPanelService;
constructor(
extHostContext: IExtHostContext,
@IOutputService outputService: IOutputService,
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
@IPanelService panelService: IPanelService
) {
super();
this._outputService = outputService;
this._layoutService = layoutService;
this._panelService = panelService;
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostOutputService);
const setVisibleChannel = () => {
const panel = this._panelService.getActivePanel();
const visibleChannel: IOutputChannel | null = panel && panel.getId() === OUTPUT_PANEL_ID ? this._outputService.getActiveChannel() : null;
this._proxy.$setVisibleChannel(visibleChannel ? visibleChannel.id : null);
};
this._register(Event.any<any>(this._outputService.onActiveOutputChannel, this._panelService.onDidPanelOpen, this._panelService.onDidPanelClose)(() => setVisibleChannel()));
setVisibleChannel();
}
public $register(label: string, log: boolean, file?: UriComponents): Promise<string> {
const id = 'extension-output-#' + (MainThreadOutputService._idPool++);
Registry.as<IOutputChannelRegistry>(Extensions.OutputChannels).registerChannel({ id, label, file: file ? URI.revive(file) : undefined, log });
this._register(toDisposable(() => this.$dispose(id)));
return Promise.resolve(id);
}
public $append(channelId: string, value: string): Promise<void> | undefined {
const channel = this._getChannel(channelId);
if (channel) {
channel.append(value);
}
return undefined;
}
public $update(channelId: string): Promise<void> | undefined {
const channel = this._getChannel(channelId);
if (channel) {
channel.update();
}
return undefined;
}
public $clear(channelId: string, till: number): Promise<void> | undefined {
const channel = this._getChannel(channelId);
if (channel) {
channel.clear(till);
}
return undefined;
}
public $reveal(channelId: string, preserveFocus: boolean): Promise<void> | undefined {
const channel = this._getChannel(channelId);
if (channel) {
this._outputService.showChannel(channel.id, preserveFocus);
}
return undefined;
}
public $close(channelId: string): Promise<void> | undefined {
const panel = this._panelService.getActivePanel();
if (panel && panel.getId() === OUTPUT_PANEL_ID) {
const activeChannel = this._outputService.getActiveChannel();
if (activeChannel && channelId === activeChannel.id) {
this._layoutService.setPanelHidden(true);
}
}
return undefined;
}
public $dispose(channelId: string): Promise<void> | undefined {
const channel = this._getChannel(channelId);
if (channel) {
channel.dispose();
}
return undefined;
}
private _getChannel(channelId: string): IOutputChannel | null {
return this._outputService.getChannel(channelId);
}
}

View File

@@ -0,0 +1,58 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IProgress, IProgressService2, IProgressStep, IProgressOptions } from 'vs/platform/progress/common/progress';
import { MainThreadProgressShape, MainContext, IExtHostContext, ExtHostProgressShape, ExtHostContext } from '../common/extHost.protocol';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
@extHostNamedCustomer(MainContext.MainThreadProgress)
export class MainThreadProgress implements MainThreadProgressShape {
private readonly _progressService: IProgressService2;
private _progress = new Map<number, { resolve: () => void, progress: IProgress<IProgressStep> }>();
private readonly _proxy: ExtHostProgressShape;
constructor(
extHostContext: IExtHostContext,
@IProgressService2 progressService: IProgressService2
) {
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostProgress);
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, () => this._proxy.$acceptProgressCanceled(handle));
}
$progressReport(handle: number, message: IProgressStep): void {
const entry = this._progress.get(handle);
if (entry) {
entry.progress.report(message);
}
}
$progressEnd(handle: number): void {
const entry = this._progress.get(handle);
if (entry) {
entry.resolve();
this._progress.delete(handle);
}
}
private _createTask(handle: number) {
return (progress: IProgress<IProgressStep>) => {
return new Promise<any>(resolve => {
this._progress.set(handle, { resolve, progress });
});
};
}
}

View File

@@ -0,0 +1,212 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IPickOptions, IInputOptions, IQuickInputService, IQuickInput } from 'vs/platform/quickinput/common/quickInput';
import { ExtHostContext, MainThreadQuickOpenShape, ExtHostQuickOpenShape, TransferQuickPickItems, MainContext, IExtHostContext, TransferQuickInput, TransferQuickInputButton, IInputBoxOptions } from 'vs/workbench/api/common/extHost.protocol';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { URI } from 'vs/base/common/uri';
import { CancellationToken } from 'vs/base/common/cancellation';
interface QuickInputSession {
input: IQuickInput;
handlesToItems: Map<number, TransferQuickPickItems>;
}
@extHostNamedCustomer(MainContext.MainThreadQuickOpen)
export class MainThreadQuickOpen implements MainThreadQuickOpenShape {
private readonly _proxy: ExtHostQuickOpenShape;
private readonly _quickInputService: IQuickInputService;
private readonly _items: Record<number, {
resolve(items: TransferQuickPickItems[]): void;
reject(error: Error): void;
}> = {};
constructor(
extHostContext: IExtHostContext,
@IQuickInputService quickInputService: IQuickInputService
) {
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostQuickOpen);
this._quickInputService = quickInputService;
}
public dispose(): void {
}
$show(instance: number, options: IPickOptions<TransferQuickPickItems>, token: CancellationToken): Promise<number | number[] | undefined> {
const contents = new Promise<TransferQuickPickItems[]>((resolve, reject) => {
this._items[instance] = { resolve, reject };
});
options = {
...options,
onDidFocus: el => {
if (el) {
this._proxy.$onItemSelected((<TransferQuickPickItems>el).handle);
}
}
};
if (options.canPickMany) {
return this._quickInputService.pick(contents, options as { canPickMany: true }, token).then(items => {
if (items) {
return items.map(item => item.handle);
}
return undefined;
});
} else {
return this._quickInputService.pick(contents, options, token).then(item => {
if (item) {
return item.handle;
}
return undefined;
});
}
}
$setItems(instance: number, items: TransferQuickPickItems[]): Promise<void> {
if (this._items[instance]) {
this._items[instance].resolve(items);
delete this._items[instance];
}
return Promise.resolve();
}
$setError(instance: number, error: Error): Promise<void> {
if (this._items[instance]) {
this._items[instance].reject(error);
delete this._items[instance];
}
return Promise.resolve();
}
// ---- input
$input(options: IInputBoxOptions | undefined, validateInput: boolean, token: CancellationToken): Promise<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 this._quickInputService.input(inputOptions, token);
}
// ---- QuickInput
private sessions = new Map<number, QuickInputSession>();
$createOrUpdate(params: TransferQuickInput): Promise<void> {
const sessionId = params.id;
let session = this.sessions.get(sessionId);
if (!session) {
if (params.type === 'quickPick') {
const input = this._quickInputService.createQuickPick();
input.onDidAccept(() => {
this._proxy.$onDidAccept(sessionId);
});
input.onDidChangeActive(items => {
this._proxy.$onDidChangeActive(sessionId, items.map(item => (item as TransferQuickPickItems).handle));
});
input.onDidChangeSelection(items => {
this._proxy.$onDidChangeSelection(sessionId, items.map(item => (item as TransferQuickPickItems).handle));
});
input.onDidTriggerButton(button => {
this._proxy.$onDidTriggerButton(sessionId, (button as TransferQuickInputButton).handle);
});
input.onDidChangeValue(value => {
this._proxy.$onDidChangeValue(sessionId, value);
});
input.onDidHide(() => {
this._proxy.$onDidHide(sessionId);
});
session = {
input,
handlesToItems: new Map()
};
} else {
const input = this._quickInputService.createInputBox();
input.onDidAccept(() => {
this._proxy.$onDidAccept(sessionId);
});
input.onDidTriggerButton(button => {
this._proxy.$onDidTriggerButton(sessionId, (button as TransferQuickInputButton).handle);
});
input.onDidChangeValue(value => {
this._proxy.$onDidChangeValue(sessionId, value);
});
input.onDidHide(() => {
this._proxy.$onDidHide(sessionId);
});
session = {
input,
handlesToItems: new Map()
};
}
this.sessions.set(sessionId, session);
}
const { input, handlesToItems } = session;
for (const param in params) {
if (param === 'id' || param === 'type') {
continue;
}
if (param === 'visible') {
if (params.visible) {
input.show();
} else {
input.hide();
}
} else if (param === 'items') {
handlesToItems.clear();
params[param].forEach(item => {
handlesToItems.set(item.handle, item);
});
input[param] = params[param];
} else if (param === 'activeItems' || param === 'selectedItems') {
input[param] = params[param]
.filter(handle => handlesToItems.has(handle))
.map(handle => handlesToItems.get(handle));
} else if (param === 'buttons') {
input[param] = params.buttons!.map(button => {
if (button.handle === -1) {
return this._quickInputService.backButton;
}
const { iconPath, tooltip, handle } = button;
return {
iconPath: iconPath && {
dark: URI.revive(iconPath.dark),
light: iconPath.light && URI.revive(iconPath.light)
},
tooltip,
handle
};
});
} else {
input[param] = params[param];
}
}
return Promise.resolve(undefined);
}
$dispose(sessionId: number): Promise<void> {
const session = this.sessions.get(sessionId);
if (session) {
session.input.dispose();
this.sessions.delete(sessionId);
}
return Promise.resolve(undefined);
}
}

View File

@@ -0,0 +1,438 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { URI, UriComponents } 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, IInputValidation } from 'vs/workbench/contrib/scm/common/scm';
import { ExtHostContext, MainThreadSCMShape, ExtHostSCMShape, SCMProviderFeatures, SCMRawResourceSplices, SCMGroupFeatures, MainContext, IExtHostContext } from '../common/extHost.protocol';
import { Command } from 'vs/editor/common/modes';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { ISplice, Sequence } from 'vs/base/common/sequence';
import { CancellationToken } from 'vs/base/common/cancellation';
class MainThreadSCMResourceGroup implements ISCMResourceGroup {
readonly elements: ISCMResource[] = [];
private _onDidSplice = new Emitter<ISplice<ISCMResource>>();
readonly onDidSplice = this._onDidSplice.event;
get hideWhenEmpty(): boolean { return !!this.features.hideWhenEmpty; }
private _onDidChange = new Emitter<void>();
get onDidChange(): Event<void> { return this._onDidChange.event; }
constructor(
private readonly sourceControlHandle: number,
private readonly handle: number,
public provider: ISCMProvider,
public features: SCMGroupFeatures,
public label: string,
public id: string
) { }
toJSON(): any {
return {
$mid: 4,
sourceControlHandle: this.sourceControlHandle,
groupHandle: this.handle
};
}
splice(start: number, deleteCount: number, toInsert: ISCMResource[]) {
this.elements.splice(start, deleteCount, ...toInsert);
this._onDidSplice.fire({ start, deleteCount, toInsert });
}
$updateGroup(features: SCMGroupFeatures): void {
this.features = assign(this.features, features);
this._onDidChange.fire();
}
$updateGroupLabel(label: string): void {
this.label = label;
this._onDidChange.fire();
}
}
class MainThreadSCMResource implements ISCMResource {
constructor(
private readonly proxy: ExtHostSCMShape,
private readonly sourceControlHandle: number,
private readonly groupHandle: number,
private readonly handle: number,
public sourceUri: URI,
public resourceGroup: ISCMResourceGroup,
public decorations: ISCMResourceDecorations
) { }
open(): Promise<void> {
return this.proxy.$executeResourceCommand(this.sourceControlHandle, this.groupHandle, this.handle);
}
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; }
readonly groups = new Sequence<MainThreadSCMResourceGroup>();
private readonly _groupsByHandle: { [handle: number]: MainThreadSCMResourceGroup; } = Object.create(null);
// get groups(): ISequence<ISCMResourceGroup> {
// return {
// elements: this._groups,
// onDidSplice: this._onDidSplice.event
// };
// // return this._groups
// // .filter(g => g.resources.elements.length > 0 || !g.features.hideWhenEmpty);
// }
private _onDidChangeResources = new Emitter<void>();
get onDidChangeResources(): Event<void> { return this._onDidChangeResources.event; }
private features: SCMProviderFeatures = {};
get handle(): number { return this._handle; }
get label(): string { return this._label; }
get rootUri(): URI | undefined { return this._rootUri; }
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; }
get count(): number | undefined { return this.features.count; }
private _onDidChangeCommitTemplate = new Emitter<string>();
get onDidChangeCommitTemplate(): Event<string> { return this._onDidChangeCommitTemplate.event; }
private _onDidChangeStatusBarCommands = new Emitter<Command[]>();
get onDidChangeStatusBarCommands(): Event<Command[]> { return this._onDidChangeStatusBarCommands.event; }
private _onDidChange = new Emitter<void>();
get onDidChange(): Event<void> { return this._onDidChange.event; }
constructor(
private readonly proxy: ExtHostSCMShape,
private readonly _handle: number,
private readonly _contextValue: string,
private readonly _label: string,
private readonly _rootUri: URI | undefined,
@ISCMService scmService: ISCMService
) { }
$updateSourceControl(features: SCMProviderFeatures): void {
this.features = assign(this.features, features);
this._onDidChange.fire();
if (typeof features.commitTemplate !== 'undefined') {
this._onDidChangeCommitTemplate.fire(this.commitTemplate!);
}
if (typeof features.statusBarCommands !== 'undefined') {
this._onDidChangeStatusBarCommands.fire(this.statusBarCommands!);
}
}
$registerGroup(handle: number, id: string, label: string): void {
const group = new MainThreadSCMResourceGroup(
this.handle,
handle,
this,
{},
label,
id
);
this._groupsByHandle[handle] = group;
this.groups.splice(this.groups.elements.length, 0, [group]);
}
$updateGroup(handle: number, features: SCMGroupFeatures): void {
const group = this._groupsByHandle[handle];
if (!group) {
return;
}
group.$updateGroup(features);
}
$updateGroupLabel(handle: number, label: string): void {
const group = this._groupsByHandle[handle];
if (!group) {
return;
}
group.$updateGroupLabel(label);
}
$spliceGroupResourceStates(splices: SCMRawResourceSplices[]): void {
for (const [groupHandle, groupSlices] of splices) {
const group = this._groupsByHandle[groupHandle];
if (!group) {
console.warn(`SCM group ${groupHandle} not found in provider ${this.label}`);
continue;
}
// reverse the splices sequence in order to apply them correctly
groupSlices.reverse();
for (const [start, deleteCount, rawResources] of groupSlices) {
const resources = rawResources.map(rawResource => {
const [handle, sourceUri, icons, tooltip, strikeThrough, faded, source, letter, color] = rawResource;
const icon = icons[0];
const iconDark = icons[1] || icon;
const decorations = {
icon: icon ? URI.parse(icon) : undefined,
iconDark: iconDark ? URI.parse(iconDark) : undefined,
tooltip,
strikeThrough,
faded,
source,
letter,
color: color ? color.id : undefined
};
return new MainThreadSCMResource(
this.proxy,
this.handle,
groupHandle,
handle,
URI.revive(sourceUri),
group,
decorations
);
});
group.splice(start, deleteCount, resources);
}
}
this._onDidChangeResources.fire();
}
$unregisterGroup(handle: number): void {
const group = this._groupsByHandle[handle];
if (!group) {
return;
}
delete this._groupsByHandle[handle];
this.groups.splice(this.groups.elements.indexOf(group), 1);
}
async getOriginalResource(uri: URI): Promise<URI | null> {
if (!this.features.hasQuickDiffProvider) {
return null;
}
const result = await this.proxy.$provideOriginalResource(this.handle, uri, CancellationToken.None);
return result && URI.revive(result);
}
toJSON(): any {
return {
$mid: 5,
handle: this.handle
};
}
dispose(): void {
}
}
@extHostNamedCustomer(MainContext.MainThreadSCM)
export class MainThreadSCM implements MainThreadSCMShape {
private readonly _proxy: ExtHostSCMShape;
private _repositories: { [handle: number]: ISCMRepository; } = Object.create(null);
private _inputDisposables: { [handle: number]: IDisposable; } = Object.create(null);
private _disposables: IDisposable[] = [];
constructor(
extHostContext: IExtHostContext,
@ISCMService private readonly scmService: ISCMService
) {
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostSCM);
Event.debounce(scmService.onDidChangeSelectedRepositories, (_, e) => e, 100)
(this.onDidChangeSelectedRepositories, this, this._disposables);
}
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, rootUri: UriComponents | undefined): void {
const provider = new MainThreadSCMProvider(this._proxy, handle, id, label, rootUri && URI.revive(rootUri), this.scmService);
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);
}
$spliceResourceStates(sourceControlHandle: number, splices: SCMRawResourceSplices[]): void {
const repository = this._repositories[sourceControlHandle];
if (!repository) {
return;
}
const provider = repository.provider as MainThreadSCMProvider;
provider.$spliceGroupResourceStates(splices);
}
$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;
}
$setInputBoxPlaceholder(sourceControlHandle: number, placeholder: string): void {
const repository = this._repositories[sourceControlHandle];
if (!repository) {
return;
}
repository.input.placeholder = placeholder;
}
$setInputBoxVisibility(sourceControlHandle: number, visible: boolean): void {
const repository = this._repositories[sourceControlHandle];
if (!repository) {
return;
}
repository.input.visible = visible;
}
$setValidationProviderIsEnabled(sourceControlHandle: number, enabled: boolean): void {
const repository = this._repositories[sourceControlHandle];
if (!repository) {
return;
}
if (enabled) {
repository.input.validateInput = async (value, pos): Promise<IInputValidation | undefined> => {
const result = await this._proxy.$validateInput(sourceControlHandle, value, pos);
return result && { message: result[0], type: result[1] };
};
} else {
repository.input.validateInput = async () => undefined;
}
}
private onDidChangeSelectedRepositories(repositories: ISCMRepository[]): void {
const handles = repositories
.filter(r => r.provider instanceof MainThreadSCMProvider)
.map(r => (r.provider as MainThreadSCMProvider).handle);
this._proxy.$setSelectedSourceControls(handles);
}
}

View File

@@ -0,0 +1,467 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { isNonEmptyArray } from 'vs/base/common/arrays';
import { IdleValue, sequence } from 'vs/base/common/async';
import { CancellationTokenSource, CancellationToken } from 'vs/base/common/cancellation';
import * as strings from 'vs/base/common/strings';
import { ICodeEditor, IActiveCodeEditor } from 'vs/editor/browser/editorBrowser';
import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { trimTrailingWhitespace } from 'vs/editor/common/commands/trimTrailingWhitespaceCommand';
import { ICodeActionsOnSaveOptions } from 'vs/editor/common/config/editorOptions';
import { EditOperation } from 'vs/editor/common/core/editOperation';
import { Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
import { Selection } from 'vs/editor/common/core/selection';
import { IIdentifiedSingleEditOperation, ITextModel } from 'vs/editor/common/model';
import { CodeAction, TextEdit } from 'vs/editor/common/modes';
import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService';
import { shouldSynchronizeModel } from 'vs/editor/common/services/modelService';
import { getCodeActions } from 'vs/editor/contrib/codeAction/codeAction';
import { applyCodeAction } from 'vs/editor/contrib/codeAction/codeActionCommands';
import { CodeActionKind } from 'vs/editor/contrib/codeAction/codeActionTrigger';
import { getDocumentFormattingEdits, FormatMode } from 'vs/editor/contrib/format/format';
import { FormattingEdit } from 'vs/editor/contrib/format/formattingEdit';
import { SnippetController2 } from 'vs/editor/contrib/snippet/snippetController2';
import { localize } from 'vs/nls';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ILogService } from 'vs/platform/log/common/log';
import { IProgressService2, ProgressLocation } from 'vs/platform/progress/common/progress';
import { extHostCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel';
// {{SQL CARBON EDIT}}
import { ISaveParticipant, SaveReason, IResolvedTextFileEditorModel, ITextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles';
import { ExtHostContext, ExtHostDocumentSaveParticipantShape, IExtHostContext } from '../common/extHost.protocol';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
// {{SQL CARBON EDIT}}
import { INotebookService } from 'sql/workbench/services/notebook/common/notebookService';
/*
* An update participant that ensures any un-tracked changes are synced to the JSON file contents for a
* Notebook before save occurs. While every effort is made to ensure model changes are notified and a listener
* updates the backing model in-place, this is a backup mechanism to hard-update the file before save in case
* some are missed.
*/
class NotebookUpdateParticipant implements ISaveParticipantParticipant {
constructor(
@INotebookService private notebookService: INotebookService
) {
// Nothing
}
public participate(model: ITextFileEditorModel, env: { reason: SaveReason }): Promise<void> {
let uriString = model.getResource().toString();
let notebookEditor = this.notebookService.listNotebookEditors().find((editor) => editor.id === uriString);
if (notebookEditor) {
notebookEditor.notebookParams.input.updateModel();
}
return Promise.resolve();
}
}
export interface ISaveParticipantParticipant extends ISaveParticipant {
// progressMessage: string;
}
class TrimWhitespaceParticipant implements ISaveParticipantParticipant {
constructor(
@IConfigurationService private readonly configurationService: IConfigurationService,
@ICodeEditorService private readonly codeEditorService: ICodeEditorService
) {
// Nothing
}
async participate(model: IResolvedTextFileEditorModel, env: { reason: SaveReason }): Promise<void> {
if (this.configurationService.getValue('files.trimTrailingWhitespace', { overrideIdentifier: model.textEditorModel.getLanguageIdentifier().language, resource: model.getResource() })) {
this.doTrimTrailingWhitespace(model.textEditorModel, env.reason === SaveReason.AUTO);
}
}
private doTrimTrailingWhitespace(model: ITextModel, isAutoSaved: boolean): void {
let prevSelection: Selection[] = [];
let cursors: Position[] = [];
const 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 = prevSelection.map(s => s.getPosition());
const snippetsRange = SnippetController2.get(editor).getSessionEnclosingRange();
if (snippetsRange) {
for (let lineNumber = snippetsRange.startLineNumber; lineNumber <= snippetsRange.endLineNumber; lineNumber++) {
cursors.push(new Position(lineNumber, model.getLineMaxColumn(lineNumber)));
}
}
}
}
const ops = trimTrailingWhitespace(model, cursors);
if (!ops.length) {
return; // Nothing to do
}
model.pushEditOperations(prevSelection, ops, (edits) => prevSelection);
}
}
function findEditor(model: ITextModel, codeEditorService: ICodeEditorService): IActiveCodeEditor | null {
let candidate: IActiveCodeEditor | null = null;
if (model.isAttachedToEditor()) {
for (const editor of codeEditorService.listCodeEditors()) {
if (editor.hasModel() && editor.getModel() === model) {
if (editor.hasTextFocus()) {
return editor; // favour focused editor if there are multiple
}
candidate = editor;
}
}
}
return candidate;
}
export class FinalNewLineParticipant implements ISaveParticipantParticipant {
constructor(
@IConfigurationService private readonly configurationService: IConfigurationService,
@ICodeEditorService private readonly codeEditorService: ICodeEditorService
) {
// Nothing
}
async participate(model: IResolvedTextFileEditorModel, env: { reason: SaveReason }): Promise<void> {
if (this.configurationService.getValue('files.insertFinalNewline', { overrideIdentifier: model.textEditorModel.getLanguageIdentifier().language, resource: model.getResource() })) {
this.doInsertFinalNewLine(model.textEditorModel);
}
}
private doInsertFinalNewLine(model: ITextModel): void {
const lineCount = model.getLineCount();
const lastLine = model.getLineContent(lineCount);
const lastLineIsEmptyOrWhitespace = strings.lastNonWhitespaceIndex(lastLine) === -1;
if (!lineCount || lastLineIsEmptyOrWhitespace) {
return;
}
let prevSelection: Selection[] = [];
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);
}
}
}
export class TrimFinalNewLinesParticipant implements ISaveParticipantParticipant {
constructor(
@IConfigurationService private readonly configurationService: IConfigurationService,
@ICodeEditorService private readonly codeEditorService: ICodeEditorService
) {
// Nothing
}
async participate(model: IResolvedTextFileEditorModel, env: { reason: SaveReason }): Promise<void> {
if (this.configurationService.getValue('files.trimFinalNewlines', { overrideIdentifier: model.textEditorModel.getLanguageIdentifier().language, resource: model.getResource() })) {
this.doTrimFinalNewLines(model.textEditorModel, env.reason === SaveReason.AUTO);
}
}
/**
* returns 0 if the entire file is empty or whitespace only
*/
private findLastLineWithContent(model: ITextModel): number {
for (let lineNumber = model.getLineCount(); lineNumber >= 1; lineNumber--) {
const lineContent = model.getLineContent(lineNumber);
if (strings.lastNonWhitespaceIndex(lineContent) !== -1) {
// this line has content
return lineNumber;
}
}
// no line has content
return 0;
}
private doTrimFinalNewLines(model: ITextModel, isAutoSaved: boolean): void {
const lineCount = model.getLineCount();
// Do not insert new line if file does not end with new line
if (lineCount === 1) {
return;
}
let prevSelection: Selection[] = [];
let cannotTouchLineNumber = 0;
const editor = findEditor(model, this.codeEditorService);
if (editor) {
prevSelection = editor.getSelections();
if (isAutoSaved) {
for (let i = 0, len = prevSelection.length; i < len; i++) {
const positionLineNumber = prevSelection[i].positionLineNumber;
if (positionLineNumber > cannotTouchLineNumber) {
cannotTouchLineNumber = positionLineNumber;
}
}
}
}
const lastLineNumberWithContent = this.findLastLineWithContent(model);
const deleteFromLineNumber = Math.max(lastLineNumberWithContent + 1, cannotTouchLineNumber + 1);
const deletionRange = model.validateRange(new Range(deleteFromLineNumber, 1, lineCount, model.getLineMaxColumn(lineCount)));
if (deletionRange.isEmpty()) {
return;
}
model.pushEditOperations(prevSelection, [EditOperation.delete(deletionRange)], edits => prevSelection);
if (editor) {
editor.setSelections(prevSelection);
}
}
}
class FormatOnSaveParticipant implements ISaveParticipantParticipant {
constructor(
@ICodeEditorService private readonly _editorService: ICodeEditorService,
@IEditorWorkerService private readonly _editorWorkerService: IEditorWorkerService,
@IConfigurationService private readonly _configurationService: IConfigurationService,
@ITelemetryService private readonly _telemetryService: ITelemetryService,
) {
// Nothing
}
async participate(editorModel: IResolvedTextFileEditorModel, env: { reason: SaveReason }): Promise<void> {
const model = editorModel.textEditorModel;
if (env.reason === SaveReason.AUTO
|| !this._configurationService.getValue('editor.formatOnSave', { overrideIdentifier: model.getLanguageIdentifier().language, resource: editorModel.getResource() })) {
return undefined;
}
const versionNow = model.getVersionId();
const timeout = this._configurationService.getValue<number>('editor.formatOnSaveTimeout', { overrideIdentifier: model.getLanguageIdentifier().language, resource: editorModel.getResource() });
return new Promise<TextEdit[] | null | undefined>((resolve, reject) => {
const source = new CancellationTokenSource();
const request = getDocumentFormattingEdits(this._telemetryService, this._editorWorkerService, model, model.getFormattingOptions(), FormatMode.Auto, source.token);
setTimeout(() => {
reject(localize('timeout.formatOnSave', "Aborted format on save after {0}ms", timeout));
source.cancel();
}, timeout);
request.then(resolve, reject);
}).then(edits => {
if (isNonEmptyArray(edits) && versionNow === model.getVersionId()) {
const editor = findEditor(model, this._editorService);
if (editor) {
this._editsWithEditor(editor, edits);
} else {
this._editWithModel(model, edits);
}
}
});
}
private _editsWithEditor(editor: ICodeEditor, edits: TextEdit[]): void {
FormattingEdit.execute(editor, edits);
}
private _editWithModel(model: ITextModel, edits: TextEdit[]): 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 null;
});
}
private static _asIdentEdit({ text, range }: TextEdit): IIdentifiedSingleEditOperation {
return {
text,
range: Range.lift(range),
forceMoveMarkers: true
};
}
}
class CodeActionOnSaveParticipant implements ISaveParticipant {
constructor(
@IBulkEditService private readonly _bulkEditService: IBulkEditService,
@ICommandService private readonly _commandService: ICommandService,
@IConfigurationService private readonly _configurationService: IConfigurationService
) { }
async participate(editorModel: IResolvedTextFileEditorModel, env: { reason: SaveReason }): Promise<void> {
if (env.reason === SaveReason.AUTO) {
return undefined;
}
const model = editorModel.textEditorModel;
const settingsOverrides = { overrideIdentifier: model.getLanguageIdentifier().language, resource: editorModel.getResource() };
const setting = this._configurationService.getValue<ICodeActionsOnSaveOptions>('editor.codeActionsOnSave', settingsOverrides);
if (!setting) {
return undefined;
}
const codeActionsOnSave = Object.keys(setting)
.filter(x => setting[x]).map(x => new CodeActionKind(x))
.sort((a, b) => {
if (a.value === CodeActionKind.SourceFixAll.value) {
return -1;
}
if (b.value === CodeActionKind.SourceFixAll.value) {
return 1;
}
return 0;
});
if (!codeActionsOnSave.length) {
return undefined;
}
const tokenSource = new CancellationTokenSource();
const timeout = this._configurationService.getValue<number>('editor.codeActionsOnSaveTimeout', settingsOverrides);
return Promise.race([
new Promise<void>((_resolve, reject) =>
setTimeout(() => {
tokenSource.cancel();
reject(localize('codeActionsOnSave.didTimeout', "Aborted codeActionsOnSave after {0}ms", timeout));
}, timeout)),
this.applyOnSaveActions(model, codeActionsOnSave, tokenSource.token)
]).then(() => {
tokenSource.cancel();
}, (e) => {
tokenSource.cancel();
return Promise.reject(e);
});
}
private async applyOnSaveActions(model: ITextModel, codeActionsOnSave: CodeActionKind[], token: CancellationToken): Promise<void> {
for (const codeActionKind of codeActionsOnSave) {
const actionsToRun = await this.getActionsToRun(model, codeActionKind, token);
try {
await this.applyCodeActions(actionsToRun.actions);
} catch {
// Failure to apply a code action should not block other on save actions
}
}
}
private async applyCodeActions(actionsToRun: ReadonlyArray<CodeAction>) {
for (const action of actionsToRun) {
await applyCodeAction(action, this._bulkEditService, this._commandService);
}
}
private getActionsToRun(model: ITextModel, codeActionKind: CodeActionKind, token: CancellationToken) {
return getCodeActions(model, model.getFullModelRange(), {
type: 'auto',
filter: { kind: codeActionKind, includeSourceActions: true },
}, token);
}
}
class ExtHostSaveParticipant implements ISaveParticipantParticipant {
private readonly _proxy: ExtHostDocumentSaveParticipantShape;
constructor(extHostContext: IExtHostContext) {
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostDocumentSaveParticipant);
}
async participate(editorModel: IResolvedTextFileEditorModel, env: { reason: SaveReason }): Promise<void> {
if (!shouldSynchronizeModel(editorModel.textEditorModel)) {
// the model never made it to the extension
// host meaning we cannot participate in its save
return undefined;
}
return new Promise<any>((resolve, reject) => {
setTimeout(() => reject(localize('timeout.onWillSave', "Aborted onWillSaveTextDocument-event after 1750ms")), 1750);
this._proxy.$participateInSave(editorModel.getResource(), env.reason).then(values => {
for (const success of values) {
if (!success) {
return Promise.reject(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 readonly _saveParticipants: IdleValue<ISaveParticipantParticipant[]>;
constructor(
extHostContext: IExtHostContext,
@IInstantiationService instantiationService: IInstantiationService,
@IProgressService2 private readonly _progressService: IProgressService2,
@ILogService private readonly _logService: ILogService
) {
this._saveParticipants = new IdleValue(() => [
instantiationService.createInstance(TrimWhitespaceParticipant),
instantiationService.createInstance(CodeActionOnSaveParticipant),
instantiationService.createInstance(FormatOnSaveParticipant),
instantiationService.createInstance(FinalNewLineParticipant),
instantiationService.createInstance(TrimFinalNewLinesParticipant),
// {{SQL CARBON EDIT}}
instantiationService.createInstance(NotebookUpdateParticipant),
instantiationService.createInstance(ExtHostSaveParticipant, extHostContext),
]);
// Hook into model
TextFileEditorModel.setSaveParticipant(this);
}
dispose(): void {
TextFileEditorModel.setSaveParticipant(null);
this._saveParticipants.dispose();
}
async participate(model: IResolvedTextFileEditorModel, env: { reason: SaveReason }): Promise<void> {
return this._progressService.withProgress({ location: ProgressLocation.Window }, progress => {
progress.report({ message: localize('saveParticipants', "Running Save Participants...") });
const promiseFactory = this._saveParticipants.getValue().map(p => () => {
return p.participate(model, env);
});
return sequence(promiseFactory).then(() => { }, err => this._logService.warn(err));
});
}
}

View File

@@ -0,0 +1,170 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { CancellationToken } from 'vs/base/common/cancellation';
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import { values } from 'vs/base/common/map';
import { URI, UriComponents } from 'vs/base/common/uri';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { IFileMatch, IFileQuery, IRawFileMatch2, ISearchComplete, ISearchCompleteStats, ISearchProgressItem, ISearchResultProvider, ISearchService, ITextQuery, QueryType, SearchProviderType } from 'vs/workbench/services/search/common/search';
import { ExtHostContext, ExtHostSearchShape, IExtHostContext, MainContext, MainThreadSearchShape } from '../common/extHost.protocol';
@extHostNamedCustomer(MainContext.MainThreadSearch)
export class MainThreadSearch implements MainThreadSearchShape {
private readonly _proxy: ExtHostSearchShape;
private readonly _searchProvider = new Map<number, RemoteSearchProvider>();
constructor(
extHostContext: IExtHostContext,
@ISearchService private readonly _searchService: ISearchService,
@ITelemetryService private readonly _telemetryService: ITelemetryService
) {
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostSearch);
}
dispose(): void {
this._searchProvider.forEach(value => value.dispose());
this._searchProvider.clear();
}
$registerTextSearchProvider(handle: number, scheme: string): void {
this._searchProvider.set(handle, new RemoteSearchProvider(this._searchService, SearchProviderType.text, scheme, handle, this._proxy));
}
$registerFileSearchProvider(handle: number, scheme: string): void {
this._searchProvider.set(handle, new RemoteSearchProvider(this._searchService, SearchProviderType.file, scheme, handle, this._proxy));
}
$unregisterProvider(handle: number): void {
dispose(this._searchProvider.get(handle));
this._searchProvider.delete(handle);
}
$handleFileMatch(handle: number, session, data: UriComponents[]): void {
const provider = this._searchProvider.get(handle);
if (!provider) {
throw new Error('Got result for unknown provider');
}
provider.handleFindMatch(session, data);
}
$handleTextMatch(handle: number, session, data: IRawFileMatch2[]): void {
const provider = this._searchProvider.get(handle);
if (!provider) {
throw new Error('Got result for unknown provider');
}
provider.handleFindMatch(session, data);
}
$handleTelemetry(eventName: string, data: any): void {
this._telemetryService.publicLog(eventName, data);
}
}
class SearchOperation {
private static _idPool = 0;
constructor(
readonly progress?: (match: IFileMatch) => any,
readonly id: number = ++SearchOperation._idPool,
readonly matches = new Map<string, IFileMatch>()
) {
//
}
addMatch(match: IFileMatch): void {
if (this.matches.has(match.resource.toString())) {
// Merge with previous IFileMatches
// TODO@rob clean up text/file result types
this.matches.get(match.resource.toString())!.results!.push(...match.results!);
} else {
this.matches.set(match.resource.toString(), match);
}
if (this.progress) {
this.progress(match);
}
}
}
class RemoteSearchProvider implements ISearchResultProvider, IDisposable {
private readonly _registrations: IDisposable[];
private readonly _searches = new Map<number, SearchOperation>();
constructor(
searchService: ISearchService,
type: SearchProviderType,
private readonly _scheme: string,
private readonly _handle: number,
private readonly _proxy: ExtHostSearchShape
) {
this._registrations = [searchService.registerSearchResultProvider(this._scheme, type, this)];
}
dispose(): void {
dispose(this._registrations);
}
fileSearch(query: IFileQuery, token: CancellationToken = CancellationToken.None): Promise<ISearchComplete> {
return this.doSearch(query, undefined, token);
}
textSearch(query: ITextQuery, onProgress?: (p: ISearchProgressItem) => void, token: CancellationToken = CancellationToken.None): Promise<ISearchComplete | undefined> {
return this.doSearch(query, onProgress, token);
}
doSearch(query: ITextQuery | IFileQuery, onProgress?: (p: ISearchProgressItem) => void, token: CancellationToken = CancellationToken.None): Promise<ISearchComplete> {
if (!query.folderQueries.length) {
throw new Error('Empty folderQueries');
}
const search = new SearchOperation(onProgress);
this._searches.set(search.id, search);
const searchP = query.type === QueryType.File
? this._proxy.$provideFileSearchResults(this._handle, search.id, query, token)
: this._proxy.$provideTextSearchResults(this._handle, search.id, query, token);
return Promise.resolve(searchP).then((result: ISearchCompleteStats) => {
this._searches.delete(search.id);
return { results: values(search.matches), stats: result.stats, limitHit: result.limitHit };
}, err => {
this._searches.delete(search.id);
return Promise.reject(err);
});
}
clearCache(cacheKey: string): Promise<void> {
return Promise.resolve(this._proxy.$clearCache(cacheKey));
}
handleFindMatch(session: number, dataOrUri: Array<UriComponents | IRawFileMatch2>): void {
const searchOp = this._searches.get(session);
if (!searchOp) {
// ignore...
return;
}
dataOrUri.forEach(result => {
if ((<IRawFileMatch2>result).results) {
searchOp.addMatch({
resource: URI.revive((<IRawFileMatch2>result).resource),
results: (<IRawFileMatch2>result).results
});
} else {
searchOp.addMatch({
resource: URI.revive(result)
});
}
});
}
}

View 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.
*--------------------------------------------------------------------------------------------*/
import { IStatusbarService, StatusbarAlignment as MainThreadStatusBarAlignment } from 'vs/platform/statusbar/common/statusbar';
import { IDisposable } from 'vs/base/common/lifecycle';
import { MainThreadStatusBarShape, MainContext, IExtHostContext } from '../common/extHost.protocol';
import { ThemeColor } from 'vs/platform/theme/common/themeService';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
@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: ExtensionIdentifier, text: string, tooltip: string, command: string, color: string | ThemeColor, alignment: MainThreadStatusBarAlignment, priority: number): void {
// Dispose any old
this.$dispose(id);
// Add new
const entry = this._statusbarService.addEntry({ text, tooltip, command, color, extensionId }, alignment, priority);
this._entries[id] = entry;
}
$dispose(id: number) {
const disposeable = this._entries[id];
if (disposeable) {
disposeable.dispose();
}
delete this._entries[id];
}
}

View File

@@ -0,0 +1,71 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { MainThreadStorageShape, MainContext, IExtHostContext, ExtHostStorageShape, ExtHostContext } from '../common/extHost.protocol';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { IDisposable } from 'vs/base/common/lifecycle';
@extHostNamedCustomer(MainContext.MainThreadStorage)
export class MainThreadStorage implements MainThreadStorageShape {
private readonly _storageService: IStorageService;
private readonly _proxy: ExtHostStorageShape;
private readonly _storageListener: IDisposable;
private readonly _sharedStorageKeysToWatch: Map<string, boolean> = new Map<string, boolean>();
constructor(
extHostContext: IExtHostContext,
@IStorageService storageService: IStorageService
) {
this._storageService = storageService;
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostStorage);
this._storageListener = this._storageService.onDidChangeStorage(e => {
const shared = e.scope === StorageScope.GLOBAL;
if (shared && this._sharedStorageKeysToWatch.has(e.key)) {
try {
this._proxy.$acceptValue(shared, e.key, this._getValue(shared, e.key));
} catch (error) {
// ignore parsing errors that can happen
}
}
});
}
dispose(): void {
this._storageListener.dispose();
}
$getValue<T>(shared: boolean, key: string): Promise<T | undefined> {
if (shared) {
this._sharedStorageKeysToWatch.set(key, true);
}
try {
return Promise.resolve(this._getValue<T>(shared, key));
} catch (error) {
return Promise.reject(error);
}
}
private _getValue<T>(shared: boolean, key: string): T | undefined {
const jsonValue = this._storageService.get(key, shared ? StorageScope.GLOBAL : StorageScope.WORKSPACE);
if (!jsonValue) {
return undefined;
}
return JSON.parse(jsonValue);
}
$setValue(shared: boolean, key: string, value: object): Promise<void> {
let jsonValue: string;
try {
jsonValue = JSON.stringify(value);
this._storageService.store(key, jsonValue, shared ? StorageScope.GLOBAL : StorageScope.WORKSPACE);
} catch (err) {
return Promise.reject(err);
}
return Promise.resolve(undefined);
}
}

View 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.
*--------------------------------------------------------------------------------------------*/
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { MainThreadTelemetryShape, MainContext, IExtHostContext } from '../common/extHost.protocol';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
@extHostNamedCustomer(MainContext.MainThreadTelemetry)
export class MainThreadTelemetry implements MainThreadTelemetryShape {
private static readonly _name = 'pluginHostTelemetry';
constructor(
extHostContext: IExtHostContext,
@ITelemetryService private readonly _telemetryService: ITelemetryService
) {
//
}
dispose(): void {
//
}
$publicLog(eventName: string, data: any = Object.create(null)): void {
// __GDPR__COMMON__ "pluginHostTelemetry" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }
data[MainThreadTelemetry._name] = true;
this._telemetryService.publicLog(eventName, data);
}
}

View File

@@ -0,0 +1,270 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { ITerminalService, ITerminalInstance, IShellLaunchConfig, ITerminalProcessExtHostProxy, ITerminalProcessExtHostRequest, ITerminalDimensions, EXT_HOST_CREATION_DELAY } from 'vs/workbench/contrib/terminal/common/terminal';
import { ExtHostContext, ExtHostTerminalServiceShape, MainThreadTerminalServiceShape, MainContext, IExtHostContext, ShellLaunchConfigDto } from 'vs/workbench/api/common/extHost.protocol';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { UriComponents, URI } from 'vs/base/common/uri';
import { StopWatch } from 'vs/base/common/stopwatch';
@extHostNamedCustomer(MainContext.MainThreadTerminalService)
export class MainThreadTerminalService implements MainThreadTerminalServiceShape {
private _proxy: ExtHostTerminalServiceShape;
private _remoteAuthority: string | null;
private _toDispose: IDisposable[] = [];
private _terminalProcesses: { [id: number]: ITerminalProcessExtHostProxy } = {};
private _terminalOnDidWriteDataListeners: { [id: number]: IDisposable } = {};
private _terminalOnDidAcceptInputListeners: { [id: number]: IDisposable } = {};
constructor(
extHostContext: IExtHostContext,
@ITerminalService private readonly terminalService: ITerminalService
) {
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostTerminalService);
this._remoteAuthority = extHostContext.remoteAuthority;
this._toDispose.push(terminalService.onInstanceCreated((instance) => {
// Delay this message so the TerminalInstance constructor has a chance to finish and
// return the ID normally to the extension host. The ID that is passed here will be used
// to register non-extension API terminals in the extension host.
setTimeout(() => {
this._onTerminalOpened(instance);
this._onInstanceDimensionsChanged(instance);
}, EXT_HOST_CREATION_DELAY);
}));
this._toDispose.push(terminalService.onInstanceDisposed(instance => this._onTerminalDisposed(instance)));
this._toDispose.push(terminalService.onInstanceProcessIdReady(instance => this._onTerminalProcessIdReady(instance)));
this._toDispose.push(terminalService.onInstanceDimensionsChanged(instance => this._onInstanceDimensionsChanged(instance)));
this._toDispose.push(terminalService.onInstanceRequestExtHostProcess(request => this._onTerminalRequestExtHostProcess(request)));
this._toDispose.push(terminalService.onActiveInstanceChanged(instance => this._onActiveTerminalChanged(instance ? instance.id : null)));
this._toDispose.push(terminalService.onInstanceTitleChanged(instance => this._onTitleChanged(instance.id, instance.title)));
// Set initial ext host state
this.terminalService.terminalInstances.forEach(t => {
this._onTerminalOpened(t);
t.processReady.then(() => this._onTerminalProcessIdReady(t));
});
const activeInstance = this.terminalService.getActiveInstance();
if (activeInstance) {
this._proxy.$acceptActiveTerminalChanged(activeInstance.id);
}
}
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[] | string, cwd?: string | UriComponents, env?: { [key: string]: string }, waitOnExit?: boolean, strictEnv?: boolean): Promise<{ id: number, name: string }> {
const shellLaunchConfig: IShellLaunchConfig = {
name,
executable: shellPath,
args: shellArgs,
cwd: typeof cwd === 'string' ? cwd : URI.revive(cwd),
waitOnExit,
ignoreConfigurationCwd: true,
env,
strictEnv
};
const terminal = this.terminalService.createTerminal(shellLaunchConfig);
return Promise.resolve({
id: terminal.id,
name: terminal.title
});
}
public $createTerminalRenderer(name: string): Promise<number> {
const instance = this.terminalService.createTerminalRenderer(name);
return Promise.resolve(instance.id);
}
public $show(terminalId: number, preserveFocus: boolean): void {
const terminalInstance = this.terminalService.getInstanceFromId(terminalId);
if (terminalInstance) {
this.terminalService.setActiveInstance(terminalInstance);
this.terminalService.showPanel(!preserveFocus);
}
}
public $hide(terminalId: number): void {
const instance = this.terminalService.getActiveInstance();
if (instance && instance.id === terminalId) {
this.terminalService.hidePanel();
}
}
public $dispose(terminalId: number): void {
const terminalInstance = this.terminalService.getInstanceFromId(terminalId);
if (terminalInstance) {
terminalInstance.dispose();
}
}
public $terminalRendererWrite(terminalId: number, text: string): void {
const terminalInstance = this.terminalService.getInstanceFromId(terminalId);
if (terminalInstance && terminalInstance.shellLaunchConfig.isRendererOnly) {
terminalInstance.write(text);
}
}
public $terminalRendererSetName(terminalId: number, name: string): void {
const terminalInstance = this.terminalService.getInstanceFromId(terminalId);
if (terminalInstance && terminalInstance.shellLaunchConfig.isRendererOnly) {
terminalInstance.setTitle(name, false);
}
}
public $terminalRendererSetDimensions(terminalId: number, dimensions: ITerminalDimensions): void {
const terminalInstance = this.terminalService.getInstanceFromId(terminalId);
if (terminalInstance && terminalInstance.shellLaunchConfig.isRendererOnly) {
terminalInstance.setDimensions(dimensions);
}
}
public $terminalRendererRegisterOnInputListener(terminalId: number): void {
const terminalInstance = this.terminalService.getInstanceFromId(terminalId);
if (!terminalInstance) {
return;
}
// Listener already registered
if (this._terminalOnDidAcceptInputListeners.hasOwnProperty(terminalId)) {
return;
}
// Register
this._terminalOnDidAcceptInputListeners[terminalId] = terminalInstance.onRendererInput(data => this._onTerminalRendererInput(terminalId, data));
terminalInstance.addDisposable(this._terminalOnDidAcceptInputListeners[terminalId]);
}
public $sendText(terminalId: number, text: string, addNewLine: boolean): void {
const terminalInstance = this.terminalService.getInstanceFromId(terminalId);
if (terminalInstance) {
terminalInstance.sendText(text, addNewLine);
}
}
public $registerOnDataListener(terminalId: number): void {
const terminalInstance = this.terminalService.getInstanceFromId(terminalId);
if (!terminalInstance) {
return;
}
// Listener already registered
if (this._terminalOnDidWriteDataListeners[terminalId]) {
return;
}
// Register
this._terminalOnDidWriteDataListeners[terminalId] = terminalInstance.onData(data => {
this._onTerminalData(terminalId, data);
});
terminalInstance.addDisposable(this._terminalOnDidWriteDataListeners[terminalId]);
}
private _onActiveTerminalChanged(terminalId: number | null): void {
this._proxy.$acceptActiveTerminalChanged(terminalId);
}
private _onTerminalData(terminalId: number, data: string): void {
this._proxy.$acceptTerminalProcessData(terminalId, data);
}
private _onTitleChanged(terminalId: number, name: string): void {
this._proxy.$acceptTerminalTitleChange(terminalId, name);
}
private _onTerminalRendererInput(terminalId: number, data: string): void {
this._proxy.$acceptTerminalRendererInput(terminalId, data);
}
private _onTerminalDisposed(terminalInstance: ITerminalInstance): void {
this._proxy.$acceptTerminalClosed(terminalInstance.id);
}
private _onTerminalOpened(terminalInstance: ITerminalInstance): void {
if (terminalInstance.title) {
this._proxy.$acceptTerminalOpened(terminalInstance.id, terminalInstance.title);
} else {
terminalInstance.waitForTitle().then(title => {
this._proxy.$acceptTerminalOpened(terminalInstance.id, title);
});
}
}
private _onTerminalProcessIdReady(terminalInstance: ITerminalInstance): void {
if (terminalInstance.processId === undefined) {
return;
}
this._proxy.$acceptTerminalProcessId(terminalInstance.id, terminalInstance.processId);
}
private _onInstanceDimensionsChanged(instance: ITerminalInstance): void {
this._proxy.$acceptTerminalDimensions(instance.id, instance.cols, instance.rows);
}
private _onTerminalRequestExtHostProcess(request: ITerminalProcessExtHostRequest): void {
// Only allow processes on remote ext hosts
if (!this._remoteAuthority) {
return;
}
this._terminalProcesses[request.proxy.terminalId] = request.proxy;
const shellLaunchConfigDto: ShellLaunchConfigDto = {
name: request.shellLaunchConfig.name,
executable: request.shellLaunchConfig.executable,
args: request.shellLaunchConfig.args,
cwd: request.shellLaunchConfig.cwd,
env: request.shellLaunchConfig.env
};
this._proxy.$createProcess(request.proxy.terminalId, shellLaunchConfigDto, request.activeWorkspaceRootUri, request.cols, request.rows);
request.proxy.onInput(data => this._proxy.$acceptProcessInput(request.proxy.terminalId, data));
request.proxy.onResize(dimensions => this._proxy.$acceptProcessResize(request.proxy.terminalId, dimensions.cols, dimensions.rows));
request.proxy.onShutdown(immediate => this._proxy.$acceptProcessShutdown(request.proxy.terminalId, immediate));
request.proxy.onRequestCwd(() => this._proxy.$acceptProcessRequestCwd(request.proxy.terminalId));
request.proxy.onRequestInitialCwd(() => this._proxy.$acceptProcessRequestInitialCwd(request.proxy.terminalId));
request.proxy.onRequestLatency(() => this._onRequestLatency(request.proxy.terminalId));
}
public $sendProcessTitle(terminalId: number, title: string): void {
this._terminalProcesses[terminalId].emitTitle(title);
}
public $sendProcessData(terminalId: number, data: string): void {
this._terminalProcesses[terminalId].emitData(data);
}
public $sendProcessPid(terminalId: number, pid: number): void {
this._terminalProcesses[terminalId].emitPid(pid);
}
public $sendProcessExit(terminalId: number, exitCode: number): void {
this._terminalProcesses[terminalId].emitExit(exitCode);
delete this._terminalProcesses[terminalId];
}
public $sendProcessInitialCwd(terminalId: number, initialCwd: string): void {
this._terminalProcesses[terminalId].emitInitialCwd(initialCwd);
}
public $sendProcessCwd(terminalId: number, cwd: string): void {
this._terminalProcesses[terminalId].emitCwd(cwd);
}
private async _onRequestLatency(terminalId: number): Promise<void> {
const COUNT = 2;
let sum = 0;
for (let i = 0; i < COUNT; i++) {
const sw = StopWatch.create(true);
await this._proxy.$acceptProcessRequestLatency(terminalId);
sw.stop();
sum += sw.elapsed();
}
this._terminalProcesses[terminalId].emitLatency(sum / COUNT);
}
}

View File

@@ -0,0 +1,212 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Disposable } from 'vs/base/common/lifecycle';
import { ExtHostContext, MainThreadTreeViewsShape, ExtHostTreeViewsShape, MainContext, IExtHostContext } from '../common/extHost.protocol';
import { ITreeViewDataProvider, ITreeItem, IViewsService, ITreeView, IViewsRegistry, ITreeViewDescriptor, IRevealOptions, Extensions } from 'vs/workbench/common/views';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { distinct } from 'vs/base/common/arrays';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { isUndefinedOrNull, isNumber } from 'vs/base/common/types';
import { IMarkdownString } from 'vs/base/common/htmlContent';
import { Registry } from 'vs/platform/registry/common/platform';
@extHostNamedCustomer(MainContext.MainThreadTreeViews)
export class MainThreadTreeViews extends Disposable implements MainThreadTreeViewsShape {
private readonly _proxy: ExtHostTreeViewsShape;
private readonly _dataProviders: Map<string, TreeViewDataProvider> = new Map<string, TreeViewDataProvider>();
constructor(
extHostContext: IExtHostContext,
@IViewsService private readonly viewsService: IViewsService,
@INotificationService private readonly notificationService: INotificationService
) {
super();
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostTreeViews);
}
$registerTreeViewDataProvider(treeViewId: string, options: { showCollapseAll: boolean }): void {
const dataProvider = new TreeViewDataProvider(treeViewId, this._proxy, this.notificationService);
this._dataProviders.set(treeViewId, dataProvider);
const viewer = this.getTreeView(treeViewId);
if (viewer) {
viewer.dataProvider = dataProvider;
viewer.showCollapseAllAction = !!options.showCollapseAll;
this.registerListeners(treeViewId, viewer);
this._proxy.$setVisible(treeViewId, viewer.visible);
} else {
this.notificationService.error('No view is registered with id: ' + treeViewId);
}
}
$reveal(treeViewId: string, item: ITreeItem, parentChain: ITreeItem[], options: IRevealOptions): Promise<void> {
return this.viewsService.openView(treeViewId, options.focus)
.then(() => {
const viewer = this.getTreeView(treeViewId);
if (viewer) {
return this.reveal(viewer, this._dataProviders.get(treeViewId)!, item, parentChain, options);
}
return undefined;
});
}
$refresh(treeViewId: string, itemsToRefreshByHandle: { [treeItemHandle: string]: ITreeItem }): Promise<void> {
const viewer = this.getTreeView(treeViewId);
const dataProvider = this._dataProviders.get(treeViewId);
if (viewer && dataProvider) {
const itemsToRefresh = dataProvider.getItemsToRefresh(itemsToRefreshByHandle);
return viewer.refresh(itemsToRefresh.length ? itemsToRefresh : undefined);
}
return Promise.resolve();
}
$setMessage(treeViewId: string, message: string | IMarkdownString): void {
const viewer = this.getTreeView(treeViewId);
if (viewer) {
viewer.message = message;
}
}
private async reveal(treeView: ITreeView, dataProvider: TreeViewDataProvider, itemIn: ITreeItem, parentChain: ITreeItem[], options: IRevealOptions): Promise<void> {
options = options ? options : { select: false, focus: false };
const select = isUndefinedOrNull(options.select) ? false : options.select;
const focus = isUndefinedOrNull(options.focus) ? false : options.focus;
let expand = Math.min(isNumber(options.expand) ? options.expand : options.expand === true ? 1 : 0, 3);
if (dataProvider.isEmpty()) {
// Refresh if empty
await treeView.refresh();
}
for (const parent of parentChain) {
await treeView.expand(parent);
}
const item = dataProvider.getItem(itemIn.handle);
if (item) {
await treeView.reveal(item);
if (select) {
treeView.setSelection([item]);
}
if (focus) {
treeView.setFocus(item);
}
let itemsToExpand = [item];
for (; itemsToExpand.length > 0 && expand > 0; expand--) {
await treeView.expand(itemsToExpand);
itemsToExpand = itemsToExpand.reduce((result, itemValue) => {
const item = dataProvider.getItem(itemValue.handle);
if (item && item.children && item.children.length) {
result.push(...item.children);
}
return result;
}, [] as ITreeItem[]);
}
}
}
private registerListeners(treeViewId: string, treeView: ITreeView): void {
this._register(treeView.onDidExpandItem(item => this._proxy.$setExpanded(treeViewId, item.handle, true)));
this._register(treeView.onDidCollapseItem(item => this._proxy.$setExpanded(treeViewId, item.handle, false)));
this._register(treeView.onDidChangeSelection(items => this._proxy.$setSelection(treeViewId, items.map(({ handle }) => handle))));
this._register(treeView.onDidChangeVisibility(isVisible => this._proxy.$setVisible(treeViewId, isVisible)));
}
private getTreeView(treeViewId: string): ITreeView | null {
const viewDescriptor: ITreeViewDescriptor = <ITreeViewDescriptor>Registry.as<IViewsRegistry>(Extensions.ViewsRegistry).getView(treeViewId);
return viewDescriptor ? viewDescriptor.treeView : null;
}
dispose(): void {
this._dataProviders.forEach((dataProvider, treeViewId) => {
const treeView = this.getTreeView(treeViewId);
if (treeView) {
treeView.dataProvider = null;
}
});
this._dataProviders.clear();
super.dispose();
}
}
// {{SQL CARBON EDIT}}
export type TreeItemHandle = string;
// {{SQL CARBON EDIT}}
export class TreeViewDataProvider implements ITreeViewDataProvider {
private readonly itemsMap: Map<TreeItemHandle, ITreeItem> = new Map<TreeItemHandle, ITreeItem>();
// {{SQL CARBON EDIT}}
constructor(protected readonly treeViewId: string,
protected readonly _proxy: ExtHostTreeViewsShape,
private readonly notificationService: INotificationService
) {
}
getChildren(treeItem?: ITreeItem): Promise<ITreeItem[]> {
return Promise.resolve(this._proxy.$getChildren(this.treeViewId, treeItem ? treeItem.handle : undefined)
.then(
children => this.postGetChildren(children),
err => {
this.notificationService.error(err);
return [];
}));
}
getItemsToRefresh(itemsToRefreshByHandle: { [treeItemHandle: string]: ITreeItem }): ITreeItem[] {
const itemsToRefresh: ITreeItem[] = [];
if (itemsToRefreshByHandle) {
for (const treeItemHandle of Object.keys(itemsToRefreshByHandle)) {
const currentTreeItem = this.getItem(treeItemHandle);
if (currentTreeItem) { // Refresh only if the item exists
const treeItem = itemsToRefreshByHandle[treeItemHandle];
// Update the current item with refreshed item
this.updateTreeItem(currentTreeItem, treeItem);
if (treeItemHandle === treeItem.handle) {
itemsToRefresh.push(currentTreeItem);
} else {
// Update maps when handle is changed and refresh parent
this.itemsMap.delete(treeItemHandle);
this.itemsMap.set(currentTreeItem.handle, currentTreeItem);
const parent = treeItem.parentHandle ? this.itemsMap.get(treeItem.parentHandle) : null;
if (parent) {
itemsToRefresh.push(parent);
}
}
}
}
}
return itemsToRefresh;
}
getItem(treeItemHandle: string): ITreeItem | undefined {
return this.itemsMap.get(treeItemHandle);
}
isEmpty(): boolean {
return this.itemsMap.size === 0;
}
private postGetChildren(elements: ITreeItem[]): ITreeItem[] {
const result: ITreeItem[] = [];
if (elements) {
for (const element of elements) {
this.itemsMap.set(element.handle, element);
result.push(element);
}
}
return result;
}
private updateTreeItem(current: ITreeItem, treeItem: ITreeItem): void {
treeItem.children = treeItem.children ? treeItem.children : undefined;
if (current) {
const properties = distinct([...Object.keys(current), ...Object.keys(treeItem)]);
for (const property of properties) {
current[property] = treeItem[property];
}
}
}
}

View File

@@ -0,0 +1,41 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Event } from 'vs/base/common/event';
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import { URI, UriComponents } from 'vs/base/common/uri';
import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { ExtHostContext, ExtHostWindowShape, IExtHostContext, MainContext, MainThreadWindowShape } from '../common/extHost.protocol';
@extHostNamedCustomer(MainContext.MainThreadWindow)
export class MainThreadWindow implements MainThreadWindowShape {
private readonly proxy: ExtHostWindowShape;
private disposables: IDisposable[] = [];
constructor(
extHostContext: IExtHostContext,
@IWindowService private readonly windowService: IWindowService,
@IWindowsService private readonly windowsService: IWindowsService
) {
this.proxy = extHostContext.getProxy(ExtHostContext.ExtHostWindow);
Event.latch(windowService.onDidChangeFocus)
(this.proxy.$onDidChangeWindowFocus, this.proxy, this.disposables);
}
dispose(): void {
this.disposables = dispose(this.disposables);
}
$getWindowVisibility(): Promise<boolean> {
return this.windowService.isFocused();
}
$openUri(uri: UriComponents): Promise<boolean> {
return this.windowsService.openExternal(URI.revive(uri).toString(true));
}
}

View File

@@ -0,0 +1,228 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
import { isPromiseCanceledError } from 'vs/base/common/errors';
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import { URI, UriComponents } from 'vs/base/common/uri';
import { localize } from 'vs/nls';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { ILabelService } from 'vs/platform/label/common/label';
import { IFileMatch, IPatternInfo, ISearchProgressItem, ISearchService } from 'vs/workbench/services/search/common/search';
import { IStatusbarService } from 'vs/platform/statusbar/common/statusbar';
import { IWindowService } from 'vs/platform/windows/common/windows';
import { IWorkspaceContextService, WorkbenchState, IWorkspace } from 'vs/platform/workspace/common/workspace';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { ITextQueryBuilderOptions, QueryBuilder } from 'vs/workbench/contrib/search/common/queryBuilder';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing';
import { ExtHostContext, ExtHostWorkspaceShape, IExtHostContext, MainContext, MainThreadWorkspaceShape, IWorkspaceData, ITextSearchComplete } from '../common/extHost.protocol';
@extHostNamedCustomer(MainContext.MainThreadWorkspace)
export class MainThreadWorkspace implements MainThreadWorkspaceShape {
private readonly _toDispose: IDisposable[] = [];
private readonly _activeCancelTokens: { [id: number]: CancellationTokenSource } = Object.create(null);
private readonly _proxy: ExtHostWorkspaceShape;
private readonly _queryBuilder = this._instantiationService.createInstance(QueryBuilder);
constructor(
extHostContext: IExtHostContext,
@ISearchService private readonly _searchService: ISearchService,
@IWorkspaceContextService private readonly _contextService: IWorkspaceContextService,
@ITextFileService private readonly _textFileService: ITextFileService,
@IWorkspaceEditingService private readonly _workspaceEditingService: IWorkspaceEditingService,
@IStatusbarService private readonly _statusbarService: IStatusbarService,
@IWindowService private readonly _windowService: IWindowService,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@ILabelService private readonly _labelService: ILabelService
) {
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostWorkspace);
this._contextService.getCompleteWorkspace().then(workspace => this._proxy.$initializeWorkspace(this.getWorkspaceData(workspace)));
this._contextService.onDidChangeWorkspaceFolders(this._onDidChangeWorkspace, this, this._toDispose);
this._contextService.onDidChangeWorkbenchState(this._onDidChangeWorkspace, this, this._toDispose);
}
dispose(): void {
dispose(this._toDispose);
for (let requestId in this._activeCancelTokens) {
const tokenSource = this._activeCancelTokens[requestId];
tokenSource.cancel();
}
}
// --- workspace ---
$updateWorkspaceFolders(extensionName: string, index: number, deleteCount: number, foldersToAdd: { uri: UriComponents, name?: string }[]): Promise<void> {
const workspaceFoldersToAdd = foldersToAdd.map(f => ({ uri: URI.revive(f.uri), name: f.name }));
// Indicate in status message
this._statusbarService.setStatusMessage(this.getStatusMessage(extensionName, workspaceFoldersToAdd.length, deleteCount), 10 * 1000 /* 10s */);
return this._workspaceEditingService.updateFolders(index, deleteCount, workspaceFoldersToAdd, true);
}
private getStatusMessage(extensionName: string, addCount: number, removeCount: number): string {
let message: string;
const wantsToAdd = addCount > 0;
const wantsToDelete = removeCount > 0;
// Add Folders
if (wantsToAdd && !wantsToDelete) {
if (addCount === 1) {
message = localize('folderStatusMessageAddSingleFolder', "Extension '{0}' added 1 folder to the workspace", extensionName);
} else {
message = localize('folderStatusMessageAddMultipleFolders', "Extension '{0}' added {1} folders to the workspace", extensionName, addCount);
}
}
// Delete Folders
else if (wantsToDelete && !wantsToAdd) {
if (removeCount === 1) {
message = localize('folderStatusMessageRemoveSingleFolder', "Extension '{0}' removed 1 folder from the workspace", extensionName);
} else {
message = localize('folderStatusMessageRemoveMultipleFolders', "Extension '{0}' removed {1} folders from the workspace", extensionName, removeCount);
}
}
// Change Folders
else {
message = localize('folderStatusChangeFolder', "Extension '{0}' changed folders of the workspace", extensionName);
}
return message;
}
private _onDidChangeWorkspace(): void {
this._proxy.$acceptWorkspaceData(this.getWorkspaceData(this._contextService.getWorkspace()));
}
private getWorkspaceData(workspace: IWorkspace): IWorkspaceData | null {
if (this._contextService.getWorkbenchState() === WorkbenchState.EMPTY) {
return null;
}
return {
configuration: workspace.configuration || undefined,
folders: workspace.folders,
id: workspace.id,
name: this._labelService.getWorkspaceLabel(workspace)
};
}
// --- search ---
$startFileSearch(includePattern: string, _includeFolder: UriComponents | undefined, excludePatternOrDisregardExcludes: string | false | undefined, maxResults: number, token: CancellationToken): Promise<URI[] | undefined> {
const includeFolder = URI.revive(_includeFolder);
const workspace = this._contextService.getWorkspace();
if (!workspace.folders.length) {
return Promise.resolve(undefined);
}
const query = this._queryBuilder.file(
includeFolder ? [includeFolder] : workspace.folders.map(f => f.uri),
{
maxResults,
disregardExcludeSettings: (excludePatternOrDisregardExcludes === false) || undefined,
disregardSearchExcludeSettings: true,
disregardIgnoreFiles: true,
includePattern,
excludePattern: typeof excludePatternOrDisregardExcludes === 'string' ? excludePatternOrDisregardExcludes : undefined,
_reason: 'startFileSearch'
});
return this._searchService.fileSearch(query, token).then(result => {
return result.results.map(m => m.resource);
}, err => {
if (!isPromiseCanceledError(err)) {
return Promise.reject(err);
}
return undefined;
});
}
$startTextSearch(pattern: IPatternInfo, options: ITextQueryBuilderOptions, requestId: number, token: CancellationToken): Promise<ITextSearchComplete> {
const workspace = this._contextService.getWorkspace();
const folders = workspace.folders.map(folder => folder.uri);
const query = this._queryBuilder.text(pattern, folders, options);
query._reason = 'startTextSearch';
const onProgress = (p: ISearchProgressItem) => {
if ((<IFileMatch>p).results) {
this._proxy.$handleTextSearchResult(<IFileMatch>p, requestId);
}
};
const search = this._searchService.textSearch(query, token, onProgress).then(
result => {
return { limitHit: result.limitHit };
},
err => {
if (!isPromiseCanceledError(err)) {
return Promise.reject(err);
}
return undefined;
});
return search;
}
$checkExists(includes: string[], token: CancellationToken): Promise<boolean> {
const queryBuilder = this._instantiationService.createInstance(QueryBuilder);
const folders = this._contextService.getWorkspace().folders.map(folder => folder.uri);
const query = queryBuilder.file(folders, {
_reason: 'checkExists',
includePattern: includes.join(', '),
expandPatterns: true,
exists: true
});
return this._searchService.fileSearch(query, token).then(
result => {
return result.limitHit;
},
err => {
if (!isPromiseCanceledError(err)) {
return Promise.reject(err);
}
return undefined;
});
}
// --- save & edit resources ---
$saveAll(includeUntitled?: boolean): Promise<boolean> {
return this._textFileService.saveAll(includeUntitled).then(result => {
return result.results.every(each => each.success === true);
});
}
$resolveProxy(url: string): Promise<string | undefined> {
return this._windowService.resolveProxy(url);
}
}
CommandsRegistry.registerCommand('_workbench.enterWorkspace', async function (accessor: ServicesAccessor, workspace: URI, disableExtensions: string[]) {
const workspaceEditingService = accessor.get(IWorkspaceEditingService);
const extensionService = accessor.get(IExtensionService);
const windowService = accessor.get(IWindowService);
if (disableExtensions && disableExtensions.length) {
const runningExtensions = await extensionService.getExtensions();
// If requested extension to disable is running, then reload window with given workspace
if (disableExtensions && runningExtensions.some(runningExtension => disableExtensions.some(id => ExtensionIdentifier.equals(runningExtension.identifier, id)))) {
return windowService.openWindow([{ uri: workspace, typeHint: 'file' }], { args: { _: [], 'disable-extension': disableExtensions } });
}
}
return workspaceEditingService.enterWorkspace(workspace);
});