mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-30 09:35:39 -05:00
VSCode merge (#4610)
* Merge from vscode e388c734f30757875976c7e326d6cfeee77710de * fix yarn lcoks * remove small issue
This commit is contained in:
30
src/vs/workbench/api/browser/mainThreadClipboard.ts
Normal file
30
src/vs/workbench/api/browser/mainThreadClipboard.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
105
src/vs/workbench/api/browser/mainThreadCommands.ts
Normal file
105
src/vs/workbench/api/browser/mainThreadCommands.ts
Normal 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('');
|
||||
}
|
||||
}
|
||||
90
src/vs/workbench/api/browser/mainThreadConfiguration.ts
Normal file
90
src/vs/workbench/api/browser/mainThreadConfiguration.ts
Normal 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
|
||||
};
|
||||
}
|
||||
}
|
||||
385
src/vs/workbench/api/browser/mainThreadDebugService.ts
Normal file
385
src/vs/workbench/api/browser/mainThreadDebugService.ts
Normal 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}}
|
||||
*/
|
||||
126
src/vs/workbench/api/browser/mainThreadDecorations.ts
Normal file
126
src/vs/workbench/api/browser/mainThreadDecorations.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
49
src/vs/workbench/api/browser/mainThreadDiagnostics.ts
Normal file
49
src/vs/workbench/api/browser/mainThreadDiagnostics.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
60
src/vs/workbench/api/browser/mainThreadDialogs.ts
Normal file
60
src/vs/workbench/api/browser/mainThreadDialogs.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
27
src/vs/workbench/api/browser/mainThreadErrors.ts
Normal file
27
src/vs/workbench/api/browser/mainThreadErrors.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
164
src/vs/workbench/api/browser/mainThreadFileSystem.ts
Normal file
164
src/vs/workbench/api/browser/mainThreadFileSystem.ts
Normal 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));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
43
src/vs/workbench/api/browser/mainThreadLanguages.ts
Normal file
43
src/vs/workbench/api/browser/mainThreadLanguages.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
22
src/vs/workbench/api/browser/mainThreadLogService.ts
Normal file
22
src/vs/workbench/api/browser/mainThreadLogService.ts
Normal 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)));
|
||||
}
|
||||
|
||||
}
|
||||
123
src/vs/workbench/api/browser/mainThreadMessageService.ts
Normal file
123
src/vs/workbench/api/browser/mainThreadMessageService.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
110
src/vs/workbench/api/browser/mainThreadOutputService.ts
Normal file
110
src/vs/workbench/api/browser/mainThreadOutputService.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
58
src/vs/workbench/api/browser/mainThreadProgress.ts
Normal file
58
src/vs/workbench/api/browser/mainThreadProgress.ts
Normal 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 });
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
212
src/vs/workbench/api/browser/mainThreadQuickOpen.ts
Normal file
212
src/vs/workbench/api/browser/mainThreadQuickOpen.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
438
src/vs/workbench/api/browser/mainThreadSCM.ts
Normal file
438
src/vs/workbench/api/browser/mainThreadSCM.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
467
src/vs/workbench/api/browser/mainThreadSaveParticipant.ts
Normal file
467
src/vs/workbench/api/browser/mainThreadSaveParticipant.ts
Normal 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));
|
||||
});
|
||||
}
|
||||
}
|
||||
170
src/vs/workbench/api/browser/mainThreadSearch.ts
Normal file
170
src/vs/workbench/api/browser/mainThreadSearch.ts
Normal 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)
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
49
src/vs/workbench/api/browser/mainThreadStatusBar.ts
Normal file
49
src/vs/workbench/api/browser/mainThreadStatusBar.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
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];
|
||||
}
|
||||
}
|
||||
71
src/vs/workbench/api/browser/mainThreadStorage.ts
Normal file
71
src/vs/workbench/api/browser/mainThreadStorage.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
31
src/vs/workbench/api/browser/mainThreadTelemetry.ts
Normal file
31
src/vs/workbench/api/browser/mainThreadTelemetry.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
270
src/vs/workbench/api/browser/mainThreadTerminalService.ts
Normal file
270
src/vs/workbench/api/browser/mainThreadTerminalService.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
212
src/vs/workbench/api/browser/mainThreadTreeViews.ts
Normal file
212
src/vs/workbench/api/browser/mainThreadTreeViews.ts
Normal 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];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
41
src/vs/workbench/api/browser/mainThreadWindow.ts
Normal file
41
src/vs/workbench/api/browser/mainThreadWindow.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
228
src/vs/workbench/api/browser/mainThreadWorkspace.ts
Normal file
228
src/vs/workbench/api/browser/mainThreadWorkspace.ts
Normal 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);
|
||||
});
|
||||
Reference in New Issue
Block a user