mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-18 17:22:45 -05:00
Merge from vscode 31e03b8ffbb218a87e3941f2b63a249f061fe0e4 (#4986)
This commit is contained in:
@@ -1,205 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import * as typeConverters from 'vs/workbench/api/node/extHostTypeConverters';
|
||||
import { CommandsRegistry, ICommandService, ICommandHandler } from 'vs/platform/commands/common/commands';
|
||||
import { ITextEditorOptions } from 'vs/platform/editor/common/editor';
|
||||
import { EditorViewColumn } from 'vs/workbench/api/common/shared/editor';
|
||||
import { EditorGroupLayout } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IWindowsService, IOpenSettings, IURIToOpen } from 'vs/platform/windows/common/windows';
|
||||
import { IDownloadService } from 'vs/platform/download/common/download';
|
||||
import { IWorkspacesService, hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { IRecent } from 'vs/platform/history/common/history';
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// The following commands are registered on both sides separately.
|
||||
//
|
||||
// We are trying to maintain backwards compatibility for cases where
|
||||
// API commands are encoded as markdown links, for example.
|
||||
// -----------------------------------------------------------------
|
||||
|
||||
export interface ICommandsExecutor {
|
||||
executeCommand<T>(id: string, ...args: any[]): Promise<T | undefined>;
|
||||
}
|
||||
|
||||
function adjustHandler(handler: (executor: ICommandsExecutor, ...args: any[]) => any): ICommandHandler {
|
||||
return (accessor, ...args: any[]) => {
|
||||
return handler(accessor.get(ICommandService), ...args);
|
||||
};
|
||||
}
|
||||
|
||||
interface IOpenFolderAPICommandOptions {
|
||||
forceNewWindow?: boolean;
|
||||
noRecentEntry?: boolean;
|
||||
}
|
||||
|
||||
export class OpenFolderAPICommand {
|
||||
public static ID = 'vscode.openFolder';
|
||||
public static execute(executor: ICommandsExecutor, uri?: URI, forceNewWindow?: boolean): Promise<any>;
|
||||
public static execute(executor: ICommandsExecutor, uri?: URI, options?: IOpenFolderAPICommandOptions): Promise<any>;
|
||||
public static execute(executor: ICommandsExecutor, uri?: URI, arg: boolean | IOpenFolderAPICommandOptions = {}): Promise<any> {
|
||||
if (typeof arg === 'boolean') {
|
||||
arg = { forceNewWindow: arg };
|
||||
}
|
||||
if (!uri) {
|
||||
return executor.executeCommand('_files.pickFolderAndOpen', arg.forceNewWindow);
|
||||
}
|
||||
const options: IOpenSettings = { forceNewWindow: arg.forceNewWindow, noRecentEntry: arg.noRecentEntry };
|
||||
uri = URI.revive(uri);
|
||||
const uriToOpen: IURIToOpen = hasWorkspaceFileExtension(uri.path) ? { workspaceUri: uri } : { folderUri: uri };
|
||||
return executor.executeCommand('_files.windowOpen', [uriToOpen], options);
|
||||
}
|
||||
}
|
||||
CommandsRegistry.registerCommand({
|
||||
id: OpenFolderAPICommand.ID,
|
||||
handler: adjustHandler(OpenFolderAPICommand.execute),
|
||||
description: {
|
||||
description: 'Open a folder or workspace in the current window or new window depending on the newWindow argument. Note that opening in the same window will shutdown the current extension host process and start a new one on the given folder/workspace unless the newWindow parameter is set to true.',
|
||||
args: [
|
||||
{ name: 'uri', description: '(optional) Uri of the folder or workspace file to open. If not provided, a native dialog will ask the user for the folder', constraint: (value: any) => value === undefined || value instanceof URI },
|
||||
{ name: 'options', description: '(optional) Options. Object with the following properties: `forceNewWindow `: Whether to open the folder/workspace in a new window or the same. Defaults to opening in the same window. `noRecentEntry`: Wheter the opened URI will appear in the \'Open Recent\' list. Defaults to true. Note, for backward compatibility, options can also be of type boolean, representing the `forceNewWindow` setting.', constraint: (value: any) => value === undefined || typeof value === 'object' || typeof value === 'boolean' }
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
interface INewWindowAPICommandOptions {
|
||||
reuseWindow?: boolean;
|
||||
}
|
||||
|
||||
export class NewWindowAPICommand {
|
||||
public static ID = 'vscode.newWindow';
|
||||
public static execute(executor: ICommandsExecutor, options?: INewWindowAPICommandOptions): Promise<any> {
|
||||
return executor.executeCommand('_files.newWindow', options);
|
||||
}
|
||||
}
|
||||
CommandsRegistry.registerCommand({
|
||||
id: NewWindowAPICommand.ID,
|
||||
handler: adjustHandler(NewWindowAPICommand.execute),
|
||||
description: {
|
||||
description: 'Opens an new window',
|
||||
args: [
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
export class DiffAPICommand {
|
||||
public static ID = 'vscode.diff';
|
||||
public static execute(executor: ICommandsExecutor, left: URI, right: URI, label: string, options?: vscode.TextDocumentShowOptions): Promise<any> {
|
||||
return executor.executeCommand('_workbench.diff', [
|
||||
left, right,
|
||||
label,
|
||||
undefined,
|
||||
typeConverters.TextEditorOptions.from(options),
|
||||
options ? typeConverters.ViewColumn.from(options.viewColumn) : undefined
|
||||
]);
|
||||
}
|
||||
}
|
||||
CommandsRegistry.registerCommand(DiffAPICommand.ID, adjustHandler(DiffAPICommand.execute));
|
||||
|
||||
export class OpenAPICommand {
|
||||
public static ID = 'vscode.open';
|
||||
public static execute(executor: ICommandsExecutor, resource: URI, columnOrOptions?: vscode.ViewColumn | vscode.TextDocumentShowOptions, label?: string): Promise<any> {
|
||||
let options: ITextEditorOptions | undefined;
|
||||
let position: EditorViewColumn | undefined;
|
||||
|
||||
if (columnOrOptions) {
|
||||
if (typeof columnOrOptions === 'number') {
|
||||
position = typeConverters.ViewColumn.from(columnOrOptions);
|
||||
} else {
|
||||
options = typeConverters.TextEditorOptions.from(columnOrOptions);
|
||||
position = typeConverters.ViewColumn.from(columnOrOptions.viewColumn);
|
||||
}
|
||||
}
|
||||
|
||||
return executor.executeCommand('_workbench.open', [
|
||||
resource,
|
||||
options,
|
||||
position,
|
||||
label
|
||||
]);
|
||||
}
|
||||
}
|
||||
CommandsRegistry.registerCommand(OpenAPICommand.ID, adjustHandler(OpenAPICommand.execute));
|
||||
|
||||
CommandsRegistry.registerCommand('_workbench.removeFromRecentlyOpened', function (accessor: ServicesAccessor, uri: URI) {
|
||||
const windowsService = accessor.get(IWindowsService);
|
||||
return windowsService.removeFromRecentlyOpened([uri]).then(() => undefined);
|
||||
});
|
||||
|
||||
export class RemoveFromRecentlyOpenedAPICommand {
|
||||
public static ID = 'vscode.removeFromRecentlyOpened';
|
||||
public static execute(executor: ICommandsExecutor, path: string | URI): Promise<any> {
|
||||
if (typeof path === 'string') {
|
||||
path = path.match(/^[^:/?#]+:\/\//) ? URI.parse(path) : URI.file(path);
|
||||
} else {
|
||||
path = URI.revive(path); // called from extension host
|
||||
}
|
||||
return executor.executeCommand('_workbench.removeFromRecentlyOpened', path);
|
||||
}
|
||||
}
|
||||
CommandsRegistry.registerCommand(RemoveFromRecentlyOpenedAPICommand.ID, adjustHandler(RemoveFromRecentlyOpenedAPICommand.execute));
|
||||
|
||||
interface RecentEntry {
|
||||
uri: URI;
|
||||
type: 'workspace' | 'folder' | 'file';
|
||||
label?: string;
|
||||
}
|
||||
|
||||
CommandsRegistry.registerCommand('_workbench.addToRecentlyOpened', async function (accessor: ServicesAccessor, recentEntry: RecentEntry) {
|
||||
const windowsService = accessor.get(IWindowsService);
|
||||
const workspacesService = accessor.get(IWorkspacesService);
|
||||
let recent: IRecent | undefined = undefined;
|
||||
const uri = recentEntry.uri;
|
||||
const label = recentEntry.label;
|
||||
if (recentEntry.type === 'workspace') {
|
||||
const workspace = await workspacesService.getWorkspaceIdentifier(uri);
|
||||
recent = { workspace, label };
|
||||
} else if (recentEntry.type === 'folder') {
|
||||
recent = { folderUri: uri, label };
|
||||
} else {
|
||||
recent = { fileUri: uri, label };
|
||||
}
|
||||
return windowsService.addRecentlyOpened([recent]);
|
||||
});
|
||||
|
||||
export class SetEditorLayoutAPICommand {
|
||||
public static ID = 'vscode.setEditorLayout';
|
||||
public static execute(executor: ICommandsExecutor, layout: EditorGroupLayout): Promise<any> {
|
||||
return executor.executeCommand('layoutEditorGroups', layout);
|
||||
}
|
||||
}
|
||||
CommandsRegistry.registerCommand({
|
||||
id: SetEditorLayoutAPICommand.ID,
|
||||
handler: adjustHandler(SetEditorLayoutAPICommand.execute),
|
||||
description: {
|
||||
description: 'Set Editor Layout',
|
||||
args: [{
|
||||
name: 'args',
|
||||
schema: {
|
||||
'type': 'object',
|
||||
'required': ['groups'],
|
||||
'properties': {
|
||||
'orientation': {
|
||||
'type': 'number',
|
||||
'default': 0,
|
||||
'enum': [0, 1]
|
||||
},
|
||||
'groups': {
|
||||
'$ref': '#/definitions/editorGroupsSchema', // defined in keybindingService.ts ...
|
||||
'default': [{}, {}],
|
||||
}
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
||||
});
|
||||
|
||||
CommandsRegistry.registerCommand('_workbench.downloadResource', function (accessor: ServicesAccessor, resource: URI) {
|
||||
const downloadService = accessor.get(IDownloadService);
|
||||
return downloadService.download(resource).then(location => URI.file(location));
|
||||
});
|
||||
@@ -16,58 +16,60 @@ import { OverviewRulerLane } from 'vs/editor/common/model';
|
||||
import * as languageConfiguration from 'vs/editor/common/modes/languageConfiguration';
|
||||
import { score } from 'vs/editor/common/modes/languageSelector';
|
||||
import * as files from 'vs/platform/files/common/files';
|
||||
import { ExtHostContext, IInitData, IMainContext, MainContext, MainThreadKeytarShape } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { ExtHostApiCommands } from 'vs/workbench/api/node/extHostApiCommands';
|
||||
import { ExtHostClipboard } from 'vs/workbench/api/node/extHostClipboard';
|
||||
import { ExtHostCommands } from 'vs/workbench/api/node/extHostCommands';
|
||||
import { ExtHostComments } from 'vs/workbench/api/node/extHostComments';
|
||||
import { ExtHostConfiguration, ExtHostConfigProvider } from 'vs/workbench/api/node/extHostConfiguration';
|
||||
import { ExtHostContext, IInitData, IMainContext, MainContext, MainThreadKeytarShape, IEnvironment, MainThreadWindowShape, MainThreadTelemetryShape } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { ExtHostApiCommands } from 'vs/workbench/api/common/extHostApiCommands';
|
||||
import { ExtHostClipboard } from 'vs/workbench/api/common/extHostClipboard';
|
||||
import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands';
|
||||
import { ExtHostComments } from 'vs/workbench/api/common/extHostComments';
|
||||
import { ExtHostConfiguration, ExtHostConfigProvider } from 'vs/workbench/api/common/extHostConfiguration';
|
||||
// {{SQL CARBON EDIT}} - Remove ExtHostDebugService
|
||||
// import { ExtHostDebugService } from 'vs/workbench/api/node/extHostDebugService';
|
||||
// {{SQL CARBON EDIT}} - Import product
|
||||
import product from 'vs/platform/product/node/product';
|
||||
import { ExtHostDecorations } from 'vs/workbench/api/node/extHostDecorations';
|
||||
import { ExtHostDiagnostics } from 'vs/workbench/api/node/extHostDiagnostics';
|
||||
import { ExtHostDialogs } from 'vs/workbench/api/node/extHostDialogs';
|
||||
import { ExtHostDocumentContentProvider } from 'vs/workbench/api/node/extHostDocumentContentProviders';
|
||||
import { ExtHostDocumentSaveParticipant } from 'vs/workbench/api/node/extHostDocumentSaveParticipant';
|
||||
import { ExtHostDocuments } from 'vs/workbench/api/node/extHostDocuments';
|
||||
import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/node/extHostDocumentsAndEditors';
|
||||
import { ExtensionActivatedByAPI } from 'vs/workbench/api/node/extHostExtensionActivator';
|
||||
import { ExtHostDecorations } from 'vs/workbench/api/common/extHostDecorations';
|
||||
import { ExtHostDiagnostics } from 'vs/workbench/api/common/extHostDiagnostics';
|
||||
import { ExtHostDialogs } from 'vs/workbench/api/common/extHostDialogs';
|
||||
import { ExtHostDocumentContentProvider } from 'vs/workbench/api/common/extHostDocumentContentProviders';
|
||||
import { ExtHostDocumentSaveParticipant } from 'vs/workbench/api/common/extHostDocumentSaveParticipant';
|
||||
import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments';
|
||||
import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors';
|
||||
import { ExtensionActivatedByAPI } from 'vs/workbench/api/common/extHostExtensionActivator';
|
||||
import { ExtHostExtensionService } from 'vs/workbench/api/node/extHostExtensionService';
|
||||
import { ExtHostFileSystem } from 'vs/workbench/api/node/extHostFileSystem';
|
||||
import { ExtHostFileSystemEventService } from 'vs/workbench/api/node/extHostFileSystemEventService';
|
||||
import { ExtHostHeapService } from 'vs/workbench/api/node/extHostHeapService';
|
||||
import { ExtHostLanguageFeatures, ISchemeTransformer } from 'vs/workbench/api/node/extHostLanguageFeatures';
|
||||
import { ExtHostLanguages } from 'vs/workbench/api/node/extHostLanguages';
|
||||
import { ExtHostLogService } from 'vs/workbench/api/node/extHostLogService';
|
||||
import { ExtHostMessageService } from 'vs/workbench/api/node/extHostMessageService';
|
||||
import { ExtHostOutputService } from 'vs/workbench/api/node/extHostOutputService';
|
||||
import { ExtHostProgress } from 'vs/workbench/api/node/extHostProgress';
|
||||
import { ExtHostQuickOpen } from 'vs/workbench/api/node/extHostQuickOpen';
|
||||
import { ExtHostSCM } from 'vs/workbench/api/node/extHostSCM';
|
||||
import { ExtHostFileSystem } from 'vs/workbench/api/common/extHostFileSystem';
|
||||
import { ExtHostFileSystemEventService } from 'vs/workbench/api/common/extHostFileSystemEventService';
|
||||
import { ExtHostHeapService } from 'vs/workbench/api/common/extHostHeapService';
|
||||
import { ExtHostLanguageFeatures, ISchemeTransformer } from 'vs/workbench/api/common/extHostLanguageFeatures';
|
||||
import { ExtHostLanguages } from 'vs/workbench/api/common/extHostLanguages';
|
||||
import { ExtHostLogService } from 'vs/workbench/api/common/extHostLogService';
|
||||
import { ExtHostMessageService } from 'vs/workbench/api/common/extHostMessageService';
|
||||
import { ExtHostOutputService } from 'vs/workbench/api/common/extHostOutput';
|
||||
import { LogOutputChannelFactory } from 'vs/workbench/api/node/extHostOutputService';
|
||||
import { ExtHostProgress } from 'vs/workbench/api/common/extHostProgress';
|
||||
import { ExtHostQuickOpen } from 'vs/workbench/api/common/extHostQuickOpen';
|
||||
import { ExtHostSCM } from 'vs/workbench/api/common/extHostSCM';
|
||||
import { ExtHostSearch } from 'vs/workbench/api/node/extHostSearch';
|
||||
import { ExtHostStatusBar } from 'vs/workbench/api/node/extHostStatusBar';
|
||||
import { ExtHostStorage } from 'vs/workbench/api/node/extHostStorage';
|
||||
import { ExtHostStatusBar } from 'vs/workbench/api/common/extHostStatusBar';
|
||||
import { ExtHostStorage } from 'vs/workbench/api/common/extHostStorage';
|
||||
import { ExtHostTask } from 'vs/workbench/api/node/extHostTask';
|
||||
import { ExtHostTerminalService } from 'vs/workbench/api/node/extHostTerminalService';
|
||||
import { ExtHostEditors } from 'vs/workbench/api/node/extHostTextEditors';
|
||||
import { ExtHostTreeViews } from 'vs/workbench/api/node/extHostTreeViews';
|
||||
import * as typeConverters from 'vs/workbench/api/node/extHostTypeConverters';
|
||||
import * as extHostTypes from 'vs/workbench/api/node/extHostTypes';
|
||||
import { ExtHostUrls } from 'vs/workbench/api/node/extHostUrls';
|
||||
import { ExtHostWebviews } from 'vs/workbench/api/node/extHostWebview';
|
||||
import { ExtHostWindow } from 'vs/workbench/api/node/extHostWindow';
|
||||
import { ExtHostWorkspace } from 'vs/workbench/api/node/extHostWorkspace';
|
||||
import { ExtHostEditors } from 'vs/workbench/api/common/extHostTextEditors';
|
||||
import { ExtHostTreeViews } from 'vs/workbench/api/common/extHostTreeViews';
|
||||
import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters';
|
||||
import * as extHostTypes from 'vs/workbench/api/common/extHostTypes';
|
||||
import { ExtHostUrls } from 'vs/workbench/api/common/extHostUrls';
|
||||
import { ExtHostWebviews } from 'vs/workbench/api/common/extHostWebview';
|
||||
import { ExtHostWindow } from 'vs/workbench/api/common/extHostWindow';
|
||||
import { ExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace';
|
||||
import { throwProposedApiError, checkProposedApiEnabled, nullExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { ProxyIdentifier } from 'vs/workbench/services/extensions/common/proxyIdentifier';
|
||||
import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/node/extensionDescriptionRegistry';
|
||||
import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry';
|
||||
import * as vscode from 'vscode';
|
||||
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { originalFSPath } from 'vs/base/common/resources';
|
||||
import { CLIServer } from 'vs/workbench/api/node/extHostCLIServer';
|
||||
import { withNullAsUndefined } from 'vs/base/common/types';
|
||||
import { values } from 'vs/base/common/collections';
|
||||
import { endsWith } from 'vs/base/common/strings';
|
||||
|
||||
export interface IExtensionApiFactory {
|
||||
(extension: IExtensionDescription, registry: ExtensionDescriptionRegistry, configProvider: ExtHostConfigProvider): typeof vscode;
|
||||
@@ -126,7 +128,7 @@ export function createApiFactory(
|
||||
const extHostWindow = rpcProtocol.set(ExtHostContext.ExtHostWindow, new ExtHostWindow(rpcProtocol));
|
||||
rpcProtocol.set(ExtHostContext.ExtHostExtensionService, extensionService);
|
||||
const extHostProgress = rpcProtocol.set(ExtHostContext.ExtHostProgress, new ExtHostProgress(rpcProtocol.getProxy(MainContext.MainThreadProgress)));
|
||||
const extHostOutputService = rpcProtocol.set(ExtHostContext.ExtHostOutputService, new ExtHostOutputService(initData.logsLocation, rpcProtocol));
|
||||
const extHostOutputService = rpcProtocol.set(ExtHostContext.ExtHostOutputService, new ExtHostOutputService(LogOutputChannelFactory, initData.logsLocation, rpcProtocol));
|
||||
rpcProtocol.set(ExtHostContext.ExtHostStorage, extHostStorage);
|
||||
if (initData.remoteAuthority) {
|
||||
const cliServer = new CLIServer(extHostCommands);
|
||||
@@ -880,34 +882,59 @@ class Extension<T> implements vscode.Extension<T> {
|
||||
}
|
||||
}
|
||||
|
||||
interface LoadFunction {
|
||||
(request: string, parent: { filename: string; }, isMain: any): any;
|
||||
}
|
||||
|
||||
interface INodeModuleFactory {
|
||||
readonly nodeModuleName: string;
|
||||
load(request: string, parent: { filename: string; }): any;
|
||||
readonly nodeModuleName: string | string[];
|
||||
load(request: string, parent: { filename: string; }, isMain: any, original: LoadFunction): any;
|
||||
alternaiveModuleName?(name: string): string | undefined;
|
||||
}
|
||||
|
||||
export class NodeModuleRequireInterceptor {
|
||||
public static INSTANCE = new NodeModuleRequireInterceptor();
|
||||
|
||||
private readonly _factories: Map<string, INodeModuleFactory>;
|
||||
private readonly _alternatives: ((moduleName: string) => string | undefined)[];
|
||||
|
||||
constructor() {
|
||||
this._factories = new Map<string, INodeModuleFactory>();
|
||||
this._installInterceptor(this._factories);
|
||||
this._alternatives = [];
|
||||
this._installInterceptor(this._factories, this._alternatives);
|
||||
}
|
||||
|
||||
private _installInterceptor(factories: Map<string, INodeModuleFactory>): void {
|
||||
private _installInterceptor(factories: Map<string, INodeModuleFactory>, alternatives: ((moduleName: string) => string | undefined)[]): void {
|
||||
const node_module = <any>require.__$__nodeRequire('module');
|
||||
const original = node_module._load;
|
||||
node_module._load = function load(request: string, parent: { filename: string; }, isMain: any) {
|
||||
for (let alternativeModuleName of alternatives) {
|
||||
let alternative = alternativeModuleName(request);
|
||||
if (alternative) {
|
||||
request = alternative;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!factories.has(request)) {
|
||||
return original.apply(this, arguments);
|
||||
}
|
||||
return factories.get(request)!.load(request, parent);
|
||||
return factories.get(request)!.load(request, parent, isMain, original);
|
||||
};
|
||||
}
|
||||
|
||||
public register(interceptor: INodeModuleFactory): void {
|
||||
this._factories.set(interceptor.nodeModuleName, interceptor);
|
||||
if (Array.isArray(interceptor.nodeModuleName)) {
|
||||
for (let moduleName of interceptor.nodeModuleName) {
|
||||
this._factories.set(moduleName, interceptor);
|
||||
}
|
||||
} else {
|
||||
this._factories.set(interceptor.nodeModuleName, interceptor);
|
||||
}
|
||||
if (typeof interceptor.alternaiveModuleName === 'function') {
|
||||
this._alternatives.push((moduleName) => {
|
||||
return interceptor.alternaiveModuleName!(moduleName);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -957,11 +984,24 @@ interface IKeytarModule {
|
||||
}
|
||||
|
||||
export class KeytarNodeModuleFactory implements INodeModuleFactory {
|
||||
public readonly nodeModuleName = 'keytar';
|
||||
public readonly nodeModuleName: string = 'keytar';
|
||||
|
||||
private alternativeNames: Set<string> | undefined;
|
||||
private _impl: IKeytarModule;
|
||||
|
||||
constructor(mainThreadKeytar: MainThreadKeytarShape) {
|
||||
constructor(mainThreadKeytar: MainThreadKeytarShape, environment: IEnvironment) {
|
||||
if (environment.appRoot) {
|
||||
let appRoot = environment.appRoot.fsPath;
|
||||
if (process.platform === 'win32') {
|
||||
appRoot = appRoot.replace(/\\/g, '/');
|
||||
}
|
||||
if (appRoot[appRoot.length - 1] === '/') {
|
||||
appRoot = appRoot.substr(0, appRoot.length - 1);
|
||||
}
|
||||
this.alternativeNames = new Set();
|
||||
this.alternativeNames.add(`${appRoot}/node_modules.asar/keytar`);
|
||||
this.alternativeNames.add(`${appRoot}/node_modules/keytar`);
|
||||
}
|
||||
this._impl = {
|
||||
getPassword: (service: string, account: string): Promise<string | null> => {
|
||||
return mainThreadKeytar.$getPassword(service, account);
|
||||
@@ -981,4 +1021,100 @@ export class KeytarNodeModuleFactory implements INodeModuleFactory {
|
||||
public load(request: string, parent: { filename: string; }): any {
|
||||
return this._impl;
|
||||
}
|
||||
|
||||
public alternaiveModuleName(name: string): string | undefined {
|
||||
const length = name.length;
|
||||
// We need at least something like: `?/keytar` which requires
|
||||
// more than 7 characters.
|
||||
if (length <= 7 || !this.alternativeNames) {
|
||||
return undefined;
|
||||
}
|
||||
const sep = length - 7;
|
||||
if ((name.charAt(sep) === '/' || name.charAt(sep) === '\\') && endsWith(name, 'keytar')) {
|
||||
name = name.replace(/\\/g, '/');
|
||||
if (this.alternativeNames.has(name)) {
|
||||
return 'keytar';
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
interface OpenOptions {
|
||||
wait: boolean;
|
||||
app: string | string[];
|
||||
}
|
||||
|
||||
interface IOriginalOpen {
|
||||
(target: string, options?: OpenOptions): Thenable<any>;
|
||||
}
|
||||
|
||||
interface IOpenModule {
|
||||
(target: string, options?: OpenOptions): Thenable<void>;
|
||||
}
|
||||
|
||||
export class OpenNodeModuleFactory implements INodeModuleFactory {
|
||||
|
||||
public readonly nodeModuleName: string[] = ['open', 'opn'];
|
||||
|
||||
private _extensionId: string | undefined;
|
||||
private _original: IOriginalOpen;
|
||||
private _impl: IOpenModule;
|
||||
|
||||
constructor(mainThreadWindow: MainThreadWindowShape, private _mainThreadTelemerty: MainThreadTelemetryShape, private readonly _extensionPaths: TernarySearchTree<IExtensionDescription>) {
|
||||
this._impl = (target, options) => {
|
||||
const uri: URI = URI.parse(target);
|
||||
// If we have options use the original method.
|
||||
if (options) {
|
||||
return this.callOriginal(target, options);
|
||||
}
|
||||
if (uri.scheme === 'http' || uri.scheme === 'https') {
|
||||
return mainThreadWindow.$openUri(uri);
|
||||
} else if (uri.scheme === 'mailto') {
|
||||
return mainThreadWindow.$openUri(uri);
|
||||
}
|
||||
return this.callOriginal(target, options);
|
||||
};
|
||||
}
|
||||
|
||||
public load(request: string, parent: { filename: string; }, isMain: any, original: LoadFunction): any {
|
||||
// get extension id from filename and api for extension
|
||||
const extension = this._extensionPaths.findSubstr(URI.file(parent.filename).fsPath);
|
||||
if (extension) {
|
||||
this._extensionId = extension.identifier.value;
|
||||
this.sendShimmingTelemetry();
|
||||
}
|
||||
|
||||
this._original = original(request, parent, isMain);
|
||||
return this._impl;
|
||||
}
|
||||
|
||||
private callOriginal(target: string, options: OpenOptions | undefined): Thenable<any> {
|
||||
this.sendNoForwardTelemetry();
|
||||
return this._original(target, options);
|
||||
}
|
||||
|
||||
private sendShimmingTelemetry(): void {
|
||||
if (!this._extensionId) {
|
||||
return;
|
||||
}
|
||||
/* __GDPR__
|
||||
"shimming.open" : {
|
||||
"extension": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
}
|
||||
*/
|
||||
this._mainThreadTelemerty.$publicLog('shimming.open', { extension: this._extensionId });
|
||||
}
|
||||
|
||||
private sendNoForwardTelemetry(): void {
|
||||
if (!this._extensionId) {
|
||||
return;
|
||||
}
|
||||
/* __GDPR__
|
||||
"shimming.open.call.noForward" : {
|
||||
"extension": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
}
|
||||
*/
|
||||
this._mainThreadTelemerty.$publicLog('shimming.open.call.noForward', { extension: this._extensionId });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,552 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import * as vscode from 'vscode';
|
||||
import * as typeConverters from 'vs/workbench/api/node/extHostTypeConverters';
|
||||
import * as types from 'vs/workbench/api/node/extHostTypes';
|
||||
import { IRawColorInfo, WorkspaceEditDto } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { ISingleEditOperation } from 'vs/editor/common/model';
|
||||
import * as modes from 'vs/editor/common/modes';
|
||||
import * as search from 'vs/workbench/contrib/search/common/search';
|
||||
import { ICommandHandlerDescription } from 'vs/platform/commands/common/commands';
|
||||
import { ExtHostCommands } from 'vs/workbench/api/node/extHostCommands';
|
||||
import { CustomCodeAction } from 'vs/workbench/api/node/extHostLanguageFeatures';
|
||||
import { ICommandsExecutor, OpenFolderAPICommand, DiffAPICommand, OpenAPICommand, RemoveFromRecentlyOpenedAPICommand, SetEditorLayoutAPICommand } from './apiCommands';
|
||||
import { EditorGroupLayout } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { isFalsyOrEmpty } from 'vs/base/common/arrays';
|
||||
|
||||
export class ExtHostApiCommands {
|
||||
|
||||
static register(commands: ExtHostCommands) {
|
||||
return new ExtHostApiCommands(commands).registerCommands();
|
||||
}
|
||||
|
||||
private _commands: ExtHostCommands;
|
||||
private _disposables: IDisposable[] = [];
|
||||
|
||||
private constructor(commands: ExtHostCommands) {
|
||||
this._commands = commands;
|
||||
}
|
||||
|
||||
registerCommands() {
|
||||
this._register('vscode.executeWorkspaceSymbolProvider', this._executeWorkspaceSymbolProvider, {
|
||||
description: 'Execute all workspace symbol provider.',
|
||||
args: [{ name: 'query', description: 'Search string', constraint: String }],
|
||||
returns: 'A promise that resolves to an array of SymbolInformation-instances.'
|
||||
|
||||
});
|
||||
this._register('vscode.executeDefinitionProvider', this._executeDefinitionProvider, {
|
||||
description: 'Execute all definition provider.',
|
||||
args: [
|
||||
{ name: 'uri', description: 'Uri of a text document', constraint: URI },
|
||||
{ name: 'position', description: 'Position of a symbol', constraint: types.Position }
|
||||
],
|
||||
returns: 'A promise that resolves to an array of Location-instances.'
|
||||
});
|
||||
this._register('vscode.executeDeclarationProvider', this._executeDeclaraionProvider, {
|
||||
description: 'Execute all declaration provider.',
|
||||
args: [
|
||||
{ name: 'uri', description: 'Uri of a text document', constraint: URI },
|
||||
{ name: 'position', description: 'Position of a symbol', constraint: types.Position }
|
||||
],
|
||||
returns: 'A promise that resolves to an array of Location-instances.'
|
||||
});
|
||||
this._register('vscode.executeTypeDefinitionProvider', this._executeTypeDefinitionProvider, {
|
||||
description: 'Execute all type definition providers.',
|
||||
args: [
|
||||
{ name: 'uri', description: 'Uri of a text document', constraint: URI },
|
||||
{ name: 'position', description: 'Position of a symbol', constraint: types.Position }
|
||||
],
|
||||
returns: 'A promise that resolves to an array of Location-instances.'
|
||||
});
|
||||
this._register('vscode.executeImplementationProvider', this._executeImplementationProvider, {
|
||||
description: 'Execute all implementation providers.',
|
||||
args: [
|
||||
{ name: 'uri', description: 'Uri of a text document', constraint: URI },
|
||||
{ name: 'position', description: 'Position of a symbol', constraint: types.Position }
|
||||
],
|
||||
returns: 'A promise that resolves to an array of Location-instance.'
|
||||
});
|
||||
this._register('vscode.executeHoverProvider', this._executeHoverProvider, {
|
||||
description: 'Execute all hover provider.',
|
||||
args: [
|
||||
{ name: 'uri', description: 'Uri of a text document', constraint: URI },
|
||||
{ name: 'position', description: 'Position of a symbol', constraint: types.Position }
|
||||
],
|
||||
returns: 'A promise that resolves to an array of Hover-instances.'
|
||||
});
|
||||
this._register('vscode.executeDocumentHighlights', this._executeDocumentHighlights, {
|
||||
description: 'Execute document highlight provider.',
|
||||
args: [
|
||||
{ name: 'uri', description: 'Uri of a text document', constraint: URI },
|
||||
{ name: 'position', description: 'Position in a text document', constraint: types.Position }
|
||||
],
|
||||
returns: 'A promise that resolves to an array of DocumentHighlight-instances.'
|
||||
});
|
||||
this._register('vscode.executeReferenceProvider', this._executeReferenceProvider, {
|
||||
description: 'Execute reference provider.',
|
||||
args: [
|
||||
{ name: 'uri', description: 'Uri of a text document', constraint: URI },
|
||||
{ name: 'position', description: 'Position in a text document', constraint: types.Position }
|
||||
],
|
||||
returns: 'A promise that resolves to an array of Location-instances.'
|
||||
});
|
||||
this._register('vscode.executeDocumentRenameProvider', this._executeDocumentRenameProvider, {
|
||||
description: 'Execute rename provider.',
|
||||
args: [
|
||||
{ name: 'uri', description: 'Uri of a text document', constraint: URI },
|
||||
{ name: 'position', description: 'Position in a text document', constraint: types.Position },
|
||||
{ name: 'newName', description: 'The new symbol name', constraint: String }
|
||||
],
|
||||
returns: 'A promise that resolves to a WorkspaceEdit.'
|
||||
});
|
||||
this._register('vscode.executeSignatureHelpProvider', this._executeSignatureHelpProvider, {
|
||||
description: 'Execute signature help provider.',
|
||||
args: [
|
||||
{ name: 'uri', description: 'Uri of a text document', constraint: URI },
|
||||
{ name: 'position', description: 'Position in a text document', constraint: types.Position },
|
||||
{ name: 'triggerCharacter', description: '(optional) Trigger signature help when the user types the character, like `,` or `(`', constraint: (value: any) => value === undefined || typeof value === 'string' }
|
||||
],
|
||||
returns: 'A promise that resolves to SignatureHelp.'
|
||||
});
|
||||
this._register('vscode.executeDocumentSymbolProvider', this._executeDocumentSymbolProvider, {
|
||||
description: 'Execute document symbol provider.',
|
||||
args: [
|
||||
{ name: 'uri', description: 'Uri of a text document', constraint: URI }
|
||||
],
|
||||
returns: 'A promise that resolves to an array of SymbolInformation and DocumentSymbol instances.'
|
||||
});
|
||||
this._register('vscode.executeCompletionItemProvider', this._executeCompletionItemProvider, {
|
||||
description: 'Execute completion item provider.',
|
||||
args: [
|
||||
{ name: 'uri', description: 'Uri of a text document', constraint: URI },
|
||||
{ name: 'position', description: 'Position in a text document', constraint: types.Position },
|
||||
{ name: 'triggerCharacter', description: '(optional) Trigger completion when the user types the character, like `,` or `(`', constraint: (value: any) => value === undefined || typeof value === 'string' },
|
||||
{ name: 'itemResolveCount', description: '(optional) Number of completions to resolve (too large numbers slow down completions)', constraint: (value: any) => value === undefined || typeof value === 'number' }
|
||||
],
|
||||
returns: 'A promise that resolves to a CompletionList-instance.'
|
||||
});
|
||||
this._register('vscode.executeCodeActionProvider', this._executeCodeActionProvider, {
|
||||
description: 'Execute code action provider.',
|
||||
args: [
|
||||
{ name: 'uri', description: 'Uri of a text document', constraint: URI },
|
||||
{ name: 'range', description: 'Range in a text document', constraint: types.Range },
|
||||
{ name: 'kind', description: '(optional) Code action kind to return code actions for', constraint: (value: any) => !value || typeof value.value === 'string' },
|
||||
],
|
||||
returns: 'A promise that resolves to an array of Command-instances.'
|
||||
});
|
||||
this._register('vscode.executeCodeLensProvider', this._executeCodeLensProvider, {
|
||||
description: 'Execute CodeLens provider.',
|
||||
args: [
|
||||
{ name: 'uri', description: 'Uri of a text document', constraint: URI },
|
||||
{ name: 'itemResolveCount', description: '(optional) Number of lenses that should be resolved and returned. Will only retrun resolved lenses, will impact performance)', constraint: (value: any) => value === undefined || typeof value === 'number' }
|
||||
],
|
||||
returns: 'A promise that resolves to an array of CodeLens-instances.'
|
||||
});
|
||||
this._register('vscode.executeFormatDocumentProvider', this._executeFormatDocumentProvider, {
|
||||
description: 'Execute document format provider.',
|
||||
args: [
|
||||
{ name: 'uri', description: 'Uri of a text document', constraint: URI },
|
||||
{ name: 'options', description: 'Formatting options' }
|
||||
],
|
||||
returns: 'A promise that resolves to an array of TextEdits.'
|
||||
});
|
||||
this._register('vscode.executeFormatRangeProvider', this._executeFormatRangeProvider, {
|
||||
description: 'Execute range format provider.',
|
||||
args: [
|
||||
{ name: 'uri', description: 'Uri of a text document', constraint: URI },
|
||||
{ name: 'range', description: 'Range in a text document', constraint: types.Range },
|
||||
{ name: 'options', description: 'Formatting options' }
|
||||
],
|
||||
returns: 'A promise that resolves to an array of TextEdits.'
|
||||
});
|
||||
this._register('vscode.executeFormatOnTypeProvider', this._executeFormatOnTypeProvider, {
|
||||
description: 'Execute document format provider.',
|
||||
args: [
|
||||
{ name: 'uri', description: 'Uri of a text document', constraint: URI },
|
||||
{ name: 'position', description: 'Position in a text document', constraint: types.Position },
|
||||
{ name: 'ch', description: 'Character that got typed', constraint: String },
|
||||
{ name: 'options', description: 'Formatting options' }
|
||||
],
|
||||
returns: 'A promise that resolves to an array of TextEdits.'
|
||||
});
|
||||
this._register('vscode.executeLinkProvider', this._executeDocumentLinkProvider, {
|
||||
description: 'Execute document link provider.',
|
||||
args: [
|
||||
{ name: 'uri', description: 'Uri of a text document', constraint: URI }
|
||||
],
|
||||
returns: 'A promise that resolves to an array of DocumentLink-instances.'
|
||||
});
|
||||
this._register('vscode.executeDocumentColorProvider', this._executeDocumentColorProvider, {
|
||||
description: 'Execute document color provider.',
|
||||
args: [
|
||||
{ name: 'uri', description: 'Uri of a text document', constraint: URI },
|
||||
],
|
||||
returns: 'A promise that resolves to an array of ColorInformation objects.'
|
||||
});
|
||||
this._register('vscode.executeColorPresentationProvider', this._executeColorPresentationProvider, {
|
||||
description: 'Execute color presentation provider.',
|
||||
args: [
|
||||
{ name: 'color', description: 'The color to show and insert', constraint: types.Color },
|
||||
{ name: 'context', description: 'Context object with uri and range' }
|
||||
],
|
||||
returns: 'A promise that resolves to an array of ColorPresentation objects.'
|
||||
});
|
||||
this._register('vscode.executeSelectionRangeProvider', this._executeSelectionRangeProvider, {
|
||||
description: 'Execute selection range provider.',
|
||||
args: [
|
||||
{ name: 'uri', description: 'Uri of a text document', constraint: URI },
|
||||
{ name: 'positions', description: 'Positions in a text document', constraint: Array.isArray }
|
||||
],
|
||||
returns: 'A promise that resolves to an array of ranges.'
|
||||
});
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// The following commands are registered on both sides separately.
|
||||
//
|
||||
// We are trying to maintain backwards compatibility for cases where
|
||||
// API commands are encoded as markdown links, for example.
|
||||
// -----------------------------------------------------------------
|
||||
|
||||
type ICommandHandler = (...args: any[]) => any;
|
||||
const adjustHandler = (handler: (executor: ICommandsExecutor, ...args: any[]) => any): ICommandHandler => {
|
||||
return (...args: any[]) => {
|
||||
return handler(this._commands, ...args);
|
||||
};
|
||||
};
|
||||
|
||||
this._register(OpenFolderAPICommand.ID, adjustHandler(OpenFolderAPICommand.execute), {
|
||||
description: 'Open a folder or workspace in the current window or new window depending on the newWindow argument. Note that opening in the same window will shutdown the current extension host process and start a new one on the given folder/workspace unless the newWindow parameter is set to true.',
|
||||
args: [
|
||||
{ name: 'uri', description: '(optional) Uri of the folder or workspace file to open. If not provided, a native dialog will ask the user for the folder', constraint: (value: any) => value === undefined || value instanceof URI },
|
||||
{ name: 'options', description: '(optional) Options. Object with the following properties: `forceNewWindow `: Whether to open the folder/workspace in a new window or the same. Defaults to opening in the same window. `noRecentEntry`: Wheter the opened URI will appear in the \'Open Recent\' list. Defaults to true. Note, for backward compatibility, options can also be of type boolean, representing the `forceNewWindow` setting.', constraint: (value: any) => value === undefined || typeof value === 'object' || typeof value === 'boolean' }
|
||||
]
|
||||
});
|
||||
|
||||
this._register(DiffAPICommand.ID, adjustHandler(DiffAPICommand.execute), {
|
||||
description: 'Opens the provided resources in the diff editor to compare their contents.',
|
||||
args: [
|
||||
{ name: 'left', description: 'Left-hand side resource of the diff editor', constraint: URI },
|
||||
{ name: 'right', description: 'Right-hand side resource of the diff editor', constraint: URI },
|
||||
{ name: 'title', description: '(optional) Human readable title for the diff editor', constraint: (v: any) => v === undefined || typeof v === 'string' },
|
||||
{ name: 'options', description: '(optional) Editor options, see vscode.TextDocumentShowOptions' }
|
||||
]
|
||||
});
|
||||
|
||||
this._register(OpenAPICommand.ID, adjustHandler(OpenAPICommand.execute), {
|
||||
description: 'Opens the provided resource in the editor. Can be a text or binary file, or a http(s) url. If you need more control over the options for opening a text file, use vscode.window.showTextDocument instead.',
|
||||
args: [
|
||||
{ name: 'resource', description: 'Resource to open', constraint: URI },
|
||||
{ name: 'columnOrOptions', description: '(optional) Either the column in which to open or editor options, see vscode.TextDocumentShowOptions', constraint: (v: any) => v === undefined || typeof v === 'number' || typeof v === 'object' }
|
||||
]
|
||||
});
|
||||
|
||||
this._register(RemoveFromRecentlyOpenedAPICommand.ID, adjustHandler(RemoveFromRecentlyOpenedAPICommand.execute), {
|
||||
description: 'Removes an entry with the given path from the recently opened list.',
|
||||
args: [
|
||||
{ name: 'path', description: 'Path to remove from recently opened.', constraint: (value: any) => typeof value === 'string' }
|
||||
]
|
||||
});
|
||||
|
||||
this._register(SetEditorLayoutAPICommand.ID, adjustHandler(SetEditorLayoutAPICommand.execute), {
|
||||
description: 'Sets the editor layout. The layout is described as object with an initial (optional) orientation (0 = horizontal, 1 = vertical) and an array of editor groups within. Each editor group can have a size and another array of editor groups that will be laid out orthogonal to the orientation. If editor group sizes are provided, their sum must be 1 to be applied per row or column. Example for a 2x2 grid: `{ orientation: 0, groups: [{ groups: [{}, {}], size: 0.5 }, { groups: [{}, {}], size: 0.5 }] }`',
|
||||
args: [
|
||||
{ name: 'layout', description: 'The editor layout to set.', constraint: (value: EditorGroupLayout) => typeof value === 'object' && Array.isArray(value.groups) }
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
// --- command impl
|
||||
|
||||
private _register(id: string, handler: (...args: any[]) => any, description?: ICommandHandlerDescription): void {
|
||||
const disposable = this._commands.registerCommand(false, id, handler, this, description);
|
||||
this._disposables.push(disposable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute workspace symbol provider.
|
||||
*
|
||||
* @param query Search string to match query symbol names
|
||||
* @return A promise that resolves to an array of symbol information.
|
||||
*/
|
||||
private _executeWorkspaceSymbolProvider(query: string): Promise<types.SymbolInformation[]> {
|
||||
return this._commands.executeCommand<[search.IWorkspaceSymbolProvider, search.IWorkspaceSymbol[]][]>('_executeWorkspaceSymbolProvider', { query }).then(value => {
|
||||
const result: types.SymbolInformation[] = [];
|
||||
if (Array.isArray(value)) {
|
||||
for (let tuple of value) {
|
||||
result.push(...tuple[1].map(typeConverters.WorkspaceSymbol.to));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
private _executeDefinitionProvider(resource: URI, position: types.Position): Promise<types.Location[] | undefined> {
|
||||
const args = {
|
||||
resource,
|
||||
position: position && typeConverters.Position.from(position)
|
||||
};
|
||||
return this._commands.executeCommand<modes.Location[]>('_executeDefinitionProvider', args)
|
||||
.then(tryMapWith(typeConverters.location.to));
|
||||
}
|
||||
|
||||
private _executeDeclaraionProvider(resource: URI, position: types.Position): Promise<types.Location[] | undefined> {
|
||||
const args = {
|
||||
resource,
|
||||
position: position && typeConverters.Position.from(position)
|
||||
};
|
||||
return this._commands.executeCommand<modes.Location[]>('_executeDeclarationProvider', args)
|
||||
.then(tryMapWith(typeConverters.location.to));
|
||||
}
|
||||
|
||||
private _executeTypeDefinitionProvider(resource: URI, position: types.Position): Promise<types.Location[] | undefined> {
|
||||
const args = {
|
||||
resource,
|
||||
position: position && typeConverters.Position.from(position)
|
||||
};
|
||||
return this._commands.executeCommand<modes.Location[]>('_executeTypeDefinitionProvider', args)
|
||||
.then(tryMapWith(typeConverters.location.to));
|
||||
}
|
||||
|
||||
private _executeImplementationProvider(resource: URI, position: types.Position): Promise<types.Location[] | undefined> {
|
||||
const args = {
|
||||
resource,
|
||||
position: position && typeConverters.Position.from(position)
|
||||
};
|
||||
return this._commands.executeCommand<modes.Location[]>('_executeImplementationProvider', args)
|
||||
.then(tryMapWith(typeConverters.location.to));
|
||||
}
|
||||
|
||||
private _executeHoverProvider(resource: URI, position: types.Position): Promise<types.Hover[] | undefined> {
|
||||
const args = {
|
||||
resource,
|
||||
position: position && typeConverters.Position.from(position)
|
||||
};
|
||||
return this._commands.executeCommand<modes.Hover[]>('_executeHoverProvider', args)
|
||||
.then(tryMapWith(typeConverters.Hover.to));
|
||||
}
|
||||
|
||||
private _executeDocumentHighlights(resource: URI, position: types.Position): Promise<types.DocumentHighlight[] | undefined> {
|
||||
const args = {
|
||||
resource,
|
||||
position: position && typeConverters.Position.from(position)
|
||||
};
|
||||
return this._commands.executeCommand<modes.DocumentHighlight[]>('_executeDocumentHighlights', args)
|
||||
.then(tryMapWith(typeConverters.DocumentHighlight.to));
|
||||
}
|
||||
|
||||
private _executeReferenceProvider(resource: URI, position: types.Position): Promise<types.Location[] | undefined> {
|
||||
const args = {
|
||||
resource,
|
||||
position: position && typeConverters.Position.from(position)
|
||||
};
|
||||
return this._commands.executeCommand<modes.Location[]>('_executeReferenceProvider', args)
|
||||
.then(tryMapWith(typeConverters.location.to));
|
||||
}
|
||||
|
||||
private _executeDocumentRenameProvider(resource: URI, position: types.Position, newName: string): Promise<types.WorkspaceEdit> {
|
||||
const args = {
|
||||
resource,
|
||||
position: position && typeConverters.Position.from(position),
|
||||
newName
|
||||
};
|
||||
return this._commands.executeCommand<WorkspaceEditDto>('_executeDocumentRenameProvider', args).then(value => {
|
||||
if (!value) {
|
||||
return undefined;
|
||||
}
|
||||
if (value.rejectReason) {
|
||||
return Promise.reject<any>(new Error(value.rejectReason));
|
||||
}
|
||||
return typeConverters.WorkspaceEdit.to(value);
|
||||
});
|
||||
}
|
||||
|
||||
private _executeSignatureHelpProvider(resource: URI, position: types.Position, triggerCharacter: string): Promise<types.SignatureHelp | undefined> {
|
||||
const args = {
|
||||
resource,
|
||||
position: position && typeConverters.Position.from(position),
|
||||
triggerCharacter
|
||||
};
|
||||
return this._commands.executeCommand<modes.SignatureHelp>('_executeSignatureHelpProvider', args).then(value => {
|
||||
if (value) {
|
||||
return typeConverters.SignatureHelp.to(value);
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
|
||||
private _executeCompletionItemProvider(resource: URI, position: types.Position, triggerCharacter: string, maxItemsToResolve: number): Promise<types.CompletionList | undefined> {
|
||||
const args = {
|
||||
resource,
|
||||
position: position && typeConverters.Position.from(position),
|
||||
triggerCharacter,
|
||||
maxItemsToResolve
|
||||
};
|
||||
return this._commands.executeCommand<modes.CompletionList>('_executeCompletionItemProvider', args).then(result => {
|
||||
if (result) {
|
||||
const items = result.suggestions.map(suggestion => typeConverters.CompletionItem.to(suggestion));
|
||||
return new types.CompletionList(items, result.incomplete);
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
|
||||
private _executeDocumentColorProvider(resource: URI): Promise<types.ColorInformation[]> {
|
||||
const args = {
|
||||
resource
|
||||
};
|
||||
return this._commands.executeCommand<IRawColorInfo[]>('_executeDocumentColorProvider', args).then(result => {
|
||||
if (result) {
|
||||
return result.map(ci => ({ range: typeConverters.Range.to(ci.range), color: typeConverters.Color.to(ci.color) }));
|
||||
}
|
||||
return [];
|
||||
});
|
||||
}
|
||||
|
||||
private _executeSelectionRangeProvider(resource: URI, positions: types.Position[]): Promise<vscode.SelectionRange[][]> {
|
||||
const pos = positions.map(typeConverters.Position.from);
|
||||
const args = {
|
||||
resource,
|
||||
position: pos[0],
|
||||
positions: pos
|
||||
};
|
||||
return this._commands.executeCommand<modes.SelectionRange[][]>('_executeSelectionRangeProvider', args).then(result => {
|
||||
return result.map(oneResult => oneResult.map(typeConverters.SelectionRange.to));
|
||||
});
|
||||
}
|
||||
|
||||
private _executeColorPresentationProvider(color: types.Color, context: { uri: URI, range: types.Range }): Promise<types.ColorPresentation[]> {
|
||||
const args = {
|
||||
resource: context.uri,
|
||||
color: typeConverters.Color.from(color),
|
||||
range: typeConverters.Range.from(context.range),
|
||||
};
|
||||
return this._commands.executeCommand<modes.IColorPresentation[]>('_executeColorPresentationProvider', args).then(result => {
|
||||
if (result) {
|
||||
return result.map(typeConverters.ColorPresentation.to);
|
||||
}
|
||||
return [];
|
||||
});
|
||||
}
|
||||
|
||||
private _executeDocumentSymbolProvider(resource: URI): Promise<vscode.SymbolInformation[] | undefined> {
|
||||
const args = {
|
||||
resource
|
||||
};
|
||||
return this._commands.executeCommand<modes.DocumentSymbol[]>('_executeDocumentSymbolProvider', args).then((value): vscode.SymbolInformation[] | undefined => {
|
||||
if (isFalsyOrEmpty(value)) {
|
||||
return undefined;
|
||||
}
|
||||
class MergedInfo extends types.SymbolInformation implements vscode.DocumentSymbol {
|
||||
static to(symbol: modes.DocumentSymbol): MergedInfo {
|
||||
const res = new MergedInfo(
|
||||
symbol.name,
|
||||
typeConverters.SymbolKind.to(symbol.kind),
|
||||
symbol.containerName || '',
|
||||
new types.Location(resource, typeConverters.Range.to(symbol.range))
|
||||
);
|
||||
res.detail = symbol.detail;
|
||||
res.range = res.location.range;
|
||||
res.selectionRange = typeConverters.Range.to(symbol.selectionRange);
|
||||
res.children = symbol.children ? symbol.children.map(MergedInfo.to) : [];
|
||||
return res;
|
||||
}
|
||||
|
||||
detail: string;
|
||||
range: vscode.Range;
|
||||
selectionRange: vscode.Range;
|
||||
children: vscode.DocumentSymbol[];
|
||||
containerName: string;
|
||||
}
|
||||
return value.map(MergedInfo.to);
|
||||
});
|
||||
}
|
||||
|
||||
private _executeCodeActionProvider(resource: URI, range: types.Range, kind?: string): Promise<(vscode.CodeAction | vscode.Command)[] | undefined> {
|
||||
const args = {
|
||||
resource,
|
||||
range: typeConverters.Range.from(range),
|
||||
kind
|
||||
};
|
||||
return this._commands.executeCommand<CustomCodeAction[]>('_executeCodeActionProvider', args)
|
||||
.then(tryMapWith(codeAction => {
|
||||
if (codeAction._isSynthetic) {
|
||||
if (!codeAction.command) {
|
||||
throw new Error('Synthetic code actions must have a command');
|
||||
}
|
||||
return this._commands.converter.fromInternal(codeAction.command);
|
||||
} else {
|
||||
const ret = new types.CodeAction(
|
||||
codeAction.title,
|
||||
codeAction.kind ? new types.CodeActionKind(codeAction.kind) : undefined
|
||||
);
|
||||
if (codeAction.edit) {
|
||||
ret.edit = typeConverters.WorkspaceEdit.to(codeAction.edit);
|
||||
}
|
||||
if (codeAction.command) {
|
||||
ret.command = this._commands.converter.fromInternal(codeAction.command);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private _executeCodeLensProvider(resource: URI, itemResolveCount: number): Promise<vscode.CodeLens[] | undefined> {
|
||||
const args = { resource, itemResolveCount };
|
||||
return this._commands.executeCommand<modes.ICodeLensSymbol[]>('_executeCodeLensProvider', args)
|
||||
.then(tryMapWith(item => {
|
||||
return new types.CodeLens(
|
||||
typeConverters.Range.to(item.range),
|
||||
item.command ? this._commands.converter.fromInternal(item.command) : undefined);
|
||||
}));
|
||||
|
||||
}
|
||||
|
||||
private _executeFormatDocumentProvider(resource: URI, options: vscode.FormattingOptions): Promise<vscode.TextEdit[] | undefined> {
|
||||
const args = {
|
||||
resource,
|
||||
options
|
||||
};
|
||||
return this._commands.executeCommand<ISingleEditOperation[]>('_executeFormatDocumentProvider', args)
|
||||
.then(tryMapWith(edit => new types.TextEdit(typeConverters.Range.to(edit.range), edit.text)));
|
||||
}
|
||||
|
||||
private _executeFormatRangeProvider(resource: URI, range: types.Range, options: vscode.FormattingOptions): Promise<vscode.TextEdit[] | undefined> {
|
||||
const args = {
|
||||
resource,
|
||||
range: typeConverters.Range.from(range),
|
||||
options
|
||||
};
|
||||
return this._commands.executeCommand<ISingleEditOperation[]>('_executeFormatRangeProvider', args)
|
||||
.then(tryMapWith(edit => new types.TextEdit(typeConverters.Range.to(edit.range), edit.text)));
|
||||
}
|
||||
|
||||
private _executeFormatOnTypeProvider(resource: URI, position: types.Position, ch: string, options: vscode.FormattingOptions): Promise<vscode.TextEdit[] | undefined> {
|
||||
const args = {
|
||||
resource,
|
||||
position: typeConverters.Position.from(position),
|
||||
ch,
|
||||
options
|
||||
};
|
||||
return this._commands.executeCommand<ISingleEditOperation[]>('_executeFormatOnTypeProvider', args)
|
||||
.then(tryMapWith(edit => new types.TextEdit(typeConverters.Range.to(edit.range), edit.text)));
|
||||
}
|
||||
|
||||
private _executeDocumentLinkProvider(resource: URI): Promise<vscode.DocumentLink[] | undefined> {
|
||||
return this._commands.executeCommand<modes.ILink[]>('_executeLinkProvider', resource)
|
||||
.then(tryMapWith(typeConverters.DocumentLink.to));
|
||||
}
|
||||
}
|
||||
|
||||
function tryMapWith<T, R>(f: (x: T) => R) {
|
||||
return (value: T[]) => {
|
||||
if (Array.isArray(value)) {
|
||||
return value.map(f);
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
import { generateRandomPipeName } from 'vs/base/parts/ipc/node/ipc.net';
|
||||
import * as http from 'http';
|
||||
import * as fs from 'fs';
|
||||
import { ExtHostCommands } from 'vs/workbench/api/node/extHostCommands';
|
||||
import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands';
|
||||
import { IURIToOpen, IOpenSettings } from 'vs/platform/windows/common/windows';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspaces';
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IMainContext, MainContext, MainThreadClipboardShape } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export class ExtHostClipboard implements vscode.Clipboard {
|
||||
|
||||
private readonly _proxy: MainThreadClipboardShape;
|
||||
|
||||
constructor(mainContext: IMainContext) {
|
||||
this._proxy = mainContext.getProxy(MainContext.MainThreadClipboard);
|
||||
}
|
||||
|
||||
readText(): Promise<string> {
|
||||
return this._proxy.$readText();
|
||||
}
|
||||
|
||||
writeText(value: string): Promise<void> {
|
||||
return this._proxy.$writeText(value);
|
||||
}
|
||||
}
|
||||
@@ -1,267 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { validateConstraint } from 'vs/base/common/types';
|
||||
import { ICommandHandlerDescription } from 'vs/platform/commands/common/commands';
|
||||
import * as extHostTypes from 'vs/workbench/api/node/extHostTypes';
|
||||
import * as extHostTypeConverter from 'vs/workbench/api/node/extHostTypeConverters';
|
||||
import { cloneAndChange } from 'vs/base/common/objects';
|
||||
import { MainContext, MainThreadCommandsShape, ExtHostCommandsShape, ObjectIdentifier, IMainContext, CommandDto } from '../common/extHost.protocol';
|
||||
import { ExtHostHeapService } from 'vs/workbench/api/node/extHostHeapService';
|
||||
import { isNonEmptyArray } from 'vs/base/common/arrays';
|
||||
import * as modes from 'vs/editor/common/modes';
|
||||
import * as vscode from 'vscode';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { revive } from 'vs/base/common/marshalling';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
|
||||
interface CommandHandler {
|
||||
callback: Function;
|
||||
thisArg: any;
|
||||
description?: ICommandHandlerDescription;
|
||||
}
|
||||
|
||||
export interface ArgumentProcessor {
|
||||
processArgument(arg: any): any;
|
||||
}
|
||||
|
||||
export class ExtHostCommands implements ExtHostCommandsShape {
|
||||
|
||||
private readonly _commands = new Map<string, CommandHandler>();
|
||||
private readonly _proxy: MainThreadCommandsShape;
|
||||
private readonly _converter: CommandsConverter;
|
||||
private readonly _logService: ILogService;
|
||||
private readonly _argumentProcessors: ArgumentProcessor[];
|
||||
|
||||
constructor(
|
||||
mainContext: IMainContext,
|
||||
heapService: ExtHostHeapService,
|
||||
logService: ILogService
|
||||
) {
|
||||
this._proxy = mainContext.getProxy(MainContext.MainThreadCommands);
|
||||
this._logService = logService;
|
||||
this._converter = new CommandsConverter(this, heapService);
|
||||
this._argumentProcessors = [
|
||||
{
|
||||
processArgument(a) {
|
||||
// URI, Regex
|
||||
return revive(a, 0);
|
||||
}
|
||||
},
|
||||
{
|
||||
processArgument(arg) {
|
||||
return cloneAndChange(arg, function (obj) {
|
||||
// Reverse of https://github.com/Microsoft/vscode/blob/1f28c5fc681f4c01226460b6d1c7e91b8acb4a5b/src/vs/workbench/api/node/extHostCommands.ts#L112-L127
|
||||
if (Range.isIRange(obj)) {
|
||||
return extHostTypeConverter.Range.to(obj);
|
||||
}
|
||||
if (Position.isIPosition(obj)) {
|
||||
return extHostTypeConverter.Position.to(obj);
|
||||
}
|
||||
if (Range.isIRange((obj as modes.Location).range) && URI.isUri((obj as modes.Location).uri)) {
|
||||
return extHostTypeConverter.location.to(obj);
|
||||
}
|
||||
if (!Array.isArray(obj)) {
|
||||
return obj;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
get converter(): CommandsConverter {
|
||||
return this._converter;
|
||||
}
|
||||
|
||||
registerArgumentProcessor(processor: ArgumentProcessor): void {
|
||||
this._argumentProcessors.push(processor);
|
||||
}
|
||||
|
||||
registerCommand(global: boolean, id: string, callback: <T>(...args: any[]) => T | Thenable<T>, thisArg?: any, description?: ICommandHandlerDescription): extHostTypes.Disposable {
|
||||
this._logService.trace('ExtHostCommands#registerCommand', id);
|
||||
|
||||
if (!id.trim().length) {
|
||||
throw new Error('invalid id');
|
||||
}
|
||||
|
||||
if (this._commands.has(id)) {
|
||||
throw new Error(`command '${id}' already exists`);
|
||||
}
|
||||
|
||||
this._commands.set(id, { callback, thisArg, description });
|
||||
if (global) {
|
||||
this._proxy.$registerCommand(id);
|
||||
}
|
||||
|
||||
return new extHostTypes.Disposable(() => {
|
||||
if (this._commands.delete(id)) {
|
||||
if (global) {
|
||||
this._proxy.$unregisterCommand(id);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
executeCommand<T>(id: string, ...args: any[]): Promise<T> {
|
||||
this._logService.trace('ExtHostCommands#executeCommand', id);
|
||||
|
||||
if (this._commands.has(id)) {
|
||||
// we stay inside the extension host and support
|
||||
// to pass any kind of parameters around
|
||||
return this._executeContributedCommand<T>(id, args);
|
||||
|
||||
} else {
|
||||
// automagically convert some argument types
|
||||
|
||||
args = cloneAndChange(args, function (value) {
|
||||
if (value instanceof extHostTypes.Position) {
|
||||
return extHostTypeConverter.Position.from(value);
|
||||
}
|
||||
if (value instanceof extHostTypes.Range) {
|
||||
return extHostTypeConverter.Range.from(value);
|
||||
}
|
||||
if (value instanceof extHostTypes.Location) {
|
||||
return extHostTypeConverter.location.from(value);
|
||||
}
|
||||
if (!Array.isArray(value)) {
|
||||
return value;
|
||||
}
|
||||
});
|
||||
|
||||
return this._proxy.$executeCommand<T>(id, args).then(result => revive(result, 0));
|
||||
}
|
||||
}
|
||||
|
||||
private _executeContributedCommand<T>(id: string, args: any[]): Promise<T> {
|
||||
const command = this._commands.get(id);
|
||||
if (!command) {
|
||||
throw new Error('Unknown command');
|
||||
}
|
||||
let { callback, thisArg, description } = command;
|
||||
if (description) {
|
||||
for (let i = 0; i < description.args.length; i++) {
|
||||
try {
|
||||
validateConstraint(args[i], description.args[i].constraint);
|
||||
} catch (err) {
|
||||
return Promise.reject(new Error(`Running the contributed command: '${id}' failed. Illegal argument '${description.args[i].name}' - ${description.args[i].description}`));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const result = callback.apply(thisArg, args);
|
||||
return Promise.resolve(result);
|
||||
} catch (err) {
|
||||
this._logService.error(err, id);
|
||||
return Promise.reject(new Error(`Running the contributed command: '${id}' failed.`));
|
||||
}
|
||||
}
|
||||
|
||||
$executeContributedCommand<T>(id: string, ...args: any[]): Promise<T> {
|
||||
this._logService.trace('ExtHostCommands#$executeContributedCommand', id);
|
||||
|
||||
if (!this._commands.has(id)) {
|
||||
return Promise.reject(new Error(`Contributed command '${id}' does not exist.`));
|
||||
} else {
|
||||
args = args.map(arg => this._argumentProcessors.reduce((r, p) => p.processArgument(r), arg));
|
||||
return this._executeContributedCommand(id, args);
|
||||
}
|
||||
}
|
||||
|
||||
getCommands(filterUnderscoreCommands: boolean = false): Promise<string[]> {
|
||||
this._logService.trace('ExtHostCommands#getCommands', filterUnderscoreCommands);
|
||||
|
||||
return this._proxy.$getCommands().then(result => {
|
||||
if (filterUnderscoreCommands) {
|
||||
result = result.filter(command => command[0] !== '_');
|
||||
}
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
$getContributedCommandHandlerDescriptions(): Promise<{ [id: string]: string | ICommandHandlerDescription }> {
|
||||
const result: { [id: string]: string | ICommandHandlerDescription } = Object.create(null);
|
||||
this._commands.forEach((command, id) => {
|
||||
let { description } = command;
|
||||
if (description) {
|
||||
result[id] = description;
|
||||
}
|
||||
});
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class CommandsConverter {
|
||||
|
||||
private readonly _delegatingCommandId: string;
|
||||
private _commands: ExtHostCommands;
|
||||
private _heap: ExtHostHeapService;
|
||||
|
||||
// --- conversion between internal and api commands
|
||||
constructor(commands: ExtHostCommands, heap: ExtHostHeapService) {
|
||||
this._delegatingCommandId = `_internal_command_delegation_${Date.now()}`;
|
||||
this._commands = commands;
|
||||
this._heap = heap;
|
||||
this._commands.registerCommand(true, this._delegatingCommandId, this._executeConvertedCommand, this);
|
||||
}
|
||||
|
||||
toInternal(command: vscode.Command): CommandDto;
|
||||
toInternal(command: undefined): undefined;
|
||||
toInternal(command: vscode.Command | undefined): CommandDto | undefined;
|
||||
toInternal(command: vscode.Command | undefined): CommandDto | undefined {
|
||||
|
||||
if (!command) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const result: CommandDto = {
|
||||
$ident: undefined,
|
||||
id: command.command,
|
||||
title: command.title,
|
||||
};
|
||||
|
||||
if (command.command && isNonEmptyArray(command.arguments)) {
|
||||
// we have a contributed command with arguments. that
|
||||
// means we don't want to send the arguments around
|
||||
|
||||
const id = this._heap.keep(command);
|
||||
result.$ident = id;
|
||||
|
||||
result.id = this._delegatingCommandId;
|
||||
result.arguments = [id];
|
||||
}
|
||||
|
||||
if (command.tooltip) {
|
||||
result.tooltip = command.tooltip;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
fromInternal(command: modes.Command): vscode.Command {
|
||||
|
||||
const id = ObjectIdentifier.of(command);
|
||||
if (typeof id === 'number') {
|
||||
return this._heap.get<vscode.Command>(id);
|
||||
|
||||
} else {
|
||||
return {
|
||||
command: command.id,
|
||||
title: command.title,
|
||||
arguments: command.arguments
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private _executeConvertedCommand<R>(...args: any[]): Promise<R> {
|
||||
const actualCmd = this._heap.get<vscode.Command>(args[0]);
|
||||
return this._commands.executeCommand(actualCmd.command, ...(actualCmd.arguments || []));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,760 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { asPromise } from 'vs/base/common/async';
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import * as modes from 'vs/editor/common/modes';
|
||||
import { ExtHostDocuments } from 'vs/workbench/api/node/extHostDocuments';
|
||||
import * as extHostTypeConverter from 'vs/workbench/api/node/extHostTypeConverters';
|
||||
import * as types from 'vs/workbench/api/node/extHostTypes';
|
||||
import * as vscode from 'vscode';
|
||||
import { ExtHostCommentsShape, IMainContext, MainContext, MainThreadCommentsShape } from '../common/extHost.protocol';
|
||||
import { CommandsConverter, ExtHostCommands } from './extHostCommands';
|
||||
import { IRange } from 'vs/editor/common/core/range';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { debounce } from 'vs/base/common/decorators';
|
||||
|
||||
interface HandlerData<T> {
|
||||
|
||||
extensionId: ExtensionIdentifier;
|
||||
provider: T;
|
||||
}
|
||||
|
||||
type ProviderHandle = number;
|
||||
|
||||
export class ExtHostComments implements ExtHostCommentsShape {
|
||||
private static handlePool = 0;
|
||||
|
||||
private _proxy: MainThreadCommentsShape;
|
||||
|
||||
private _commentControllers: Map<ProviderHandle, ExtHostCommentController> = new Map<ProviderHandle, ExtHostCommentController>();
|
||||
|
||||
private _commentControllersByExtension: Map<string, ExtHostCommentController[]> = new Map<string, ExtHostCommentController[]>();
|
||||
|
||||
private _documentProviders = new Map<number, HandlerData<vscode.DocumentCommentProvider>>();
|
||||
private _workspaceProviders = new Map<number, HandlerData<vscode.WorkspaceCommentProvider>>();
|
||||
|
||||
constructor(
|
||||
mainContext: IMainContext,
|
||||
private _commands: ExtHostCommands,
|
||||
private readonly _documents: ExtHostDocuments,
|
||||
) {
|
||||
this._proxy = mainContext.getProxy(MainContext.MainThreadComments);
|
||||
|
||||
_commands.registerArgumentProcessor({
|
||||
processArgument: arg => {
|
||||
if (arg && arg.$mid === 6) {
|
||||
const commentController = this._commentControllers.get(arg.handle);
|
||||
|
||||
if (!commentController) {
|
||||
return arg;
|
||||
}
|
||||
|
||||
return commentController;
|
||||
} else if (arg && arg.$mid === 7) {
|
||||
const commentController = this._commentControllers.get(arg.commentControlHandle);
|
||||
|
||||
if (!commentController) {
|
||||
return arg;
|
||||
}
|
||||
|
||||
const commentThread = commentController.getCommentThread(arg.commentThreadHandle);
|
||||
|
||||
if (!commentThread) {
|
||||
return arg;
|
||||
}
|
||||
|
||||
return commentThread;
|
||||
}
|
||||
|
||||
return arg;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
createCommentController(extension: IExtensionDescription, id: string, label: string): vscode.CommentController {
|
||||
const handle = ExtHostComments.handlePool++;
|
||||
const commentController = new ExtHostCommentController(extension, handle, this._commands.converter, this._proxy, id, label);
|
||||
this._commentControllers.set(commentController.handle, commentController);
|
||||
|
||||
const commentControllers = this._commentControllersByExtension.get(ExtensionIdentifier.toKey(extension.identifier)) || [];
|
||||
commentControllers.push(commentController);
|
||||
this._commentControllersByExtension.set(ExtensionIdentifier.toKey(extension.identifier), commentControllers);
|
||||
|
||||
return commentController;
|
||||
}
|
||||
|
||||
$onCommentWidgetInputChange(commentControllerHandle: number, input: string): Promise<number | undefined> {
|
||||
const commentController = this._commentControllers.get(commentControllerHandle);
|
||||
|
||||
if (!commentController) {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
commentController.$onCommentWidgetInputChange(input);
|
||||
return Promise.resolve(commentControllerHandle);
|
||||
}
|
||||
|
||||
$provideCommentingRanges(commentControllerHandle: number, uriComponents: UriComponents, token: CancellationToken): Promise<IRange[] | undefined> {
|
||||
const commentController = this._commentControllers.get(commentControllerHandle);
|
||||
|
||||
if (!commentController || !commentController.commentingRangeProvider) {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
const document = this._documents.getDocument(URI.revive(uriComponents));
|
||||
return asPromise(() => {
|
||||
return commentController.commentingRangeProvider!.provideCommentingRanges(document, token);
|
||||
}).then(ranges => ranges ? ranges.map(x => extHostTypeConverter.Range.from(x)) : undefined);
|
||||
}
|
||||
|
||||
$provideReactionGroup(commentControllerHandle: number): Promise<modes.CommentReaction[] | undefined> {
|
||||
const commentController = this._commentControllers.get(commentControllerHandle);
|
||||
|
||||
if (!commentController || !commentController.reactionProvider) {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
return asPromise(() => {
|
||||
return commentController!.reactionProvider!.availableReactions;
|
||||
}).then(reactions => reactions.map(reaction => convertToReaction2(commentController.reactionProvider, reaction)));
|
||||
}
|
||||
|
||||
$toggleReaction(commentControllerHandle: number, threadHandle: number, uri: UriComponents, comment: modes.Comment, reaction: modes.CommentReaction): Promise<void> {
|
||||
const document = this._documents.getDocument(URI.revive(uri));
|
||||
const commentController = this._commentControllers.get(commentControllerHandle);
|
||||
|
||||
if (!commentController || !commentController.reactionProvider || !commentController.reactionProvider.toggleReaction) {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
return asPromise(() => {
|
||||
const commentThread = commentController.getCommentThread(threadHandle);
|
||||
if (commentThread) {
|
||||
const vscodeComment = commentThread.getComment(comment.commentId);
|
||||
|
||||
if (commentController !== undefined && commentController.reactionProvider && commentController.reactionProvider.toggleReaction && vscodeComment) {
|
||||
return commentController.reactionProvider.toggleReaction(document, vscodeComment, convertFromReaction(reaction));
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.resolve(undefined);
|
||||
});
|
||||
}
|
||||
|
||||
$createNewCommentWidgetCallback(commentControllerHandle: number, uriComponents: UriComponents, range: IRange, token: CancellationToken): Promise<void> {
|
||||
const commentController = this._commentControllers.get(commentControllerHandle);
|
||||
|
||||
if (!commentController) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
if (!(commentController as any).emptyCommentThreadFactory && !(commentController.commentingRangeProvider && commentController.commentingRangeProvider.createEmptyCommentThread)) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const document = this._documents.getDocument(URI.revive(uriComponents));
|
||||
return asPromise(() => {
|
||||
// TODO, remove this once GH PR stable deprecates `emptyCommentThreadFactory`.
|
||||
if ((commentController as any).emptyCommentThreadFactory) {
|
||||
return (commentController as any).emptyCommentThreadFactory!.createEmptyCommentThread(document, extHostTypeConverter.Range.to(range));
|
||||
}
|
||||
|
||||
if (commentController.commentingRangeProvider && commentController.commentingRangeProvider.createEmptyCommentThread) {
|
||||
return commentController.commentingRangeProvider.createEmptyCommentThread(document, extHostTypeConverter.Range.to(range));
|
||||
}
|
||||
}).then(() => Promise.resolve());
|
||||
}
|
||||
|
||||
registerWorkspaceCommentProvider(
|
||||
extensionId: ExtensionIdentifier,
|
||||
provider: vscode.WorkspaceCommentProvider
|
||||
): vscode.Disposable {
|
||||
const handle = ExtHostComments.handlePool++;
|
||||
this._workspaceProviders.set(handle, {
|
||||
extensionId,
|
||||
provider
|
||||
});
|
||||
this._proxy.$registerWorkspaceCommentProvider(handle, extensionId);
|
||||
this.registerListeners(handle, extensionId, provider);
|
||||
|
||||
return {
|
||||
dispose: () => {
|
||||
this._proxy.$unregisterWorkspaceCommentProvider(handle);
|
||||
this._workspaceProviders.delete(handle);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
registerDocumentCommentProvider(
|
||||
extensionId: ExtensionIdentifier,
|
||||
provider: vscode.DocumentCommentProvider
|
||||
): vscode.Disposable {
|
||||
const handle = ExtHostComments.handlePool++;
|
||||
this._documentProviders.set(handle, {
|
||||
extensionId,
|
||||
provider
|
||||
});
|
||||
this._proxy.$registerDocumentCommentProvider(handle, {
|
||||
startDraftLabel: provider.startDraftLabel,
|
||||
deleteDraftLabel: provider.deleteDraftLabel,
|
||||
finishDraftLabel: provider.finishDraftLabel,
|
||||
reactionGroup: provider.reactionGroup ? provider.reactionGroup.map(reaction => convertToReaction(provider, reaction)) : undefined
|
||||
});
|
||||
this.registerListeners(handle, extensionId, provider);
|
||||
|
||||
return {
|
||||
dispose: () => {
|
||||
this._proxy.$unregisterDocumentCommentProvider(handle);
|
||||
this._documentProviders.delete(handle);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
$createNewCommentThread(handle: number, uri: UriComponents, range: IRange, text: string): Promise<modes.CommentThread | null> {
|
||||
const data = this._documents.getDocumentData(URI.revive(uri));
|
||||
const ran = <vscode.Range>extHostTypeConverter.Range.to(range);
|
||||
|
||||
if (!data || !data.document) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
const handlerData = this.getDocumentProvider(handle);
|
||||
return asPromise(() => {
|
||||
return handlerData.provider.createNewCommentThread(data.document, ran, text, CancellationToken.None);
|
||||
}).then(commentThread => commentThread ? convertToCommentThread(handlerData.extensionId, handlerData.provider, commentThread, this._commands.converter) : null);
|
||||
}
|
||||
|
||||
$replyToCommentThread(handle: number, uri: UriComponents, range: IRange, thread: modes.CommentThread, text: string): Promise<modes.CommentThread | null> {
|
||||
const data = this._documents.getDocumentData(URI.revive(uri));
|
||||
const ran = <vscode.Range>extHostTypeConverter.Range.to(range);
|
||||
|
||||
if (!data || !data.document) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
const handlerData = this.getDocumentProvider(handle);
|
||||
return asPromise(() => {
|
||||
return handlerData.provider.replyToCommentThread(data.document, ran, convertFromCommentThread(thread), text, CancellationToken.None);
|
||||
}).then(commentThread => commentThread ? convertToCommentThread(handlerData.extensionId, handlerData.provider, commentThread, this._commands.converter) : null);
|
||||
}
|
||||
|
||||
$editComment(handle: number, uri: UriComponents, comment: modes.Comment, text: string): Promise<void> {
|
||||
const document = this._documents.getDocument(URI.revive(uri));
|
||||
const handlerData = this.getDocumentProvider(handle);
|
||||
if (!handlerData.provider.editComment) {
|
||||
return Promise.reject(new Error('not implemented'));
|
||||
}
|
||||
return asPromise(() => {
|
||||
return handlerData.provider.editComment!(document, convertFromComment(comment), text, CancellationToken.None);
|
||||
});
|
||||
}
|
||||
|
||||
$deleteComment(handle: number, uri: UriComponents, comment: modes.Comment): Promise<void> {
|
||||
const document = this._documents.getDocument(URI.revive(uri));
|
||||
const handlerData = this.getDocumentProvider(handle);
|
||||
if (!handlerData.provider.deleteComment) {
|
||||
return Promise.reject(new Error('not implemented'));
|
||||
}
|
||||
return asPromise(() => {
|
||||
return handlerData.provider.deleteComment!(document, convertFromComment(comment), CancellationToken.None);
|
||||
});
|
||||
}
|
||||
|
||||
$startDraft(handle: number, uri: UriComponents): Promise<void> {
|
||||
const document = this._documents.getDocument(URI.revive(uri));
|
||||
|
||||
const handlerData = this.getDocumentProvider(handle);
|
||||
if (!handlerData.provider.startDraft) {
|
||||
return Promise.reject(new Error('not implemented'));
|
||||
}
|
||||
return asPromise(() => {
|
||||
return handlerData.provider.startDraft!(document, CancellationToken.None);
|
||||
});
|
||||
}
|
||||
|
||||
$deleteDraft(handle: number, uri: UriComponents): Promise<void> {
|
||||
const document = this._documents.getDocument(URI.revive(uri));
|
||||
const handlerData = this.getDocumentProvider(handle);
|
||||
if (!handlerData.provider.deleteDraft) {
|
||||
return Promise.reject(new Error('not implemented'));
|
||||
}
|
||||
return asPromise(() => {
|
||||
return handlerData.provider.deleteDraft!(document, CancellationToken.None);
|
||||
});
|
||||
}
|
||||
|
||||
$finishDraft(handle: number, uri: UriComponents): Promise<void> {
|
||||
const document = this._documents.getDocument(URI.revive(uri));
|
||||
const handlerData = this.getDocumentProvider(handle);
|
||||
if (!handlerData.provider.finishDraft) {
|
||||
return Promise.reject(new Error('not implemented'));
|
||||
}
|
||||
return asPromise(() => {
|
||||
return handlerData.provider.finishDraft!(document, CancellationToken.None);
|
||||
});
|
||||
}
|
||||
|
||||
$addReaction(handle: number, uri: UriComponents, comment: modes.Comment, reaction: modes.CommentReaction): Promise<void> {
|
||||
const document = this._documents.getDocument(URI.revive(uri));
|
||||
const handlerData = this.getDocumentProvider(handle);
|
||||
if (!handlerData.provider.addReaction) {
|
||||
return Promise.reject(new Error('not implemented'));
|
||||
}
|
||||
return asPromise(() => {
|
||||
return handlerData.provider.addReaction!(document, convertFromComment(comment), convertFromReaction(reaction));
|
||||
});
|
||||
}
|
||||
|
||||
$deleteReaction(handle: number, uri: UriComponents, comment: modes.Comment, reaction: modes.CommentReaction): Promise<void> {
|
||||
const document = this._documents.getDocument(URI.revive(uri));
|
||||
const handlerData = this.getDocumentProvider(handle);
|
||||
if (!handlerData.provider.deleteReaction) {
|
||||
return Promise.reject(new Error('not implemented'));
|
||||
}
|
||||
return asPromise(() => {
|
||||
return handlerData.provider.deleteReaction!(document, convertFromComment(comment), convertFromReaction(reaction));
|
||||
});
|
||||
}
|
||||
|
||||
$provideDocumentComments(handle: number, uri: UriComponents): Promise<modes.CommentInfo | null> {
|
||||
const document = this._documents.getDocument(URI.revive(uri));
|
||||
const handlerData = this.getDocumentProvider(handle);
|
||||
return asPromise(() => {
|
||||
return handlerData.provider.provideDocumentComments(document, CancellationToken.None);
|
||||
}).then(commentInfo => commentInfo ? convertCommentInfo(handle, handlerData.extensionId, handlerData.provider, commentInfo, this._commands.converter) : null);
|
||||
}
|
||||
|
||||
$provideWorkspaceComments(handle: number): Promise<modes.CommentThread[] | null> {
|
||||
const handlerData = this._workspaceProviders.get(handle);
|
||||
if (!handlerData) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
return asPromise(() => {
|
||||
return handlerData.provider.provideWorkspaceComments(CancellationToken.None);
|
||||
}).then(comments =>
|
||||
comments.map(comment => convertToCommentThread(handlerData.extensionId, handlerData.provider, comment, this._commands.converter)
|
||||
));
|
||||
}
|
||||
|
||||
private registerListeners(handle: number, extensionId: ExtensionIdentifier, provider: vscode.DocumentCommentProvider | vscode.WorkspaceCommentProvider) {
|
||||
provider.onDidChangeCommentThreads(event => {
|
||||
|
||||
this._proxy.$onDidCommentThreadsChange(handle, {
|
||||
changed: event.changed.map(thread => convertToCommentThread(extensionId, provider, thread, this._commands.converter)),
|
||||
added: event.added.map(thread => convertToCommentThread(extensionId, provider, thread, this._commands.converter)),
|
||||
removed: event.removed.map(thread => convertToCommentThread(extensionId, provider, thread, this._commands.converter)),
|
||||
draftMode: !!(provider as vscode.DocumentCommentProvider).startDraft && !!(provider as vscode.DocumentCommentProvider).finishDraft ? (event.inDraftMode ? modes.DraftMode.InDraft : modes.DraftMode.NotInDraft) : modes.DraftMode.NotSupported
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private getDocumentProvider(handle: number): HandlerData<vscode.DocumentCommentProvider> {
|
||||
const provider = this._documentProviders.get(handle);
|
||||
if (!provider) {
|
||||
throw new Error('unknown provider');
|
||||
}
|
||||
return provider;
|
||||
}
|
||||
}
|
||||
|
||||
export class ExtHostCommentThread implements vscode.CommentThread {
|
||||
private static _handlePool: number = 0;
|
||||
readonly handle = ExtHostCommentThread._handlePool++;
|
||||
get threadId(): string {
|
||||
return this._threadId;
|
||||
}
|
||||
|
||||
get resource(): vscode.Uri {
|
||||
return this._resource;
|
||||
}
|
||||
|
||||
private _onDidUpdateCommentThread = new Emitter<void>();
|
||||
readonly onDidUpdateCommentThread = this._onDidUpdateCommentThread.event;
|
||||
|
||||
set range(range: vscode.Range) {
|
||||
if (range.isEqual(this._range)) {
|
||||
this._range = range;
|
||||
this._onDidUpdateCommentThread.fire();
|
||||
}
|
||||
}
|
||||
|
||||
get range(): vscode.Range {
|
||||
return this._range;
|
||||
}
|
||||
|
||||
private _label: string;
|
||||
|
||||
get label(): string {
|
||||
return this._label;
|
||||
}
|
||||
|
||||
set label(label: string) {
|
||||
this._label = label;
|
||||
this._onDidUpdateCommentThread.fire();
|
||||
}
|
||||
|
||||
get comments(): vscode.Comment[] {
|
||||
return this._comments;
|
||||
}
|
||||
|
||||
set comments(newComments: vscode.Comment[]) {
|
||||
this._comments = newComments;
|
||||
this._onDidUpdateCommentThread.fire();
|
||||
}
|
||||
|
||||
private _acceptInputCommand: vscode.Command;
|
||||
get acceptInputCommand(): vscode.Command {
|
||||
return this._acceptInputCommand;
|
||||
}
|
||||
|
||||
set acceptInputCommand(acceptInputCommand: vscode.Command) {
|
||||
this._acceptInputCommand = acceptInputCommand;
|
||||
this._onDidUpdateCommentThread.fire();
|
||||
}
|
||||
|
||||
private _additionalCommands: vscode.Command[] = [];
|
||||
get additionalCommands(): vscode.Command[] {
|
||||
return this._additionalCommands;
|
||||
}
|
||||
|
||||
set additionalCommands(additionalCommands: vscode.Command[]) {
|
||||
this._additionalCommands = additionalCommands;
|
||||
this._onDidUpdateCommentThread.fire();
|
||||
}
|
||||
|
||||
private _deleteCommand?: vscode.Command;
|
||||
get deleteComand(): vscode.Command | undefined {
|
||||
return this._deleteCommand;
|
||||
}
|
||||
|
||||
set deleteCommand(deleteCommand: vscode.Command) {
|
||||
this._deleteCommand = deleteCommand;
|
||||
this._onDidUpdateCommentThread.fire();
|
||||
}
|
||||
|
||||
private _collapseState?: vscode.CommentThreadCollapsibleState;
|
||||
|
||||
get collapsibleState(): vscode.CommentThreadCollapsibleState {
|
||||
return this._collapseState!;
|
||||
}
|
||||
|
||||
set collapsibleState(newState: vscode.CommentThreadCollapsibleState) {
|
||||
this._collapseState = newState;
|
||||
this._onDidUpdateCommentThread.fire();
|
||||
}
|
||||
|
||||
private _localDisposables: types.Disposable[];
|
||||
|
||||
constructor(
|
||||
private _proxy: MainThreadCommentsShape,
|
||||
private readonly _commandsConverter: CommandsConverter,
|
||||
private _commentController: ExtHostCommentController,
|
||||
private _threadId: string,
|
||||
private _resource: vscode.Uri,
|
||||
private _range: vscode.Range,
|
||||
private _comments: vscode.Comment[]
|
||||
) {
|
||||
this._proxy.$createCommentThread(
|
||||
this._commentController.handle,
|
||||
this.handle,
|
||||
this._threadId,
|
||||
this._resource,
|
||||
extHostTypeConverter.Range.from(this._range)
|
||||
);
|
||||
|
||||
this._localDisposables = [];
|
||||
|
||||
this._localDisposables.push(this.onDidUpdateCommentThread(() => {
|
||||
this.eventuallyUpdateCommentThread();
|
||||
}));
|
||||
|
||||
// set up comments after ctor to batch update events.
|
||||
this.comments = _comments;
|
||||
}
|
||||
|
||||
@debounce(100)
|
||||
eventuallyUpdateCommentThread(): void {
|
||||
const commentThreadRange = extHostTypeConverter.Range.from(this._range);
|
||||
const label = this.label;
|
||||
const comments = this._comments.map(cmt => { return convertToModeComment(this._commentController, cmt, this._commandsConverter); });
|
||||
const acceptInputCommand = this._acceptInputCommand ? this._commandsConverter.toInternal(this._acceptInputCommand) : undefined;
|
||||
const additionalCommands = this._additionalCommands ? this._additionalCommands.map(x => this._commandsConverter.toInternal(x)) : [];
|
||||
const deleteCommand = this._deleteCommand ? this._commandsConverter.toInternal(this._deleteCommand) : undefined;
|
||||
const collapsibleState = convertToCollapsibleState(this._collapseState);
|
||||
|
||||
this._proxy.$updateCommentThread(
|
||||
this._commentController.handle,
|
||||
this.handle,
|
||||
this._threadId,
|
||||
this._resource,
|
||||
commentThreadRange,
|
||||
label,
|
||||
comments,
|
||||
acceptInputCommand,
|
||||
additionalCommands,
|
||||
deleteCommand,
|
||||
collapsibleState
|
||||
);
|
||||
}
|
||||
|
||||
getComment(commentId: string): vscode.Comment | undefined {
|
||||
const comments = this._comments.filter(comment => comment.commentId === commentId);
|
||||
|
||||
if (comments && comments.length) {
|
||||
return comments[0];
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this._localDisposables.forEach(disposable => disposable.dispose());
|
||||
this._proxy.$deleteCommentThread(
|
||||
this._commentController.handle,
|
||||
this.handle
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class ExtHostCommentInputBox implements vscode.CommentInputBox {
|
||||
private _onDidChangeValue = new Emitter<string>();
|
||||
|
||||
get onDidChangeValue(): Event<string> {
|
||||
return this._onDidChangeValue.event;
|
||||
}
|
||||
private _value: string = '';
|
||||
get value(): string {
|
||||
return this._value;
|
||||
}
|
||||
|
||||
set value(newInput: string) {
|
||||
this._value = newInput;
|
||||
this._onDidChangeValue.fire(this._value);
|
||||
this._proxy.$setInputValue(this.commentControllerHandle, newInput);
|
||||
}
|
||||
|
||||
constructor(
|
||||
private _proxy: MainThreadCommentsShape,
|
||||
|
||||
public commentControllerHandle: number,
|
||||
input: string
|
||||
) {
|
||||
this._value = input;
|
||||
}
|
||||
|
||||
setInput(input: string) {
|
||||
this._value = input;
|
||||
}
|
||||
}
|
||||
class ExtHostCommentController implements vscode.CommentController {
|
||||
get id(): string {
|
||||
return this._id;
|
||||
}
|
||||
|
||||
get label(): string {
|
||||
return this._label;
|
||||
}
|
||||
|
||||
public inputBox?: ExtHostCommentInputBox;
|
||||
public activeCommentingRange?: vscode.Range;
|
||||
|
||||
public get handle(): number {
|
||||
return this._handle;
|
||||
}
|
||||
|
||||
private _threads: Map<number, ExtHostCommentThread> = new Map<number, ExtHostCommentThread>();
|
||||
commentingRangeProvider?: vscode.CommentingRangeProvider;
|
||||
|
||||
private _commentReactionProvider?: vscode.CommentReactionProvider;
|
||||
|
||||
get reactionProvider(): vscode.CommentReactionProvider | undefined {
|
||||
return this._commentReactionProvider;
|
||||
}
|
||||
|
||||
set reactionProvider(provider: vscode.CommentReactionProvider | undefined) {
|
||||
this._commentReactionProvider = provider;
|
||||
if (provider) {
|
||||
this._proxy.$updateCommentControllerFeatures(this.handle, { reactionGroup: provider.availableReactions.map(reaction => convertToReaction2(provider, reaction)) });
|
||||
}
|
||||
}
|
||||
|
||||
constructor(
|
||||
_extension: IExtensionDescription,
|
||||
private _handle: number,
|
||||
private readonly _commandsConverter: CommandsConverter,
|
||||
private _proxy: MainThreadCommentsShape,
|
||||
private _id: string,
|
||||
private _label: string
|
||||
) {
|
||||
this._proxy.$registerCommentController(this.handle, _id, _label);
|
||||
}
|
||||
|
||||
createCommentThread(id: string, resource: vscode.Uri, range: vscode.Range, comments: vscode.Comment[]): vscode.CommentThread {
|
||||
const commentThread = new ExtHostCommentThread(this._proxy, this._commandsConverter, this, id, resource, range, comments);
|
||||
this._threads.set(commentThread.handle, commentThread);
|
||||
return commentThread;
|
||||
}
|
||||
|
||||
$onCommentWidgetInputChange(input: string) {
|
||||
if (!this.inputBox) {
|
||||
this.inputBox = new ExtHostCommentInputBox(this._proxy, this.handle, input);
|
||||
} else {
|
||||
this.inputBox.setInput(input);
|
||||
}
|
||||
}
|
||||
|
||||
getCommentThread(handle: number) {
|
||||
return this._threads.get(handle);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._threads.forEach(value => {
|
||||
value.dispose();
|
||||
});
|
||||
|
||||
this._proxy.$unregisterCommentController(this.handle);
|
||||
}
|
||||
}
|
||||
|
||||
function convertCommentInfo(owner: number, extensionId: ExtensionIdentifier, provider: vscode.DocumentCommentProvider, vscodeCommentInfo: vscode.CommentInfo, commandsConverter: CommandsConverter): modes.CommentInfo {
|
||||
return {
|
||||
extensionId: extensionId.value,
|
||||
threads: vscodeCommentInfo.threads.map(x => convertToCommentThread(extensionId, provider, x, commandsConverter)),
|
||||
commentingRanges: vscodeCommentInfo.commentingRanges ? vscodeCommentInfo.commentingRanges.map(range => extHostTypeConverter.Range.from(range)) : [],
|
||||
draftMode: provider.startDraft && provider.finishDraft ? (vscodeCommentInfo.inDraftMode ? modes.DraftMode.InDraft : modes.DraftMode.NotInDraft) : modes.DraftMode.NotSupported
|
||||
};
|
||||
}
|
||||
|
||||
function convertToCommentThread(extensionId: ExtensionIdentifier, provider: vscode.DocumentCommentProvider | vscode.WorkspaceCommentProvider, vscodeCommentThread: vscode.CommentThread, commandsConverter: CommandsConverter): modes.CommentThread {
|
||||
return {
|
||||
extensionId: extensionId.value,
|
||||
threadId: vscodeCommentThread.threadId,
|
||||
resource: vscodeCommentThread.resource.toString(),
|
||||
range: extHostTypeConverter.Range.from(vscodeCommentThread.range),
|
||||
comments: vscodeCommentThread.comments.map(comment => convertToComment(provider, comment, commandsConverter)),
|
||||
collapsibleState: vscodeCommentThread.collapsibleState
|
||||
};
|
||||
}
|
||||
|
||||
function convertFromCommentThread(commentThread: modes.CommentThread): vscode.CommentThread {
|
||||
return {
|
||||
threadId: commentThread.threadId!,
|
||||
resource: URI.parse(commentThread.resource!),
|
||||
range: extHostTypeConverter.Range.to(commentThread.range),
|
||||
comments: commentThread.comments ? commentThread.comments.map(convertFromComment) : [],
|
||||
collapsibleState: commentThread.collapsibleState
|
||||
};
|
||||
}
|
||||
|
||||
function convertFromComment(comment: modes.Comment): vscode.Comment {
|
||||
let userIconPath: URI | undefined;
|
||||
if (comment.userIconPath) {
|
||||
try {
|
||||
userIconPath = URI.parse(comment.userIconPath);
|
||||
} catch (e) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
commentId: comment.commentId,
|
||||
body: extHostTypeConverter.MarkdownString.to(comment.body),
|
||||
userName: comment.userName,
|
||||
userIconPath: userIconPath,
|
||||
canEdit: comment.canEdit,
|
||||
canDelete: comment.canDelete,
|
||||
isDraft: comment.isDraft,
|
||||
commentReactions: comment.commentReactions ? comment.commentReactions.map(reaction => {
|
||||
return {
|
||||
label: reaction.label,
|
||||
count: reaction.count,
|
||||
hasReacted: reaction.hasReacted
|
||||
};
|
||||
}) : undefined
|
||||
};
|
||||
}
|
||||
|
||||
function convertToModeComment(commentController: ExtHostCommentController, vscodeComment: vscode.Comment, commandsConverter: CommandsConverter): modes.Comment {
|
||||
const iconPath = vscodeComment.userIconPath ? vscodeComment.userIconPath.toString() : vscodeComment.gravatar;
|
||||
|
||||
return {
|
||||
commentId: vscodeComment.commentId,
|
||||
body: extHostTypeConverter.MarkdownString.from(vscodeComment.body),
|
||||
userName: vscodeComment.userName,
|
||||
userIconPath: iconPath,
|
||||
isDraft: vscodeComment.isDraft,
|
||||
selectCommand: vscodeComment.selectCommand ? commandsConverter.toInternal(vscodeComment.selectCommand) : undefined,
|
||||
editCommand: vscodeComment.editCommand ? commandsConverter.toInternal(vscodeComment.editCommand) : undefined,
|
||||
deleteCommand: vscodeComment.deleteCommand ? commandsConverter.toInternal(vscodeComment.deleteCommand) : undefined,
|
||||
label: vscodeComment.label,
|
||||
commentReactions: vscodeComment.commentReactions ? vscodeComment.commentReactions.map(reaction => convertToReaction2(commentController.reactionProvider, reaction)) : undefined
|
||||
};
|
||||
}
|
||||
|
||||
function convertToComment(provider: vscode.DocumentCommentProvider | vscode.WorkspaceCommentProvider, vscodeComment: vscode.Comment, commandsConverter: CommandsConverter): modes.Comment {
|
||||
const canEdit = !!(provider as vscode.DocumentCommentProvider).editComment && vscodeComment.canEdit;
|
||||
const canDelete = !!(provider as vscode.DocumentCommentProvider).deleteComment && vscodeComment.canDelete;
|
||||
const iconPath = vscodeComment.userIconPath ? vscodeComment.userIconPath.toString() : vscodeComment.gravatar;
|
||||
|
||||
return {
|
||||
commentId: vscodeComment.commentId,
|
||||
body: extHostTypeConverter.MarkdownString.from(vscodeComment.body),
|
||||
userName: vscodeComment.userName,
|
||||
userIconPath: iconPath,
|
||||
canEdit: canEdit,
|
||||
canDelete: canDelete,
|
||||
selectCommand: vscodeComment.command ? commandsConverter.toInternal(vscodeComment.command) : undefined,
|
||||
isDraft: vscodeComment.isDraft,
|
||||
commentReactions: vscodeComment.commentReactions ? vscodeComment.commentReactions.map(reaction => convertToReaction(provider, reaction)) : undefined
|
||||
};
|
||||
}
|
||||
|
||||
function convertToReaction(provider: vscode.DocumentCommentProvider | vscode.WorkspaceCommentProvider, reaction: vscode.CommentReaction): modes.CommentReaction {
|
||||
const providerCanDeleteReaction = !!(provider as vscode.DocumentCommentProvider).deleteReaction;
|
||||
const providerCanAddReaction = !!(provider as vscode.DocumentCommentProvider).addReaction;
|
||||
|
||||
return {
|
||||
label: reaction.label,
|
||||
iconPath: reaction.iconPath ? extHostTypeConverter.pathOrURIToURI(reaction.iconPath) : undefined,
|
||||
count: reaction.count,
|
||||
hasReacted: reaction.hasReacted,
|
||||
canEdit: (reaction.hasReacted && providerCanDeleteReaction) || (!reaction.hasReacted && providerCanAddReaction)
|
||||
};
|
||||
}
|
||||
|
||||
function convertToReaction2(provider: vscode.CommentReactionProvider | undefined, reaction: vscode.CommentReaction): modes.CommentReaction {
|
||||
return {
|
||||
label: reaction.label,
|
||||
iconPath: reaction.iconPath ? extHostTypeConverter.pathOrURIToURI(reaction.iconPath) : undefined,
|
||||
count: reaction.count,
|
||||
hasReacted: reaction.hasReacted,
|
||||
canEdit: provider !== undefined ? !!provider.toggleReaction : false
|
||||
};
|
||||
}
|
||||
|
||||
function convertFromReaction(reaction: modes.CommentReaction): vscode.CommentReaction {
|
||||
return {
|
||||
label: reaction.label,
|
||||
count: reaction.count,
|
||||
hasReacted: reaction.hasReacted
|
||||
};
|
||||
}
|
||||
|
||||
function convertToCollapsibleState(kind: vscode.CommentThreadCollapsibleState | undefined): modes.CommentThreadCollapsibleState {
|
||||
if (kind !== undefined) {
|
||||
switch (kind) {
|
||||
case types.CommentThreadCollapsibleState.Expanded:
|
||||
return modes.CommentThreadCollapsibleState.Expanded;
|
||||
case types.CommentThreadCollapsibleState.Collapsed:
|
||||
return modes.CommentThreadCollapsibleState.Collapsed;
|
||||
}
|
||||
}
|
||||
return modes.CommentThreadCollapsibleState.Collapsed;
|
||||
}
|
||||
@@ -1,272 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { mixin, deepClone } from 'vs/base/common/objects';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import * as vscode from 'vscode';
|
||||
import { ExtHostWorkspace } from 'vs/workbench/api/node/extHostWorkspace';
|
||||
import { ExtHostConfigurationShape, MainThreadConfigurationShape, IWorkspaceConfigurationChangeEventData, IConfigurationInitData } from '../common/extHost.protocol';
|
||||
import { ConfigurationTarget as ExtHostConfigurationTarget } from './extHostTypes';
|
||||
import { IConfigurationData, ConfigurationTarget, IConfigurationModel } from 'vs/platform/configuration/common/configuration';
|
||||
import { Configuration, ConfigurationChangeEvent, ConfigurationModel } from 'vs/platform/configuration/common/configurationModels';
|
||||
import { WorkspaceConfigurationChangeEvent } from 'vs/workbench/services/configuration/common/configurationModels';
|
||||
import { ResourceMap } from 'vs/base/common/map';
|
||||
import { ConfigurationScope, OVERRIDE_PROPERTY_PATTERN } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
import { isObject } from 'vs/base/common/types';
|
||||
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
|
||||
import { Barrier } from 'vs/base/common/async';
|
||||
|
||||
function lookUp(tree: any, key: string) {
|
||||
if (key) {
|
||||
const parts = key.split('.');
|
||||
let node = tree;
|
||||
for (let i = 0; node && i < parts.length; i++) {
|
||||
node = node[parts[i]];
|
||||
}
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
type ConfigurationInspect<T> = {
|
||||
key: string;
|
||||
defaultValue?: T;
|
||||
globalValue?: T;
|
||||
workspaceValue?: T;
|
||||
workspaceFolderValue?: T;
|
||||
};
|
||||
|
||||
export class ExtHostConfiguration implements ExtHostConfigurationShape {
|
||||
|
||||
private readonly _proxy: MainThreadConfigurationShape;
|
||||
private readonly _extHostWorkspace: ExtHostWorkspace;
|
||||
private readonly _barrier: Barrier;
|
||||
private _actual: ExtHostConfigProvider | null;
|
||||
|
||||
constructor(proxy: MainThreadConfigurationShape, extHostWorkspace: ExtHostWorkspace) {
|
||||
this._proxy = proxy;
|
||||
this._extHostWorkspace = extHostWorkspace;
|
||||
this._barrier = new Barrier();
|
||||
this._actual = null;
|
||||
}
|
||||
|
||||
public getConfigProvider(): Promise<ExtHostConfigProvider> {
|
||||
return this._barrier.wait().then(_ => this._actual!);
|
||||
}
|
||||
|
||||
$initializeConfiguration(data: IConfigurationInitData): void {
|
||||
this._actual = new ExtHostConfigProvider(this._proxy, this._extHostWorkspace, data);
|
||||
this._barrier.open();
|
||||
}
|
||||
|
||||
$acceptConfigurationChanged(data: IConfigurationInitData, eventData: IWorkspaceConfigurationChangeEventData): void {
|
||||
this.getConfigProvider().then(provider => provider.$acceptConfigurationChanged(data, eventData));
|
||||
}
|
||||
}
|
||||
|
||||
export class ExtHostConfigProvider {
|
||||
|
||||
private readonly _onDidChangeConfiguration = new Emitter<vscode.ConfigurationChangeEvent>();
|
||||
private readonly _proxy: MainThreadConfigurationShape;
|
||||
private readonly _extHostWorkspace: ExtHostWorkspace;
|
||||
private _configurationScopes: { [key: string]: ConfigurationScope };
|
||||
private _configuration: Configuration;
|
||||
|
||||
constructor(proxy: MainThreadConfigurationShape, extHostWorkspace: ExtHostWorkspace, data: IConfigurationInitData) {
|
||||
this._proxy = proxy;
|
||||
this._extHostWorkspace = extHostWorkspace;
|
||||
this._configuration = ExtHostConfigProvider.parse(data);
|
||||
this._configurationScopes = data.configurationScopes;
|
||||
}
|
||||
|
||||
get onDidChangeConfiguration(): Event<vscode.ConfigurationChangeEvent> {
|
||||
return this._onDidChangeConfiguration && this._onDidChangeConfiguration.event;
|
||||
}
|
||||
|
||||
$acceptConfigurationChanged(data: IConfigurationInitData, eventData: IWorkspaceConfigurationChangeEventData) {
|
||||
this._configuration = ExtHostConfigProvider.parse(data);
|
||||
this._configurationScopes = data.configurationScopes;
|
||||
this._onDidChangeConfiguration.fire(this._toConfigurationChangeEvent(eventData));
|
||||
}
|
||||
|
||||
getConfiguration(section?: string, resource?: URI, extensionId?: ExtensionIdentifier): vscode.WorkspaceConfiguration {
|
||||
const config = this._toReadonlyValue(section
|
||||
? lookUp(this._configuration.getValue(undefined, { resource }, this._extHostWorkspace.workspace), section)
|
||||
: this._configuration.getValue(undefined, { resource }, this._extHostWorkspace.workspace));
|
||||
|
||||
if (section) {
|
||||
this._validateConfigurationAccess(section, resource, extensionId);
|
||||
}
|
||||
|
||||
function parseConfigurationTarget(arg: boolean | ExtHostConfigurationTarget): ConfigurationTarget | null {
|
||||
if (arg === undefined || arg === null) {
|
||||
return null;
|
||||
}
|
||||
if (typeof arg === 'boolean') {
|
||||
return arg ? ConfigurationTarget.USER : ConfigurationTarget.WORKSPACE;
|
||||
}
|
||||
|
||||
switch (arg) {
|
||||
case ExtHostConfigurationTarget.Global: return ConfigurationTarget.USER;
|
||||
case ExtHostConfigurationTarget.Workspace: return ConfigurationTarget.WORKSPACE;
|
||||
case ExtHostConfigurationTarget.WorkspaceFolder: return ConfigurationTarget.WORKSPACE_FOLDER;
|
||||
}
|
||||
}
|
||||
|
||||
const result: vscode.WorkspaceConfiguration = {
|
||||
has(key: string): boolean {
|
||||
return typeof lookUp(config, key) !== 'undefined';
|
||||
},
|
||||
get: <T>(key: string, defaultValue?: T) => {
|
||||
this._validateConfigurationAccess(section ? `${section}.${key}` : key, resource, extensionId);
|
||||
let result = lookUp(config, key);
|
||||
if (typeof result === 'undefined') {
|
||||
result = defaultValue;
|
||||
} else {
|
||||
let clonedConfig: any | undefined = undefined;
|
||||
const cloneOnWriteProxy = (target: any, accessor: string): any => {
|
||||
let clonedTarget: any | undefined = undefined;
|
||||
const cloneTarget = () => {
|
||||
clonedConfig = clonedConfig ? clonedConfig : deepClone(config);
|
||||
clonedTarget = clonedTarget ? clonedTarget : lookUp(clonedConfig, accessor);
|
||||
};
|
||||
return isObject(target) ?
|
||||
new Proxy(target, {
|
||||
get: (target: any, property: string) => {
|
||||
if (typeof property === 'string' && property.toLowerCase() === 'tojson') {
|
||||
cloneTarget();
|
||||
return () => clonedTarget;
|
||||
}
|
||||
if (clonedConfig) {
|
||||
clonedTarget = clonedTarget ? clonedTarget : lookUp(clonedConfig, accessor);
|
||||
return clonedTarget[property];
|
||||
}
|
||||
const result = target[property];
|
||||
if (typeof property === 'string') {
|
||||
return cloneOnWriteProxy(result, `${accessor}.${property}`);
|
||||
}
|
||||
return result;
|
||||
},
|
||||
set: (_target: any, property: string, value: any) => {
|
||||
cloneTarget();
|
||||
if (clonedTarget) {
|
||||
clonedTarget[property] = value;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
deleteProperty: (_target: any, property: string) => {
|
||||
cloneTarget();
|
||||
if (clonedTarget) {
|
||||
delete clonedTarget[property];
|
||||
}
|
||||
return true;
|
||||
},
|
||||
defineProperty: (_target: any, property: string, descriptor: any) => {
|
||||
cloneTarget();
|
||||
if (clonedTarget) {
|
||||
Object.defineProperty(clonedTarget, property, descriptor);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}) : target;
|
||||
};
|
||||
result = cloneOnWriteProxy(result, key);
|
||||
}
|
||||
return result;
|
||||
},
|
||||
update: (key: string, value: any, arg: ExtHostConfigurationTarget | boolean) => {
|
||||
key = section ? `${section}.${key}` : key;
|
||||
const target = parseConfigurationTarget(arg);
|
||||
if (value !== undefined) {
|
||||
return this._proxy.$updateConfigurationOption(target, key, value, resource);
|
||||
} else {
|
||||
return this._proxy.$removeConfigurationOption(target, key, resource);
|
||||
}
|
||||
},
|
||||
inspect: <T>(key: string): ConfigurationInspect<T> | undefined => {
|
||||
key = section ? `${section}.${key}` : key;
|
||||
const config = deepClone(this._configuration.inspect<T>(key, { resource }, this._extHostWorkspace.workspace));
|
||||
if (config) {
|
||||
return {
|
||||
key,
|
||||
defaultValue: config.default,
|
||||
globalValue: config.user,
|
||||
workspaceValue: config.workspace,
|
||||
workspaceFolderValue: config.workspaceFolder
|
||||
};
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
if (typeof config === 'object') {
|
||||
mixin(result, config, false);
|
||||
}
|
||||
|
||||
return <vscode.WorkspaceConfiguration>Object.freeze(result);
|
||||
}
|
||||
|
||||
private _toReadonlyValue(result: any): any {
|
||||
const readonlyProxy = (target: any): any => {
|
||||
return isObject(target) ?
|
||||
new Proxy(target, {
|
||||
get: (target: any, property: string) => readonlyProxy(target[property]),
|
||||
set: (_target: any, property: string, _value: any) => { throw new Error(`TypeError: Cannot assign to read only property '${property}' of object`); },
|
||||
deleteProperty: (_target: any, property: string) => { throw new Error(`TypeError: Cannot delete read only property '${property}' of object`); },
|
||||
defineProperty: (_target: any, property: string) => { throw new Error(`TypeError: Cannot define property '${property}' for a readonly object`); },
|
||||
setPrototypeOf: (_target: any) => { throw new Error(`TypeError: Cannot set prototype for a readonly object`); },
|
||||
isExtensible: () => false,
|
||||
preventExtensions: () => true
|
||||
}) : target;
|
||||
};
|
||||
return readonlyProxy(result);
|
||||
}
|
||||
|
||||
private _validateConfigurationAccess(key: string, resource: URI | undefined, extensionId?: ExtensionIdentifier): void {
|
||||
const scope = OVERRIDE_PROPERTY_PATTERN.test(key) ? ConfigurationScope.RESOURCE : this._configurationScopes[key];
|
||||
const extensionIdText = extensionId ? `[${extensionId.value}] ` : '';
|
||||
if (ConfigurationScope.RESOURCE === scope) {
|
||||
if (resource === undefined) {
|
||||
console.warn(`${extensionIdText}Accessing a resource scoped configuration without providing a resource is not expected. To get the effective value for '${key}', provide the URI of a resource or 'null' for any resource.`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (ConfigurationScope.WINDOW === scope) {
|
||||
if (resource) {
|
||||
console.warn(`${extensionIdText}Accessing a window scoped configuration for a resource is not expected. To associate '${key}' to a resource, define its scope to 'resource' in configuration contributions in 'package.json'.`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private _toConfigurationChangeEvent(data: IWorkspaceConfigurationChangeEventData): vscode.ConfigurationChangeEvent {
|
||||
const changedConfiguration = new ConfigurationModel(data.changedConfiguration.contents, data.changedConfiguration.keys, data.changedConfiguration.overrides);
|
||||
const changedConfigurationByResource: ResourceMap<ConfigurationModel> = new ResourceMap<ConfigurationModel>();
|
||||
for (const key of Object.keys(data.changedConfigurationByResource)) {
|
||||
const resource = URI.parse(key);
|
||||
const model = data.changedConfigurationByResource[key];
|
||||
changedConfigurationByResource.set(resource, new ConfigurationModel(model.contents, model.keys, model.overrides));
|
||||
}
|
||||
const event = new WorkspaceConfigurationChangeEvent(new ConfigurationChangeEvent(changedConfiguration, changedConfigurationByResource), this._extHostWorkspace.workspace);
|
||||
return Object.freeze({
|
||||
affectsConfiguration: (section: string, resource?: URI) => event.affectsConfiguration(section, resource)
|
||||
});
|
||||
}
|
||||
|
||||
private static parse(data: IConfigurationData): Configuration {
|
||||
const defaultConfiguration = ExtHostConfigProvider.parseConfigurationModel(data.defaults);
|
||||
const userConfiguration = ExtHostConfigProvider.parseConfigurationModel(data.user);
|
||||
const workspaceConfiguration = ExtHostConfigProvider.parseConfigurationModel(data.workspace);
|
||||
const folders: ResourceMap<ConfigurationModel> = Object.keys(data.folders).reduce((result, key) => {
|
||||
result.set(URI.parse(key), ExtHostConfigProvider.parseConfigurationModel(data.folders[key]));
|
||||
return result;
|
||||
}, new ResourceMap<ConfigurationModel>());
|
||||
return new Configuration(defaultConfiguration, userConfiguration, new ConfigurationModel(), workspaceConfiguration, folders, new ConfigurationModel(), new ResourceMap<ConfigurationModel>(), false);
|
||||
}
|
||||
|
||||
private static parseConfigurationModel(model: IConfigurationModel): ConfigurationModel {
|
||||
return new ConfigurationModel(model.contents, model.keys, model.overrides).freeze();
|
||||
}
|
||||
}
|
||||
@@ -16,24 +16,24 @@ import {
|
||||
IMainContext, IBreakpointsDeltaDto, ISourceMultiBreakpointDto, IFunctionBreakpointDto, IDebugSessionDto
|
||||
} from 'vs/workbench/api/common/extHost.protocol';
|
||||
import * as vscode from 'vscode';
|
||||
import { Disposable, Position, Location, SourceBreakpoint, FunctionBreakpoint, DebugAdapterServer, DebugAdapterExecutable } from 'vs/workbench/api/node/extHostTypes';
|
||||
import { Disposable, Position, Location, SourceBreakpoint, FunctionBreakpoint, DebugAdapterServer, DebugAdapterExecutable } from 'vs/workbench/api/common/extHostTypes';
|
||||
import { ExecutableDebugAdapter, SocketDebugAdapter } from 'vs/workbench/contrib/debug/node/debugAdapter';
|
||||
import { AbstractDebugAdapter } from 'vs/workbench/contrib/debug/common/abstractDebugAdapter';
|
||||
import { IExtHostWorkspaceProvider } from 'vs/workbench/api/node/extHostWorkspace';
|
||||
import { IExtHostWorkspaceProvider } from 'vs/workbench/api/common/extHostWorkspace';
|
||||
import { ExtHostExtensionService } from 'vs/workbench/api/node/extHostExtensionService';
|
||||
import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/node/extHostDocumentsAndEditors';
|
||||
import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors';
|
||||
import { ITerminalSettings, IDebuggerContribution, IConfig, IDebugAdapter, IDebugAdapterServer, IDebugAdapterExecutable, IAdapterDescriptor } from 'vs/workbench/contrib/debug/common/debug';
|
||||
import { getTerminalLauncher, hasChildProcesses, prepareCommand } from 'vs/workbench/contrib/debug/node/terminals';
|
||||
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
|
||||
import { AbstractVariableResolverService } from 'vs/workbench/services/configurationResolver/common/variableResolver';
|
||||
import { ExtHostConfiguration, ExtHostConfigProvider } from './extHostConfiguration';
|
||||
import { ExtHostConfiguration, ExtHostConfigProvider } from '../common/extHostConfiguration';
|
||||
import { convertToVSCPaths, convertToDAPaths, isDebuggerMainContribution } from 'vs/workbench/contrib/debug/common/debugUtils';
|
||||
import { ExtHostTerminalService } from 'vs/workbench/api/node/extHostTerminalService';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { ExtHostCommands } from 'vs/workbench/api/node/extHostCommands';
|
||||
import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/node/extensionDescriptionRegistry';
|
||||
import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands';
|
||||
import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry';
|
||||
import { IProcessEnvironment } from 'vs/base/common/platform';
|
||||
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { MainContext, IMainContext, ExtHostDecorationsShape, MainThreadDecorationsShape, DecorationData, DecorationRequest, DecorationReply } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { Disposable } from 'vs/workbench/api/node/extHostTypes';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
|
||||
import { asArray } from 'vs/base/common/arrays';
|
||||
|
||||
interface ProviderData {
|
||||
provider: vscode.DecorationProvider;
|
||||
extensionId: ExtensionIdentifier;
|
||||
}
|
||||
|
||||
export class ExtHostDecorations implements ExtHostDecorationsShape {
|
||||
|
||||
private static _handlePool = 0;
|
||||
|
||||
private readonly _provider = new Map<number, ProviderData>();
|
||||
private readonly _proxy: MainThreadDecorationsShape;
|
||||
|
||||
constructor(mainContext: IMainContext) {
|
||||
this._proxy = mainContext.getProxy(MainContext.MainThreadDecorations);
|
||||
}
|
||||
|
||||
registerDecorationProvider(provider: vscode.DecorationProvider, extensionId: ExtensionIdentifier): vscode.Disposable {
|
||||
const handle = ExtHostDecorations._handlePool++;
|
||||
this._provider.set(handle, { provider, extensionId });
|
||||
this._proxy.$registerDecorationProvider(handle, extensionId.value);
|
||||
|
||||
const listener = provider.onDidChangeDecorations(e => {
|
||||
this._proxy.$onDidChange(handle, !e ? null : asArray(e));
|
||||
});
|
||||
|
||||
return new Disposable(() => {
|
||||
listener.dispose();
|
||||
this._proxy.$unregisterDecorationProvider(handle);
|
||||
this._provider.delete(handle);
|
||||
});
|
||||
}
|
||||
|
||||
$provideDecorations(requests: DecorationRequest[], token: CancellationToken): Promise<DecorationReply> {
|
||||
const result: DecorationReply = Object.create(null);
|
||||
return Promise.all(requests.map(request => {
|
||||
const { handle, uri, id } = request;
|
||||
const entry = this._provider.get(handle);
|
||||
if (!entry) {
|
||||
// might have been unregistered in the meantime
|
||||
return undefined;
|
||||
}
|
||||
const { provider, extensionId } = entry;
|
||||
return Promise.resolve(provider.provideDecoration(URI.revive(uri), token)).then(data => {
|
||||
if (data && data.letter && data.letter.length !== 1) {
|
||||
console.warn(`INVALID decoration from extension '${extensionId.value}'. The 'letter' must be set and be one character, not '${data.letter}'.`);
|
||||
}
|
||||
if (data) {
|
||||
result[id] = <DecorationData>[data.priority, data.bubble, data.title, data.letter, data.color, data.source];
|
||||
|
||||
}
|
||||
}, err => {
|
||||
console.error(err);
|
||||
});
|
||||
|
||||
})).then(() => {
|
||||
return result;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,314 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { localize } from 'vs/nls';
|
||||
import { IMarkerData, MarkerSeverity } from 'vs/platform/markers/common/markers';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import * as vscode from 'vscode';
|
||||
import { MainContext, MainThreadDiagnosticsShape, ExtHostDiagnosticsShape, IMainContext } from '../common/extHost.protocol';
|
||||
import { DiagnosticSeverity } from './extHostTypes';
|
||||
import * as converter from './extHostTypeConverters';
|
||||
import { mergeSort } from 'vs/base/common/arrays';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { keys } from 'vs/base/common/map';
|
||||
|
||||
export class DiagnosticCollection implements vscode.DiagnosticCollection {
|
||||
|
||||
private readonly _name: string;
|
||||
private readonly _owner: string;
|
||||
private readonly _maxDiagnosticsPerFile: number;
|
||||
private readonly _onDidChangeDiagnostics: Emitter<(vscode.Uri | string)[]>;
|
||||
private readonly _proxy: MainThreadDiagnosticsShape;
|
||||
|
||||
private _isDisposed = false;
|
||||
private _data = new Map<string, vscode.Diagnostic[]>();
|
||||
|
||||
constructor(name: string, owner: string, maxDiagnosticsPerFile: number, proxy: MainThreadDiagnosticsShape, onDidChangeDiagnostics: Emitter<(vscode.Uri | string)[]>) {
|
||||
this._name = name;
|
||||
this._owner = owner;
|
||||
this._maxDiagnosticsPerFile = maxDiagnosticsPerFile;
|
||||
this._proxy = proxy;
|
||||
this._onDidChangeDiagnostics = onDidChangeDiagnostics;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
if (!this._isDisposed) {
|
||||
this._onDidChangeDiagnostics.fire(keys(this._data));
|
||||
this._proxy.$clear(this._owner);
|
||||
this._data = undefined!;
|
||||
this._isDisposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
get name(): string {
|
||||
this._checkDisposed();
|
||||
return this._name;
|
||||
}
|
||||
|
||||
set(uri: vscode.Uri, diagnostics: vscode.Diagnostic[]): void;
|
||||
set(entries: [vscode.Uri, vscode.Diagnostic[]][]): void;
|
||||
set(first: vscode.Uri | [vscode.Uri, vscode.Diagnostic[]][], diagnostics?: vscode.Diagnostic[]) {
|
||||
|
||||
if (!first) {
|
||||
// this set-call is a clear-call
|
||||
this.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
// the actual implementation for #set
|
||||
|
||||
this._checkDisposed();
|
||||
let toSync: vscode.Uri[] = [];
|
||||
|
||||
if (first instanceof URI) {
|
||||
|
||||
if (!diagnostics) {
|
||||
// remove this entry
|
||||
this.delete(first);
|
||||
return;
|
||||
}
|
||||
|
||||
// update single row
|
||||
this._data.set(first.toString(), diagnostics.slice());
|
||||
toSync = [first];
|
||||
|
||||
} else if (Array.isArray(first)) {
|
||||
// update many rows
|
||||
toSync = [];
|
||||
let lastUri: vscode.Uri | undefined;
|
||||
|
||||
// ensure stable-sort
|
||||
mergeSort(first, DiagnosticCollection._compareIndexedTuplesByUri);
|
||||
|
||||
for (const tuple of first) {
|
||||
const [uri, diagnostics] = tuple;
|
||||
if (!lastUri || uri.toString() !== lastUri.toString()) {
|
||||
if (lastUri && this._data.get(lastUri.toString())!.length === 0) {
|
||||
this._data.delete(lastUri.toString());
|
||||
}
|
||||
lastUri = uri;
|
||||
toSync.push(uri);
|
||||
this._data.set(uri.toString(), []);
|
||||
}
|
||||
|
||||
if (!diagnostics) {
|
||||
// [Uri, undefined] means clear this
|
||||
const currentDiagnostics = this._data.get(uri.toString());
|
||||
if (currentDiagnostics) {
|
||||
currentDiagnostics.length = 0;
|
||||
}
|
||||
} else {
|
||||
const currentDiagnostics = this._data.get(uri.toString());
|
||||
if (currentDiagnostics) {
|
||||
currentDiagnostics.push(...diagnostics);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// send event for extensions
|
||||
this._onDidChangeDiagnostics.fire(toSync);
|
||||
|
||||
// compute change and send to main side
|
||||
const entries: [URI, IMarkerData[]][] = [];
|
||||
for (let uri of toSync) {
|
||||
let marker: IMarkerData[] = [];
|
||||
const diagnostics = this._data.get(uri.toString());
|
||||
if (diagnostics) {
|
||||
|
||||
// no more than N diagnostics per file
|
||||
if (diagnostics.length > this._maxDiagnosticsPerFile) {
|
||||
marker = [];
|
||||
const order = [DiagnosticSeverity.Error, DiagnosticSeverity.Warning, DiagnosticSeverity.Information, DiagnosticSeverity.Hint];
|
||||
orderLoop: for (let i = 0; i < 4; i++) {
|
||||
for (let diagnostic of diagnostics) {
|
||||
if (diagnostic.severity === order[i]) {
|
||||
const len = marker.push(converter.Diagnostic.from(diagnostic));
|
||||
if (len === this._maxDiagnosticsPerFile) {
|
||||
break orderLoop;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// add 'signal' marker for showing omitted errors/warnings
|
||||
marker.push({
|
||||
severity: MarkerSeverity.Info,
|
||||
message: localize({ key: 'limitHit', comment: ['amount of errors/warning skipped due to limits'] }, "Not showing {0} further errors and warnings.", diagnostics.length - this._maxDiagnosticsPerFile),
|
||||
startLineNumber: marker[marker.length - 1].startLineNumber,
|
||||
startColumn: marker[marker.length - 1].startColumn,
|
||||
endLineNumber: marker[marker.length - 1].endLineNumber,
|
||||
endColumn: marker[marker.length - 1].endColumn
|
||||
});
|
||||
} else {
|
||||
marker = diagnostics.map(diag => converter.Diagnostic.from(diag));
|
||||
}
|
||||
}
|
||||
|
||||
entries.push([uri, marker]);
|
||||
}
|
||||
|
||||
this._proxy.$changeMany(this._owner, entries);
|
||||
}
|
||||
|
||||
delete(uri: vscode.Uri): void {
|
||||
this._checkDisposed();
|
||||
this._onDidChangeDiagnostics.fire([uri]);
|
||||
this._data.delete(uri.toString());
|
||||
this._proxy.$changeMany(this._owner, [[uri, undefined]]);
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this._checkDisposed();
|
||||
this._onDidChangeDiagnostics.fire(keys(this._data));
|
||||
this._data.clear();
|
||||
this._proxy.$clear(this._owner);
|
||||
}
|
||||
|
||||
forEach(callback: (uri: URI, diagnostics: vscode.Diagnostic[], collection: DiagnosticCollection) => any, thisArg?: any): void {
|
||||
this._checkDisposed();
|
||||
this._data.forEach((value, key) => {
|
||||
const uri = URI.parse(key);
|
||||
callback.apply(thisArg, [uri, this.get(uri), this]);
|
||||
});
|
||||
}
|
||||
|
||||
get(uri: URI): vscode.Diagnostic[] {
|
||||
this._checkDisposed();
|
||||
const result = this._data.get(uri.toString());
|
||||
if (Array.isArray(result)) {
|
||||
return <vscode.Diagnostic[]>Object.freeze(result.slice(0));
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
has(uri: URI): boolean {
|
||||
this._checkDisposed();
|
||||
return Array.isArray(this._data.get(uri.toString()));
|
||||
}
|
||||
|
||||
private _checkDisposed() {
|
||||
if (this._isDisposed) {
|
||||
throw new Error('illegal state - object is disposed');
|
||||
}
|
||||
}
|
||||
|
||||
private static _compareIndexedTuplesByUri(a: [vscode.Uri, vscode.Diagnostic[]], b: [vscode.Uri, vscode.Diagnostic[]]): number {
|
||||
if (a[0].toString() < b[0].toString()) {
|
||||
return -1;
|
||||
} else if (a[0].toString() > b[0].toString()) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class ExtHostDiagnostics implements ExtHostDiagnosticsShape {
|
||||
|
||||
private static _idPool: number = 0;
|
||||
private static readonly _maxDiagnosticsPerFile: number = 1000;
|
||||
|
||||
private readonly _proxy: MainThreadDiagnosticsShape;
|
||||
private readonly _collections = new Map<string, DiagnosticCollection>();
|
||||
private readonly _onDidChangeDiagnostics = new Emitter<(vscode.Uri | string)[]>();
|
||||
|
||||
static _debouncer(last: (vscode.Uri | string)[], current: (vscode.Uri | string)[]): (vscode.Uri | string)[] {
|
||||
if (!last) {
|
||||
return current;
|
||||
} else {
|
||||
return last.concat(current);
|
||||
}
|
||||
}
|
||||
|
||||
static _mapper(last: (vscode.Uri | string)[]): { uris: vscode.Uri[] } {
|
||||
const uris: vscode.Uri[] = [];
|
||||
const map = new Set<string>();
|
||||
for (const uri of last) {
|
||||
if (typeof uri === 'string') {
|
||||
if (!map.has(uri)) {
|
||||
map.add(uri);
|
||||
uris.push(URI.parse(uri));
|
||||
}
|
||||
} else {
|
||||
if (!map.has(uri.toString())) {
|
||||
map.add(uri.toString());
|
||||
uris.push(uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
Object.freeze(uris);
|
||||
return { uris };
|
||||
}
|
||||
|
||||
readonly onDidChangeDiagnostics: Event<vscode.DiagnosticChangeEvent> = Event.map(Event.debounce(this._onDidChangeDiagnostics.event, ExtHostDiagnostics._debouncer, 50), ExtHostDiagnostics._mapper);
|
||||
|
||||
constructor(mainContext: IMainContext) {
|
||||
this._proxy = mainContext.getProxy(MainContext.MainThreadDiagnostics);
|
||||
}
|
||||
|
||||
createDiagnosticCollection(name?: string): vscode.DiagnosticCollection {
|
||||
let { _collections, _proxy, _onDidChangeDiagnostics } = this;
|
||||
let owner: string;
|
||||
if (!name) {
|
||||
name = '_generated_diagnostic_collection_name_#' + ExtHostDiagnostics._idPool++;
|
||||
owner = name;
|
||||
} else if (!_collections.has(name)) {
|
||||
owner = name;
|
||||
} else {
|
||||
console.warn(`DiagnosticCollection with name '${name}' does already exist.`);
|
||||
do {
|
||||
owner = name + ExtHostDiagnostics._idPool++;
|
||||
} while (_collections.has(owner));
|
||||
}
|
||||
|
||||
const result = new class extends DiagnosticCollection {
|
||||
constructor() {
|
||||
super(name!, owner, ExtHostDiagnostics._maxDiagnosticsPerFile, _proxy, _onDidChangeDiagnostics);
|
||||
_collections.set(owner, this);
|
||||
}
|
||||
dispose() {
|
||||
super.dispose();
|
||||
_collections.delete(owner);
|
||||
}
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
getDiagnostics(resource: vscode.Uri): vscode.Diagnostic[];
|
||||
getDiagnostics(): [vscode.Uri, vscode.Diagnostic[]][];
|
||||
getDiagnostics(resource?: vscode.Uri): vscode.Diagnostic[] | [vscode.Uri, vscode.Diagnostic[]][];
|
||||
getDiagnostics(resource?: vscode.Uri): vscode.Diagnostic[] | [vscode.Uri, vscode.Diagnostic[]][] {
|
||||
if (resource) {
|
||||
return this._getDiagnostics(resource);
|
||||
} else {
|
||||
const index = new Map<string, number>();
|
||||
const res: [vscode.Uri, vscode.Diagnostic[]][] = [];
|
||||
this._collections.forEach(collection => {
|
||||
collection.forEach((uri, diagnostics) => {
|
||||
let idx = index.get(uri.toString());
|
||||
if (typeof idx === 'undefined') {
|
||||
idx = res.length;
|
||||
index.set(uri.toString(), idx);
|
||||
res.push([uri, []]);
|
||||
}
|
||||
res[idx][1] = res[idx][1].concat(...diagnostics);
|
||||
});
|
||||
});
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
private _getDiagnostics(resource: vscode.Uri): vscode.Diagnostic[] {
|
||||
let res: vscode.Diagnostic[] = [];
|
||||
this._collections.forEach(collection => {
|
||||
if (collection.has(resource)) {
|
||||
res = res.concat(collection.get(resource));
|
||||
}
|
||||
});
|
||||
return res;
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { MainContext, MainThreadDiaglogsShape, IMainContext } from 'vs/workbench/api/common/extHost.protocol';
|
||||
|
||||
export class ExtHostDialogs {
|
||||
|
||||
private readonly _proxy: MainThreadDiaglogsShape;
|
||||
|
||||
constructor(mainContext: IMainContext) {
|
||||
this._proxy = mainContext.getProxy(MainContext.MainThreadDialogs);
|
||||
}
|
||||
|
||||
showOpenDialog(options: vscode.OpenDialogOptions): Promise<URI[] | undefined> {
|
||||
return this._proxy.$showOpenDialog(options).then(filepaths => {
|
||||
return filepaths ? filepaths.map(URI.revive) : undefined;
|
||||
});
|
||||
}
|
||||
|
||||
showSaveDialog(options: vscode.SaveDialogOptions): Promise<URI | undefined> {
|
||||
return this._proxy.$showSaveDialog(options).then(filepath => {
|
||||
return filepath ? URI.revive(filepath) : undefined;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Disposable } from 'vs/workbench/api/node/extHostTypes';
|
||||
import * as vscode from 'vscode';
|
||||
import { MainContext, ExtHostDocumentContentProvidersShape, MainThreadDocumentContentProvidersShape, IMainContext } from '../common/extHost.protocol';
|
||||
import { ExtHostDocumentsAndEditors } from './extHostDocumentsAndEditors';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
|
||||
export class ExtHostDocumentContentProvider implements ExtHostDocumentContentProvidersShape {
|
||||
|
||||
private static _handlePool = 0;
|
||||
|
||||
private readonly _documentContentProviders = new Map<number, vscode.TextDocumentContentProvider>();
|
||||
private readonly _proxy: MainThreadDocumentContentProvidersShape;
|
||||
|
||||
constructor(
|
||||
mainContext: IMainContext,
|
||||
private readonly _documentsAndEditors: ExtHostDocumentsAndEditors,
|
||||
private readonly _logService: ILogService,
|
||||
) {
|
||||
this._proxy = mainContext.getProxy(MainContext.MainThreadDocumentContentProviders);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
// todo@joh
|
||||
}
|
||||
|
||||
registerTextDocumentContentProvider(scheme: string, provider: vscode.TextDocumentContentProvider): vscode.Disposable {
|
||||
// todo@remote
|
||||
// check with scheme from fs-providers!
|
||||
if (scheme === Schemas.file || scheme === Schemas.untitled) {
|
||||
throw new Error(`scheme '${scheme}' already registered`);
|
||||
}
|
||||
|
||||
const handle = ExtHostDocumentContentProvider._handlePool++;
|
||||
|
||||
this._documentContentProviders.set(handle, provider);
|
||||
this._proxy.$registerTextContentProvider(handle, scheme);
|
||||
|
||||
let subscription: IDisposable | undefined;
|
||||
if (typeof provider.onDidChange === 'function') {
|
||||
subscription = provider.onDidChange(uri => {
|
||||
if (uri.scheme !== scheme) {
|
||||
this._logService.warn(`Provider for scheme '${scheme}' is firing event for schema '${uri.scheme}' which will be IGNORED`);
|
||||
return;
|
||||
}
|
||||
if (this._documentsAndEditors.getDocument(uri)) {
|
||||
this.$provideTextDocumentContent(handle, uri).then(value => {
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
|
||||
const document = this._documentsAndEditors.getDocument(uri);
|
||||
if (!document) {
|
||||
// disposed in the meantime
|
||||
return;
|
||||
}
|
||||
|
||||
// create lines and compare
|
||||
const lines = value.split(/\r\n|\r|\n/);
|
||||
|
||||
// broadcast event when content changed
|
||||
if (!document.equalLines(lines)) {
|
||||
return this._proxy.$onVirtualDocumentChange(uri, value);
|
||||
}
|
||||
|
||||
}, onUnexpectedError);
|
||||
}
|
||||
});
|
||||
}
|
||||
return new Disposable(() => {
|
||||
if (this._documentContentProviders.delete(handle)) {
|
||||
this._proxy.$unregisterTextContentProvider(handle);
|
||||
}
|
||||
if (subscription) {
|
||||
subscription.dispose();
|
||||
subscription = undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$provideTextDocumentContent(handle: number, uri: UriComponents): Promise<string | null | undefined> {
|
||||
const provider = this._documentContentProviders.get(handle);
|
||||
if (!provider) {
|
||||
return Promise.reject(new Error(`unsupported uri-scheme: ${uri.scheme}`));
|
||||
}
|
||||
return Promise.resolve(provider.provideTextDocumentContent(URI.revive(uri), CancellationToken.None));
|
||||
}
|
||||
}
|
||||
@@ -1,266 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ok } from 'vs/base/common/assert';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { regExpLeadsToEndlessLoop } from 'vs/base/common/strings';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { MirrorTextModel } from 'vs/editor/common/model/mirrorTextModel';
|
||||
import { ensureValidWordDefinition, getWordAtText } from 'vs/editor/common/model/wordHelper';
|
||||
import { MainThreadDocumentsShape } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { EndOfLine, Position, Range } from 'vs/workbench/api/node/extHostTypes';
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
const _modeId2WordDefinition = new Map<string, RegExp>();
|
||||
export function setWordDefinitionFor(modeId: string, wordDefinition: RegExp | undefined): void {
|
||||
_modeId2WordDefinition.set(modeId, wordDefinition);
|
||||
}
|
||||
export function getWordDefinitionFor(modeId: string): RegExp | undefined {
|
||||
return _modeId2WordDefinition.get(modeId);
|
||||
}
|
||||
|
||||
export class ExtHostDocumentData extends MirrorTextModel {
|
||||
|
||||
private _proxy: MainThreadDocumentsShape;
|
||||
private _languageId: string;
|
||||
private _isDirty: boolean;
|
||||
private _document: vscode.TextDocument;
|
||||
private _textLines: vscode.TextLine[] = [];
|
||||
private _isDisposed: boolean = false;
|
||||
|
||||
constructor(proxy: MainThreadDocumentsShape, uri: URI, lines: string[], eol: string,
|
||||
languageId: string, versionId: number, isDirty: boolean
|
||||
) {
|
||||
super(uri, lines, eol, versionId);
|
||||
this._proxy = proxy;
|
||||
this._languageId = languageId;
|
||||
this._isDirty = isDirty;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
// we don't really dispose documents but let
|
||||
// extensions still read from them. some
|
||||
// operations, live saving, will now error tho
|
||||
ok(!this._isDisposed);
|
||||
this._isDisposed = true;
|
||||
this._isDirty = false;
|
||||
}
|
||||
|
||||
equalLines(lines: string[]): boolean {
|
||||
const len = lines.length;
|
||||
if (len !== this._lines.length) {
|
||||
return false;
|
||||
}
|
||||
for (let i = 0; i < len; i++) {
|
||||
if (lines[i] !== this._lines[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
get document(): vscode.TextDocument {
|
||||
if (!this._document) {
|
||||
const data = this;
|
||||
this._document = {
|
||||
get uri() { return data._uri; },
|
||||
get fileName() { return data._uri.fsPath; },
|
||||
get isUntitled() { return data._uri.scheme === Schemas.untitled; },
|
||||
get languageId() { return data._languageId; },
|
||||
get version() { return data._versionId; },
|
||||
get isClosed() { return data._isDisposed; },
|
||||
get isDirty() { return data._isDirty; },
|
||||
save() { return data._save(); },
|
||||
getText(range?) { return range ? data._getTextInRange(range) : data.getText(); },
|
||||
get eol() { return data._eol === '\n' ? EndOfLine.LF : EndOfLine.CRLF; },
|
||||
get lineCount() { return data._lines.length; },
|
||||
lineAt(lineOrPos: number | vscode.Position) { return data._lineAt(lineOrPos); },
|
||||
offsetAt(pos) { return data._offsetAt(pos); },
|
||||
positionAt(offset) { return data._positionAt(offset); },
|
||||
validateRange(ran) { return data._validateRange(ran); },
|
||||
validatePosition(pos) { return data._validatePosition(pos); },
|
||||
getWordRangeAtPosition(pos, regexp?) { return data._getWordRangeAtPosition(pos, regexp); }
|
||||
};
|
||||
}
|
||||
return Object.freeze(this._document);
|
||||
}
|
||||
|
||||
_acceptLanguageId(newLanguageId: string): void {
|
||||
ok(!this._isDisposed);
|
||||
this._languageId = newLanguageId;
|
||||
}
|
||||
|
||||
_acceptIsDirty(isDirty: boolean): void {
|
||||
ok(!this._isDisposed);
|
||||
this._isDirty = isDirty;
|
||||
}
|
||||
|
||||
private _save(): Promise<boolean> {
|
||||
if (this._isDisposed) {
|
||||
return Promise.reject(new Error('Document has been closed'));
|
||||
}
|
||||
return this._proxy.$trySaveDocument(this._uri);
|
||||
}
|
||||
|
||||
private _getTextInRange(_range: vscode.Range): string {
|
||||
const range = this._validateRange(_range);
|
||||
|
||||
if (range.isEmpty) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (range.isSingleLine) {
|
||||
return this._lines[range.start.line].substring(range.start.character, range.end.character);
|
||||
}
|
||||
|
||||
const lineEnding = this._eol,
|
||||
startLineIndex = range.start.line,
|
||||
endLineIndex = range.end.line,
|
||||
resultLines: string[] = [];
|
||||
|
||||
resultLines.push(this._lines[startLineIndex].substring(range.start.character));
|
||||
for (let i = startLineIndex + 1; i < endLineIndex; i++) {
|
||||
resultLines.push(this._lines[i]);
|
||||
}
|
||||
resultLines.push(this._lines[endLineIndex].substring(0, range.end.character));
|
||||
|
||||
return resultLines.join(lineEnding);
|
||||
}
|
||||
|
||||
private _lineAt(lineOrPosition: number | vscode.Position): vscode.TextLine {
|
||||
|
||||
let line: number | undefined;
|
||||
if (lineOrPosition instanceof Position) {
|
||||
line = lineOrPosition.line;
|
||||
} else if (typeof lineOrPosition === 'number') {
|
||||
line = lineOrPosition;
|
||||
}
|
||||
|
||||
if (typeof line !== 'number' || line < 0 || line >= this._lines.length) {
|
||||
throw new Error('Illegal value for `line`');
|
||||
}
|
||||
|
||||
let result = this._textLines[line];
|
||||
if (!result || result.lineNumber !== line || result.text !== this._lines[line]) {
|
||||
|
||||
const text = this._lines[line];
|
||||
const firstNonWhitespaceCharacterIndex = /^(\s*)/.exec(text)![1].length;
|
||||
const range = new Range(line, 0, line, text.length);
|
||||
const rangeIncludingLineBreak = line < this._lines.length - 1
|
||||
? new Range(line, 0, line + 1, 0)
|
||||
: range;
|
||||
|
||||
result = Object.freeze({
|
||||
lineNumber: line,
|
||||
range,
|
||||
rangeIncludingLineBreak,
|
||||
text,
|
||||
firstNonWhitespaceCharacterIndex, //TODO@api, rename to 'leadingWhitespaceLength'
|
||||
isEmptyOrWhitespace: firstNonWhitespaceCharacterIndex === text.length
|
||||
});
|
||||
|
||||
this._textLines[line] = result;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private _offsetAt(position: vscode.Position): number {
|
||||
position = this._validatePosition(position);
|
||||
this._ensureLineStarts();
|
||||
return this._lineStarts!.getAccumulatedValue(position.line - 1) + position.character;
|
||||
}
|
||||
|
||||
private _positionAt(offset: number): vscode.Position {
|
||||
offset = Math.floor(offset);
|
||||
offset = Math.max(0, offset);
|
||||
|
||||
this._ensureLineStarts();
|
||||
const out = this._lineStarts!.getIndexOf(offset);
|
||||
|
||||
const lineLength = this._lines[out.index].length;
|
||||
|
||||
// Ensure we return a valid position
|
||||
return new Position(out.index, Math.min(out.remainder, lineLength));
|
||||
}
|
||||
|
||||
// ---- range math
|
||||
|
||||
private _validateRange(range: vscode.Range): vscode.Range {
|
||||
if (!(range instanceof Range)) {
|
||||
throw new Error('Invalid argument');
|
||||
}
|
||||
|
||||
const start = this._validatePosition(range.start);
|
||||
const end = this._validatePosition(range.end);
|
||||
|
||||
if (start === range.start && end === range.end) {
|
||||
return range;
|
||||
}
|
||||
return new Range(start.line, start.character, end.line, end.character);
|
||||
}
|
||||
|
||||
private _validatePosition(position: vscode.Position): vscode.Position {
|
||||
if (!(position instanceof Position)) {
|
||||
throw new Error('Invalid argument');
|
||||
}
|
||||
|
||||
let { line, character } = position;
|
||||
let hasChanged = false;
|
||||
|
||||
if (line < 0) {
|
||||
line = 0;
|
||||
character = 0;
|
||||
hasChanged = true;
|
||||
}
|
||||
else if (line >= this._lines.length) {
|
||||
line = this._lines.length - 1;
|
||||
character = this._lines[line].length;
|
||||
hasChanged = true;
|
||||
}
|
||||
else {
|
||||
const maxCharacter = this._lines[line].length;
|
||||
if (character < 0) {
|
||||
character = 0;
|
||||
hasChanged = true;
|
||||
}
|
||||
else if (character > maxCharacter) {
|
||||
character = maxCharacter;
|
||||
hasChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasChanged) {
|
||||
return position;
|
||||
}
|
||||
return new Position(line, character);
|
||||
}
|
||||
|
||||
private _getWordRangeAtPosition(_position: vscode.Position, regexp?: RegExp): vscode.Range | undefined {
|
||||
const position = this._validatePosition(_position);
|
||||
|
||||
if (!regexp) {
|
||||
// use default when custom-regexp isn't provided
|
||||
regexp = getWordDefinitionFor(this._languageId);
|
||||
|
||||
} else if (regExpLeadsToEndlessLoop(regexp)) {
|
||||
// use default when custom-regexp is bad
|
||||
console.warn(`[getWordRangeAtPosition]: ignoring custom regexp '${regexp.source}' because it matches the empty string.`);
|
||||
regexp = getWordDefinitionFor(this._languageId);
|
||||
}
|
||||
|
||||
const wordAtText = getWordAtText(
|
||||
position.character + 1,
|
||||
ensureValidWordDefinition(regexp),
|
||||
this._lines[position.line],
|
||||
0
|
||||
);
|
||||
|
||||
if (wordAtText) {
|
||||
return new Range(position.line, wordAtText.startColumn - 1, position.line, wordAtText.endColumn - 1);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
@@ -1,175 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import { sequence } from 'vs/base/common/async';
|
||||
import { illegalState } from 'vs/base/common/errors';
|
||||
import { ExtHostDocumentSaveParticipantShape, MainThreadTextEditorsShape, ResourceTextEditDto } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { TextEdit } from 'vs/workbench/api/node/extHostTypes';
|
||||
import { Range, TextDocumentSaveReason, EndOfLine } from 'vs/workbench/api/node/extHostTypeConverters';
|
||||
import { ExtHostDocuments } from 'vs/workbench/api/node/extHostDocuments';
|
||||
import { SaveReason } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import * as vscode from 'vscode';
|
||||
import { LinkedList } from 'vs/base/common/linkedList';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
|
||||
type Listener = [Function, any, IExtensionDescription];
|
||||
|
||||
export class ExtHostDocumentSaveParticipant implements ExtHostDocumentSaveParticipantShape {
|
||||
|
||||
private readonly _callbacks = new LinkedList<Listener>();
|
||||
private readonly _badListeners = new WeakMap<Function, number>();
|
||||
|
||||
constructor(
|
||||
private readonly _logService: ILogService,
|
||||
private readonly _documents: ExtHostDocuments,
|
||||
private readonly _mainThreadEditors: MainThreadTextEditorsShape,
|
||||
private readonly _thresholds: { timeout: number; errors: number; } = { timeout: 1500, errors: 3 }
|
||||
) {
|
||||
//
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._callbacks.clear();
|
||||
}
|
||||
|
||||
getOnWillSaveTextDocumentEvent(extension: IExtensionDescription): Event<vscode.TextDocumentWillSaveEvent> {
|
||||
return (listener, thisArg, disposables) => {
|
||||
const remove = this._callbacks.push([listener, thisArg, extension]);
|
||||
const result = { dispose: remove };
|
||||
if (Array.isArray(disposables)) {
|
||||
disposables.push(result);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
$participateInSave(data: UriComponents, reason: SaveReason): Promise<boolean[]> {
|
||||
const resource = URI.revive(data);
|
||||
const entries = this._callbacks.toArray();
|
||||
|
||||
let didTimeout = false;
|
||||
const didTimeoutHandle = setTimeout(() => didTimeout = true, this._thresholds.timeout);
|
||||
|
||||
const promise = sequence(entries.map(listener => {
|
||||
return () => {
|
||||
|
||||
if (didTimeout) {
|
||||
// timeout - no more listeners
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const document = this._documents.getDocument(resource);
|
||||
return this._deliverEventAsyncAndBlameBadListeners(listener, <any>{ document, reason: TextDocumentSaveReason.to(reason) });
|
||||
};
|
||||
}));
|
||||
return promise.finally(() => clearTimeout(didTimeoutHandle));
|
||||
}
|
||||
|
||||
private _deliverEventAsyncAndBlameBadListeners([listener, thisArg, extension]: Listener, stubEvent: vscode.TextDocumentWillSaveEvent): Promise<any> {
|
||||
const errors = this._badListeners.get(listener);
|
||||
if (typeof errors === 'number' && errors > this._thresholds.errors) {
|
||||
// bad listener - ignore
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
return this._deliverEventAsync(extension, listener, thisArg, stubEvent).then(() => {
|
||||
// don't send result across the wire
|
||||
return true;
|
||||
|
||||
}, err => {
|
||||
|
||||
this._logService.error(`onWillSaveTextDocument-listener from extension '${extension.identifier.value}' threw ERROR`);
|
||||
this._logService.error(err);
|
||||
|
||||
if (!(err instanceof Error) || (<Error>err).message !== 'concurrent_edits') {
|
||||
const errors = this._badListeners.get(listener);
|
||||
this._badListeners.set(listener, !errors ? 1 : errors + 1);
|
||||
|
||||
if (typeof errors === 'number' && errors > this._thresholds.errors) {
|
||||
this._logService.info(`onWillSaveTextDocument-listener from extension '${extension.identifier.value}' will now be IGNORED because of timeouts and/or errors`);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
private _deliverEventAsync(extension: IExtensionDescription, listener: Function, thisArg: any, stubEvent: vscode.TextDocumentWillSaveEvent): Promise<any> {
|
||||
|
||||
const promises: Promise<vscode.TextEdit[]>[] = [];
|
||||
|
||||
const t1 = Date.now();
|
||||
const { document, reason } = stubEvent;
|
||||
const { version } = document;
|
||||
|
||||
const event = Object.freeze(<vscode.TextDocumentWillSaveEvent>{
|
||||
document,
|
||||
reason,
|
||||
waitUntil(p: Promise<any | vscode.TextEdit[]>) {
|
||||
if (Object.isFrozen(promises)) {
|
||||
throw illegalState('waitUntil can not be called async');
|
||||
}
|
||||
promises.push(Promise.resolve(p));
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
// fire event
|
||||
listener.apply(thisArg, [event]);
|
||||
} catch (err) {
|
||||
return Promise.reject(err);
|
||||
}
|
||||
|
||||
// freeze promises after event call
|
||||
Object.freeze(promises);
|
||||
|
||||
return new Promise<vscode.TextEdit[][]>((resolve, reject) => {
|
||||
// join on all listener promises, reject after timeout
|
||||
const handle = setTimeout(() => reject(new Error('timeout')), this._thresholds.timeout);
|
||||
|
||||
return Promise.all(promises).then(edits => {
|
||||
this._logService.debug(`onWillSaveTextDocument-listener from extension '${extension.identifier.value}' finished after ${(Date.now() - t1)}ms`);
|
||||
clearTimeout(handle);
|
||||
resolve(edits);
|
||||
}).catch(err => {
|
||||
clearTimeout(handle);
|
||||
reject(err);
|
||||
});
|
||||
|
||||
}).then(values => {
|
||||
|
||||
const resourceEdit: ResourceTextEditDto = {
|
||||
resource: document.uri,
|
||||
edits: []
|
||||
};
|
||||
|
||||
for (const value of values) {
|
||||
if (Array.isArray(value) && (<vscode.TextEdit[]>value).every(e => e instanceof TextEdit)) {
|
||||
for (const { newText, newEol, range } of value) {
|
||||
resourceEdit.edits.push({
|
||||
range: range && Range.from(range),
|
||||
text: newText,
|
||||
eol: EndOfLine.from(newEol)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// apply edits if any and if document
|
||||
// didn't change somehow in the meantime
|
||||
if (resourceEdit.edits.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (version === document.version) {
|
||||
return this._mainThreadEditors.$tryApplyWorkspaceEdit({ edits: [resourceEdit] });
|
||||
}
|
||||
|
||||
return Promise.reject(new Error('concurrent_edits'));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,164 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 } from 'vs/base/common/lifecycle';
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import { IModelChangedEvent } from 'vs/editor/common/model/mirrorTextModel';
|
||||
import { ExtHostDocumentsShape, IMainContext, MainContext, MainThreadDocumentsShape } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { ExtHostDocumentData, setWordDefinitionFor } from 'vs/workbench/api/node/extHostDocumentData';
|
||||
import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/node/extHostDocumentsAndEditors';
|
||||
import * as TypeConverters from 'vs/workbench/api/node/extHostTypeConverters';
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export class ExtHostDocuments implements ExtHostDocumentsShape {
|
||||
|
||||
private _onDidAddDocument = new Emitter<vscode.TextDocument>();
|
||||
private _onDidRemoveDocument = new Emitter<vscode.TextDocument>();
|
||||
private _onDidChangeDocument = new Emitter<vscode.TextDocumentChangeEvent>();
|
||||
private _onDidSaveDocument = new Emitter<vscode.TextDocument>();
|
||||
|
||||
readonly onDidAddDocument: Event<vscode.TextDocument> = this._onDidAddDocument.event;
|
||||
readonly onDidRemoveDocument: Event<vscode.TextDocument> = this._onDidRemoveDocument.event;
|
||||
readonly onDidChangeDocument: Event<vscode.TextDocumentChangeEvent> = this._onDidChangeDocument.event;
|
||||
readonly onDidSaveDocument: Event<vscode.TextDocument> = this._onDidSaveDocument.event;
|
||||
|
||||
private _toDispose: IDisposable[];
|
||||
private _proxy: MainThreadDocumentsShape;
|
||||
private _documentsAndEditors: ExtHostDocumentsAndEditors;
|
||||
private _documentLoader = new Map<string, Promise<ExtHostDocumentData>>();
|
||||
|
||||
constructor(mainContext: IMainContext, documentsAndEditors: ExtHostDocumentsAndEditors) {
|
||||
this._proxy = mainContext.getProxy(MainContext.MainThreadDocuments);
|
||||
this._documentsAndEditors = documentsAndEditors;
|
||||
|
||||
this._toDispose = [
|
||||
this._documentsAndEditors.onDidRemoveDocuments(documents => {
|
||||
for (const data of documents) {
|
||||
this._onDidRemoveDocument.fire(data.document);
|
||||
}
|
||||
}),
|
||||
this._documentsAndEditors.onDidAddDocuments(documents => {
|
||||
for (const data of documents) {
|
||||
this._onDidAddDocument.fire(data.document);
|
||||
}
|
||||
})
|
||||
];
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
dispose(this._toDispose);
|
||||
}
|
||||
|
||||
public getAllDocumentData(): ExtHostDocumentData[] {
|
||||
return this._documentsAndEditors.allDocuments();
|
||||
}
|
||||
|
||||
public getDocumentData(resource: vscode.Uri): ExtHostDocumentData | undefined {
|
||||
if (!resource) {
|
||||
return undefined;
|
||||
}
|
||||
const data = this._documentsAndEditors.getDocument(resource);
|
||||
if (data) {
|
||||
return data;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public getDocument(resource: vscode.Uri): vscode.TextDocument {
|
||||
const data = this.getDocumentData(resource);
|
||||
if (!data || !data.document) {
|
||||
throw new Error('Unable to retrieve document from URI');
|
||||
}
|
||||
return data.document;
|
||||
}
|
||||
|
||||
public ensureDocumentData(uri: URI): Promise<ExtHostDocumentData> {
|
||||
|
||||
const cached = this._documentsAndEditors.getDocument(uri);
|
||||
if (cached) {
|
||||
return Promise.resolve(cached);
|
||||
}
|
||||
|
||||
let promise = this._documentLoader.get(uri.toString());
|
||||
if (!promise) {
|
||||
promise = this._proxy.$tryOpenDocument(uri).then(() => {
|
||||
this._documentLoader.delete(uri.toString());
|
||||
return this._documentsAndEditors.getDocument(uri);
|
||||
}, err => {
|
||||
this._documentLoader.delete(uri.toString());
|
||||
return Promise.reject(err);
|
||||
});
|
||||
this._documentLoader.set(uri.toString(), promise);
|
||||
}
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
public createDocumentData(options?: { language?: string; content?: string }): Promise<URI> {
|
||||
return this._proxy.$tryCreateDocument(options).then(data => URI.revive(data));
|
||||
}
|
||||
|
||||
public $acceptModelModeChanged(uriComponents: UriComponents, oldModeId: string, newModeId: string): void {
|
||||
const uri = URI.revive(uriComponents);
|
||||
const data = this._documentsAndEditors.getDocument(uri);
|
||||
if (!data) {
|
||||
throw new Error('unknown document');
|
||||
}
|
||||
// Treat a mode change as a remove + add
|
||||
|
||||
this._onDidRemoveDocument.fire(data.document);
|
||||
data._acceptLanguageId(newModeId);
|
||||
this._onDidAddDocument.fire(data.document);
|
||||
}
|
||||
|
||||
public $acceptModelSaved(uriComponents: UriComponents): void {
|
||||
const uri = URI.revive(uriComponents);
|
||||
const data = this._documentsAndEditors.getDocument(uri);
|
||||
if (!data) {
|
||||
throw new Error('unknown document');
|
||||
}
|
||||
this.$acceptDirtyStateChanged(uriComponents, false);
|
||||
this._onDidSaveDocument.fire(data.document);
|
||||
}
|
||||
|
||||
public $acceptDirtyStateChanged(uriComponents: UriComponents, isDirty: boolean): void {
|
||||
const uri = URI.revive(uriComponents);
|
||||
const data = this._documentsAndEditors.getDocument(uri);
|
||||
if (!data) {
|
||||
throw new Error('unknown document');
|
||||
}
|
||||
data._acceptIsDirty(isDirty);
|
||||
this._onDidChangeDocument.fire({
|
||||
document: data.document,
|
||||
contentChanges: []
|
||||
});
|
||||
}
|
||||
|
||||
public $acceptModelChanged(uriComponents: UriComponents, events: IModelChangedEvent, isDirty: boolean): void {
|
||||
const uri = URI.revive(uriComponents);
|
||||
const data = this._documentsAndEditors.getDocument(uri);
|
||||
if (!data) {
|
||||
throw new Error('unknown document');
|
||||
}
|
||||
data._acceptIsDirty(isDirty);
|
||||
data.onEvents(events);
|
||||
this._onDidChangeDocument.fire({
|
||||
document: data.document,
|
||||
contentChanges: events.changes.map((change) => {
|
||||
return {
|
||||
range: TypeConverters.Range.to(change.range),
|
||||
rangeOffset: change.rangeOffset,
|
||||
rangeLength: change.rangeLength,
|
||||
text: change.text
|
||||
};
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
public setWordDefinitionFor(modeId: string, wordDefinition: RegExp | undefined): void {
|
||||
setWordDefinitionFor(modeId, wordDefinition);
|
||||
}
|
||||
}
|
||||
@@ -1,161 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'vs/base/common/assert';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { dispose } from 'vs/base/common/lifecycle';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { ExtHostDocumentsAndEditorsShape, IDocumentsAndEditorsDelta, IMainContext, MainContext } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { ExtHostDocumentData } from 'vs/workbench/api/node/extHostDocumentData';
|
||||
import { ExtHostTextEditor } from 'vs/workbench/api/node/extHostTextEditor';
|
||||
import * as typeConverters from 'vs/workbench/api/node/extHostTypeConverters';
|
||||
import { Disposable } from 'vs/workbench/api/node/extHostTypes';
|
||||
|
||||
export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsShape {
|
||||
|
||||
private _disposables: Disposable[] = [];
|
||||
|
||||
private _activeEditorId: string | null;
|
||||
|
||||
private readonly _editors = new Map<string, ExtHostTextEditor>();
|
||||
private readonly _documents = new Map<string, ExtHostDocumentData>();
|
||||
|
||||
private readonly _onDidAddDocuments = new Emitter<ExtHostDocumentData[]>();
|
||||
private readonly _onDidRemoveDocuments = new Emitter<ExtHostDocumentData[]>();
|
||||
private readonly _onDidChangeVisibleTextEditors = new Emitter<ExtHostTextEditor[]>();
|
||||
private readonly _onDidChangeActiveTextEditor = new Emitter<ExtHostTextEditor | undefined>();
|
||||
|
||||
readonly onDidAddDocuments: Event<ExtHostDocumentData[]> = this._onDidAddDocuments.event;
|
||||
readonly onDidRemoveDocuments: Event<ExtHostDocumentData[]> = this._onDidRemoveDocuments.event;
|
||||
readonly onDidChangeVisibleTextEditors: Event<ExtHostTextEditor[]> = this._onDidChangeVisibleTextEditors.event;
|
||||
readonly onDidChangeActiveTextEditor: Event<ExtHostTextEditor | undefined> = this._onDidChangeActiveTextEditor.event;
|
||||
|
||||
constructor(
|
||||
private readonly _mainContext: IMainContext,
|
||||
) { }
|
||||
|
||||
dispose() {
|
||||
this._disposables = dispose(this._disposables);
|
||||
}
|
||||
|
||||
$acceptDocumentsAndEditorsDelta(delta: IDocumentsAndEditorsDelta): void {
|
||||
|
||||
const removedDocuments: ExtHostDocumentData[] = [];
|
||||
const addedDocuments: ExtHostDocumentData[] = [];
|
||||
const removedEditors: ExtHostTextEditor[] = [];
|
||||
|
||||
if (delta.removedDocuments) {
|
||||
for (const uriComponent of delta.removedDocuments) {
|
||||
const uri = URI.revive(uriComponent);
|
||||
const id = uri.toString();
|
||||
const data = this._documents.get(id);
|
||||
this._documents.delete(id);
|
||||
if (data) {
|
||||
removedDocuments.push(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (delta.addedDocuments) {
|
||||
for (const data of delta.addedDocuments) {
|
||||
const resource = URI.revive(data.uri);
|
||||
assert.ok(!this._documents.has(resource.toString()), `document '${resource} already exists!'`);
|
||||
|
||||
const documentData = new ExtHostDocumentData(
|
||||
this._mainContext.getProxy(MainContext.MainThreadDocuments),
|
||||
resource,
|
||||
data.lines,
|
||||
data.EOL,
|
||||
data.modeId,
|
||||
data.versionId,
|
||||
data.isDirty
|
||||
);
|
||||
this._documents.set(resource.toString(), documentData);
|
||||
addedDocuments.push(documentData);
|
||||
}
|
||||
}
|
||||
|
||||
if (delta.removedEditors) {
|
||||
for (const id of delta.removedEditors) {
|
||||
const editor = this._editors.get(id);
|
||||
this._editors.delete(id);
|
||||
if (editor) {
|
||||
removedEditors.push(editor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (delta.addedEditors) {
|
||||
for (const data of delta.addedEditors) {
|
||||
const resource = URI.revive(data.documentUri);
|
||||
assert.ok(this._documents.has(resource.toString()), `document '${resource}' does not exist`);
|
||||
assert.ok(!this._editors.has(data.id), `editor '${data.id}' already exists!`);
|
||||
|
||||
const documentData = this._documents.get(resource.toString())!;
|
||||
const editor = new ExtHostTextEditor(
|
||||
this._mainContext.getProxy(MainContext.MainThreadTextEditors),
|
||||
data.id,
|
||||
documentData,
|
||||
data.selections.map(typeConverters.Selection.to),
|
||||
data.options,
|
||||
data.visibleRanges.map(range => typeConverters.Range.to(range)),
|
||||
typeof data.editorPosition === 'number' ? typeConverters.ViewColumn.to(data.editorPosition) : undefined
|
||||
);
|
||||
this._editors.set(data.id, editor);
|
||||
}
|
||||
}
|
||||
|
||||
if (delta.newActiveEditor !== undefined) {
|
||||
assert.ok(delta.newActiveEditor === null || this._editors.has(delta.newActiveEditor), `active editor '${delta.newActiveEditor}' does not exist`);
|
||||
this._activeEditorId = delta.newActiveEditor;
|
||||
}
|
||||
|
||||
dispose(removedDocuments);
|
||||
dispose(removedEditors);
|
||||
|
||||
// now that the internal state is complete, fire events
|
||||
if (delta.removedDocuments) {
|
||||
this._onDidRemoveDocuments.fire(removedDocuments);
|
||||
}
|
||||
if (delta.addedDocuments) {
|
||||
this._onDidAddDocuments.fire(addedDocuments);
|
||||
}
|
||||
|
||||
if (delta.removedEditors || delta.addedEditors) {
|
||||
this._onDidChangeVisibleTextEditors.fire(this.allEditors());
|
||||
}
|
||||
if (delta.newActiveEditor !== undefined) {
|
||||
this._onDidChangeActiveTextEditor.fire(this.activeEditor());
|
||||
}
|
||||
}
|
||||
|
||||
getDocument(uri: URI): ExtHostDocumentData | undefined {
|
||||
return this._documents.get(uri.toString());
|
||||
}
|
||||
|
||||
allDocuments(): ExtHostDocumentData[] {
|
||||
const result: ExtHostDocumentData[] = [];
|
||||
this._documents.forEach(data => result.push(data));
|
||||
return result;
|
||||
}
|
||||
|
||||
getEditor(id: string): ExtHostTextEditor | undefined {
|
||||
return this._editors.get(id);
|
||||
}
|
||||
|
||||
activeEditor(): ExtHostTextEditor | undefined {
|
||||
if (!this._activeEditorId) {
|
||||
return undefined;
|
||||
} else {
|
||||
return this._editors.get(this._activeEditorId);
|
||||
}
|
||||
}
|
||||
|
||||
allEditors(): ExtHostTextEditor[] {
|
||||
const result: ExtHostTextEditor[] = [];
|
||||
this._editors.forEach(data => result.push(data));
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -1,388 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/node/extensionDescriptionRegistry';
|
||||
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
|
||||
import { ExtensionActivationError, MissingDependencyError } from 'vs/workbench/services/extensions/common/extensions';
|
||||
|
||||
const NO_OP_VOID_PROMISE = Promise.resolve<void>(undefined);
|
||||
|
||||
export interface IExtensionMemento {
|
||||
get<T>(key: string, defaultValue: T): T;
|
||||
update(key: string, value: any): Promise<boolean>;
|
||||
}
|
||||
|
||||
export interface IExtensionContext {
|
||||
subscriptions: IDisposable[];
|
||||
workspaceState: IExtensionMemento;
|
||||
globalState: IExtensionMemento;
|
||||
extensionPath: string;
|
||||
storagePath: string;
|
||||
globalStoragePath: string;
|
||||
asAbsolutePath(relativePath: string): string;
|
||||
readonly logPath: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the source code (module) of an extension.
|
||||
*/
|
||||
export interface IExtensionModule {
|
||||
activate?(ctx: IExtensionContext): Promise<IExtensionAPI>;
|
||||
deactivate?(): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the API of an extension (return value of `activate`).
|
||||
*/
|
||||
export interface IExtensionAPI {
|
||||
// _extensionAPIBrand: any;
|
||||
}
|
||||
|
||||
/* __GDPR__FRAGMENT__
|
||||
"ExtensionActivationTimes" : {
|
||||
"startup": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
|
||||
"codeLoadingTime" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
|
||||
"activateCallTime" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
|
||||
"activateResolvedTime" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }
|
||||
}
|
||||
*/
|
||||
export class ExtensionActivationTimes {
|
||||
|
||||
public static readonly NONE = new ExtensionActivationTimes(false, -1, -1, -1);
|
||||
|
||||
public readonly startup: boolean;
|
||||
public readonly codeLoadingTime: number;
|
||||
public readonly activateCallTime: number;
|
||||
public readonly activateResolvedTime: number;
|
||||
|
||||
constructor(startup: boolean, codeLoadingTime: number, activateCallTime: number, activateResolvedTime: number) {
|
||||
this.startup = startup;
|
||||
this.codeLoadingTime = codeLoadingTime;
|
||||
this.activateCallTime = activateCallTime;
|
||||
this.activateResolvedTime = activateResolvedTime;
|
||||
}
|
||||
}
|
||||
|
||||
export class ExtensionActivationTimesBuilder {
|
||||
|
||||
private readonly _startup: boolean;
|
||||
private _codeLoadingStart: number;
|
||||
private _codeLoadingStop: number;
|
||||
private _activateCallStart: number;
|
||||
private _activateCallStop: number;
|
||||
private _activateResolveStart: number;
|
||||
private _activateResolveStop: number;
|
||||
|
||||
constructor(startup: boolean) {
|
||||
this._startup = startup;
|
||||
this._codeLoadingStart = -1;
|
||||
this._codeLoadingStop = -1;
|
||||
this._activateCallStart = -1;
|
||||
this._activateCallStop = -1;
|
||||
this._activateResolveStart = -1;
|
||||
this._activateResolveStop = -1;
|
||||
}
|
||||
|
||||
private _delta(start: number, stop: number): number {
|
||||
if (start === -1 || stop === -1) {
|
||||
return -1;
|
||||
}
|
||||
return stop - start;
|
||||
}
|
||||
|
||||
public build(): ExtensionActivationTimes {
|
||||
return new ExtensionActivationTimes(
|
||||
this._startup,
|
||||
this._delta(this._codeLoadingStart, this._codeLoadingStop),
|
||||
this._delta(this._activateCallStart, this._activateCallStop),
|
||||
this._delta(this._activateResolveStart, this._activateResolveStop)
|
||||
);
|
||||
}
|
||||
|
||||
public codeLoadingStart(): void {
|
||||
this._codeLoadingStart = Date.now();
|
||||
}
|
||||
|
||||
public codeLoadingStop(): void {
|
||||
this._codeLoadingStop = Date.now();
|
||||
}
|
||||
|
||||
public activateCallStart(): void {
|
||||
this._activateCallStart = Date.now();
|
||||
}
|
||||
|
||||
public activateCallStop(): void {
|
||||
this._activateCallStop = Date.now();
|
||||
}
|
||||
|
||||
public activateResolveStart(): void {
|
||||
this._activateResolveStart = Date.now();
|
||||
}
|
||||
|
||||
public activateResolveStop(): void {
|
||||
this._activateResolveStop = Date.now();
|
||||
}
|
||||
}
|
||||
|
||||
export class ActivatedExtension {
|
||||
|
||||
public readonly activationFailed: boolean;
|
||||
public readonly activationFailedError: Error | null;
|
||||
public readonly activationTimes: ExtensionActivationTimes;
|
||||
public readonly module: IExtensionModule;
|
||||
public readonly exports: IExtensionAPI | undefined;
|
||||
public readonly subscriptions: IDisposable[];
|
||||
|
||||
constructor(
|
||||
activationFailed: boolean,
|
||||
activationFailedError: Error | null,
|
||||
activationTimes: ExtensionActivationTimes,
|
||||
module: IExtensionModule,
|
||||
exports: IExtensionAPI | undefined,
|
||||
subscriptions: IDisposable[]
|
||||
) {
|
||||
this.activationFailed = activationFailed;
|
||||
this.activationFailedError = activationFailedError;
|
||||
this.activationTimes = activationTimes;
|
||||
this.module = module;
|
||||
this.exports = exports;
|
||||
this.subscriptions = subscriptions;
|
||||
}
|
||||
}
|
||||
|
||||
export class EmptyExtension extends ActivatedExtension {
|
||||
constructor(activationTimes: ExtensionActivationTimes) {
|
||||
super(false, null, activationTimes, { activate: undefined, deactivate: undefined }, undefined, []);
|
||||
}
|
||||
}
|
||||
|
||||
export class HostExtension extends ActivatedExtension {
|
||||
constructor() {
|
||||
super(false, null, ExtensionActivationTimes.NONE, { activate: undefined, deactivate: undefined }, undefined, []);
|
||||
}
|
||||
}
|
||||
|
||||
export class FailedExtension extends ActivatedExtension {
|
||||
constructor(activationError: Error) {
|
||||
super(true, activationError, ExtensionActivationTimes.NONE, { activate: undefined, deactivate: undefined }, undefined, []);
|
||||
}
|
||||
}
|
||||
|
||||
export interface IExtensionsActivatorHost {
|
||||
onExtensionActivationError(extensionId: ExtensionIdentifier, error: ExtensionActivationError): void;
|
||||
actualActivateExtension(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise<ActivatedExtension>;
|
||||
}
|
||||
|
||||
export class ExtensionActivatedByEvent {
|
||||
constructor(
|
||||
public readonly startup: boolean,
|
||||
public readonly activationEvent: string
|
||||
) { }
|
||||
}
|
||||
|
||||
export class ExtensionActivatedByAPI {
|
||||
constructor(
|
||||
public readonly startup: boolean
|
||||
) { }
|
||||
}
|
||||
|
||||
export type ExtensionActivationReason = ExtensionActivatedByEvent | ExtensionActivatedByAPI;
|
||||
|
||||
export class ExtensionsActivator {
|
||||
|
||||
private readonly _registry: ExtensionDescriptionRegistry;
|
||||
private readonly _resolvedExtensionsSet: Set<string>;
|
||||
private readonly _hostExtensionsMap: Map<string, ExtensionIdentifier>;
|
||||
private readonly _host: IExtensionsActivatorHost;
|
||||
private readonly _activatingExtensions: Map<string, Promise<void>>;
|
||||
private readonly _activatedExtensions: Map<string, ActivatedExtension>;
|
||||
/**
|
||||
* A map of already activated events to speed things up if the same activation event is triggered multiple times.
|
||||
*/
|
||||
private readonly _alreadyActivatedEvents: { [activationEvent: string]: boolean; };
|
||||
|
||||
constructor(registry: ExtensionDescriptionRegistry, resolvedExtensions: ExtensionIdentifier[], hostExtensions: ExtensionIdentifier[], host: IExtensionsActivatorHost) {
|
||||
this._registry = registry;
|
||||
this._resolvedExtensionsSet = new Set<string>();
|
||||
resolvedExtensions.forEach((extensionId) => this._resolvedExtensionsSet.add(ExtensionIdentifier.toKey(extensionId)));
|
||||
this._hostExtensionsMap = new Map<string, ExtensionIdentifier>();
|
||||
hostExtensions.forEach((extensionId) => this._hostExtensionsMap.set(ExtensionIdentifier.toKey(extensionId), extensionId));
|
||||
this._host = host;
|
||||
this._activatingExtensions = new Map<string, Promise<void>>();
|
||||
this._activatedExtensions = new Map<string, ActivatedExtension>();
|
||||
this._alreadyActivatedEvents = Object.create(null);
|
||||
}
|
||||
|
||||
public isActivated(extensionId: ExtensionIdentifier): boolean {
|
||||
const extensionKey = ExtensionIdentifier.toKey(extensionId);
|
||||
|
||||
return this._activatedExtensions.has(extensionKey);
|
||||
}
|
||||
|
||||
public getActivatedExtension(extensionId: ExtensionIdentifier): ActivatedExtension {
|
||||
const extensionKey = ExtensionIdentifier.toKey(extensionId);
|
||||
|
||||
const activatedExtension = this._activatedExtensions.get(extensionKey);
|
||||
if (!activatedExtension) {
|
||||
throw new Error('Extension `' + extensionId.value + '` is not known or not activated');
|
||||
}
|
||||
return activatedExtension;
|
||||
}
|
||||
|
||||
public activateByEvent(activationEvent: string, reason: ExtensionActivationReason): Promise<void> {
|
||||
if (this._alreadyActivatedEvents[activationEvent]) {
|
||||
return NO_OP_VOID_PROMISE;
|
||||
}
|
||||
const activateExtensions = this._registry.getExtensionDescriptionsForActivationEvent(activationEvent);
|
||||
return this._activateExtensions(activateExtensions.map(e => e.identifier), reason).then(() => {
|
||||
this._alreadyActivatedEvents[activationEvent] = true;
|
||||
});
|
||||
}
|
||||
|
||||
public activateById(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise<void> {
|
||||
const desc = this._registry.getExtensionDescription(extensionId);
|
||||
if (!desc) {
|
||||
throw new Error('Extension `' + extensionId + '` is not known');
|
||||
}
|
||||
|
||||
return this._activateExtensions([desc.identifier], reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle semantics related to dependencies for `currentExtension`.
|
||||
* semantics: `redExtensions` must wait for `greenExtensions`.
|
||||
*/
|
||||
private _handleActivateRequest(currentExtensionId: ExtensionIdentifier, greenExtensions: { [id: string]: ExtensionIdentifier; }, redExtensions: ExtensionIdentifier[]): void {
|
||||
if (this._hostExtensionsMap.has(ExtensionIdentifier.toKey(currentExtensionId))) {
|
||||
greenExtensions[ExtensionIdentifier.toKey(currentExtensionId)] = currentExtensionId;
|
||||
return;
|
||||
}
|
||||
|
||||
const currentExtension = this._registry.getExtensionDescription(currentExtensionId)!;
|
||||
const depIds = (typeof currentExtension.extensionDependencies === 'undefined' ? [] : currentExtension.extensionDependencies);
|
||||
let currentExtensionGetsGreenLight = true;
|
||||
|
||||
for (let j = 0, lenJ = depIds.length; j < lenJ; j++) {
|
||||
const depId = depIds[j];
|
||||
|
||||
if (this._resolvedExtensionsSet.has(ExtensionIdentifier.toKey(depId))) {
|
||||
// This dependency is already resolved
|
||||
continue;
|
||||
}
|
||||
|
||||
const dep = this._activatedExtensions.get(ExtensionIdentifier.toKey(depId));
|
||||
if (dep && !dep.activationFailed) {
|
||||
// the dependency is already activated OK
|
||||
continue;
|
||||
}
|
||||
|
||||
if (dep && dep.activationFailed) {
|
||||
// Error condition 2: a dependency has already failed activation
|
||||
this._host.onExtensionActivationError(currentExtension.identifier, nls.localize('failedDep1', "Cannot activate extension '{0}' because it depends on extension '{1}', which failed to activate.", currentExtension.displayName || currentExtension.identifier.value, depId));
|
||||
const error = new Error(`Dependency ${depId} failed to activate`);
|
||||
(<any>error).detail = dep.activationFailedError;
|
||||
this._activatedExtensions.set(ExtensionIdentifier.toKey(currentExtension.identifier), new FailedExtension(error));
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._hostExtensionsMap.has(ExtensionIdentifier.toKey(depId))) {
|
||||
// must first wait for the dependency to activate
|
||||
currentExtensionGetsGreenLight = false;
|
||||
greenExtensions[ExtensionIdentifier.toKey(depId)] = this._hostExtensionsMap.get(ExtensionIdentifier.toKey(depId))!;
|
||||
continue;
|
||||
}
|
||||
|
||||
const depDesc = this._registry.getExtensionDescription(depId);
|
||||
if (depDesc) {
|
||||
// must first wait for the dependency to activate
|
||||
currentExtensionGetsGreenLight = false;
|
||||
greenExtensions[ExtensionIdentifier.toKey(depId)] = depDesc.identifier;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Error condition 1: unknown dependency
|
||||
this._host.onExtensionActivationError(currentExtension.identifier, new MissingDependencyError(depId));
|
||||
const error = new Error(`Unknown dependency '${depId}'`);
|
||||
this._activatedExtensions.set(ExtensionIdentifier.toKey(currentExtension.identifier), new FailedExtension(error));
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentExtensionGetsGreenLight) {
|
||||
greenExtensions[ExtensionIdentifier.toKey(currentExtension.identifier)] = currentExtensionId;
|
||||
} else {
|
||||
redExtensions.push(currentExtensionId);
|
||||
}
|
||||
}
|
||||
|
||||
private _activateExtensions(extensionIds: ExtensionIdentifier[], reason: ExtensionActivationReason): Promise<void> {
|
||||
// console.log('_activateExtensions: ', extensionIds.map(p => p.value));
|
||||
if (extensionIds.length === 0) {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
extensionIds = extensionIds.filter((p) => !this._activatedExtensions.has(ExtensionIdentifier.toKey(p)));
|
||||
if (extensionIds.length === 0) {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
const greenMap: { [id: string]: ExtensionIdentifier; } = Object.create(null),
|
||||
red: ExtensionIdentifier[] = [];
|
||||
|
||||
for (let i = 0, len = extensionIds.length; i < len; i++) {
|
||||
this._handleActivateRequest(extensionIds[i], greenMap, red);
|
||||
}
|
||||
|
||||
// Make sure no red is also green
|
||||
for (let i = 0, len = red.length; i < len; i++) {
|
||||
const redExtensionKey = ExtensionIdentifier.toKey(red[i]);
|
||||
if (greenMap[redExtensionKey]) {
|
||||
delete greenMap[redExtensionKey];
|
||||
}
|
||||
}
|
||||
|
||||
const green = Object.keys(greenMap).map(id => greenMap[id]);
|
||||
|
||||
// console.log('greenExtensions: ', green.map(p => p.id));
|
||||
// console.log('redExtensions: ', red.map(p => p.id));
|
||||
|
||||
if (red.length === 0) {
|
||||
// Finally reached only leafs!
|
||||
return Promise.all(green.map((p) => this._activateExtension(p, reason))).then(_ => undefined);
|
||||
}
|
||||
|
||||
return this._activateExtensions(green, reason).then(_ => {
|
||||
return this._activateExtensions(red, reason);
|
||||
});
|
||||
}
|
||||
|
||||
private _activateExtension(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise<void> {
|
||||
const extensionKey = ExtensionIdentifier.toKey(extensionId);
|
||||
|
||||
if (this._activatedExtensions.has(extensionKey)) {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
const currentlyActivatingExtension = this._activatingExtensions.get(extensionKey);
|
||||
if (currentlyActivatingExtension) {
|
||||
return currentlyActivatingExtension;
|
||||
}
|
||||
|
||||
const newlyActivatingExtension = this._host.actualActivateExtension(extensionId, reason).then(undefined, (err) => {
|
||||
this._host.onExtensionActivationError(extensionId, nls.localize('activationError', "Activating extension '{0}' failed: {1}.", extensionId.value, err.message));
|
||||
console.error('Activating extension `' + extensionId.value + '` failed: ', err.message);
|
||||
console.log('Here is the error stack: ', err.stack);
|
||||
// Treat the extension as being empty
|
||||
return new FailedExtension(err);
|
||||
}).then((x: ActivatedExtension) => {
|
||||
this._activatedExtensions.set(extensionKey, x);
|
||||
this._activatingExtensions.delete(extensionKey);
|
||||
});
|
||||
|
||||
this._activatingExtensions.set(extensionKey, newlyActivatingExtension);
|
||||
return newlyActivatingExtension;
|
||||
}
|
||||
}
|
||||
@@ -16,15 +16,15 @@ import * as pfs from 'vs/base/node/pfs';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
// {{SQL CARBON EDIT}} - Remove createApiFactory initializeExtensionApi, and IExtensionApiFactory imports
|
||||
//import { createApiFactory, IExtensionApiFactory, NodeModuleRequireInterceptor, VSCodeNodeModuleFactory } from 'vs/workbench/api/node/extHost.api.impl';
|
||||
import { NodeModuleRequireInterceptor, VSCodeNodeModuleFactory, KeytarNodeModuleFactory } from 'vs/workbench/api/node/extHost.api.impl';
|
||||
import { NodeModuleRequireInterceptor, KeytarNodeModuleFactory, OpenNodeModuleFactory } from 'vs/workbench/api/node/extHost.api.impl';
|
||||
import { ExtHostExtensionServiceShape, IEnvironment, IInitData, IMainContext, MainContext, MainThreadExtensionServiceShape, MainThreadTelemetryShape, MainThreadWorkspaceShape, IStaticWorkspaceData } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { ExtHostConfiguration } from 'vs/workbench/api/node/extHostConfiguration';
|
||||
import { ActivatedExtension, EmptyExtension, ExtensionActivatedByAPI, ExtensionActivatedByEvent, ExtensionActivationReason, ExtensionActivationTimes, ExtensionActivationTimesBuilder, ExtensionsActivator, IExtensionAPI, IExtensionContext, IExtensionMemento, IExtensionModule, HostExtension } from 'vs/workbench/api/node/extHostExtensionActivator';
|
||||
import { ExtHostLogService } from 'vs/workbench/api/node/extHostLogService';
|
||||
import { ExtHostStorage } from 'vs/workbench/api/node/extHostStorage';
|
||||
import { ExtHostWorkspace } from 'vs/workbench/api/node/extHostWorkspace';
|
||||
import { ExtHostConfiguration } from 'vs/workbench/api/common/extHostConfiguration';
|
||||
import { ActivatedExtension, EmptyExtension, ExtensionActivatedByAPI, ExtensionActivatedByEvent, ExtensionActivationReason, ExtensionActivationTimes, ExtensionActivationTimesBuilder, ExtensionsActivator, IExtensionAPI, IExtensionContext, IExtensionMemento, IExtensionModule, HostExtension } from 'vs/workbench/api/common/extHostExtensionActivator';
|
||||
import { ExtHostLogService } from 'vs/workbench/api/common/extHostLogService';
|
||||
import { ExtHostStorage } from 'vs/workbench/api/common/extHostStorage';
|
||||
import { ExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace';
|
||||
import { ExtensionActivationError } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/node/extensionDescriptionRegistry';
|
||||
import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry';
|
||||
import { connectProxyResolver } from 'vs/workbench/services/extensions/node/proxyResolver';
|
||||
import { CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import * as errors from 'vs/base/common/errors';
|
||||
@@ -35,6 +35,7 @@ import { IWorkspace } from 'vs/platform/workspace/common/workspace';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { withNullAsUndefined } from 'vs/base/common/types';
|
||||
import { realpath } from 'vs/base/node/extpath';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
|
||||
class ExtensionMemento implements IExtensionMemento {
|
||||
|
||||
@@ -165,6 +166,7 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape {
|
||||
private readonly _extHostContext: IMainContext;
|
||||
private readonly _extHostWorkspace: ExtHostWorkspace;
|
||||
private readonly _extHostConfiguration: ExtHostConfiguration;
|
||||
private readonly _environment: IEnvironment;
|
||||
private readonly _extHostLogService: ExtHostLogService;
|
||||
|
||||
private readonly _mainThreadWorkspaceProxy: MainThreadWorkspaceShape;
|
||||
@@ -190,6 +192,7 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape {
|
||||
extHostContext: IMainContext,
|
||||
extHostWorkspace: ExtHostWorkspace,
|
||||
extHostConfiguration: ExtHostConfiguration,
|
||||
environment: IEnvironment,
|
||||
extHostLogService: ExtHostLogService
|
||||
) {
|
||||
this._nativeExit = nativeExit;
|
||||
@@ -197,6 +200,7 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape {
|
||||
this._extHostContext = extHostContext;
|
||||
this._extHostWorkspace = extHostWorkspace;
|
||||
this._extHostConfiguration = extHostConfiguration;
|
||||
this._environment = environment;
|
||||
this._extHostLogService = extHostLogService;
|
||||
|
||||
this._mainThreadWorkspaceProxy = this._extHostContext.getProxy(MainContext.MainThreadWorkspace);
|
||||
@@ -246,12 +250,18 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape {
|
||||
private async _initialize(): Promise<void> {
|
||||
try {
|
||||
const configProvider = await this._extHostConfiguration.getConfigProvider();
|
||||
|
||||
const extensionPaths = await this.getExtensionPathIndex();
|
||||
// {{SQL CARBON EDIT}} - disable VSCodeNodeModuleFactory and use older initializeExtensionApi
|
||||
// const extensionPaths = await this.getExtensionPathIndex();
|
||||
// NodeModuleRequireInterceptor.INSTANCE.register(new VSCodeNodeModuleFactory(this._extensionApiFactory, extensionPaths, this._registry, configProvider));
|
||||
await initializeExtensionApi(this, this._extensionApiFactory, this._registry, configProvider);
|
||||
NodeModuleRequireInterceptor.INSTANCE.register(new KeytarNodeModuleFactory(this._extHostContext.getProxy(MainContext.MainThreadKeytar)));
|
||||
NodeModuleRequireInterceptor.INSTANCE.register(new KeytarNodeModuleFactory(this._extHostContext.getProxy(MainContext.MainThreadKeytar), this._environment));
|
||||
if (this._initData.remoteAuthority) {
|
||||
NodeModuleRequireInterceptor.INSTANCE.register(new OpenNodeModuleFactory(
|
||||
this._extHostContext.getProxy(MainContext.MainThreadWindow),
|
||||
this._extHostContext.getProxy(MainContext.MainThreadTelemetry),
|
||||
extensionPaths
|
||||
));
|
||||
}
|
||||
|
||||
// Do this when extension service exists, but extensions are not being activated yet.
|
||||
await connectProxyResolver(this._extHostWorkspace, configProvider, this, this._extHostLogService, this._mainThreadTelemetryProxy);
|
||||
@@ -608,7 +618,7 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape {
|
||||
}
|
||||
|
||||
private _doHandleExtensionTests(): Promise<void> {
|
||||
const { extensionDevelopmentLocationURI, extensionTestsLocationURI } = this._initData.environment;
|
||||
const { extensionDevelopmentLocationURI: extensionDevelopmentLocationURI, extensionTestsLocationURI } = this._initData.environment;
|
||||
if (!(extensionDevelopmentLocationURI && extensionTestsLocationURI && extensionTestsLocationURI.scheme === Schemas.file)) {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
@@ -759,13 +769,12 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape {
|
||||
return n;
|
||||
}
|
||||
|
||||
public async $test_up(b: Buffer): Promise<number> {
|
||||
return b.length;
|
||||
public async $test_up(b: VSBuffer): Promise<number> {
|
||||
return b.byteLength;
|
||||
}
|
||||
|
||||
public async $test_down(size: number): Promise<Buffer> {
|
||||
const b = Buffer.alloc(size, Math.random() % 256);
|
||||
return b;
|
||||
public async $test_down(size: number): Promise<VSBuffer> {
|
||||
return VSBuffer.wrap(Buffer.alloc(size, Math.random() % 256));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,320 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { MainContext, IMainContext, ExtHostFileSystemShape, MainThreadFileSystemShape, IFileChangeDto } from '../common/extHost.protocol';
|
||||
import * as vscode from 'vscode';
|
||||
import * as files from 'vs/platform/files/common/files';
|
||||
import { IDisposable, toDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { FileChangeType } from 'vs/workbench/api/node/extHostTypes';
|
||||
import * as typeConverter from 'vs/workbench/api/node/extHostTypeConverters';
|
||||
import { ExtHostLanguageFeatures } from 'vs/workbench/api/node/extHostLanguageFeatures';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { ResourceLabelFormatter } from 'vs/platform/label/common/label';
|
||||
import { State, StateMachine, LinkComputer, Edge } from 'vs/editor/common/modes/linkComputer';
|
||||
import { commonPrefixLength } from 'vs/base/common/strings';
|
||||
import { CharCode } from 'vs/base/common/charCode';
|
||||
|
||||
class FsLinkProvider {
|
||||
|
||||
private _schemes: string[] = [];
|
||||
private _stateMachine?: StateMachine;
|
||||
|
||||
add(scheme: string): void {
|
||||
this._stateMachine = undefined;
|
||||
this._schemes.push(scheme);
|
||||
}
|
||||
|
||||
delete(scheme: string): void {
|
||||
const idx = this._schemes.indexOf(scheme);
|
||||
if (idx >= 0) {
|
||||
this._schemes.splice(idx, 1);
|
||||
this._stateMachine = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private _initStateMachine(): void {
|
||||
if (!this._stateMachine) {
|
||||
|
||||
// sort and compute common prefix with previous scheme
|
||||
// then build state transitions based on the data
|
||||
const schemes = this._schemes.sort();
|
||||
const edges: Edge[] = [];
|
||||
let prevScheme: string | undefined;
|
||||
let prevState: State;
|
||||
let nextState = State.LastKnownState;
|
||||
for (const scheme of schemes) {
|
||||
|
||||
// skip the common prefix of the prev scheme
|
||||
// and continue with its last state
|
||||
let pos = !prevScheme ? 0 : commonPrefixLength(prevScheme, scheme);
|
||||
if (pos === 0) {
|
||||
prevState = State.Start;
|
||||
} else {
|
||||
prevState = nextState;
|
||||
}
|
||||
|
||||
for (; pos < scheme.length; pos++) {
|
||||
// keep creating new (next) states until the
|
||||
// end (and the BeforeColon-state) is reached
|
||||
if (pos + 1 === scheme.length) {
|
||||
nextState = State.BeforeColon;
|
||||
} else {
|
||||
nextState += 1;
|
||||
}
|
||||
edges.push([prevState, scheme.toUpperCase().charCodeAt(pos), nextState]);
|
||||
edges.push([prevState, scheme.toLowerCase().charCodeAt(pos), nextState]);
|
||||
prevState = nextState;
|
||||
}
|
||||
|
||||
prevScheme = scheme;
|
||||
}
|
||||
|
||||
// all link must match this pattern `<scheme>:/<more>`
|
||||
edges.push([State.BeforeColon, CharCode.Colon, State.AfterColon]);
|
||||
edges.push([State.AfterColon, CharCode.Slash, State.End]);
|
||||
|
||||
this._stateMachine = new StateMachine(edges);
|
||||
}
|
||||
}
|
||||
|
||||
provideDocumentLinks(document: vscode.TextDocument): vscode.ProviderResult<vscode.DocumentLink[]> {
|
||||
this._initStateMachine();
|
||||
|
||||
const result: vscode.DocumentLink[] = [];
|
||||
const links = LinkComputer.computeLinks({
|
||||
getLineContent(lineNumber: number): string {
|
||||
return document.lineAt(lineNumber - 1).text;
|
||||
},
|
||||
getLineCount(): number {
|
||||
return document.lineCount;
|
||||
}
|
||||
}, this._stateMachine);
|
||||
|
||||
for (const link of links) {
|
||||
const docLink = typeConverter.DocumentLink.to(link);
|
||||
if (docLink.target) {
|
||||
result.push(docLink);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
export class ExtHostFileSystem implements ExtHostFileSystemShape {
|
||||
|
||||
private readonly _proxy: MainThreadFileSystemShape;
|
||||
private readonly _linkProvider = new FsLinkProvider();
|
||||
private readonly _fsProvider = new Map<number, vscode.FileSystemProvider>();
|
||||
private readonly _usedSchemes = new Set<string>();
|
||||
private readonly _watches = new Map<number, IDisposable>();
|
||||
|
||||
private _linkProviderRegistration: IDisposable;
|
||||
// Used as a handle both for file system providers and resource label formatters (being lazy)
|
||||
private _handlePool: number = 0;
|
||||
|
||||
constructor(mainContext: IMainContext, private _extHostLanguageFeatures: ExtHostLanguageFeatures) {
|
||||
this._proxy = mainContext.getProxy(MainContext.MainThreadFileSystem);
|
||||
this._usedSchemes.add(Schemas.file);
|
||||
this._usedSchemes.add(Schemas.untitled);
|
||||
this._usedSchemes.add(Schemas.vscode);
|
||||
this._usedSchemes.add(Schemas.inMemory);
|
||||
this._usedSchemes.add(Schemas.internal);
|
||||
this._usedSchemes.add(Schemas.http);
|
||||
this._usedSchemes.add(Schemas.https);
|
||||
this._usedSchemes.add(Schemas.mailto);
|
||||
this._usedSchemes.add(Schemas.data);
|
||||
this._usedSchemes.add(Schemas.command);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
dispose(this._linkProviderRegistration);
|
||||
}
|
||||
|
||||
private _registerLinkProviderIfNotYetRegistered(): void {
|
||||
if (!this._linkProviderRegistration) {
|
||||
this._linkProviderRegistration = this._extHostLanguageFeatures.registerDocumentLinkProvider(undefined, '*', this._linkProvider);
|
||||
}
|
||||
}
|
||||
|
||||
registerFileSystemProvider(scheme: string, provider: vscode.FileSystemProvider, options: { isCaseSensitive?: boolean, isReadonly?: boolean } = {}) {
|
||||
|
||||
if (this._usedSchemes.has(scheme)) {
|
||||
throw new Error(`a provider for the scheme '${scheme}' is already registered`);
|
||||
}
|
||||
|
||||
//
|
||||
this._registerLinkProviderIfNotYetRegistered();
|
||||
|
||||
const handle = this._handlePool++;
|
||||
this._linkProvider.add(scheme);
|
||||
this._usedSchemes.add(scheme);
|
||||
this._fsProvider.set(handle, provider);
|
||||
|
||||
let capabilites = files.FileSystemProviderCapabilities.FileReadWrite;
|
||||
if (options.isCaseSensitive) {
|
||||
capabilites += files.FileSystemProviderCapabilities.PathCaseSensitive;
|
||||
}
|
||||
if (options.isReadonly) {
|
||||
capabilites += files.FileSystemProviderCapabilities.Readonly;
|
||||
}
|
||||
if (typeof provider.copy === 'function') {
|
||||
capabilites += files.FileSystemProviderCapabilities.FileFolderCopy;
|
||||
}
|
||||
if (typeof provider.open === 'function' && typeof provider.close === 'function'
|
||||
&& typeof provider.read === 'function' && typeof provider.write === 'function'
|
||||
) {
|
||||
capabilites += files.FileSystemProviderCapabilities.FileOpenReadWriteClose;
|
||||
}
|
||||
|
||||
this._proxy.$registerFileSystemProvider(handle, scheme, capabilites);
|
||||
|
||||
const subscription = provider.onDidChangeFile(event => {
|
||||
const mapped: IFileChangeDto[] = [];
|
||||
for (const e of event) {
|
||||
let { uri: resource, type } = e;
|
||||
if (resource.scheme !== scheme) {
|
||||
// dropping events for wrong scheme
|
||||
continue;
|
||||
}
|
||||
let newType: files.FileChangeType | undefined;
|
||||
switch (type) {
|
||||
case FileChangeType.Changed:
|
||||
newType = files.FileChangeType.UPDATED;
|
||||
break;
|
||||
case FileChangeType.Created:
|
||||
newType = files.FileChangeType.ADDED;
|
||||
break;
|
||||
case FileChangeType.Deleted:
|
||||
newType = files.FileChangeType.DELETED;
|
||||
break;
|
||||
default:
|
||||
throw new Error('Unknown FileChangeType');
|
||||
}
|
||||
mapped.push({ resource, type: newType });
|
||||
}
|
||||
this._proxy.$onFileSystemChange(handle, mapped);
|
||||
});
|
||||
|
||||
return toDisposable(() => {
|
||||
subscription.dispose();
|
||||
this._linkProvider.delete(scheme);
|
||||
this._usedSchemes.delete(scheme);
|
||||
this._fsProvider.delete(handle);
|
||||
this._proxy.$unregisterProvider(handle);
|
||||
});
|
||||
}
|
||||
|
||||
registerResourceLabelFormatter(formatter: ResourceLabelFormatter): IDisposable {
|
||||
const handle = this._handlePool++;
|
||||
this._proxy.$registerResourceLabelFormatter(handle, formatter);
|
||||
|
||||
return toDisposable(() => {
|
||||
this._proxy.$unregisterResourceLabelFormatter(handle);
|
||||
});
|
||||
}
|
||||
|
||||
private static _asIStat(stat: vscode.FileStat): files.IStat {
|
||||
const { type, ctime, mtime, size } = stat;
|
||||
return { type, ctime, mtime, size };
|
||||
}
|
||||
|
||||
$stat(handle: number, resource: UriComponents): Promise<files.IStat> {
|
||||
return Promise.resolve(this.getProvider(handle).stat(URI.revive(resource))).then(ExtHostFileSystem._asIStat);
|
||||
}
|
||||
|
||||
$readdir(handle: number, resource: UriComponents): Promise<[string, files.FileType][]> {
|
||||
return Promise.resolve(this.getProvider(handle).readDirectory(URI.revive(resource)));
|
||||
}
|
||||
|
||||
$readFile(handle: number, resource: UriComponents): Promise<Buffer> {
|
||||
return Promise.resolve(this.getProvider(handle).readFile(URI.revive(resource))).then(data => {
|
||||
return Buffer.isBuffer(data) ? data : Buffer.from(data.buffer, data.byteOffset, data.byteLength);
|
||||
});
|
||||
}
|
||||
|
||||
$writeFile(handle: number, resource: UriComponents, content: Buffer, opts: files.FileWriteOptions): Promise<void> {
|
||||
return Promise.resolve(this.getProvider(handle).writeFile(URI.revive(resource), content, opts));
|
||||
}
|
||||
|
||||
$delete(handle: number, resource: UriComponents, opts: files.FileDeleteOptions): Promise<void> {
|
||||
return Promise.resolve(this.getProvider(handle).delete(URI.revive(resource), opts));
|
||||
}
|
||||
|
||||
$rename(handle: number, oldUri: UriComponents, newUri: UriComponents, opts: files.FileOverwriteOptions): Promise<void> {
|
||||
return Promise.resolve(this.getProvider(handle).rename(URI.revive(oldUri), URI.revive(newUri), opts));
|
||||
}
|
||||
|
||||
$copy(handle: number, oldUri: UriComponents, newUri: UriComponents, opts: files.FileOverwriteOptions): Promise<void> {
|
||||
const provider = this.getProvider(handle);
|
||||
if (!provider.copy) {
|
||||
throw new Error('FileSystemProvider does not implement "copy"');
|
||||
}
|
||||
return Promise.resolve(provider.copy(URI.revive(oldUri), URI.revive(newUri), opts));
|
||||
}
|
||||
|
||||
$mkdir(handle: number, resource: UriComponents): Promise<void> {
|
||||
return Promise.resolve(this.getProvider(handle).createDirectory(URI.revive(resource)));
|
||||
}
|
||||
|
||||
$watch(handle: number, session: number, resource: UriComponents, opts: files.IWatchOptions): void {
|
||||
const subscription = this.getProvider(handle).watch(URI.revive(resource), opts);
|
||||
this._watches.set(session, subscription);
|
||||
}
|
||||
|
||||
$unwatch(_handle: number, session: number): void {
|
||||
const subscription = this._watches.get(session);
|
||||
if (subscription) {
|
||||
subscription.dispose();
|
||||
this._watches.delete(session);
|
||||
}
|
||||
}
|
||||
|
||||
$open(handle: number, resource: UriComponents, opts: files.FileOpenOptions): Promise<number> {
|
||||
const provider = this.getProvider(handle);
|
||||
if (!provider.open) {
|
||||
throw new Error('FileSystemProvider does not implement "open"');
|
||||
}
|
||||
return Promise.resolve(provider.open(URI.revive(resource), opts));
|
||||
}
|
||||
|
||||
$close(handle: number, fd: number): Promise<void> {
|
||||
const provider = this.getProvider(handle);
|
||||
if (!provider.close) {
|
||||
throw new Error('FileSystemProvider does not implement "close"');
|
||||
}
|
||||
return Promise.resolve(provider.close(fd));
|
||||
}
|
||||
|
||||
$read(handle: number, fd: number, pos: number, length: number): Promise<Buffer> {
|
||||
const provider = this.getProvider(handle);
|
||||
if (!provider.read) {
|
||||
throw new Error('FileSystemProvider does not implement "read"');
|
||||
}
|
||||
const data = Buffer.allocUnsafe(length);
|
||||
return Promise.resolve(provider.read(fd, pos, data, 0, length)).then(read => {
|
||||
return data.slice(0, read); // don't send zeros
|
||||
});
|
||||
}
|
||||
|
||||
$write(handle: number, fd: number, pos: number, data: Buffer): Promise<number> {
|
||||
const provider = this.getProvider(handle);
|
||||
if (!provider.write) {
|
||||
throw new Error('FileSystemProvider does not implement "write"');
|
||||
}
|
||||
return Promise.resolve(provider.write(fd, pos, data, 0, data.length));
|
||||
}
|
||||
|
||||
private getProvider(handle: number): vscode.FileSystemProvider {
|
||||
const provider = this._fsProvider.get(handle);
|
||||
if (!provider) {
|
||||
const err = new Error();
|
||||
err.name = 'ENOPRO';
|
||||
err.message = `no provider`;
|
||||
throw err;
|
||||
}
|
||||
return provider;
|
||||
}
|
||||
}
|
||||
@@ -1,182 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { flatten } from 'vs/base/common/arrays';
|
||||
import { AsyncEmitter, Emitter, Event } from 'vs/base/common/event';
|
||||
import { IRelativePattern, parse } from 'vs/base/common/glob';
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/node/extHostDocumentsAndEditors';
|
||||
import * as vscode from 'vscode';
|
||||
import { ExtHostFileSystemEventServiceShape, FileSystemEvents, IMainContext, MainContext, ResourceFileEditDto, ResourceTextEditDto, MainThreadTextEditorsShape } from '../common/extHost.protocol';
|
||||
import * as typeConverter from './extHostTypeConverters';
|
||||
import { Disposable, WorkspaceEdit } from './extHostTypes';
|
||||
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
|
||||
class FileSystemWatcher implements vscode.FileSystemWatcher {
|
||||
|
||||
private _onDidCreate = new Emitter<vscode.Uri>();
|
||||
private _onDidChange = new Emitter<vscode.Uri>();
|
||||
private _onDidDelete = new Emitter<vscode.Uri>();
|
||||
private _disposable: Disposable;
|
||||
private _config: number;
|
||||
|
||||
get ignoreCreateEvents(): boolean {
|
||||
return Boolean(this._config & 0b001);
|
||||
}
|
||||
|
||||
get ignoreChangeEvents(): boolean {
|
||||
return Boolean(this._config & 0b010);
|
||||
}
|
||||
|
||||
get ignoreDeleteEvents(): boolean {
|
||||
return Boolean(this._config & 0b100);
|
||||
}
|
||||
|
||||
constructor(dispatcher: Event<FileSystemEvents>, globPattern: string | IRelativePattern, ignoreCreateEvents?: boolean, ignoreChangeEvents?: boolean, ignoreDeleteEvents?: boolean) {
|
||||
|
||||
this._config = 0;
|
||||
if (ignoreCreateEvents) {
|
||||
this._config += 0b001;
|
||||
}
|
||||
if (ignoreChangeEvents) {
|
||||
this._config += 0b010;
|
||||
}
|
||||
if (ignoreDeleteEvents) {
|
||||
this._config += 0b100;
|
||||
}
|
||||
|
||||
const parsedPattern = parse(globPattern);
|
||||
|
||||
const subscription = dispatcher(events => {
|
||||
if (!ignoreCreateEvents) {
|
||||
for (let created of events.created) {
|
||||
const uri = URI.revive(created);
|
||||
if (parsedPattern(uri.fsPath)) {
|
||||
this._onDidCreate.fire(uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!ignoreChangeEvents) {
|
||||
for (let changed of events.changed) {
|
||||
const uri = URI.revive(changed);
|
||||
if (parsedPattern(uri.fsPath)) {
|
||||
this._onDidChange.fire(uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!ignoreDeleteEvents) {
|
||||
for (let deleted of events.deleted) {
|
||||
const uri = URI.revive(deleted);
|
||||
if (parsedPattern(uri.fsPath)) {
|
||||
this._onDidDelete.fire(uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this._disposable = Disposable.from(this._onDidCreate, this._onDidChange, this._onDidDelete, subscription);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this._disposable.dispose();
|
||||
}
|
||||
|
||||
get onDidCreate(): Event<vscode.Uri> {
|
||||
return this._onDidCreate.event;
|
||||
}
|
||||
|
||||
get onDidChange(): Event<vscode.Uri> {
|
||||
return this._onDidChange.event;
|
||||
}
|
||||
|
||||
get onDidDelete(): Event<vscode.Uri> {
|
||||
return this._onDidDelete.event;
|
||||
}
|
||||
}
|
||||
|
||||
interface WillRenameListener {
|
||||
extension: IExtensionDescription;
|
||||
(e: vscode.FileWillRenameEvent): any;
|
||||
}
|
||||
|
||||
export class ExtHostFileSystemEventService implements ExtHostFileSystemEventServiceShape {
|
||||
|
||||
private readonly _onFileEvent = new Emitter<FileSystemEvents>();
|
||||
private readonly _onDidRenameFile = new Emitter<vscode.FileRenameEvent>();
|
||||
private readonly _onWillRenameFile = new AsyncEmitter<vscode.FileWillRenameEvent>();
|
||||
|
||||
readonly onDidRenameFile: Event<vscode.FileRenameEvent> = this._onDidRenameFile.event;
|
||||
|
||||
constructor(
|
||||
mainContext: IMainContext,
|
||||
private readonly _extHostDocumentsAndEditors: ExtHostDocumentsAndEditors,
|
||||
private readonly _mainThreadTextEditors: MainThreadTextEditorsShape = mainContext.getProxy(MainContext.MainThreadTextEditors)
|
||||
) {
|
||||
//
|
||||
}
|
||||
|
||||
public createFileSystemWatcher(globPattern: string | IRelativePattern, ignoreCreateEvents?: boolean, ignoreChangeEvents?: boolean, ignoreDeleteEvents?: boolean): vscode.FileSystemWatcher {
|
||||
return new FileSystemWatcher(this._onFileEvent.event, globPattern, ignoreCreateEvents, ignoreChangeEvents, ignoreDeleteEvents);
|
||||
}
|
||||
|
||||
$onFileEvent(events: FileSystemEvents) {
|
||||
this._onFileEvent.fire(events);
|
||||
}
|
||||
|
||||
$onFileRename(oldUri: UriComponents, newUri: UriComponents) {
|
||||
this._onDidRenameFile.fire(Object.freeze({ oldUri: URI.revive(oldUri), newUri: URI.revive(newUri) }));
|
||||
}
|
||||
|
||||
getOnWillRenameFileEvent(extension: IExtensionDescription): Event<vscode.FileWillRenameEvent> {
|
||||
return (listener, thisArg, disposables) => {
|
||||
const wrappedListener: WillRenameListener = <any>((e: vscode.FileWillRenameEvent) => {
|
||||
listener.call(thisArg, e);
|
||||
});
|
||||
wrappedListener.extension = extension;
|
||||
return this._onWillRenameFile.event(wrappedListener, undefined, disposables);
|
||||
};
|
||||
}
|
||||
|
||||
$onWillRename(oldUriDto: UriComponents, newUriDto: UriComponents): Promise<any> {
|
||||
const oldUri = URI.revive(oldUriDto);
|
||||
const newUri = URI.revive(newUriDto);
|
||||
|
||||
const edits: WorkspaceEdit[] = [];
|
||||
return Promise.resolve(this._onWillRenameFile.fireAsync((bucket, _listener) => {
|
||||
return {
|
||||
oldUri,
|
||||
newUri,
|
||||
waitUntil: (thenable: Promise<vscode.WorkspaceEdit>): void => {
|
||||
if (Object.isFrozen(bucket)) {
|
||||
throw new TypeError('waitUntil cannot be called async');
|
||||
}
|
||||
const index = bucket.length;
|
||||
const wrappedThenable = Promise.resolve(thenable).then(result => {
|
||||
// ignore all results except for WorkspaceEdits. Those
|
||||
// are stored in a spare array
|
||||
if (result instanceof WorkspaceEdit) {
|
||||
edits[index] = result;
|
||||
}
|
||||
});
|
||||
bucket.push(wrappedThenable);
|
||||
}
|
||||
};
|
||||
}).then((): any => {
|
||||
if (edits.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
// flatten all WorkspaceEdits collected via waitUntil-call
|
||||
// and apply them in one go.
|
||||
const allEdits = new Array<Array<ResourceFileEditDto | ResourceTextEditDto>>();
|
||||
for (let edit of edits) {
|
||||
if (edit) { // sparse array
|
||||
let { edits } = typeConverter.WorkspaceEdit.from(edit, this._extHostDocumentsAndEditors);
|
||||
allEdits.push(edits);
|
||||
}
|
||||
}
|
||||
return this._mainThreadTextEditors.$tryApplyWorkspaceEdit({ edits: flatten(allEdits) });
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ExtHostHeapServiceShape } from '../common/extHost.protocol';
|
||||
|
||||
export class ExtHostHeapService implements ExtHostHeapServiceShape {
|
||||
|
||||
private static _idPool = 0;
|
||||
|
||||
private _data = new Map<number, any>();
|
||||
|
||||
keep(obj: any): number {
|
||||
const id = ExtHostHeapService._idPool++;
|
||||
this._data.set(id, obj);
|
||||
return id;
|
||||
}
|
||||
|
||||
delete(id: number): boolean {
|
||||
return this._data.delete(id);
|
||||
}
|
||||
|
||||
get<T>(id: number): T {
|
||||
return this._data.get(id);
|
||||
}
|
||||
|
||||
$onGarbageCollection(ids: number[]): void {
|
||||
for (const id of ids) {
|
||||
this.delete(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,33 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { MainContext, MainThreadLanguagesShape, IMainContext } from '../common/extHost.protocol';
|
||||
import * as vscode from 'vscode';
|
||||
import { ExtHostDocuments } from 'vs/workbench/api/node/extHostDocuments';
|
||||
|
||||
export class ExtHostLanguages {
|
||||
|
||||
private readonly _proxy: MainThreadLanguagesShape;
|
||||
private readonly _documents: ExtHostDocuments;
|
||||
|
||||
constructor(
|
||||
mainContext: IMainContext,
|
||||
documents: ExtHostDocuments
|
||||
) {
|
||||
this._proxy = mainContext.getProxy(MainContext.MainThreadLanguages);
|
||||
this._documents = documents;
|
||||
}
|
||||
|
||||
getLanguages(): Promise<string[]> {
|
||||
return this._proxy.$getLanguages();
|
||||
}
|
||||
|
||||
changeLanguage(uri: vscode.Uri, languageId: string): Promise<vscode.TextDocument | undefined> {
|
||||
return this._proxy.$changeLanguage(uri, languageId).then(() => {
|
||||
const data = this._documents.getDocumentData(uri);
|
||||
return data ? data.document : undefined;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { join } from 'vs/base/common/path';
|
||||
import { ILogService, DelegatedLogService, LogLevel } from 'vs/platform/log/common/log';
|
||||
import { createSpdLogService } from 'vs/platform/log/node/spdlogService';
|
||||
import { ExtHostLogServiceShape } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { ExtensionHostLogFileName } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
|
||||
|
||||
export class ExtHostLogService extends DelegatedLogService implements ILogService, ExtHostLogServiceShape {
|
||||
|
||||
private _logsPath: string;
|
||||
readonly logFile: URI;
|
||||
|
||||
constructor(
|
||||
logLevel: LogLevel,
|
||||
logsPath: string,
|
||||
) {
|
||||
super(createSpdLogService(ExtensionHostLogFileName, logLevel, logsPath));
|
||||
this._logsPath = logsPath;
|
||||
this.logFile = URI.file(join(logsPath, `${ExtensionHostLogFileName}.log`));
|
||||
}
|
||||
|
||||
$setLevel(level: LogLevel): void {
|
||||
this.setLevel(level);
|
||||
}
|
||||
|
||||
getLogDirectory(extensionID: ExtensionIdentifier): string {
|
||||
return join(this._logsPath, extensionID.value);
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import * as vscode from 'vscode';
|
||||
import { MainContext, MainThreadMessageServiceShape, MainThreadMessageOptions, IMainContext } from '../common/extHost.protocol';
|
||||
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
|
||||
function isMessageItem(item: any): item is vscode.MessageItem {
|
||||
return item && item.title;
|
||||
}
|
||||
|
||||
export class ExtHostMessageService {
|
||||
|
||||
private _proxy: MainThreadMessageServiceShape;
|
||||
|
||||
constructor(mainContext: IMainContext) {
|
||||
this._proxy = mainContext.getProxy(MainContext.MainThreadMessageService);
|
||||
}
|
||||
|
||||
showMessage(extension: IExtensionDescription, severity: Severity, message: string, optionsOrFirstItem: vscode.MessageOptions | string, rest: string[]): Promise<string | undefined>;
|
||||
showMessage(extension: IExtensionDescription, severity: Severity, message: string, optionsOrFirstItem: vscode.MessageOptions | vscode.MessageItem, rest: vscode.MessageItem[]): Promise<vscode.MessageItem | undefined>;
|
||||
showMessage(extension: IExtensionDescription, severity: Severity, message: string, optionsOrFirstItem: vscode.MessageOptions | vscode.MessageItem | string, rest: Array<vscode.MessageItem | string>): Promise<string | vscode.MessageItem | undefined>;
|
||||
showMessage(extension: IExtensionDescription, severity: Severity, message: string, optionsOrFirstItem: vscode.MessageOptions | string | vscode.MessageItem, rest: Array<string | vscode.MessageItem>): Promise<string | vscode.MessageItem | undefined> {
|
||||
|
||||
const options: MainThreadMessageOptions = { extension };
|
||||
let items: (string | vscode.MessageItem)[];
|
||||
|
||||
if (typeof optionsOrFirstItem === 'string' || isMessageItem(optionsOrFirstItem)) {
|
||||
items = [optionsOrFirstItem, ...rest];
|
||||
} else {
|
||||
options.modal = optionsOrFirstItem && optionsOrFirstItem.modal;
|
||||
items = rest;
|
||||
}
|
||||
|
||||
const commands: { title: string; isCloseAffordance: boolean; handle: number; }[] = [];
|
||||
|
||||
for (let handle = 0; handle < items.length; handle++) {
|
||||
const command = items[handle];
|
||||
if (typeof command === 'string') {
|
||||
commands.push({ title: command, handle, isCloseAffordance: false });
|
||||
} else if (typeof command === 'object') {
|
||||
let { title, isCloseAffordance } = command;
|
||||
commands.push({ title, isCloseAffordance: !!isCloseAffordance, handle });
|
||||
} else {
|
||||
console.warn('Invalid message item:', command);
|
||||
}
|
||||
}
|
||||
|
||||
return this._proxy.$showMessage(severity, message, options, commands).then(handle => {
|
||||
if (typeof handle === 'number') {
|
||||
return items[handle];
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -3,101 +3,16 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { MainContext, MainThreadOutputServiceShape, IMainContext, ExtHostOutputServiceShape } from '../common/extHost.protocol';
|
||||
import { MainThreadOutputServiceShape } from '../common/extHost.protocol';
|
||||
import * as vscode from 'vscode';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { join } from 'vs/base/common/path';
|
||||
import { OutputAppender } from 'vs/workbench/services/output/node/outputAppender';
|
||||
import { toLocalISOString } from 'vs/base/common/date';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { dirExists, mkdirp } from 'vs/base/node/pfs';
|
||||
import { AbstractExtHostOutputChannel, IOutputChannelFactory, ExtHostPushOutputChannel } from 'vs/workbench/api/common/extHostOutput';
|
||||
|
||||
abstract class AbstractExtHostOutputChannel extends Disposable implements vscode.OutputChannel {
|
||||
|
||||
readonly _id: Promise<string>;
|
||||
private readonly _name: string;
|
||||
protected readonly _proxy: MainThreadOutputServiceShape;
|
||||
private _disposed: boolean;
|
||||
private _offset: number;
|
||||
|
||||
protected readonly _onDidAppend: Emitter<void> = this._register(new Emitter<void>());
|
||||
readonly onDidAppend: Event<void> = this._onDidAppend.event;
|
||||
|
||||
constructor(name: string, log: boolean, file: URI | undefined, proxy: MainThreadOutputServiceShape) {
|
||||
super();
|
||||
|
||||
this._name = name;
|
||||
this._proxy = proxy;
|
||||
this._id = proxy.$register(this.name, log, file);
|
||||
this._offset = 0;
|
||||
}
|
||||
|
||||
get name(): string {
|
||||
return this._name;
|
||||
}
|
||||
|
||||
append(value: string): void {
|
||||
this.validate();
|
||||
this._offset += value ? Buffer.from(value).byteLength : 0;
|
||||
}
|
||||
|
||||
update(): void {
|
||||
this._id.then(id => this._proxy.$update(id));
|
||||
}
|
||||
|
||||
appendLine(value: string): void {
|
||||
this.validate();
|
||||
this.append(value + '\n');
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this.validate();
|
||||
const till = this._offset;
|
||||
this._id.then(id => this._proxy.$clear(id, till));
|
||||
}
|
||||
|
||||
show(columnOrPreserveFocus?: vscode.ViewColumn | boolean, preserveFocus?: boolean): void {
|
||||
this.validate();
|
||||
this._id.then(id => this._proxy.$reveal(id, !!(typeof columnOrPreserveFocus === 'boolean' ? columnOrPreserveFocus : preserveFocus)));
|
||||
}
|
||||
|
||||
hide(): void {
|
||||
this.validate();
|
||||
this._id.then(id => this._proxy.$close(id));
|
||||
}
|
||||
|
||||
protected validate(): void {
|
||||
if (this._disposed) {
|
||||
throw new Error('Channel has been closed');
|
||||
}
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
super.dispose();
|
||||
|
||||
if (!this._disposed) {
|
||||
this._id
|
||||
.then(id => this._proxy.$dispose(id))
|
||||
.then(() => this._disposed = true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ExtHostPushOutputChannel extends AbstractExtHostOutputChannel {
|
||||
|
||||
constructor(name: string, proxy: MainThreadOutputServiceShape) {
|
||||
super(name, false, undefined, proxy);
|
||||
}
|
||||
|
||||
append(value: string): void {
|
||||
super.append(value);
|
||||
this._id.then(id => this._proxy.$append(id, value));
|
||||
this._onDidAppend.fire();
|
||||
}
|
||||
}
|
||||
|
||||
class ExtHostOutputChannelBackedByFile extends AbstractExtHostOutputChannel {
|
||||
export class ExtHostOutputChannelBackedByFile extends AbstractExtHostOutputChannel {
|
||||
|
||||
private _appender: OutputAppender;
|
||||
|
||||
@@ -128,95 +43,23 @@ class ExtHostOutputChannelBackedByFile extends AbstractExtHostOutputChannel {
|
||||
}
|
||||
}
|
||||
|
||||
class ExtHostLogFileOutputChannel extends AbstractExtHostOutputChannel {
|
||||
export const LogOutputChannelFactory = new class implements IOutputChannelFactory {
|
||||
|
||||
constructor(name: string, file: URI, proxy: MainThreadOutputServiceShape) {
|
||||
super(name, true, file, proxy);
|
||||
}
|
||||
_namePool = 1;
|
||||
|
||||
append(value: string): void {
|
||||
throw new Error('Not supported');
|
||||
}
|
||||
}
|
||||
|
||||
let namePool = 1;
|
||||
async function createExtHostOutputChannel(name: string, outputDirPromise: Promise<string>, proxy: MainThreadOutputServiceShape): Promise<AbstractExtHostOutputChannel> {
|
||||
try {
|
||||
const outputDir = await outputDirPromise;
|
||||
const fileName = `${namePool++}-${name}`;
|
||||
const file = URI.file(join(outputDir, `${fileName}.log`));
|
||||
const appender = new OutputAppender(fileName, file.fsPath);
|
||||
return new ExtHostOutputChannelBackedByFile(name, appender, proxy);
|
||||
} catch (error) {
|
||||
// Do not crash if logger cannot be created
|
||||
console.log(error);
|
||||
return new ExtHostPushOutputChannel(name, proxy);
|
||||
}
|
||||
}
|
||||
|
||||
export class ExtHostOutputService implements ExtHostOutputServiceShape {
|
||||
|
||||
private readonly _outputDir: Promise<string>;
|
||||
private _proxy: MainThreadOutputServiceShape;
|
||||
private _channels: Map<string, AbstractExtHostOutputChannel> = new Map<string, AbstractExtHostOutputChannel>();
|
||||
private _visibleChannelDisposable: IDisposable;
|
||||
|
||||
constructor(logsLocation: URI, mainContext: IMainContext) {
|
||||
const outputDirPath = join(logsLocation.fsPath, `output_logging_${toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, '')}`);
|
||||
this._outputDir = dirExists(outputDirPath).then(exists => exists ? exists : mkdirp(outputDirPath).then(() => true)).then(() => outputDirPath);
|
||||
this._proxy = mainContext.getProxy(MainContext.MainThreadOutputService);
|
||||
}
|
||||
|
||||
$setVisibleChannel(channelId: string): void {
|
||||
if (this._visibleChannelDisposable) {
|
||||
this._visibleChannelDisposable = dispose(this._visibleChannelDisposable);
|
||||
}
|
||||
if (channelId) {
|
||||
const channel = this._channels.get(channelId);
|
||||
if (channel) {
|
||||
this._visibleChannelDisposable = channel.onDidAppend(() => channel.update());
|
||||
}
|
||||
async createOutputChannel(name: string, logsLocation: URI, proxy: MainThreadOutputServiceShape): Promise<AbstractExtHostOutputChannel> {
|
||||
try {
|
||||
const outputDirPath = join(logsLocation.fsPath, `output_logging_${toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, '')}`);
|
||||
const outputDir = await dirExists(outputDirPath).then(exists => exists ? exists : mkdirp(outputDirPath).then(() => true)).then(() => outputDirPath);
|
||||
const fileName = `${this._namePool++}-${name}`;
|
||||
const file = URI.file(join(outputDir, `${fileName}.log`));
|
||||
const appender = new OutputAppender(fileName, file.fsPath);
|
||||
return new ExtHostOutputChannelBackedByFile(name, appender, proxy);
|
||||
} catch (error) {
|
||||
// Do not crash if logger cannot be created
|
||||
console.log(error);
|
||||
return new ExtHostPushOutputChannel(name, proxy);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
createOutputChannel(name: string): vscode.OutputChannel {
|
||||
name = name.trim();
|
||||
if (!name) {
|
||||
throw new Error('illegal argument `name`. must not be falsy');
|
||||
} else {
|
||||
const extHostOutputChannel = createExtHostOutputChannel(name, this._outputDir, this._proxy);
|
||||
extHostOutputChannel.then(channel => channel._id.then(id => this._channels.set(id, channel)));
|
||||
return <vscode.OutputChannel>{
|
||||
append(value: string): void {
|
||||
extHostOutputChannel.then(channel => channel.append(value));
|
||||
},
|
||||
appendLine(value: string): void {
|
||||
extHostOutputChannel.then(channel => channel.appendLine(value));
|
||||
},
|
||||
clear(): void {
|
||||
extHostOutputChannel.then(channel => channel.clear());
|
||||
},
|
||||
show(columnOrPreserveFocus?: vscode.ViewColumn | boolean, preserveFocus?: boolean): void {
|
||||
extHostOutputChannel.then(channel => channel.show(columnOrPreserveFocus, preserveFocus));
|
||||
},
|
||||
hide(): void {
|
||||
extHostOutputChannel.then(channel => channel.hide());
|
||||
},
|
||||
dispose(): void {
|
||||
extHostOutputChannel.then(channel => channel.dispose());
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
createOutputChannelFromLogFile(name: string, file: URI): vscode.OutputChannel {
|
||||
name = name.trim();
|
||||
if (!name) {
|
||||
throw new Error('illegal argument `name`. must not be falsy');
|
||||
}
|
||||
if (!file) {
|
||||
throw new Error('illegal argument `file`. must not be falsy');
|
||||
}
|
||||
return new ExtHostLogFileOutputChannel(name, file, this._proxy);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ProgressOptions } from 'vscode';
|
||||
import { MainThreadProgressShape, ExtHostProgressShape } from '../common/extHost.protocol';
|
||||
import { ProgressLocation } from './extHostTypeConverters';
|
||||
import { Progress, IProgressStep } from 'vs/platform/progress/common/progress';
|
||||
import { localize } from 'vs/nls';
|
||||
import { CancellationTokenSource, CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { debounce } from 'vs/base/common/decorators';
|
||||
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
|
||||
export class ExtHostProgress implements ExtHostProgressShape {
|
||||
|
||||
private _proxy: MainThreadProgressShape;
|
||||
private _handles: number = 0;
|
||||
private _mapHandleToCancellationSource: Map<number, CancellationTokenSource> = new Map();
|
||||
|
||||
constructor(proxy: MainThreadProgressShape) {
|
||||
this._proxy = proxy;
|
||||
}
|
||||
|
||||
withProgress<R>(extension: IExtensionDescription, options: ProgressOptions, task: (progress: Progress<IProgressStep>, token: CancellationToken) => Thenable<R>): Thenable<R> {
|
||||
const handle = this._handles++;
|
||||
const { title, location, cancellable } = options;
|
||||
const source = localize('extensionSource', "{0} (Extension)", extension.displayName || extension.name);
|
||||
this._proxy.$startProgress(handle, { location: ProgressLocation.from(location), title, source, cancellable });
|
||||
return this._withProgress(handle, task, !!cancellable);
|
||||
}
|
||||
|
||||
private _withProgress<R>(handle: number, task: (progress: Progress<IProgressStep>, token: CancellationToken) => Thenable<R>, cancellable: boolean): Thenable<R> {
|
||||
let source: CancellationTokenSource | undefined;
|
||||
if (cancellable) {
|
||||
source = new CancellationTokenSource();
|
||||
this._mapHandleToCancellationSource.set(handle, source);
|
||||
}
|
||||
|
||||
const progressEnd = (handle: number): void => {
|
||||
this._proxy.$progressEnd(handle);
|
||||
this._mapHandleToCancellationSource.delete(handle);
|
||||
if (source) {
|
||||
source.dispose();
|
||||
}
|
||||
};
|
||||
|
||||
let p: Thenable<R>;
|
||||
|
||||
try {
|
||||
p = task(new ProgressCallback(this._proxy, handle), cancellable && source ? source.token : CancellationToken.None);
|
||||
} catch (err) {
|
||||
progressEnd(handle);
|
||||
throw err;
|
||||
}
|
||||
|
||||
p.then(result => progressEnd(handle), err => progressEnd(handle));
|
||||
return p;
|
||||
}
|
||||
|
||||
public $acceptProgressCanceled(handle: number): void {
|
||||
const source = this._mapHandleToCancellationSource.get(handle);
|
||||
if (source) {
|
||||
source.cancel();
|
||||
this._mapHandleToCancellationSource.delete(handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function mergeProgress(result: IProgressStep, currentValue: IProgressStep): IProgressStep {
|
||||
result.message = currentValue.message;
|
||||
if (typeof currentValue.increment === 'number') {
|
||||
if (typeof result.increment === 'number') {
|
||||
result.increment += currentValue.increment;
|
||||
} else {
|
||||
result.increment = currentValue.increment;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
class ProgressCallback extends Progress<IProgressStep> {
|
||||
constructor(private _proxy: MainThreadProgressShape, private _handle: number) {
|
||||
super(p => this.throttledReport(p));
|
||||
}
|
||||
|
||||
@debounce(100, (result: IProgressStep, currentValue: IProgressStep) => mergeProgress(result, currentValue), () => Object.create(null))
|
||||
throttledReport(p: IProgressStep): void {
|
||||
this._proxy.$progressReport(this._handle, p);
|
||||
}
|
||||
}
|
||||
@@ -1,625 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { asPromise } from 'vs/base/common/async';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { ExtHostCommands } from 'vs/workbench/api/node/extHostCommands';
|
||||
import { IExtHostWorkspaceProvider } from 'vs/workbench/api/node/extHostWorkspace';
|
||||
import { InputBox, InputBoxOptions, QuickInput, QuickInputButton, QuickPick, QuickPickItem, QuickPickOptions, WorkspaceFolder, WorkspaceFolderPickOptions } from 'vscode';
|
||||
import { ExtHostQuickOpenShape, IMainContext, MainContext, MainThreadQuickOpenShape, TransferQuickPickItems, TransferQuickInput, TransferQuickInputButton } from '../common/extHost.protocol';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { ThemeIcon, QuickInputButtons } from 'vs/workbench/api/node/extHostTypes';
|
||||
import { isPromiseCanceledError } from 'vs/base/common/errors';
|
||||
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
|
||||
import { coalesce } from 'vs/base/common/arrays';
|
||||
|
||||
export type Item = string | QuickPickItem;
|
||||
|
||||
export class ExtHostQuickOpen implements ExtHostQuickOpenShape {
|
||||
|
||||
private _proxy: MainThreadQuickOpenShape;
|
||||
private _workspace: IExtHostWorkspaceProvider;
|
||||
private _commands: ExtHostCommands;
|
||||
|
||||
private _onDidSelectItem?: (handle: number) => void;
|
||||
private _validateInput?: (input: string) => string | undefined | null | Thenable<string | undefined | null>;
|
||||
|
||||
private _sessions = new Map<number, ExtHostQuickInput>();
|
||||
|
||||
private _instances = 0;
|
||||
|
||||
constructor(mainContext: IMainContext, workspace: IExtHostWorkspaceProvider, commands: ExtHostCommands) {
|
||||
this._proxy = mainContext.getProxy(MainContext.MainThreadQuickOpen);
|
||||
this._workspace = workspace;
|
||||
this._commands = commands;
|
||||
}
|
||||
|
||||
showQuickPick(itemsOrItemsPromise: QuickPickItem[] | Promise<QuickPickItem[]>, enableProposedApi: boolean, options: QuickPickOptions & { canPickMany: true; }, token?: CancellationToken): Promise<QuickPickItem[] | undefined>;
|
||||
showQuickPick(itemsOrItemsPromise: string[] | Promise<string[]>, enableProposedApi: boolean, options?: QuickPickOptions, token?: CancellationToken): Promise<string | undefined>;
|
||||
showQuickPick(itemsOrItemsPromise: QuickPickItem[] | Promise<QuickPickItem[]>, enableProposedApi: boolean, options?: QuickPickOptions, token?: CancellationToken): Promise<QuickPickItem | undefined>;
|
||||
showQuickPick(itemsOrItemsPromise: Item[] | Promise<Item[]>, enableProposedApi: boolean, options?: QuickPickOptions, token: CancellationToken = CancellationToken.None): Promise<Item | Item[] | undefined> {
|
||||
|
||||
// clear state from last invocation
|
||||
this._onDidSelectItem = undefined;
|
||||
|
||||
const itemsPromise = <Promise<Item[]>>Promise.resolve(itemsOrItemsPromise);
|
||||
|
||||
const instance = ++this._instances;
|
||||
|
||||
const quickPickWidget = this._proxy.$show(instance, {
|
||||
placeHolder: options && options.placeHolder,
|
||||
matchOnDescription: options && options.matchOnDescription,
|
||||
matchOnDetail: options && options.matchOnDetail,
|
||||
ignoreFocusLost: options && options.ignoreFocusOut,
|
||||
canPickMany: options && options.canPickMany
|
||||
}, token);
|
||||
|
||||
const widgetClosedMarker = {};
|
||||
const widgetClosedPromise = quickPickWidget.then(() => widgetClosedMarker);
|
||||
|
||||
return Promise.race([widgetClosedPromise, itemsPromise]).then(result => {
|
||||
if (result === widgetClosedMarker) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return itemsPromise.then(items => {
|
||||
|
||||
const pickItems: TransferQuickPickItems[] = [];
|
||||
for (let handle = 0; handle < items.length; handle++) {
|
||||
|
||||
const item = items[handle];
|
||||
let label: string;
|
||||
let description: string | undefined;
|
||||
let detail: string | undefined;
|
||||
let picked: boolean | undefined;
|
||||
let alwaysShow: boolean | undefined;
|
||||
|
||||
if (typeof item === 'string') {
|
||||
label = item;
|
||||
} else {
|
||||
label = item.label;
|
||||
description = item.description;
|
||||
detail = item.detail;
|
||||
picked = item.picked;
|
||||
alwaysShow = item.alwaysShow;
|
||||
}
|
||||
pickItems.push({
|
||||
label,
|
||||
description,
|
||||
handle,
|
||||
detail,
|
||||
picked,
|
||||
alwaysShow
|
||||
});
|
||||
}
|
||||
|
||||
// handle selection changes
|
||||
if (options && typeof options.onDidSelectItem === 'function') {
|
||||
this._onDidSelectItem = (handle) => {
|
||||
options.onDidSelectItem!(items[handle]);
|
||||
};
|
||||
}
|
||||
|
||||
// show items
|
||||
this._proxy.$setItems(instance, pickItems);
|
||||
|
||||
return quickPickWidget.then(handle => {
|
||||
if (typeof handle === 'number') {
|
||||
return items[handle];
|
||||
} else if (Array.isArray(handle)) {
|
||||
return handle.map(h => items[h]);
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
});
|
||||
}).then(undefined, err => {
|
||||
if (isPromiseCanceledError(err)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
this._proxy.$setError(instance, err);
|
||||
|
||||
return Promise.reject(err);
|
||||
});
|
||||
}
|
||||
|
||||
$onItemSelected(handle: number): void {
|
||||
if (this._onDidSelectItem) {
|
||||
this._onDidSelectItem(handle);
|
||||
}
|
||||
}
|
||||
|
||||
// ---- input
|
||||
|
||||
showInput(options?: InputBoxOptions, token: CancellationToken = CancellationToken.None): Promise<string> {
|
||||
|
||||
// global validate fn used in callback below
|
||||
this._validateInput = options ? options.validateInput : undefined;
|
||||
|
||||
return this._proxy.$input(options, typeof this._validateInput === 'function', token)
|
||||
.then(undefined, err => {
|
||||
if (isPromiseCanceledError(err)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return Promise.reject(err);
|
||||
});
|
||||
}
|
||||
|
||||
$validateInput(input: string): Promise<string | null | undefined> {
|
||||
if (this._validateInput) {
|
||||
return asPromise(() => this._validateInput!(input));
|
||||
}
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
// ---- workspace folder picker
|
||||
|
||||
showWorkspaceFolderPick(options?: WorkspaceFolderPickOptions, token = CancellationToken.None): Promise<WorkspaceFolder | undefined> {
|
||||
return this._commands.executeCommand('_workbench.pickWorkspaceFolder', [options]).then(async (selectedFolder: WorkspaceFolder) => {
|
||||
if (!selectedFolder) {
|
||||
return undefined;
|
||||
}
|
||||
const workspaceFolders = await this._workspace.getWorkspaceFolders2();
|
||||
if (!workspaceFolders) {
|
||||
return undefined;
|
||||
}
|
||||
return workspaceFolders.filter(folder => folder.uri.toString() === selectedFolder.uri.toString())[0];
|
||||
});
|
||||
}
|
||||
|
||||
// ---- QuickInput
|
||||
|
||||
createQuickPick<T extends QuickPickItem>(extensionId: ExtensionIdentifier, enableProposedApi: boolean): QuickPick<T> {
|
||||
const session: ExtHostQuickPick<T> = new ExtHostQuickPick(this._proxy, extensionId, enableProposedApi, () => this._sessions.delete(session._id));
|
||||
this._sessions.set(session._id, session);
|
||||
return session;
|
||||
}
|
||||
|
||||
createInputBox(extensionId: ExtensionIdentifier): InputBox {
|
||||
const session: ExtHostInputBox = new ExtHostInputBox(this._proxy, extensionId, () => this._sessions.delete(session._id));
|
||||
this._sessions.set(session._id, session);
|
||||
return session;
|
||||
}
|
||||
|
||||
$onDidChangeValue(sessionId: number, value: string): void {
|
||||
const session = this._sessions.get(sessionId);
|
||||
if (session) {
|
||||
session._fireDidChangeValue(value);
|
||||
}
|
||||
}
|
||||
|
||||
$onDidAccept(sessionId: number): void {
|
||||
const session = this._sessions.get(sessionId);
|
||||
if (session) {
|
||||
session._fireDidAccept();
|
||||
}
|
||||
}
|
||||
|
||||
$onDidChangeActive(sessionId: number, handles: number[]): void {
|
||||
const session = this._sessions.get(sessionId);
|
||||
if (session instanceof ExtHostQuickPick) {
|
||||
session._fireDidChangeActive(handles);
|
||||
}
|
||||
}
|
||||
|
||||
$onDidChangeSelection(sessionId: number, handles: number[]): void {
|
||||
const session = this._sessions.get(sessionId);
|
||||
if (session instanceof ExtHostQuickPick) {
|
||||
session._fireDidChangeSelection(handles);
|
||||
}
|
||||
}
|
||||
|
||||
$onDidTriggerButton(sessionId: number, handle: number): void {
|
||||
const session = this._sessions.get(sessionId);
|
||||
if (session) {
|
||||
session._fireDidTriggerButton(handle);
|
||||
}
|
||||
}
|
||||
|
||||
$onDidHide(sessionId: number): void {
|
||||
const session = this._sessions.get(sessionId);
|
||||
if (session) {
|
||||
session._fireDidHide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ExtHostQuickInput implements QuickInput {
|
||||
|
||||
private static _nextId = 1;
|
||||
_id = ExtHostQuickPick._nextId++;
|
||||
|
||||
private _title: string;
|
||||
private _steps: number;
|
||||
private _totalSteps: number;
|
||||
private _visible = false;
|
||||
private _expectingHide = false;
|
||||
private _enabled = true;
|
||||
private _busy = false;
|
||||
private _ignoreFocusOut = true;
|
||||
private _value = '';
|
||||
private _placeholder: string;
|
||||
private _buttons: QuickInputButton[] = [];
|
||||
private _handlesToButtons = new Map<number, QuickInputButton>();
|
||||
private _onDidAcceptEmitter = new Emitter<void>();
|
||||
private _onDidChangeValueEmitter = new Emitter<string>();
|
||||
private _onDidTriggerButtonEmitter = new Emitter<QuickInputButton>();
|
||||
private _onDidHideEmitter = new Emitter<void>();
|
||||
private _updateTimeout: any;
|
||||
private _pendingUpdate: TransferQuickInput = { id: this._id };
|
||||
|
||||
private _disposed = false;
|
||||
protected _disposables: IDisposable[] = [
|
||||
this._onDidTriggerButtonEmitter,
|
||||
this._onDidHideEmitter,
|
||||
this._onDidAcceptEmitter,
|
||||
this._onDidChangeValueEmitter
|
||||
];
|
||||
|
||||
constructor(protected _proxy: MainThreadQuickOpenShape, protected _extensionId: ExtensionIdentifier, private _onDidDispose: () => void) {
|
||||
}
|
||||
|
||||
get title() {
|
||||
return this._title;
|
||||
}
|
||||
|
||||
set title(title: string) {
|
||||
this._title = title;
|
||||
this.update({ title });
|
||||
}
|
||||
|
||||
get step() {
|
||||
return this._steps;
|
||||
}
|
||||
|
||||
set step(step: number) {
|
||||
this._steps = step;
|
||||
this.update({ step });
|
||||
}
|
||||
|
||||
get totalSteps() {
|
||||
return this._totalSteps;
|
||||
}
|
||||
|
||||
set totalSteps(totalSteps: number) {
|
||||
this._totalSteps = totalSteps;
|
||||
this.update({ totalSteps });
|
||||
}
|
||||
|
||||
get enabled() {
|
||||
return this._enabled;
|
||||
}
|
||||
|
||||
set enabled(enabled: boolean) {
|
||||
this._enabled = enabled;
|
||||
this.update({ enabled });
|
||||
}
|
||||
|
||||
get busy() {
|
||||
return this._busy;
|
||||
}
|
||||
|
||||
set busy(busy: boolean) {
|
||||
this._busy = busy;
|
||||
this.update({ busy });
|
||||
}
|
||||
|
||||
get ignoreFocusOut() {
|
||||
return this._ignoreFocusOut;
|
||||
}
|
||||
|
||||
set ignoreFocusOut(ignoreFocusOut: boolean) {
|
||||
this._ignoreFocusOut = ignoreFocusOut;
|
||||
this.update({ ignoreFocusOut });
|
||||
}
|
||||
|
||||
get value() {
|
||||
return this._value;
|
||||
}
|
||||
|
||||
set value(value: string) {
|
||||
this._value = value;
|
||||
this.update({ value });
|
||||
}
|
||||
|
||||
get placeholder() {
|
||||
return this._placeholder;
|
||||
}
|
||||
|
||||
set placeholder(placeholder: string) {
|
||||
this._placeholder = placeholder;
|
||||
this.update({ placeholder });
|
||||
}
|
||||
|
||||
onDidChangeValue = this._onDidChangeValueEmitter.event;
|
||||
|
||||
onDidAccept = this._onDidAcceptEmitter.event;
|
||||
|
||||
get buttons() {
|
||||
return this._buttons;
|
||||
}
|
||||
|
||||
set buttons(buttons: QuickInputButton[]) {
|
||||
this._buttons = buttons.slice();
|
||||
this._handlesToButtons.clear();
|
||||
buttons.forEach((button, i) => {
|
||||
const handle = button === QuickInputButtons.Back ? -1 : i;
|
||||
this._handlesToButtons.set(handle, button);
|
||||
});
|
||||
this.update({
|
||||
buttons: buttons.map<TransferQuickInputButton>((button, i) => ({
|
||||
iconPath: getIconUris(button.iconPath),
|
||||
tooltip: button.tooltip,
|
||||
handle: button === QuickInputButtons.Back ? -1 : i,
|
||||
}))
|
||||
});
|
||||
}
|
||||
|
||||
onDidTriggerButton = this._onDidTriggerButtonEmitter.event;
|
||||
|
||||
show(): void {
|
||||
this._visible = true;
|
||||
this._expectingHide = true;
|
||||
this.update({ visible: true });
|
||||
}
|
||||
|
||||
hide(): void {
|
||||
this._visible = false;
|
||||
this.update({ visible: false });
|
||||
}
|
||||
|
||||
onDidHide = this._onDidHideEmitter.event;
|
||||
|
||||
_fireDidAccept() {
|
||||
this._onDidAcceptEmitter.fire();
|
||||
}
|
||||
|
||||
_fireDidChangeValue(value: string) {
|
||||
this._value = value;
|
||||
this._onDidChangeValueEmitter.fire(value);
|
||||
}
|
||||
|
||||
_fireDidTriggerButton(handle: number) {
|
||||
const button = this._handlesToButtons.get(handle);
|
||||
if (button) {
|
||||
this._onDidTriggerButtonEmitter.fire(button);
|
||||
}
|
||||
}
|
||||
|
||||
_fireDidHide() {
|
||||
if (this._expectingHide) {
|
||||
this._expectingHide = false;
|
||||
this._onDidHideEmitter.fire();
|
||||
}
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
if (this._disposed) {
|
||||
return;
|
||||
}
|
||||
this._disposed = true;
|
||||
this._fireDidHide();
|
||||
this._disposables = dispose(this._disposables);
|
||||
if (this._updateTimeout) {
|
||||
clearTimeout(this._updateTimeout);
|
||||
this._updateTimeout = undefined;
|
||||
}
|
||||
this._onDidDispose();
|
||||
this._proxy.$dispose(this._id);
|
||||
}
|
||||
|
||||
protected update(properties: Record<string, any>): void {
|
||||
if (this._disposed) {
|
||||
return;
|
||||
}
|
||||
for (const key of Object.keys(properties)) {
|
||||
const value = properties[key];
|
||||
this._pendingUpdate[key] = value === undefined ? null : value;
|
||||
}
|
||||
|
||||
if ('visible' in this._pendingUpdate) {
|
||||
if (this._updateTimeout) {
|
||||
clearTimeout(this._updateTimeout);
|
||||
this._updateTimeout = undefined;
|
||||
}
|
||||
this.dispatchUpdate();
|
||||
} else if (this._visible && !this._updateTimeout) {
|
||||
// Defer the update so that multiple changes to setters dont cause a redraw each
|
||||
this._updateTimeout = setTimeout(() => {
|
||||
this._updateTimeout = undefined;
|
||||
this.dispatchUpdate();
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private dispatchUpdate() {
|
||||
this._proxy.$createOrUpdate(this._pendingUpdate);
|
||||
this._pendingUpdate = { id: this._id };
|
||||
}
|
||||
}
|
||||
|
||||
function getIconUris(iconPath: QuickInputButton['iconPath']): { dark: URI, light?: URI } | undefined {
|
||||
const dark = getDarkIconUri(iconPath);
|
||||
const light = getLightIconUri(iconPath);
|
||||
if (!light && !dark) {
|
||||
return undefined;
|
||||
}
|
||||
return { dark: (dark || light)!, light };
|
||||
}
|
||||
|
||||
function getLightIconUri(iconPath: QuickInputButton['iconPath']) {
|
||||
if (iconPath && !(iconPath instanceof ThemeIcon)) {
|
||||
if (typeof iconPath === 'string'
|
||||
|| iconPath instanceof URI) {
|
||||
return getIconUri(iconPath);
|
||||
}
|
||||
return getIconUri(iconPath['light']);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function getDarkIconUri(iconPath: QuickInputButton['iconPath']) {
|
||||
if (iconPath && !(iconPath instanceof ThemeIcon) && iconPath['dark']) {
|
||||
return getIconUri(iconPath['dark']);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function getIconUri(iconPath: string | URI) {
|
||||
if (iconPath instanceof URI) {
|
||||
return iconPath;
|
||||
}
|
||||
return URI.file(iconPath);
|
||||
}
|
||||
|
||||
class ExtHostQuickPick<T extends QuickPickItem> extends ExtHostQuickInput implements QuickPick<T> {
|
||||
|
||||
private _items: T[] = [];
|
||||
private _handlesToItems = new Map<number, T>();
|
||||
private _itemsToHandles = new Map<T, number>();
|
||||
private _canSelectMany = false;
|
||||
private _matchOnDescription = true;
|
||||
private _matchOnDetail = true;
|
||||
private _activeItems: T[] = [];
|
||||
private _onDidChangeActiveEmitter = new Emitter<T[]>();
|
||||
private _selectedItems: T[] = [];
|
||||
private _onDidChangeSelectionEmitter = new Emitter<T[]>();
|
||||
|
||||
constructor(proxy: MainThreadQuickOpenShape, extensionId: ExtensionIdentifier, enableProposedApi: boolean, onDispose: () => void) {
|
||||
super(proxy, extensionId, onDispose);
|
||||
this._disposables.push(
|
||||
this._onDidChangeActiveEmitter,
|
||||
this._onDidChangeSelectionEmitter,
|
||||
);
|
||||
this.update({ type: 'quickPick' });
|
||||
}
|
||||
|
||||
get items() {
|
||||
return this._items;
|
||||
}
|
||||
|
||||
set items(items: T[]) {
|
||||
this._items = items.slice();
|
||||
this._handlesToItems.clear();
|
||||
this._itemsToHandles.clear();
|
||||
items.forEach((item, i) => {
|
||||
this._handlesToItems.set(i, item);
|
||||
this._itemsToHandles.set(item, i);
|
||||
});
|
||||
this.update({
|
||||
items: items.map((item, i) => ({
|
||||
label: item.label,
|
||||
description: item.description,
|
||||
handle: i,
|
||||
detail: item.detail,
|
||||
picked: item.picked,
|
||||
alwaysShow: item.alwaysShow
|
||||
}))
|
||||
});
|
||||
}
|
||||
|
||||
get canSelectMany() {
|
||||
return this._canSelectMany;
|
||||
}
|
||||
|
||||
set canSelectMany(canSelectMany: boolean) {
|
||||
this._canSelectMany = canSelectMany;
|
||||
this.update({ canSelectMany });
|
||||
}
|
||||
|
||||
get matchOnDescription() {
|
||||
return this._matchOnDescription;
|
||||
}
|
||||
|
||||
set matchOnDescription(matchOnDescription: boolean) {
|
||||
this._matchOnDescription = matchOnDescription;
|
||||
this.update({ matchOnDescription });
|
||||
}
|
||||
|
||||
get matchOnDetail() {
|
||||
return this._matchOnDetail;
|
||||
}
|
||||
|
||||
set matchOnDetail(matchOnDetail: boolean) {
|
||||
this._matchOnDetail = matchOnDetail;
|
||||
this.update({ matchOnDetail });
|
||||
}
|
||||
|
||||
get activeItems() {
|
||||
return this._activeItems;
|
||||
}
|
||||
|
||||
set activeItems(activeItems: T[]) {
|
||||
this._activeItems = activeItems.filter(item => this._itemsToHandles.has(item));
|
||||
this.update({ activeItems: this._activeItems.map(item => this._itemsToHandles.get(item)) });
|
||||
}
|
||||
|
||||
onDidChangeActive = this._onDidChangeActiveEmitter.event;
|
||||
|
||||
get selectedItems() {
|
||||
return this._selectedItems;
|
||||
}
|
||||
|
||||
set selectedItems(selectedItems: T[]) {
|
||||
this._selectedItems = selectedItems.filter(item => this._itemsToHandles.has(item));
|
||||
this.update({ selectedItems: this._selectedItems.map(item => this._itemsToHandles.get(item)) });
|
||||
}
|
||||
|
||||
onDidChangeSelection = this._onDidChangeSelectionEmitter.event;
|
||||
|
||||
_fireDidChangeActive(handles: number[]) {
|
||||
const items = coalesce(handles.map(handle => this._handlesToItems.get(handle)));
|
||||
this._activeItems = items;
|
||||
this._onDidChangeActiveEmitter.fire(items);
|
||||
}
|
||||
|
||||
_fireDidChangeSelection(handles: number[]) {
|
||||
const items = coalesce(handles.map(handle => this._handlesToItems.get(handle)));
|
||||
this._selectedItems = items;
|
||||
this._onDidChangeSelectionEmitter.fire(items);
|
||||
}
|
||||
}
|
||||
|
||||
class ExtHostInputBox extends ExtHostQuickInput implements InputBox {
|
||||
|
||||
private _password: boolean;
|
||||
private _prompt: string;
|
||||
private _validationMessage: string;
|
||||
|
||||
constructor(proxy: MainThreadQuickOpenShape, extensionId: ExtensionIdentifier, onDispose: () => void) {
|
||||
super(proxy, extensionId, onDispose);
|
||||
this.update({ type: 'inputBox' });
|
||||
}
|
||||
|
||||
get password() {
|
||||
return this._password;
|
||||
}
|
||||
|
||||
set password(password: boolean) {
|
||||
this._password = password;
|
||||
this.update({ password });
|
||||
}
|
||||
|
||||
get prompt() {
|
||||
return this._prompt;
|
||||
}
|
||||
|
||||
set prompt(prompt: string) {
|
||||
this._prompt = prompt;
|
||||
this.update({ prompt });
|
||||
}
|
||||
|
||||
get validationMessage() {
|
||||
return this._validationMessage;
|
||||
}
|
||||
|
||||
set validationMessage(validationMessage: string) {
|
||||
this._validationMessage = validationMessage;
|
||||
this.update({ validationMessage });
|
||||
}
|
||||
}
|
||||
@@ -1,712 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { debounce } from 'vs/base/common/decorators';
|
||||
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { asPromise } from 'vs/base/common/async';
|
||||
import { ExtHostCommands } from 'vs/workbench/api/node/extHostCommands';
|
||||
import { MainContext, MainThreadSCMShape, SCMRawResource, SCMRawResourceSplice, SCMRawResourceSplices, IMainContext, ExtHostSCMShape, CommandDto } from '../common/extHost.protocol';
|
||||
import { sortedDiff } from 'vs/base/common/arrays';
|
||||
import { comparePaths } from 'vs/base/common/comparers';
|
||||
import * as vscode from 'vscode';
|
||||
import { ISplice } from 'vs/base/common/sequence';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
|
||||
type ProviderHandle = number;
|
||||
type GroupHandle = number;
|
||||
type ResourceStateHandle = number;
|
||||
|
||||
function getIconPath(decorations?: vscode.SourceControlResourceThemableDecorations): string | undefined {
|
||||
if (!decorations) {
|
||||
return undefined;
|
||||
} else if (typeof decorations.iconPath === 'string') {
|
||||
return URI.file(decorations.iconPath).toString();
|
||||
} else if (decorations.iconPath) {
|
||||
return `${decorations.iconPath}`;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function compareResourceThemableDecorations(a: vscode.SourceControlResourceThemableDecorations, b: vscode.SourceControlResourceThemableDecorations): number {
|
||||
if (!a.iconPath && !b.iconPath) {
|
||||
return 0;
|
||||
} else if (!a.iconPath) {
|
||||
return -1;
|
||||
} else if (!b.iconPath) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
const aPath = typeof a.iconPath === 'string' ? a.iconPath : a.iconPath.fsPath;
|
||||
const bPath = typeof b.iconPath === 'string' ? b.iconPath : b.iconPath.fsPath;
|
||||
return comparePaths(aPath, bPath);
|
||||
}
|
||||
|
||||
function compareResourceStatesDecorations(a: vscode.SourceControlResourceDecorations, b: vscode.SourceControlResourceDecorations): number {
|
||||
let result = 0;
|
||||
|
||||
if (a.strikeThrough !== b.strikeThrough) {
|
||||
return a.strikeThrough ? 1 : -1;
|
||||
}
|
||||
|
||||
if (a.faded !== b.faded) {
|
||||
return a.faded ? 1 : -1;
|
||||
}
|
||||
|
||||
if (a.tooltip !== b.tooltip) {
|
||||
return (a.tooltip || '').localeCompare(b.tooltip || '');
|
||||
}
|
||||
|
||||
result = compareResourceThemableDecorations(a, b);
|
||||
|
||||
if (result !== 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (a.light && b.light) {
|
||||
result = compareResourceThemableDecorations(a.light, b.light);
|
||||
} else if (a.light) {
|
||||
return 1;
|
||||
} else if (b.light) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (result !== 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (a.dark && b.dark) {
|
||||
result = compareResourceThemableDecorations(a.dark, b.dark);
|
||||
} else if (a.dark) {
|
||||
return 1;
|
||||
} else if (b.dark) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function compareResourceStates(a: vscode.SourceControlResourceState, b: vscode.SourceControlResourceState): number {
|
||||
let result = comparePaths(a.resourceUri.fsPath, b.resourceUri.fsPath, true);
|
||||
|
||||
if (result !== 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (a.decorations && b.decorations) {
|
||||
result = compareResourceStatesDecorations(a.decorations, b.decorations);
|
||||
} else if (a.decorations) {
|
||||
return 1;
|
||||
} else if (b.decorations) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function compareArgs(a: any[], b: any[]): boolean {
|
||||
for (let i = 0; i < a.length; i++) {
|
||||
if (a[i] !== b[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function commandEquals(a: vscode.Command, b: vscode.Command): boolean {
|
||||
return a.command === b.command
|
||||
&& a.title === b.title
|
||||
&& a.tooltip === b.tooltip
|
||||
&& (a.arguments && b.arguments ? compareArgs(a.arguments, b.arguments) : a.arguments === b.arguments);
|
||||
}
|
||||
|
||||
function commandListEquals(a: vscode.Command[], b: vscode.Command[]): boolean {
|
||||
if (a.length !== b.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let i = 0; i < a.length; i++) {
|
||||
if (!commandEquals(a[i], b[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export interface IValidateInput {
|
||||
(value: string, cursorPosition: number): vscode.ProviderResult<vscode.SourceControlInputBoxValidation | undefined | null>;
|
||||
}
|
||||
|
||||
export class ExtHostSCMInputBox implements vscode.SourceControlInputBox {
|
||||
|
||||
private _value: string = '';
|
||||
|
||||
get value(): string {
|
||||
return this._value;
|
||||
}
|
||||
|
||||
set value(value: string) {
|
||||
this._proxy.$setInputBoxValue(this._sourceControlHandle, value);
|
||||
this.updateValue(value);
|
||||
}
|
||||
|
||||
private _onDidChange = new Emitter<string>();
|
||||
|
||||
get onDidChange(): Event<string> {
|
||||
return this._onDidChange.event;
|
||||
}
|
||||
|
||||
private _placeholder: string = '';
|
||||
|
||||
get placeholder(): string {
|
||||
return this._placeholder;
|
||||
}
|
||||
|
||||
set placeholder(placeholder: string) {
|
||||
this._proxy.$setInputBoxPlaceholder(this._sourceControlHandle, placeholder);
|
||||
this._placeholder = placeholder;
|
||||
}
|
||||
|
||||
private _validateInput: IValidateInput;
|
||||
|
||||
get validateInput(): IValidateInput {
|
||||
if (!this._extension.enableProposedApi) {
|
||||
throw new Error(`[${this._extension.identifier.value}]: Proposed API is only available when running out of dev or with the following command line switch: --enable-proposed-api ${this._extension.identifier.value}`);
|
||||
}
|
||||
|
||||
return this._validateInput;
|
||||
}
|
||||
|
||||
set validateInput(fn: IValidateInput) {
|
||||
if (!this._extension.enableProposedApi) {
|
||||
throw new Error(`[${this._extension.identifier.value}]: Proposed API is only available when running out of dev or with the following command line switch: --enable-proposed-api ${this._extension.identifier.value}`);
|
||||
}
|
||||
|
||||
if (fn && typeof fn !== 'function') {
|
||||
console.warn('Invalid SCM input box validation function');
|
||||
return;
|
||||
}
|
||||
|
||||
this._validateInput = fn;
|
||||
this._proxy.$setValidationProviderIsEnabled(this._sourceControlHandle, !!fn);
|
||||
}
|
||||
|
||||
private _visible: boolean = true;
|
||||
|
||||
get visible(): boolean {
|
||||
return this._visible;
|
||||
}
|
||||
|
||||
set visible(visible: boolean) {
|
||||
visible = !!visible;
|
||||
this._visible = visible;
|
||||
this._proxy.$setInputBoxVisibility(this._sourceControlHandle, visible);
|
||||
}
|
||||
|
||||
constructor(private _extension: IExtensionDescription, private _proxy: MainThreadSCMShape, private _sourceControlHandle: number) {
|
||||
// noop
|
||||
}
|
||||
|
||||
$onInputBoxValueChange(value: string): void {
|
||||
this.updateValue(value);
|
||||
}
|
||||
|
||||
private updateValue(value: string): void {
|
||||
this._value = value;
|
||||
this._onDidChange.fire(value);
|
||||
}
|
||||
}
|
||||
|
||||
class ExtHostSourceControlResourceGroup implements vscode.SourceControlResourceGroup {
|
||||
|
||||
private static _handlePool: number = 0;
|
||||
private _resourceHandlePool: number = 0;
|
||||
private _resourceStates: vscode.SourceControlResourceState[] = [];
|
||||
|
||||
private _resourceStatesMap: Map<ResourceStateHandle, vscode.SourceControlResourceState> = new Map<ResourceStateHandle, vscode.SourceControlResourceState>();
|
||||
private _resourceStatesCommandsMap: Map<ResourceStateHandle, vscode.Command> = new Map<ResourceStateHandle, vscode.Command>();
|
||||
|
||||
private _onDidUpdateResourceStates = new Emitter<void>();
|
||||
readonly onDidUpdateResourceStates = this._onDidUpdateResourceStates.event;
|
||||
private _onDidDispose = new Emitter<void>();
|
||||
readonly onDidDispose = this._onDidDispose.event;
|
||||
|
||||
private _handlesSnapshot: number[] = [];
|
||||
private _resourceSnapshot: vscode.SourceControlResourceState[] = [];
|
||||
|
||||
get id(): string { return this._id; }
|
||||
|
||||
get label(): string { return this._label; }
|
||||
set label(label: string) {
|
||||
this._label = label;
|
||||
this._proxy.$updateGroupLabel(this._sourceControlHandle, this.handle, label);
|
||||
}
|
||||
|
||||
private _hideWhenEmpty: boolean | undefined = undefined;
|
||||
get hideWhenEmpty(): boolean | undefined { return this._hideWhenEmpty; }
|
||||
set hideWhenEmpty(hideWhenEmpty: boolean | undefined) {
|
||||
this._hideWhenEmpty = hideWhenEmpty;
|
||||
this._proxy.$updateGroup(this._sourceControlHandle, this.handle, { hideWhenEmpty });
|
||||
}
|
||||
|
||||
get resourceStates(): vscode.SourceControlResourceState[] { return [...this._resourceStates]; }
|
||||
set resourceStates(resources: vscode.SourceControlResourceState[]) {
|
||||
this._resourceStates = [...resources];
|
||||
this._onDidUpdateResourceStates.fire();
|
||||
}
|
||||
|
||||
readonly handle = ExtHostSourceControlResourceGroup._handlePool++;
|
||||
private _disposables: IDisposable[] = [];
|
||||
|
||||
constructor(
|
||||
private _proxy: MainThreadSCMShape,
|
||||
private _commands: ExtHostCommands,
|
||||
private _sourceControlHandle: number,
|
||||
private _id: string,
|
||||
private _label: string,
|
||||
) {
|
||||
this._proxy.$registerGroup(_sourceControlHandle, this.handle, _id, _label);
|
||||
}
|
||||
|
||||
getResourceState(handle: number): vscode.SourceControlResourceState | undefined {
|
||||
return this._resourceStatesMap.get(handle);
|
||||
}
|
||||
|
||||
$executeResourceCommand(handle: number): Promise<void> {
|
||||
const command = this._resourceStatesCommandsMap.get(handle);
|
||||
|
||||
if (!command) {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
return asPromise(() => this._commands.executeCommand(command.command, ...(command.arguments || [])));
|
||||
}
|
||||
|
||||
_takeResourceStateSnapshot(): SCMRawResourceSplice[] {
|
||||
const snapshot = [...this._resourceStates].sort(compareResourceStates);
|
||||
const diffs = sortedDiff(this._resourceSnapshot, snapshot, compareResourceStates);
|
||||
|
||||
const splices = diffs.map<ISplice<{ rawResource: SCMRawResource, handle: number }>>(diff => {
|
||||
const toInsert = diff.toInsert.map(r => {
|
||||
const handle = this._resourceHandlePool++;
|
||||
this._resourceStatesMap.set(handle, r);
|
||||
|
||||
const sourceUri = r.resourceUri;
|
||||
const iconPath = getIconPath(r.decorations);
|
||||
const lightIconPath = r.decorations && getIconPath(r.decorations.light) || iconPath;
|
||||
const darkIconPath = r.decorations && getIconPath(r.decorations.dark) || iconPath;
|
||||
const icons: string[] = [];
|
||||
|
||||
if (r.command) {
|
||||
this._resourceStatesCommandsMap.set(handle, r.command);
|
||||
}
|
||||
|
||||
if (lightIconPath) {
|
||||
icons.push(lightIconPath);
|
||||
}
|
||||
|
||||
if (darkIconPath && (darkIconPath !== lightIconPath)) {
|
||||
icons.push(darkIconPath);
|
||||
}
|
||||
|
||||
const tooltip = (r.decorations && r.decorations.tooltip) || '';
|
||||
const strikeThrough = r.decorations && !!r.decorations.strikeThrough;
|
||||
const faded = r.decorations && !!r.decorations.faded;
|
||||
|
||||
const source = r.decorations && r.decorations.source || undefined;
|
||||
const letter = r.decorations && r.decorations.letter || undefined;
|
||||
const color = r.decorations && r.decorations.color || undefined;
|
||||
|
||||
const rawResource = [handle, <UriComponents>sourceUri, icons, tooltip, strikeThrough, faded, source, letter, color] as SCMRawResource;
|
||||
|
||||
return { rawResource, handle };
|
||||
});
|
||||
|
||||
return { start: diff.start, deleteCount: diff.deleteCount, toInsert };
|
||||
});
|
||||
|
||||
const rawResourceSplices = splices
|
||||
.map(({ start, deleteCount, toInsert }) => [start, deleteCount, toInsert.map(i => i.rawResource)] as SCMRawResourceSplice);
|
||||
|
||||
const reverseSplices = splices.reverse();
|
||||
|
||||
for (const { start, deleteCount, toInsert } of reverseSplices) {
|
||||
const handles = toInsert.map(i => i.handle);
|
||||
const handlesToDelete = this._handlesSnapshot.splice(start, deleteCount, ...handles);
|
||||
|
||||
for (const handle of handlesToDelete) {
|
||||
this._resourceStatesMap.delete(handle);
|
||||
this._resourceStatesCommandsMap.delete(handle);
|
||||
}
|
||||
}
|
||||
|
||||
this._resourceSnapshot = snapshot;
|
||||
return rawResourceSplices;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._proxy.$unregisterGroup(this._sourceControlHandle, this.handle);
|
||||
this._disposables = dispose(this._disposables);
|
||||
this._onDidDispose.fire();
|
||||
}
|
||||
}
|
||||
|
||||
class ExtHostSourceControl implements vscode.SourceControl {
|
||||
|
||||
private static _handlePool: number = 0;
|
||||
private _groups: Map<GroupHandle, ExtHostSourceControlResourceGroup> = new Map<GroupHandle, ExtHostSourceControlResourceGroup>();
|
||||
|
||||
get id(): string {
|
||||
return this._id;
|
||||
}
|
||||
|
||||
get label(): string {
|
||||
return this._label;
|
||||
}
|
||||
|
||||
get rootUri(): vscode.Uri | undefined {
|
||||
return this._rootUri;
|
||||
}
|
||||
|
||||
private _inputBox: ExtHostSCMInputBox;
|
||||
get inputBox(): ExtHostSCMInputBox { return this._inputBox; }
|
||||
|
||||
private _count: number | undefined = undefined;
|
||||
|
||||
get count(): number | undefined {
|
||||
return this._count;
|
||||
}
|
||||
|
||||
set count(count: number | undefined) {
|
||||
if (this._count === count) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._count = count;
|
||||
this._proxy.$updateSourceControl(this.handle, { count });
|
||||
}
|
||||
|
||||
private _quickDiffProvider: vscode.QuickDiffProvider | undefined = undefined;
|
||||
|
||||
get quickDiffProvider(): vscode.QuickDiffProvider | undefined {
|
||||
return this._quickDiffProvider;
|
||||
}
|
||||
|
||||
set quickDiffProvider(quickDiffProvider: vscode.QuickDiffProvider | undefined) {
|
||||
this._quickDiffProvider = quickDiffProvider;
|
||||
this._proxy.$updateSourceControl(this.handle, { hasQuickDiffProvider: !!quickDiffProvider });
|
||||
}
|
||||
|
||||
private _commitTemplate: string | undefined = undefined;
|
||||
|
||||
get commitTemplate(): string | undefined {
|
||||
return this._commitTemplate;
|
||||
}
|
||||
|
||||
set commitTemplate(commitTemplate: string | undefined) {
|
||||
this._commitTemplate = commitTemplate;
|
||||
this._proxy.$updateSourceControl(this.handle, { commitTemplate });
|
||||
}
|
||||
|
||||
private _acceptInputCommand: vscode.Command | undefined = undefined;
|
||||
|
||||
get acceptInputCommand(): vscode.Command | undefined {
|
||||
return this._acceptInputCommand;
|
||||
}
|
||||
|
||||
set acceptInputCommand(acceptInputCommand: vscode.Command | undefined) {
|
||||
this._acceptInputCommand = acceptInputCommand;
|
||||
|
||||
const internal = this._commands.converter.toInternal(acceptInputCommand);
|
||||
this._proxy.$updateSourceControl(this.handle, { acceptInputCommand: internal });
|
||||
}
|
||||
|
||||
private _statusBarCommands: vscode.Command[] | undefined = undefined;
|
||||
|
||||
get statusBarCommands(): vscode.Command[] | undefined {
|
||||
return this._statusBarCommands;
|
||||
}
|
||||
|
||||
set statusBarCommands(statusBarCommands: vscode.Command[] | undefined) {
|
||||
if (this._statusBarCommands && statusBarCommands && commandListEquals(this._statusBarCommands, statusBarCommands)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._statusBarCommands = statusBarCommands;
|
||||
|
||||
const internal = (statusBarCommands || []).map(c => this._commands.converter.toInternal(c)) as CommandDto[];
|
||||
this._proxy.$updateSourceControl(this.handle, { statusBarCommands: internal });
|
||||
}
|
||||
|
||||
private _selected: boolean = false;
|
||||
|
||||
get selected(): boolean {
|
||||
return this._selected;
|
||||
}
|
||||
|
||||
private _onDidChangeSelection = new Emitter<boolean>();
|
||||
readonly onDidChangeSelection = this._onDidChangeSelection.event;
|
||||
|
||||
private handle: number = ExtHostSourceControl._handlePool++;
|
||||
|
||||
constructor(
|
||||
_extension: IExtensionDescription,
|
||||
private _proxy: MainThreadSCMShape,
|
||||
private _commands: ExtHostCommands,
|
||||
private _id: string,
|
||||
private _label: string,
|
||||
private _rootUri?: vscode.Uri
|
||||
) {
|
||||
this._inputBox = new ExtHostSCMInputBox(_extension, this._proxy, this.handle);
|
||||
this._proxy.$registerSourceControl(this.handle, _id, _label, _rootUri);
|
||||
}
|
||||
|
||||
private updatedResourceGroups = new Set<ExtHostSourceControlResourceGroup>();
|
||||
|
||||
createResourceGroup(id: string, label: string): ExtHostSourceControlResourceGroup {
|
||||
const group = new ExtHostSourceControlResourceGroup(this._proxy, this._commands, this.handle, id, label);
|
||||
|
||||
const updateListener = group.onDidUpdateResourceStates(() => {
|
||||
this.updatedResourceGroups.add(group);
|
||||
this.eventuallyUpdateResourceStates();
|
||||
});
|
||||
|
||||
Event.once(group.onDidDispose)(() => {
|
||||
this.updatedResourceGroups.delete(group);
|
||||
updateListener.dispose();
|
||||
this._groups.delete(group.handle);
|
||||
});
|
||||
|
||||
this._groups.set(group.handle, group);
|
||||
return group;
|
||||
}
|
||||
|
||||
@debounce(100)
|
||||
eventuallyUpdateResourceStates(): void {
|
||||
const splices: SCMRawResourceSplices[] = [];
|
||||
|
||||
this.updatedResourceGroups.forEach(group => {
|
||||
const snapshot = group._takeResourceStateSnapshot();
|
||||
|
||||
if (snapshot.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
splices.push([group.handle, snapshot]);
|
||||
});
|
||||
|
||||
if (splices.length > 0) {
|
||||
this._proxy.$spliceResourceStates(this.handle, splices);
|
||||
}
|
||||
|
||||
this.updatedResourceGroups.clear();
|
||||
}
|
||||
|
||||
getResourceGroup(handle: GroupHandle): ExtHostSourceControlResourceGroup | undefined {
|
||||
return this._groups.get(handle);
|
||||
}
|
||||
|
||||
setSelectionState(selected: boolean): void {
|
||||
this._selected = selected;
|
||||
this._onDidChangeSelection.fire(selected);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._groups.forEach(group => group.dispose());
|
||||
this._proxy.$unregisterSourceControl(this.handle);
|
||||
}
|
||||
}
|
||||
|
||||
export class ExtHostSCM implements ExtHostSCMShape {
|
||||
|
||||
private static _handlePool: number = 0;
|
||||
|
||||
private _proxy: MainThreadSCMShape;
|
||||
private _sourceControls: Map<ProviderHandle, ExtHostSourceControl> = new Map<ProviderHandle, ExtHostSourceControl>();
|
||||
private _sourceControlsByExtension: Map<string, ExtHostSourceControl[]> = new Map<string, ExtHostSourceControl[]>();
|
||||
|
||||
private _onDidChangeActiveProvider = new Emitter<vscode.SourceControl>();
|
||||
get onDidChangeActiveProvider(): Event<vscode.SourceControl> { return this._onDidChangeActiveProvider.event; }
|
||||
|
||||
private _selectedSourceControlHandles = new Set<number>();
|
||||
|
||||
constructor(
|
||||
mainContext: IMainContext,
|
||||
private _commands: ExtHostCommands,
|
||||
@ILogService private readonly logService: ILogService
|
||||
) {
|
||||
this._proxy = mainContext.getProxy(MainContext.MainThreadSCM);
|
||||
|
||||
_commands.registerArgumentProcessor({
|
||||
processArgument: arg => {
|
||||
if (arg && arg.$mid === 3) {
|
||||
const sourceControl = this._sourceControls.get(arg.sourceControlHandle);
|
||||
|
||||
if (!sourceControl) {
|
||||
return arg;
|
||||
}
|
||||
|
||||
const group = sourceControl.getResourceGroup(arg.groupHandle);
|
||||
|
||||
if (!group) {
|
||||
return arg;
|
||||
}
|
||||
|
||||
return group.getResourceState(arg.handle);
|
||||
} else if (arg && arg.$mid === 4) {
|
||||
const sourceControl = this._sourceControls.get(arg.sourceControlHandle);
|
||||
|
||||
if (!sourceControl) {
|
||||
return arg;
|
||||
}
|
||||
|
||||
return sourceControl.getResourceGroup(arg.groupHandle);
|
||||
} else if (arg && arg.$mid === 5) {
|
||||
const sourceControl = this._sourceControls.get(arg.handle);
|
||||
|
||||
if (!sourceControl) {
|
||||
return arg;
|
||||
}
|
||||
|
||||
return sourceControl;
|
||||
}
|
||||
|
||||
return arg;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
createSourceControl(extension: IExtensionDescription, id: string, label: string, rootUri: vscode.Uri | undefined): vscode.SourceControl {
|
||||
this.logService.trace('ExtHostSCM#createSourceControl', extension.identifier.value, id, label, rootUri);
|
||||
|
||||
const handle = ExtHostSCM._handlePool++;
|
||||
const sourceControl = new ExtHostSourceControl(extension, this._proxy, this._commands, id, label, rootUri);
|
||||
this._sourceControls.set(handle, sourceControl);
|
||||
|
||||
const sourceControls = this._sourceControlsByExtension.get(ExtensionIdentifier.toKey(extension.identifier)) || [];
|
||||
sourceControls.push(sourceControl);
|
||||
this._sourceControlsByExtension.set(ExtensionIdentifier.toKey(extension.identifier), sourceControls);
|
||||
|
||||
return sourceControl;
|
||||
}
|
||||
|
||||
// Deprecated
|
||||
getLastInputBox(extension: IExtensionDescription): ExtHostSCMInputBox | undefined {
|
||||
this.logService.trace('ExtHostSCM#getLastInputBox', extension.identifier.value);
|
||||
|
||||
const sourceControls = this._sourceControlsByExtension.get(ExtensionIdentifier.toKey(extension.identifier));
|
||||
const sourceControl = sourceControls && sourceControls[sourceControls.length - 1];
|
||||
return sourceControl && sourceControl.inputBox;
|
||||
}
|
||||
|
||||
$provideOriginalResource(sourceControlHandle: number, uriComponents: UriComponents, token: CancellationToken): Promise<UriComponents | null> {
|
||||
const uri = URI.revive(uriComponents);
|
||||
this.logService.trace('ExtHostSCM#$provideOriginalResource', sourceControlHandle, uri.toString());
|
||||
|
||||
const sourceControl = this._sourceControls.get(sourceControlHandle);
|
||||
|
||||
if (!sourceControl || !sourceControl.quickDiffProvider || !sourceControl.quickDiffProvider.provideOriginalResource) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
return asPromise(() => sourceControl.quickDiffProvider!.provideOriginalResource!(uri, token))
|
||||
.then<UriComponents | null>(r => r || null);
|
||||
}
|
||||
|
||||
$onInputBoxValueChange(sourceControlHandle: number, value: string): Promise<void> {
|
||||
this.logService.trace('ExtHostSCM#$onInputBoxValueChange', sourceControlHandle);
|
||||
|
||||
const sourceControl = this._sourceControls.get(sourceControlHandle);
|
||||
|
||||
if (!sourceControl) {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
sourceControl.inputBox.$onInputBoxValueChange(value);
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
$executeResourceCommand(sourceControlHandle: number, groupHandle: number, handle: number): Promise<void> {
|
||||
this.logService.trace('ExtHostSCM#$executeResourceCommand', sourceControlHandle, groupHandle, handle);
|
||||
|
||||
const sourceControl = this._sourceControls.get(sourceControlHandle);
|
||||
|
||||
if (!sourceControl) {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
const group = sourceControl.getResourceGroup(groupHandle);
|
||||
|
||||
if (!group) {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
return group.$executeResourceCommand(handle);
|
||||
}
|
||||
|
||||
$validateInput(sourceControlHandle: number, value: string, cursorPosition: number): Promise<[string, number] | undefined> {
|
||||
this.logService.trace('ExtHostSCM#$validateInput', sourceControlHandle);
|
||||
|
||||
const sourceControl = this._sourceControls.get(sourceControlHandle);
|
||||
|
||||
if (!sourceControl) {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
if (!sourceControl.inputBox.validateInput) {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
return asPromise(() => sourceControl.inputBox.validateInput(value, cursorPosition)).then(result => {
|
||||
if (!result) {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
return Promise.resolve<[string, number]>([result.message, result.type]);
|
||||
});
|
||||
}
|
||||
|
||||
$setSelectedSourceControls(selectedSourceControlHandles: number[]): Promise<void> {
|
||||
this.logService.trace('ExtHostSCM#$setSelectedSourceControls', selectedSourceControlHandles);
|
||||
|
||||
const set = new Set<number>();
|
||||
|
||||
for (const handle of selectedSourceControlHandles) {
|
||||
set.add(handle);
|
||||
}
|
||||
|
||||
set.forEach(handle => {
|
||||
if (!this._selectedSourceControlHandles.has(handle)) {
|
||||
const sourceControl = this._sourceControls.get(handle);
|
||||
|
||||
if (!sourceControl) {
|
||||
return;
|
||||
}
|
||||
|
||||
sourceControl.setSelectionState(true);
|
||||
}
|
||||
});
|
||||
|
||||
this._selectedSourceControlHandles.forEach(handle => {
|
||||
if (!set.has(handle)) {
|
||||
const sourceControl = this._sourceControls.get(handle);
|
||||
|
||||
if (!sourceControl) {
|
||||
return;
|
||||
}
|
||||
|
||||
sourceControl.setSelectionState(false);
|
||||
}
|
||||
});
|
||||
|
||||
this._selectedSourceControlHandles = set;
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
}
|
||||
@@ -1,190 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { StatusbarAlignment as MainThreadStatusBarAlignment } from 'vs/platform/statusbar/common/statusbar';
|
||||
import { StatusBarAlignment as ExtHostStatusBarAlignment, Disposable, ThemeColor } from './extHostTypes';
|
||||
import { StatusBarItem, StatusBarAlignment } from 'vscode';
|
||||
import { MainContext, MainThreadStatusBarShape, IMainContext } from '../common/extHost.protocol';
|
||||
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
|
||||
|
||||
export class ExtHostStatusBarEntry implements StatusBarItem {
|
||||
private static ID_GEN = 0;
|
||||
|
||||
private _id: number;
|
||||
private _alignment: number;
|
||||
private _priority?: number;
|
||||
private _disposed: boolean;
|
||||
private _visible: boolean;
|
||||
|
||||
private _text: string;
|
||||
private _tooltip: string;
|
||||
private _color: string | ThemeColor;
|
||||
private _command: string;
|
||||
|
||||
private _timeoutHandle: any;
|
||||
private _proxy: MainThreadStatusBarShape;
|
||||
|
||||
private _extensionId?: ExtensionIdentifier;
|
||||
|
||||
constructor(proxy: MainThreadStatusBarShape, extensionId: ExtensionIdentifier | undefined, alignment: ExtHostStatusBarAlignment = ExtHostStatusBarAlignment.Left, priority?: number) {
|
||||
this._id = ExtHostStatusBarEntry.ID_GEN++;
|
||||
this._proxy = proxy;
|
||||
this._alignment = alignment;
|
||||
this._priority = priority;
|
||||
this._extensionId = extensionId;
|
||||
}
|
||||
|
||||
public get id(): number {
|
||||
return this._id;
|
||||
}
|
||||
|
||||
public get alignment(): StatusBarAlignment {
|
||||
return this._alignment;
|
||||
}
|
||||
|
||||
public get priority(): number | undefined {
|
||||
return this._priority;
|
||||
}
|
||||
|
||||
public get text(): string {
|
||||
return this._text;
|
||||
}
|
||||
|
||||
public get tooltip(): string {
|
||||
return this._tooltip;
|
||||
}
|
||||
|
||||
public get color(): string | ThemeColor {
|
||||
return this._color;
|
||||
}
|
||||
|
||||
public get command(): string {
|
||||
return this._command;
|
||||
}
|
||||
|
||||
public set text(text: string) {
|
||||
this._text = text;
|
||||
this.update();
|
||||
}
|
||||
|
||||
public set tooltip(tooltip: string) {
|
||||
this._tooltip = tooltip;
|
||||
this.update();
|
||||
}
|
||||
|
||||
public set color(color: string | ThemeColor) {
|
||||
this._color = color;
|
||||
this.update();
|
||||
}
|
||||
|
||||
public set command(command: string) {
|
||||
this._command = command;
|
||||
this.update();
|
||||
}
|
||||
|
||||
public show(): void {
|
||||
this._visible = true;
|
||||
this.update();
|
||||
}
|
||||
|
||||
public hide(): void {
|
||||
clearTimeout(this._timeoutHandle);
|
||||
this._visible = false;
|
||||
this._proxy.$dispose(this.id);
|
||||
}
|
||||
|
||||
private update(): void {
|
||||
if (this._disposed || !this._visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
clearTimeout(this._timeoutHandle);
|
||||
|
||||
// Defer the update so that multiple changes to setters dont cause a redraw each
|
||||
this._timeoutHandle = setTimeout(() => {
|
||||
this._timeoutHandle = undefined;
|
||||
|
||||
// Set to status bar
|
||||
this._proxy.$setEntry(this.id, this._extensionId, this.text, this.tooltip, this.command, this.color,
|
||||
this._alignment === ExtHostStatusBarAlignment.Left ? MainThreadStatusBarAlignment.LEFT : MainThreadStatusBarAlignment.RIGHT,
|
||||
this._priority);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this.hide();
|
||||
this._disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
class StatusBarMessage {
|
||||
|
||||
private _item: StatusBarItem;
|
||||
private _messages: { message: string }[] = [];
|
||||
|
||||
constructor(statusBar: ExtHostStatusBar) {
|
||||
this._item = statusBar.createStatusBarEntry(undefined, ExtHostStatusBarAlignment.Left, Number.MIN_VALUE);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this._messages.length = 0;
|
||||
this._item.dispose();
|
||||
}
|
||||
|
||||
setMessage(message: string): Disposable {
|
||||
const data: { message: string } = { message }; // use object to not confuse equal strings
|
||||
this._messages.unshift(data);
|
||||
this._update();
|
||||
|
||||
return new Disposable(() => {
|
||||
const idx = this._messages.indexOf(data);
|
||||
if (idx >= 0) {
|
||||
this._messages.splice(idx, 1);
|
||||
this._update();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private _update() {
|
||||
if (this._messages.length > 0) {
|
||||
this._item.text = this._messages[0].message;
|
||||
this._item.show();
|
||||
} else {
|
||||
this._item.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class ExtHostStatusBar {
|
||||
|
||||
private _proxy: MainThreadStatusBarShape;
|
||||
private _statusMessage: StatusBarMessage;
|
||||
|
||||
constructor(mainContext: IMainContext) {
|
||||
this._proxy = mainContext.getProxy(MainContext.MainThreadStatusBar);
|
||||
this._statusMessage = new StatusBarMessage(this);
|
||||
}
|
||||
|
||||
createStatusBarEntry(extensionId: ExtensionIdentifier | undefined, alignment?: ExtHostStatusBarAlignment, priority?: number): StatusBarItem {
|
||||
return new ExtHostStatusBarEntry(this._proxy, extensionId, alignment, priority);
|
||||
}
|
||||
|
||||
setStatusBarMessage(text: string, timeoutOrThenable?: number | Thenable<any>): Disposable {
|
||||
|
||||
const d = this._statusMessage.setMessage(text);
|
||||
let handle: any;
|
||||
|
||||
if (typeof timeoutOrThenable === 'number') {
|
||||
handle = setTimeout(() => d.dispose(), timeoutOrThenable);
|
||||
} else if (typeof timeoutOrThenable !== 'undefined') {
|
||||
timeoutOrThenable.then(() => d.dispose(), () => d.dispose());
|
||||
}
|
||||
|
||||
return new Disposable(() => {
|
||||
d.dispose();
|
||||
clearTimeout(handle);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { MainContext, MainThreadStorageShape, IMainContext, ExtHostStorageShape } from '../common/extHost.protocol';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
|
||||
export interface IStorageChangeEvent {
|
||||
shared: boolean;
|
||||
key: string;
|
||||
value: object;
|
||||
}
|
||||
|
||||
export class ExtHostStorage implements ExtHostStorageShape {
|
||||
|
||||
private _proxy: MainThreadStorageShape;
|
||||
|
||||
private _onDidChangeStorage = new Emitter<IStorageChangeEvent>();
|
||||
readonly onDidChangeStorage = this._onDidChangeStorage.event;
|
||||
|
||||
constructor(mainContext: IMainContext) {
|
||||
this._proxy = mainContext.getProxy(MainContext.MainThreadStorage);
|
||||
}
|
||||
|
||||
getValue<T>(shared: boolean, key: string, defaultValue?: T): Promise<T | undefined> {
|
||||
return this._proxy.$getValue<T>(shared, key).then(value => value || defaultValue);
|
||||
}
|
||||
|
||||
setValue(shared: boolean, key: string, value: object): Promise<void> {
|
||||
return this._proxy.$setValue(shared, key, value);
|
||||
}
|
||||
|
||||
$acceptValue(shared: boolean, key: string, value: object): void {
|
||||
this._onDidChangeStorage.fire({ shared, key, value });
|
||||
}
|
||||
}
|
||||
@@ -14,8 +14,8 @@ import { win32 } from 'vs/base/node/processes';
|
||||
|
||||
import { MainContext, MainThreadTaskShape, ExtHostTaskShape, IMainContext } from 'vs/workbench/api/common/extHost.protocol';
|
||||
|
||||
import * as types from 'vs/workbench/api/node/extHostTypes';
|
||||
import { ExtHostWorkspace, IExtHostWorkspaceProvider } from 'vs/workbench/api/node/extHostWorkspace';
|
||||
import * as types from 'vs/workbench/api/common/extHostTypes';
|
||||
import { ExtHostWorkspace, IExtHostWorkspaceProvider } from 'vs/workbench/api/common/extHostWorkspace';
|
||||
import * as vscode from 'vscode';
|
||||
import {
|
||||
TaskDefinitionDTO, TaskExecutionDTO, TaskPresentationOptionsDTO,
|
||||
@@ -26,8 +26,8 @@ import {
|
||||
} from '../common/shared/tasks';
|
||||
// {{SQL CARBON EDIT}}
|
||||
// import { ExtHostVariableResolverService } from 'vs/workbench/api/node/extHostDebugService';
|
||||
import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/node/extHostDocumentsAndEditors';
|
||||
import { ExtHostConfiguration } from 'vs/workbench/api/node/extHostConfiguration';
|
||||
import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors';
|
||||
import { ExtHostConfiguration } from 'vs/workbench/api/common/extHostConfiguration';
|
||||
import { ExtHostTerminalService, ExtHostTerminal } from 'vs/workbench/api/node/extHostTerminalService';
|
||||
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
|
||||
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
|
||||
@@ -11,7 +11,7 @@ import * as platform from 'vs/base/common/platform';
|
||||
import * as terminalEnvironment from 'vs/workbench/contrib/terminal/common/terminalEnvironment';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { ExtHostTerminalServiceShape, MainContext, MainThreadTerminalServiceShape, IMainContext, ShellLaunchConfigDto } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { ExtHostConfiguration } from 'vs/workbench/api/node/extHostConfiguration';
|
||||
import { ExtHostConfiguration } from 'vs/workbench/api/common/extHostConfiguration';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { EXT_HOST_CREATION_DELAY, IShellLaunchConfig } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { TerminalProcess } from 'vs/workbench/contrib/terminal/node/terminalProcess';
|
||||
|
||||
@@ -1,666 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ok } from 'vs/base/common/assert';
|
||||
import { illegalArgument, readonly } from 'vs/base/common/errors';
|
||||
import { IdGenerator } from 'vs/base/common/idGenerator';
|
||||
import { TextEditorCursorStyle } from 'vs/editor/common/config/editorOptions';
|
||||
import { IRange } from 'vs/editor/common/core/range';
|
||||
import { ISingleEditOperation } from 'vs/editor/common/model';
|
||||
import { IResolvedTextEditorConfiguration, ITextEditorConfigurationUpdate, MainThreadTextEditorsShape } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { ExtHostDocumentData } from 'vs/workbench/api/node/extHostDocumentData';
|
||||
import * as TypeConverters from 'vs/workbench/api/node/extHostTypeConverters';
|
||||
import { EndOfLine, Position, Range, Selection, SnippetString, TextEditorLineNumbersStyle, TextEditorRevealType } from 'vs/workbench/api/node/extHostTypes';
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export class TextEditorDecorationType implements vscode.TextEditorDecorationType {
|
||||
|
||||
private static readonly _Keys = new IdGenerator('TextEditorDecorationType');
|
||||
|
||||
private _proxy: MainThreadTextEditorsShape;
|
||||
public key: string;
|
||||
|
||||
constructor(proxy: MainThreadTextEditorsShape, options: vscode.DecorationRenderOptions) {
|
||||
this.key = TextEditorDecorationType._Keys.nextId();
|
||||
this._proxy = proxy;
|
||||
this._proxy.$registerTextEditorDecorationType(this.key, TypeConverters.DecorationRenderOptions.from(options));
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._proxy.$removeTextEditorDecorationType(this.key);
|
||||
}
|
||||
}
|
||||
|
||||
export interface ITextEditOperation {
|
||||
range: vscode.Range;
|
||||
text: string | null;
|
||||
forceMoveMarkers: boolean;
|
||||
}
|
||||
|
||||
export interface IEditData {
|
||||
documentVersionId: number;
|
||||
edits: ITextEditOperation[];
|
||||
setEndOfLine: EndOfLine | undefined;
|
||||
undoStopBefore: boolean;
|
||||
undoStopAfter: boolean;
|
||||
}
|
||||
|
||||
export class TextEditorEdit {
|
||||
|
||||
private readonly _document: vscode.TextDocument;
|
||||
private readonly _documentVersionId: number;
|
||||
private _collectedEdits: ITextEditOperation[];
|
||||
private _setEndOfLine: EndOfLine | undefined;
|
||||
private readonly _undoStopBefore: boolean;
|
||||
private readonly _undoStopAfter: boolean;
|
||||
|
||||
constructor(document: vscode.TextDocument, options: { undoStopBefore: boolean; undoStopAfter: boolean; }) {
|
||||
this._document = document;
|
||||
this._documentVersionId = document.version;
|
||||
this._collectedEdits = [];
|
||||
this._setEndOfLine = undefined;
|
||||
this._undoStopBefore = options.undoStopBefore;
|
||||
this._undoStopAfter = options.undoStopAfter;
|
||||
}
|
||||
|
||||
finalize(): IEditData {
|
||||
return {
|
||||
documentVersionId: this._documentVersionId,
|
||||
edits: this._collectedEdits,
|
||||
setEndOfLine: this._setEndOfLine,
|
||||
undoStopBefore: this._undoStopBefore,
|
||||
undoStopAfter: this._undoStopAfter
|
||||
};
|
||||
}
|
||||
|
||||
replace(location: Position | Range | Selection, value: string): void {
|
||||
let range: Range | null = null;
|
||||
|
||||
if (location instanceof Position) {
|
||||
range = new Range(location, location);
|
||||
} else if (location instanceof Range) {
|
||||
range = location;
|
||||
} else {
|
||||
throw new Error('Unrecognized location');
|
||||
}
|
||||
|
||||
this._pushEdit(range, value, false);
|
||||
}
|
||||
|
||||
insert(location: Position, value: string): void {
|
||||
this._pushEdit(new Range(location, location), value, true);
|
||||
}
|
||||
|
||||
delete(location: Range | Selection): void {
|
||||
let range: Range | null = null;
|
||||
|
||||
if (location instanceof Range) {
|
||||
range = location;
|
||||
} else {
|
||||
throw new Error('Unrecognized location');
|
||||
}
|
||||
|
||||
this._pushEdit(range, null, true);
|
||||
}
|
||||
|
||||
private _pushEdit(range: Range, text: string | null, forceMoveMarkers: boolean): void {
|
||||
const validRange = this._document.validateRange(range);
|
||||
this._collectedEdits.push({
|
||||
range: validRange,
|
||||
text: text,
|
||||
forceMoveMarkers: forceMoveMarkers
|
||||
});
|
||||
}
|
||||
|
||||
setEndOfLine(endOfLine: EndOfLine): void {
|
||||
if (endOfLine !== EndOfLine.LF && endOfLine !== EndOfLine.CRLF) {
|
||||
throw illegalArgument('endOfLine');
|
||||
}
|
||||
|
||||
this._setEndOfLine = endOfLine;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function deprecated(name: string, message: string = 'Refer to the documentation for further details.') {
|
||||
return (target: Object, key: string, descriptor: TypedPropertyDescriptor<any>) => {
|
||||
const originalMethod = descriptor.value;
|
||||
descriptor.value = function (...args: any[]) {
|
||||
console.warn(`[Deprecation Warning] method '${name}' is deprecated and should no longer be used. ${message}`);
|
||||
return originalMethod.apply(this, args);
|
||||
};
|
||||
|
||||
return descriptor;
|
||||
};
|
||||
}
|
||||
|
||||
export class ExtHostTextEditorOptions implements vscode.TextEditorOptions {
|
||||
|
||||
private _proxy: MainThreadTextEditorsShape;
|
||||
private _id: string;
|
||||
|
||||
private _tabSize: number;
|
||||
private _indentSize: number;
|
||||
private _insertSpaces: boolean;
|
||||
private _cursorStyle: TextEditorCursorStyle;
|
||||
private _lineNumbers: TextEditorLineNumbersStyle;
|
||||
|
||||
constructor(proxy: MainThreadTextEditorsShape, id: string, source: IResolvedTextEditorConfiguration) {
|
||||
this._proxy = proxy;
|
||||
this._id = id;
|
||||
this._accept(source);
|
||||
}
|
||||
|
||||
public _accept(source: IResolvedTextEditorConfiguration): void {
|
||||
this._tabSize = source.tabSize;
|
||||
this._indentSize = source.indentSize;
|
||||
this._insertSpaces = source.insertSpaces;
|
||||
this._cursorStyle = source.cursorStyle;
|
||||
this._lineNumbers = TypeConverters.TextEditorLineNumbersStyle.to(source.lineNumbers);
|
||||
}
|
||||
|
||||
public get tabSize(): number | string {
|
||||
return this._tabSize;
|
||||
}
|
||||
|
||||
private _validateTabSize(value: number | string): number | 'auto' | null {
|
||||
if (value === 'auto') {
|
||||
return 'auto';
|
||||
}
|
||||
if (typeof value === 'number') {
|
||||
const r = Math.floor(value);
|
||||
return (r > 0 ? r : null);
|
||||
}
|
||||
if (typeof value === 'string') {
|
||||
const r = parseInt(value, 10);
|
||||
if (isNaN(r)) {
|
||||
return null;
|
||||
}
|
||||
return (r > 0 ? r : null);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public set tabSize(value: number | string) {
|
||||
const tabSize = this._validateTabSize(value);
|
||||
if (tabSize === null) {
|
||||
// ignore invalid call
|
||||
return;
|
||||
}
|
||||
if (typeof tabSize === 'number') {
|
||||
if (this._tabSize === tabSize) {
|
||||
// nothing to do
|
||||
return;
|
||||
}
|
||||
// reflect the new tabSize value immediately
|
||||
this._tabSize = tabSize;
|
||||
}
|
||||
warnOnError(this._proxy.$trySetOptions(this._id, {
|
||||
tabSize: tabSize
|
||||
}));
|
||||
}
|
||||
|
||||
public get indentSize(): number | string {
|
||||
return this._indentSize;
|
||||
}
|
||||
|
||||
private _validateIndentSize(value: number | string): number | 'tabSize' | null {
|
||||
if (value === 'tabSize') {
|
||||
return 'tabSize';
|
||||
}
|
||||
if (typeof value === 'number') {
|
||||
const r = Math.floor(value);
|
||||
return (r > 0 ? r : null);
|
||||
}
|
||||
if (typeof value === 'string') {
|
||||
const r = parseInt(value, 10);
|
||||
if (isNaN(r)) {
|
||||
return null;
|
||||
}
|
||||
return (r > 0 ? r : null);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public set indentSize(value: number | string) {
|
||||
const indentSize = this._validateIndentSize(value);
|
||||
if (indentSize === null) {
|
||||
// ignore invalid call
|
||||
return;
|
||||
}
|
||||
if (typeof indentSize === 'number') {
|
||||
if (this._indentSize === indentSize) {
|
||||
// nothing to do
|
||||
return;
|
||||
}
|
||||
// reflect the new indentSize value immediately
|
||||
this._indentSize = indentSize;
|
||||
}
|
||||
warnOnError(this._proxy.$trySetOptions(this._id, {
|
||||
indentSize: indentSize
|
||||
}));
|
||||
}
|
||||
|
||||
public get insertSpaces(): boolean | string {
|
||||
return this._insertSpaces;
|
||||
}
|
||||
|
||||
private _validateInsertSpaces(value: boolean | string): boolean | 'auto' {
|
||||
if (value === 'auto') {
|
||||
return 'auto';
|
||||
}
|
||||
return (value === 'false' ? false : Boolean(value));
|
||||
}
|
||||
|
||||
public set insertSpaces(value: boolean | string) {
|
||||
const insertSpaces = this._validateInsertSpaces(value);
|
||||
if (typeof insertSpaces === 'boolean') {
|
||||
if (this._insertSpaces === insertSpaces) {
|
||||
// nothing to do
|
||||
return;
|
||||
}
|
||||
// reflect the new insertSpaces value immediately
|
||||
this._insertSpaces = insertSpaces;
|
||||
}
|
||||
warnOnError(this._proxy.$trySetOptions(this._id, {
|
||||
insertSpaces: insertSpaces
|
||||
}));
|
||||
}
|
||||
|
||||
public get cursorStyle(): TextEditorCursorStyle {
|
||||
return this._cursorStyle;
|
||||
}
|
||||
|
||||
public set cursorStyle(value: TextEditorCursorStyle) {
|
||||
if (this._cursorStyle === value) {
|
||||
// nothing to do
|
||||
return;
|
||||
}
|
||||
this._cursorStyle = value;
|
||||
warnOnError(this._proxy.$trySetOptions(this._id, {
|
||||
cursorStyle: value
|
||||
}));
|
||||
}
|
||||
|
||||
public get lineNumbers(): TextEditorLineNumbersStyle {
|
||||
return this._lineNumbers;
|
||||
}
|
||||
|
||||
public set lineNumbers(value: TextEditorLineNumbersStyle) {
|
||||
if (this._lineNumbers === value) {
|
||||
// nothing to do
|
||||
return;
|
||||
}
|
||||
this._lineNumbers = value;
|
||||
warnOnError(this._proxy.$trySetOptions(this._id, {
|
||||
lineNumbers: TypeConverters.TextEditorLineNumbersStyle.from(value)
|
||||
}));
|
||||
}
|
||||
|
||||
public assign(newOptions: vscode.TextEditorOptions) {
|
||||
const bulkConfigurationUpdate: ITextEditorConfigurationUpdate = {};
|
||||
let hasUpdate = false;
|
||||
|
||||
if (typeof newOptions.tabSize !== 'undefined') {
|
||||
const tabSize = this._validateTabSize(newOptions.tabSize);
|
||||
if (tabSize === 'auto') {
|
||||
hasUpdate = true;
|
||||
bulkConfigurationUpdate.tabSize = tabSize;
|
||||
} else if (typeof tabSize === 'number' && this._tabSize !== tabSize) {
|
||||
// reflect the new tabSize value immediately
|
||||
this._tabSize = tabSize;
|
||||
hasUpdate = true;
|
||||
bulkConfigurationUpdate.tabSize = tabSize;
|
||||
}
|
||||
}
|
||||
|
||||
// if (typeof newOptions.indentSize !== 'undefined') {
|
||||
// const indentSize = this._validateIndentSize(newOptions.indentSize);
|
||||
// if (indentSize === 'tabSize') {
|
||||
// hasUpdate = true;
|
||||
// bulkConfigurationUpdate.indentSize = indentSize;
|
||||
// } else if (typeof indentSize === 'number' && this._indentSize !== indentSize) {
|
||||
// // reflect the new indentSize value immediately
|
||||
// this._indentSize = indentSize;
|
||||
// hasUpdate = true;
|
||||
// bulkConfigurationUpdate.indentSize = indentSize;
|
||||
// }
|
||||
// }
|
||||
|
||||
if (typeof newOptions.insertSpaces !== 'undefined') {
|
||||
const insertSpaces = this._validateInsertSpaces(newOptions.insertSpaces);
|
||||
if (insertSpaces === 'auto') {
|
||||
hasUpdate = true;
|
||||
bulkConfigurationUpdate.insertSpaces = insertSpaces;
|
||||
} else if (this._insertSpaces !== insertSpaces) {
|
||||
// reflect the new insertSpaces value immediately
|
||||
this._insertSpaces = insertSpaces;
|
||||
hasUpdate = true;
|
||||
bulkConfigurationUpdate.insertSpaces = insertSpaces;
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof newOptions.cursorStyle !== 'undefined') {
|
||||
if (this._cursorStyle !== newOptions.cursorStyle) {
|
||||
this._cursorStyle = newOptions.cursorStyle;
|
||||
hasUpdate = true;
|
||||
bulkConfigurationUpdate.cursorStyle = newOptions.cursorStyle;
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof newOptions.lineNumbers !== 'undefined') {
|
||||
if (this._lineNumbers !== newOptions.lineNumbers) {
|
||||
this._lineNumbers = newOptions.lineNumbers;
|
||||
hasUpdate = true;
|
||||
bulkConfigurationUpdate.lineNumbers = TypeConverters.TextEditorLineNumbersStyle.from(newOptions.lineNumbers);
|
||||
}
|
||||
}
|
||||
|
||||
if (hasUpdate) {
|
||||
warnOnError(this._proxy.$trySetOptions(this._id, bulkConfigurationUpdate));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class ExtHostTextEditor implements vscode.TextEditor {
|
||||
|
||||
private readonly _proxy: MainThreadTextEditorsShape;
|
||||
private readonly _id: string;
|
||||
private readonly _documentData: ExtHostDocumentData;
|
||||
|
||||
private _selections: Selection[];
|
||||
private _options: ExtHostTextEditorOptions;
|
||||
private _visibleRanges: Range[];
|
||||
private _viewColumn: vscode.ViewColumn | undefined;
|
||||
private _disposed: boolean = false;
|
||||
private _hasDecorationsForKey: { [key: string]: boolean; };
|
||||
|
||||
get id(): string { return this._id; }
|
||||
|
||||
constructor(
|
||||
proxy: MainThreadTextEditorsShape, id: string, document: ExtHostDocumentData,
|
||||
selections: Selection[], options: IResolvedTextEditorConfiguration,
|
||||
visibleRanges: Range[], viewColumn: vscode.ViewColumn | undefined
|
||||
) {
|
||||
this._proxy = proxy;
|
||||
this._id = id;
|
||||
this._documentData = document;
|
||||
this._selections = selections;
|
||||
this._options = new ExtHostTextEditorOptions(this._proxy, this._id, options);
|
||||
this._visibleRanges = visibleRanges;
|
||||
this._viewColumn = viewColumn;
|
||||
this._hasDecorationsForKey = Object.create(null);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
ok(!this._disposed);
|
||||
this._disposed = true;
|
||||
}
|
||||
|
||||
@deprecated('TextEditor.show') show(column: vscode.ViewColumn) {
|
||||
this._proxy.$tryShowEditor(this._id, TypeConverters.ViewColumn.from(column));
|
||||
}
|
||||
|
||||
@deprecated('TextEditor.hide') hide() {
|
||||
this._proxy.$tryHideEditor(this._id);
|
||||
}
|
||||
|
||||
// ---- the document
|
||||
|
||||
get document(): vscode.TextDocument {
|
||||
return this._documentData.document;
|
||||
}
|
||||
|
||||
set document(value) {
|
||||
throw readonly('document');
|
||||
}
|
||||
|
||||
// ---- options
|
||||
|
||||
get options(): vscode.TextEditorOptions {
|
||||
return this._options;
|
||||
}
|
||||
|
||||
set options(value: vscode.TextEditorOptions) {
|
||||
if (!this._disposed) {
|
||||
this._options.assign(value);
|
||||
}
|
||||
}
|
||||
|
||||
_acceptOptions(options: IResolvedTextEditorConfiguration): void {
|
||||
ok(!this._disposed);
|
||||
this._options._accept(options);
|
||||
}
|
||||
|
||||
// ---- visible ranges
|
||||
|
||||
get visibleRanges(): Range[] {
|
||||
return this._visibleRanges;
|
||||
}
|
||||
|
||||
set visibleRanges(value: Range[]) {
|
||||
throw readonly('visibleRanges');
|
||||
}
|
||||
|
||||
_acceptVisibleRanges(value: Range[]): void {
|
||||
ok(!this._disposed);
|
||||
this._visibleRanges = value;
|
||||
}
|
||||
|
||||
// ---- view column
|
||||
|
||||
get viewColumn(): vscode.ViewColumn | undefined {
|
||||
return this._viewColumn;
|
||||
}
|
||||
|
||||
set viewColumn(value) {
|
||||
throw readonly('viewColumn');
|
||||
}
|
||||
|
||||
_acceptViewColumn(value: vscode.ViewColumn) {
|
||||
ok(!this._disposed);
|
||||
this._viewColumn = value;
|
||||
}
|
||||
|
||||
// ---- selections
|
||||
|
||||
get selection(): Selection {
|
||||
return this._selections && this._selections[0];
|
||||
}
|
||||
|
||||
set selection(value: Selection) {
|
||||
if (!(value instanceof Selection)) {
|
||||
throw illegalArgument('selection');
|
||||
}
|
||||
this._selections = [value];
|
||||
this._trySetSelection();
|
||||
}
|
||||
|
||||
get selections(): Selection[] {
|
||||
return this._selections;
|
||||
}
|
||||
|
||||
set selections(value: Selection[]) {
|
||||
if (!Array.isArray(value) || value.some(a => !(a instanceof Selection))) {
|
||||
throw illegalArgument('selections');
|
||||
}
|
||||
this._selections = value;
|
||||
this._trySetSelection();
|
||||
}
|
||||
|
||||
setDecorations(decorationType: vscode.TextEditorDecorationType, ranges: Range[] | vscode.DecorationOptions[]): void {
|
||||
const willBeEmpty = (ranges.length === 0);
|
||||
if (willBeEmpty && !this._hasDecorationsForKey[decorationType.key]) {
|
||||
// avoid no-op call to the renderer
|
||||
return;
|
||||
}
|
||||
if (willBeEmpty) {
|
||||
delete this._hasDecorationsForKey[decorationType.key];
|
||||
} else {
|
||||
this._hasDecorationsForKey[decorationType.key] = true;
|
||||
}
|
||||
this._runOnProxy(
|
||||
() => {
|
||||
if (TypeConverters.isDecorationOptionsArr(ranges)) {
|
||||
return this._proxy.$trySetDecorations(
|
||||
this._id,
|
||||
decorationType.key,
|
||||
TypeConverters.fromRangeOrRangeWithMessage(ranges)
|
||||
);
|
||||
} else {
|
||||
const _ranges: number[] = new Array<number>(4 * ranges.length);
|
||||
for (let i = 0, len = ranges.length; i < len; i++) {
|
||||
const range = ranges[i];
|
||||
_ranges[4 * i] = range.start.line + 1;
|
||||
_ranges[4 * i + 1] = range.start.character + 1;
|
||||
_ranges[4 * i + 2] = range.end.line + 1;
|
||||
_ranges[4 * i + 3] = range.end.character + 1;
|
||||
}
|
||||
return this._proxy.$trySetDecorationsFast(
|
||||
this._id,
|
||||
decorationType.key,
|
||||
_ranges
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
revealRange(range: Range, revealType: vscode.TextEditorRevealType): void {
|
||||
this._runOnProxy(
|
||||
() => this._proxy.$tryRevealRange(
|
||||
this._id,
|
||||
TypeConverters.Range.from(range),
|
||||
(revealType || TextEditorRevealType.Default)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private _trySetSelection(): Promise<vscode.TextEditor | null | undefined> {
|
||||
const selection = this._selections.map(TypeConverters.Selection.from);
|
||||
return this._runOnProxy(() => this._proxy.$trySetSelections(this._id, selection));
|
||||
}
|
||||
|
||||
_acceptSelections(selections: Selection[]): void {
|
||||
ok(!this._disposed);
|
||||
this._selections = selections;
|
||||
}
|
||||
|
||||
// ---- editing
|
||||
|
||||
edit(callback: (edit: TextEditorEdit) => void, options: { undoStopBefore: boolean; undoStopAfter: boolean; } = { undoStopBefore: true, undoStopAfter: true }): Promise<boolean> {
|
||||
if (this._disposed) {
|
||||
return Promise.reject(new Error('TextEditor#edit not possible on closed editors'));
|
||||
}
|
||||
const edit = new TextEditorEdit(this._documentData.document, options);
|
||||
callback(edit);
|
||||
return this._applyEdit(edit);
|
||||
}
|
||||
|
||||
private _applyEdit(editBuilder: TextEditorEdit): Promise<boolean> {
|
||||
const editData = editBuilder.finalize();
|
||||
|
||||
// return when there is nothing to do
|
||||
if (editData.edits.length === 0 && !editData.setEndOfLine) {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
// check that the edits are not overlapping (i.e. illegal)
|
||||
const editRanges = editData.edits.map(edit => edit.range);
|
||||
|
||||
// sort ascending (by end and then by start)
|
||||
editRanges.sort((a, b) => {
|
||||
if (a.end.line === b.end.line) {
|
||||
if (a.end.character === b.end.character) {
|
||||
if (a.start.line === b.start.line) {
|
||||
return a.start.character - b.start.character;
|
||||
}
|
||||
return a.start.line - b.start.line;
|
||||
}
|
||||
return a.end.character - b.end.character;
|
||||
}
|
||||
return a.end.line - b.end.line;
|
||||
});
|
||||
|
||||
// check that no edits are overlapping
|
||||
for (let i = 0, count = editRanges.length - 1; i < count; i++) {
|
||||
const rangeEnd = editRanges[i].end;
|
||||
const nextRangeStart = editRanges[i + 1].start;
|
||||
|
||||
if (nextRangeStart.isBefore(rangeEnd)) {
|
||||
// overlapping ranges
|
||||
return Promise.reject(
|
||||
new Error('Overlapping ranges are not allowed!')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// prepare data for serialization
|
||||
const edits = editData.edits.map((edit): ISingleEditOperation => {
|
||||
return {
|
||||
range: TypeConverters.Range.from(edit.range),
|
||||
text: edit.text,
|
||||
forceMoveMarkers: edit.forceMoveMarkers
|
||||
};
|
||||
});
|
||||
|
||||
return this._proxy.$tryApplyEdits(this._id, editData.documentVersionId, edits, {
|
||||
setEndOfLine: typeof editData.setEndOfLine === 'number' ? TypeConverters.EndOfLine.from(editData.setEndOfLine) : undefined,
|
||||
undoStopBefore: editData.undoStopBefore,
|
||||
undoStopAfter: editData.undoStopAfter
|
||||
});
|
||||
}
|
||||
|
||||
insertSnippet(snippet: SnippetString, where?: Position | Position[] | Range | Range[], options: { undoStopBefore: boolean; undoStopAfter: boolean; } = { undoStopBefore: true, undoStopAfter: true }): Promise<boolean> {
|
||||
if (this._disposed) {
|
||||
return Promise.reject(new Error('TextEditor#insertSnippet not possible on closed editors'));
|
||||
}
|
||||
let ranges: IRange[];
|
||||
|
||||
if (!where || (Array.isArray(where) && where.length === 0)) {
|
||||
ranges = this._selections.map(range => TypeConverters.Range.from(range));
|
||||
|
||||
} else if (where instanceof Position) {
|
||||
const { lineNumber, column } = TypeConverters.Position.from(where);
|
||||
ranges = [{ startLineNumber: lineNumber, startColumn: column, endLineNumber: lineNumber, endColumn: column }];
|
||||
|
||||
} else if (where instanceof Range) {
|
||||
ranges = [TypeConverters.Range.from(where)];
|
||||
} else {
|
||||
ranges = [];
|
||||
for (const posOrRange of where) {
|
||||
if (posOrRange instanceof Range) {
|
||||
ranges.push(TypeConverters.Range.from(posOrRange));
|
||||
} else {
|
||||
const { lineNumber, column } = TypeConverters.Position.from(posOrRange);
|
||||
ranges.push({ startLineNumber: lineNumber, startColumn: column, endLineNumber: lineNumber, endColumn: column });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this._proxy.$tryInsertSnippet(this._id, snippet.value, ranges, options);
|
||||
}
|
||||
|
||||
// ---- util
|
||||
|
||||
private _runOnProxy(callback: () => Promise<any>): Promise<ExtHostTextEditor | undefined | null> {
|
||||
if (this._disposed) {
|
||||
console.warn('TextEditor is closed/disposed');
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
return callback().then(() => this, err => {
|
||||
if (!(err instanceof Error && err.name === 'DISPOSED')) {
|
||||
console.warn(err);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function warnOnError(promise: Promise<any>): void {
|
||||
promise.then(undefined, (err) => {
|
||||
console.warn(err);
|
||||
});
|
||||
}
|
||||
@@ -1,159 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 * as arrays from 'vs/base/common/arrays';
|
||||
import { ExtHostEditorsShape, IEditorPropertiesChangeData, IMainContext, ITextDocumentShowOptions, ITextEditorPositionData, MainContext, MainThreadTextEditorsShape } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/node/extHostDocumentsAndEditors';
|
||||
import { ExtHostTextEditor, TextEditorDecorationType } from 'vs/workbench/api/node/extHostTextEditor';
|
||||
import * as TypeConverters from 'vs/workbench/api/node/extHostTypeConverters';
|
||||
import { TextEditorSelectionChangeKind } from 'vs/workbench/api/node/extHostTypes';
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export class ExtHostEditors implements ExtHostEditorsShape {
|
||||
|
||||
private readonly _onDidChangeTextEditorSelection = new Emitter<vscode.TextEditorSelectionChangeEvent>();
|
||||
private readonly _onDidChangeTextEditorOptions = new Emitter<vscode.TextEditorOptionsChangeEvent>();
|
||||
private readonly _onDidChangeTextEditorVisibleRanges = new Emitter<vscode.TextEditorVisibleRangesChangeEvent>();
|
||||
private readonly _onDidChangeTextEditorViewColumn = new Emitter<vscode.TextEditorViewColumnChangeEvent>();
|
||||
private readonly _onDidChangeActiveTextEditor = new Emitter<vscode.TextEditor | undefined>();
|
||||
private readonly _onDidChangeVisibleTextEditors = new Emitter<vscode.TextEditor[]>();
|
||||
|
||||
readonly onDidChangeTextEditorSelection: Event<vscode.TextEditorSelectionChangeEvent> = this._onDidChangeTextEditorSelection.event;
|
||||
readonly onDidChangeTextEditorOptions: Event<vscode.TextEditorOptionsChangeEvent> = this._onDidChangeTextEditorOptions.event;
|
||||
readonly onDidChangeTextEditorVisibleRanges: Event<vscode.TextEditorVisibleRangesChangeEvent> = this._onDidChangeTextEditorVisibleRanges.event;
|
||||
readonly onDidChangeTextEditorViewColumn: Event<vscode.TextEditorViewColumnChangeEvent> = this._onDidChangeTextEditorViewColumn.event;
|
||||
readonly onDidChangeActiveTextEditor: Event<vscode.TextEditor | undefined> = this._onDidChangeActiveTextEditor.event;
|
||||
readonly onDidChangeVisibleTextEditors: Event<vscode.TextEditor[]> = this._onDidChangeVisibleTextEditors.event;
|
||||
|
||||
|
||||
private _proxy: MainThreadTextEditorsShape;
|
||||
private _extHostDocumentsAndEditors: ExtHostDocumentsAndEditors;
|
||||
|
||||
constructor(
|
||||
mainContext: IMainContext,
|
||||
extHostDocumentsAndEditors: ExtHostDocumentsAndEditors,
|
||||
) {
|
||||
this._proxy = mainContext.getProxy(MainContext.MainThreadTextEditors);
|
||||
this._extHostDocumentsAndEditors = extHostDocumentsAndEditors;
|
||||
|
||||
this._extHostDocumentsAndEditors.onDidChangeVisibleTextEditors(e => this._onDidChangeVisibleTextEditors.fire(e));
|
||||
this._extHostDocumentsAndEditors.onDidChangeActiveTextEditor(e => this._onDidChangeActiveTextEditor.fire(e));
|
||||
}
|
||||
|
||||
getActiveTextEditor(): ExtHostTextEditor | undefined {
|
||||
return this._extHostDocumentsAndEditors.activeEditor();
|
||||
}
|
||||
|
||||
getVisibleTextEditors(): vscode.TextEditor[] {
|
||||
return this._extHostDocumentsAndEditors.allEditors();
|
||||
}
|
||||
|
||||
showTextDocument(document: vscode.TextDocument, column: vscode.ViewColumn, preserveFocus: boolean): Promise<vscode.TextEditor>;
|
||||
showTextDocument(document: vscode.TextDocument, options: { column: vscode.ViewColumn, preserveFocus: boolean, pinned: boolean }): Promise<vscode.TextEditor>;
|
||||
showTextDocument(document: vscode.TextDocument, columnOrOptions: vscode.ViewColumn | vscode.TextDocumentShowOptions | undefined, preserveFocus?: boolean): Promise<vscode.TextEditor>;
|
||||
showTextDocument(document: vscode.TextDocument, columnOrOptions: vscode.ViewColumn | vscode.TextDocumentShowOptions | undefined, preserveFocus?: boolean): Promise<vscode.TextEditor> {
|
||||
let options: ITextDocumentShowOptions;
|
||||
if (typeof columnOrOptions === 'number') {
|
||||
options = {
|
||||
position: TypeConverters.ViewColumn.from(columnOrOptions),
|
||||
preserveFocus
|
||||
};
|
||||
} else if (typeof columnOrOptions === 'object') {
|
||||
options = {
|
||||
position: TypeConverters.ViewColumn.from(columnOrOptions.viewColumn),
|
||||
preserveFocus: columnOrOptions.preserveFocus,
|
||||
selection: typeof columnOrOptions.selection === 'object' ? TypeConverters.Range.from(columnOrOptions.selection) : undefined,
|
||||
pinned: typeof columnOrOptions.preview === 'boolean' ? !columnOrOptions.preview : undefined
|
||||
};
|
||||
} else {
|
||||
options = {
|
||||
preserveFocus: false
|
||||
};
|
||||
}
|
||||
|
||||
return this._proxy.$tryShowTextDocument(document.uri, options).then(id => {
|
||||
const editor = id && this._extHostDocumentsAndEditors.getEditor(id);
|
||||
if (editor) {
|
||||
return editor;
|
||||
} else {
|
||||
throw new Error(`Failed to show text document ${document.uri.toString()}, should show in editor #${id}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
createTextEditorDecorationType(options: vscode.DecorationRenderOptions): vscode.TextEditorDecorationType {
|
||||
return new TextEditorDecorationType(this._proxy, options);
|
||||
}
|
||||
|
||||
applyWorkspaceEdit(edit: vscode.WorkspaceEdit): Promise<boolean> {
|
||||
const dto = TypeConverters.WorkspaceEdit.from(edit, this._extHostDocumentsAndEditors);
|
||||
return this._proxy.$tryApplyWorkspaceEdit(dto);
|
||||
}
|
||||
|
||||
// --- called from main thread
|
||||
|
||||
$acceptEditorPropertiesChanged(id: string, data: IEditorPropertiesChangeData): void {
|
||||
const textEditor = this._extHostDocumentsAndEditors.getEditor(id);
|
||||
if (!textEditor) {
|
||||
throw new Error('unknown text editor');
|
||||
}
|
||||
|
||||
// (1) set all properties
|
||||
if (data.options) {
|
||||
textEditor._acceptOptions(data.options);
|
||||
}
|
||||
if (data.selections) {
|
||||
const selections = data.selections.selections.map(TypeConverters.Selection.to);
|
||||
textEditor._acceptSelections(selections);
|
||||
}
|
||||
if (data.visibleRanges) {
|
||||
const visibleRanges = arrays.coalesce(data.visibleRanges.map(TypeConverters.Range.to));
|
||||
textEditor._acceptVisibleRanges(visibleRanges);
|
||||
}
|
||||
|
||||
// (2) fire change events
|
||||
if (data.options) {
|
||||
this._onDidChangeTextEditorOptions.fire({
|
||||
textEditor: textEditor,
|
||||
options: { ...data.options, lineNumbers: TypeConverters.TextEditorLineNumbersStyle.to(data.options.lineNumbers) }
|
||||
});
|
||||
}
|
||||
if (data.selections) {
|
||||
const kind = TextEditorSelectionChangeKind.fromValue(data.selections.source);
|
||||
const selections = data.selections.selections.map(TypeConverters.Selection.to);
|
||||
this._onDidChangeTextEditorSelection.fire({
|
||||
textEditor,
|
||||
selections,
|
||||
kind
|
||||
});
|
||||
}
|
||||
if (data.visibleRanges) {
|
||||
const visibleRanges = arrays.coalesce(data.visibleRanges.map(TypeConverters.Range.to));
|
||||
this._onDidChangeTextEditorVisibleRanges.fire({
|
||||
textEditor,
|
||||
visibleRanges
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$acceptEditorPositionData(data: ITextEditorPositionData): void {
|
||||
for (const id in data) {
|
||||
const textEditor = this._extHostDocumentsAndEditors.getEditor(id);
|
||||
if (!textEditor) {
|
||||
throw new Error('Unknown text editor');
|
||||
}
|
||||
const viewColumn = TypeConverters.ViewColumn.to(data[id]);
|
||||
if (textEditor.viewColumn !== viewColumn) {
|
||||
textEditor._acceptViewColumn(viewColumn);
|
||||
this._onDidChangeTextEditorViewColumn.fire({ textEditor, viewColumn });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getDiffInformation(id: string): Promise<vscode.LineChange[]> {
|
||||
return Promise.resolve(this._proxy.$getDiffInformation(id));
|
||||
}
|
||||
}
|
||||
@@ -1,629 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { localize } from 'vs/nls';
|
||||
import * as vscode from 'vscode';
|
||||
import { basename } from 'vs/base/common/resources';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { ExtHostTreeViewsShape, MainThreadTreeViewsShape } from '../common/extHost.protocol';
|
||||
import { ITreeItem, TreeViewItemHandleArg, ITreeItemLabel, IRevealOptions } from 'vs/workbench/common/views';
|
||||
import { ExtHostCommands, CommandsConverter } from 'vs/workbench/api/node/extHostCommands';
|
||||
import { asPromise } from 'vs/base/common/async';
|
||||
import { TreeItemCollapsibleState, ThemeIcon, MarkdownString } from 'vs/workbench/api/node/extHostTypes';
|
||||
import { isUndefinedOrNull, isString } from 'vs/base/common/types';
|
||||
import { equals, coalesce } from 'vs/base/common/arrays';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
import * as azdata from 'azdata';
|
||||
import { ITreeItem as sqlITreeItem } from 'sql/workbench/common/views';
|
||||
export type TreeItemHandle = string;
|
||||
|
||||
function toTreeItemLabel(label: any, extension: IExtensionDescription): ITreeItemLabel | undefined {
|
||||
if (isString(label)) {
|
||||
return { label };
|
||||
}
|
||||
|
||||
if (label
|
||||
&& typeof label === 'object'
|
||||
&& typeof label.label === 'string') {
|
||||
checkProposedApiEnabled(extension);
|
||||
let highlights: [number, number][] | undefined = undefined;
|
||||
if (Array.isArray(label.highlights)) {
|
||||
highlights = (<[number, number][]>label.highlights).filter((highlight => highlight.length === 2 && typeof highlight[0] === 'number' && typeof highlight[1] === 'number'));
|
||||
highlights = highlights.length ? highlights : undefined;
|
||||
}
|
||||
return { label: label.label, highlights };
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
||||
export class ExtHostTreeViews implements ExtHostTreeViewsShape {
|
||||
|
||||
private treeViews: Map<string, ExtHostTreeView<any>> = new Map<string, ExtHostTreeView<any>>();
|
||||
|
||||
constructor(
|
||||
private _proxy: MainThreadTreeViewsShape,
|
||||
private commands: ExtHostCommands,
|
||||
private logService: ILogService
|
||||
) {
|
||||
commands.registerArgumentProcessor({
|
||||
processArgument: arg => {
|
||||
if (arg && arg.$treeViewId && arg.$treeItemHandle) {
|
||||
return this.convertArgument(arg);
|
||||
}
|
||||
return arg;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
registerTreeDataProvider<T>(id: string, treeDataProvider: vscode.TreeDataProvider<T>, extension: IExtensionDescription): vscode.Disposable {
|
||||
const treeView = this.createTreeView(id, { treeDataProvider }, extension);
|
||||
return { dispose: () => treeView.dispose() };
|
||||
}
|
||||
|
||||
createTreeView<T>(viewId: string, options: vscode.TreeViewOptions<T>, extension: IExtensionDescription): vscode.TreeView<T> {
|
||||
if (!options || !options.treeDataProvider) {
|
||||
throw new Error('Options with treeDataProvider is mandatory');
|
||||
}
|
||||
|
||||
const treeView = this.createExtHostTreeView(viewId, options, extension);
|
||||
return {
|
||||
get onDidCollapseElement() { return treeView.onDidCollapseElement; },
|
||||
get onDidExpandElement() { return treeView.onDidExpandElement; },
|
||||
get selection() { return treeView.selectedElements; },
|
||||
get onDidChangeSelection() { return treeView.onDidChangeSelection; },
|
||||
get visible() { return treeView.visible; },
|
||||
get onDidChangeVisibility() { return treeView.onDidChangeVisibility; },
|
||||
get message() { return treeView.message; },
|
||||
set message(message: string | MarkdownString) { checkProposedApiEnabled(extension); treeView.message = message; },
|
||||
reveal: (element: T, options?: IRevealOptions): Promise<void> => {
|
||||
return treeView.reveal(element, options);
|
||||
},
|
||||
dispose: () => {
|
||||
this.treeViews.delete(viewId);
|
||||
treeView.dispose();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
$getChildren(treeViewId: string, treeItemHandle?: string): Promise<ITreeItem[]> {
|
||||
const treeView = this.treeViews.get(treeViewId);
|
||||
if (!treeView) {
|
||||
return Promise.reject(new Error(localize('treeView.notRegistered', 'No tree view with id \'{0}\' registered.', treeViewId)));
|
||||
}
|
||||
return treeView.getChildren(treeItemHandle);
|
||||
}
|
||||
|
||||
$setExpanded(treeViewId: string, treeItemHandle: string, expanded: boolean): void {
|
||||
const treeView = this.treeViews.get(treeViewId);
|
||||
if (!treeView) {
|
||||
throw new Error(localize('treeView.notRegistered', 'No tree view with id \'{0}\' registered.', treeViewId));
|
||||
}
|
||||
treeView.setExpanded(treeItemHandle, expanded);
|
||||
}
|
||||
|
||||
$setSelection(treeViewId: string, treeItemHandles: string[]): void {
|
||||
const treeView = this.treeViews.get(treeViewId);
|
||||
if (!treeView) {
|
||||
throw new Error(localize('treeView.notRegistered', 'No tree view with id \'{0}\' registered.', treeViewId));
|
||||
}
|
||||
treeView.setSelection(treeItemHandles);
|
||||
}
|
||||
|
||||
$setVisible(treeViewId: string, isVisible: boolean): void {
|
||||
const treeView = this.treeViews.get(treeViewId);
|
||||
if (!treeView) {
|
||||
throw new Error(localize('treeView.notRegistered', 'No tree view with id \'{0}\' registered.', treeViewId));
|
||||
}
|
||||
treeView.setVisible(isVisible);
|
||||
}
|
||||
|
||||
private createExtHostTreeView<T>(id: string, options: vscode.TreeViewOptions<T>, extension: IExtensionDescription): ExtHostTreeView<T> {
|
||||
const treeView = new ExtHostTreeView<T>(id, options, this._proxy, this.commands.converter, this.logService, extension);
|
||||
this.treeViews.set(id, treeView);
|
||||
return treeView;
|
||||
}
|
||||
|
||||
private convertArgument(arg: TreeViewItemHandleArg): any {
|
||||
const treeView = this.treeViews.get(arg.$treeViewId);
|
||||
return treeView ? treeView.getExtensionElement(arg.$treeItemHandle) : null;
|
||||
}
|
||||
}
|
||||
|
||||
type Root = null | undefined;
|
||||
type TreeData<T> = { message: boolean, element: T | Root | false };
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
export interface TreeNode {
|
||||
item: ITreeItem;
|
||||
parent: TreeNode | Root;
|
||||
children?: TreeNode[];
|
||||
}
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
export class ExtHostTreeView<T> extends Disposable {
|
||||
|
||||
private static LABEL_HANDLE_PREFIX = '0';
|
||||
private static ID_HANDLE_PREFIX = '1';
|
||||
|
||||
private readonly dataProvider: vscode.TreeDataProvider<T>;
|
||||
|
||||
private roots: TreeNode[] | null = null;
|
||||
private elements: Map<TreeItemHandle, T> = new Map<TreeItemHandle, T>();
|
||||
// {{SQL CARBON EDIT}}
|
||||
protected nodes: Map<T, TreeNode> = new Map<T, TreeNode>();
|
||||
|
||||
private _visible: boolean = false;
|
||||
get visible(): boolean { return this._visible; }
|
||||
|
||||
private _selectedHandles: TreeItemHandle[] = [];
|
||||
get selectedElements(): T[] { return <T[]>this._selectedHandles.map(handle => this.getExtensionElement(handle)).filter(element => !isUndefinedOrNull(element)); }
|
||||
|
||||
private _onDidExpandElement: Emitter<vscode.TreeViewExpansionEvent<T>> = this._register(new Emitter<vscode.TreeViewExpansionEvent<T>>());
|
||||
readonly onDidExpandElement: Event<vscode.TreeViewExpansionEvent<T>> = this._onDidExpandElement.event;
|
||||
|
||||
private _onDidCollapseElement: Emitter<vscode.TreeViewExpansionEvent<T>> = this._register(new Emitter<vscode.TreeViewExpansionEvent<T>>());
|
||||
readonly onDidCollapseElement: Event<vscode.TreeViewExpansionEvent<T>> = this._onDidCollapseElement.event;
|
||||
|
||||
private _onDidChangeSelection: Emitter<vscode.TreeViewSelectionChangeEvent<T>> = this._register(new Emitter<vscode.TreeViewSelectionChangeEvent<T>>());
|
||||
readonly onDidChangeSelection: Event<vscode.TreeViewSelectionChangeEvent<T>> = this._onDidChangeSelection.event;
|
||||
|
||||
private _onDidChangeVisibility: Emitter<vscode.TreeViewVisibilityChangeEvent> = this._register(new Emitter<vscode.TreeViewVisibilityChangeEvent>());
|
||||
readonly onDidChangeVisibility: Event<vscode.TreeViewVisibilityChangeEvent> = this._onDidChangeVisibility.event;
|
||||
|
||||
private _onDidChangeData: Emitter<TreeData<T>> = this._register(new Emitter<TreeData<T>>());
|
||||
|
||||
private refreshPromise: Promise<void> = Promise.resolve();
|
||||
|
||||
constructor(private viewId: string, options: vscode.TreeViewOptions<T>, private proxy: MainThreadTreeViewsShape, private commands: CommandsConverter, private logService: ILogService, private extension: IExtensionDescription) {
|
||||
super();
|
||||
this.dataProvider = options.treeDataProvider;
|
||||
// {{SQL CARBON EDIT}}
|
||||
if (this.proxy) {
|
||||
this.proxy.$registerTreeViewDataProvider(viewId, { showCollapseAll: !!options.showCollapseAll });
|
||||
}
|
||||
if (this.dataProvider.onDidChangeTreeData) {
|
||||
this._register(this.dataProvider.onDidChangeTreeData(element => this._onDidChangeData.fire({ message: false, element })));
|
||||
}
|
||||
|
||||
let refreshingPromise: Promise<void> | null;
|
||||
let promiseCallback: () => void;
|
||||
this._register(Event.debounce<TreeData<T>, { message: boolean, elements: (T | Root)[] }>(this._onDidChangeData.event, (result, current) => {
|
||||
if (!result) {
|
||||
result = { message: false, elements: [] };
|
||||
}
|
||||
if (current.element !== false) {
|
||||
if (!refreshingPromise) {
|
||||
// New refresh has started
|
||||
refreshingPromise = new Promise(c => promiseCallback = c);
|
||||
this.refreshPromise = this.refreshPromise.then(() => refreshingPromise!);
|
||||
}
|
||||
result.elements.push(current.element);
|
||||
}
|
||||
if (current.message) {
|
||||
result.message = true;
|
||||
}
|
||||
return result;
|
||||
}, 200)(({ message, elements }) => {
|
||||
if (elements.length) {
|
||||
const _promiseCallback = promiseCallback;
|
||||
refreshingPromise = null;
|
||||
this.refresh(elements).then(() => _promiseCallback());
|
||||
}
|
||||
if (message) {
|
||||
this.proxy.$setMessage(this.viewId, this._message);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
getChildren(parentHandle: TreeItemHandle | Root): Promise<ITreeItem[]> {
|
||||
const parentElement = parentHandle ? this.getExtensionElement(parentHandle) : undefined;
|
||||
if (parentHandle && !parentElement) {
|
||||
console.error(`No tree item with id \'${parentHandle}\' found.`);
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
|
||||
const childrenNodes = this.getChildrenNodes(parentHandle); // Get it from cache
|
||||
return (childrenNodes ? Promise.resolve(childrenNodes) : this.fetchChildrenNodes(parentElement))
|
||||
.then(nodes => nodes.map(n => n.item));
|
||||
}
|
||||
|
||||
getExtensionElement(treeItemHandle: TreeItemHandle): T | undefined {
|
||||
return this.elements.get(treeItemHandle);
|
||||
}
|
||||
|
||||
reveal(element: T, options?: IRevealOptions): Promise<void> {
|
||||
options = options ? options : { select: true, focus: false };
|
||||
const select = isUndefinedOrNull(options.select) ? true : options.select;
|
||||
const focus = isUndefinedOrNull(options.focus) ? false : options.focus;
|
||||
const expand = isUndefinedOrNull(options.expand) ? false : options.expand;
|
||||
|
||||
if (typeof this.dataProvider.getParent !== 'function') {
|
||||
return Promise.reject(new Error(`Required registered TreeDataProvider to implement 'getParent' method to access 'reveal' method`));
|
||||
}
|
||||
return this.refreshPromise
|
||||
.then(() => this.resolveUnknownParentChain(element))
|
||||
.then(parentChain => this.resolveTreeNode(element, parentChain[parentChain.length - 1])
|
||||
.then(treeNode => this.proxy.$reveal(this.viewId, treeNode.item, parentChain.map(p => p.item), { select, focus, expand })), error => this.logService.error(error));
|
||||
}
|
||||
|
||||
private _message: string | MarkdownString;
|
||||
get message(): string | MarkdownString {
|
||||
return this._message;
|
||||
}
|
||||
|
||||
set message(message: string | MarkdownString) {
|
||||
this._message = message;
|
||||
this._onDidChangeData.fire({ message: true, element: false });
|
||||
}
|
||||
|
||||
setExpanded(treeItemHandle: TreeItemHandle, expanded: boolean): void {
|
||||
const element = this.getExtensionElement(treeItemHandle);
|
||||
if (element) {
|
||||
if (expanded) {
|
||||
this._onDidExpandElement.fire(Object.freeze({ element }));
|
||||
} else {
|
||||
this._onDidCollapseElement.fire(Object.freeze({ element }));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setSelection(treeItemHandles: TreeItemHandle[]): void {
|
||||
if (!equals(this._selectedHandles, treeItemHandles)) {
|
||||
this._selectedHandles = treeItemHandles;
|
||||
this._onDidChangeSelection.fire(Object.freeze({ selection: this.selectedElements }));
|
||||
}
|
||||
}
|
||||
|
||||
setVisible(visible: boolean): void {
|
||||
if (visible !== this._visible) {
|
||||
this._visible = visible;
|
||||
this._onDidChangeVisibility.fire(Object.freeze({ visible: this._visible }));
|
||||
}
|
||||
}
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
protected resolveUnknownParentChain(element: T): Promise<TreeNode[]> {
|
||||
return this.resolveParent(element)
|
||||
.then((parent) => {
|
||||
if (!parent) {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
return this.resolveUnknownParentChain(parent)
|
||||
.then(result => this.resolveTreeNode(parent, result[result.length - 1])
|
||||
.then(parentNode => {
|
||||
result.push(parentNode);
|
||||
return result;
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
private resolveParent(element: T): Promise<T | Root> {
|
||||
const node = this.nodes.get(element);
|
||||
if (node) {
|
||||
return Promise.resolve(node.parent ? this.elements.get(node.parent.item.handle) : undefined);
|
||||
}
|
||||
return asPromise(() => this.dataProvider.getParent!(element));
|
||||
}
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
protected resolveTreeNode(element: T, parent?: TreeNode): Promise<TreeNode> {
|
||||
const node = this.nodes.get(element);
|
||||
if (node) {
|
||||
return Promise.resolve(node);
|
||||
}
|
||||
return asPromise(() => this.dataProvider.getTreeItem(element))
|
||||
.then(extTreeItem => this.createHandle(element, extTreeItem, parent, true))
|
||||
.then(handle => this.getChildren(parent ? parent.item.handle : undefined)
|
||||
.then(() => {
|
||||
const cachedElement = this.getExtensionElement(handle);
|
||||
if (cachedElement) {
|
||||
const node = this.nodes.get(cachedElement);
|
||||
if (node) {
|
||||
return Promise.resolve(node);
|
||||
}
|
||||
}
|
||||
throw new Error(`Cannot resolve tree item for element ${handle}`);
|
||||
}));
|
||||
}
|
||||
|
||||
private getChildrenNodes(parentNodeOrHandle: TreeNode | TreeItemHandle | Root): TreeNode[] | null {
|
||||
if (parentNodeOrHandle) {
|
||||
let parentNode: TreeNode | undefined;
|
||||
if (typeof parentNodeOrHandle === 'string') {
|
||||
const parentElement = this.getExtensionElement(parentNodeOrHandle);
|
||||
parentNode = parentElement ? this.nodes.get(parentElement) : undefined;
|
||||
} else {
|
||||
parentNode = parentNodeOrHandle;
|
||||
}
|
||||
return parentNode ? parentNode.children || null : null;
|
||||
}
|
||||
return this.roots;
|
||||
}
|
||||
|
||||
private fetchChildrenNodes(parentElement?: T): Promise<TreeNode[]> {
|
||||
// clear children cache
|
||||
this.clearChildren(parentElement);
|
||||
|
||||
const parentNode = parentElement ? this.nodes.get(parentElement) : undefined;
|
||||
return asPromise(() => this.dataProvider.getChildren(parentElement))
|
||||
.then(elements => Promise.all(
|
||||
coalesce(elements || [])
|
||||
.map(element => asPromise(() => this.dataProvider.getTreeItem(element))
|
||||
.then(extTreeItem => extTreeItem ? this.createAndRegisterTreeNode(element, extTreeItem, parentNode) : null))))
|
||||
.then(coalesce);
|
||||
}
|
||||
|
||||
private refresh(elements: (T | Root)[]): Promise<void> {
|
||||
const hasRoot = elements.some(element => !element);
|
||||
if (hasRoot) {
|
||||
this.clearAll(); // clear cache
|
||||
return this.proxy.$refresh(this.viewId);
|
||||
} else {
|
||||
const handlesToRefresh = this.getHandlesToRefresh(<T[]>elements);
|
||||
if (handlesToRefresh.length) {
|
||||
return this.refreshHandles(handlesToRefresh);
|
||||
}
|
||||
}
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
protected getHandlesToRefresh(elements: T[]): TreeItemHandle[] {
|
||||
const elementsToUpdate = new Set<TreeItemHandle>();
|
||||
for (const element of elements) {
|
||||
const elementNode = this.nodes.get(element);
|
||||
if (elementNode && !elementsToUpdate.has(elementNode.item.handle)) {
|
||||
// check if an ancestor of extElement is already in the elements to update list
|
||||
let currentNode: TreeNode | undefined = elementNode;
|
||||
while (currentNode && currentNode.parent && !elementsToUpdate.has(currentNode.parent.item.handle)) {
|
||||
const parentElement = this.elements.get(currentNode.parent.item.handle);
|
||||
currentNode = parentElement ? this.nodes.get(parentElement) : undefined;
|
||||
}
|
||||
if (currentNode && !currentNode.parent) {
|
||||
elementsToUpdate.add(elementNode.item.handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handlesToUpdate: TreeItemHandle[] = [];
|
||||
// Take only top level elements
|
||||
elementsToUpdate.forEach((handle) => {
|
||||
const element = this.elements.get(handle);
|
||||
if (element) {
|
||||
const node = this.nodes.get(element);
|
||||
if (node && (!node.parent || !elementsToUpdate.has(node.parent.item.handle))) {
|
||||
handlesToUpdate.push(handle);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return handlesToUpdate;
|
||||
}
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
protected refreshHandles(itemHandles: TreeItemHandle[]): Promise<void> {
|
||||
const itemsToRefresh: { [treeItemHandle: string]: ITreeItem } = {};
|
||||
return Promise.all(itemHandles.map(treeItemHandle =>
|
||||
this.refreshNode(treeItemHandle)
|
||||
.then(node => {
|
||||
if (node) {
|
||||
itemsToRefresh[treeItemHandle] = node.item;
|
||||
}
|
||||
})))
|
||||
.then(() => Object.keys(itemsToRefresh).length ? this.proxy.$refresh(this.viewId, itemsToRefresh) : undefined);
|
||||
}
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
protected refreshNode(treeItemHandle: TreeItemHandle): Promise<TreeNode | null> {
|
||||
const extElement = this.getExtensionElement(treeItemHandle);
|
||||
if (extElement) {
|
||||
const existing = this.nodes.get(extElement);
|
||||
if (existing) {
|
||||
this.clearChildren(extElement); // clear children cache
|
||||
return asPromise(() => this.dataProvider.getTreeItem(extElement))
|
||||
.then(extTreeItem => {
|
||||
if (extTreeItem) {
|
||||
const newNode = this.createTreeNode(extElement, extTreeItem, existing.parent);
|
||||
this.updateNodeCache(extElement, newNode, existing, existing.parent);
|
||||
return newNode;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
private createAndRegisterTreeNode(element: T, extTreeItem: vscode.TreeItem, parentNode: TreeNode | Root): TreeNode {
|
||||
const node = this.createTreeNode(element, extTreeItem, parentNode);
|
||||
if (extTreeItem.id && this.elements.has(node.item.handle)) {
|
||||
throw new Error(localize('treeView.duplicateElement', 'Element with id {0} is already registered', extTreeItem.id));
|
||||
}
|
||||
this.addNodeToCache(element, node);
|
||||
this.addNodeToParentCache(node, parentNode);
|
||||
return node;
|
||||
}
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
protected createTreeNode(element: T, extensionTreeItem: vscode.TreeItem, parent: TreeNode | Root): TreeNode {
|
||||
return {
|
||||
item: this.createTreeItem(element, extensionTreeItem, parent),
|
||||
parent,
|
||||
children: undefined
|
||||
};
|
||||
}
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
protected createTreeItem(element: T, extensionTreeItem: azdata.TreeItem, parent: TreeNode | Root): ITreeItem {
|
||||
|
||||
const handle = this.createHandle(element, extensionTreeItem, parent);
|
||||
const icon = this.getLightIconPath(extensionTreeItem);
|
||||
const item = {
|
||||
handle,
|
||||
parentHandle: parent ? parent.item.handle : undefined,
|
||||
label: toTreeItemLabel(extensionTreeItem.label, this.extension),
|
||||
description: extensionTreeItem.description,
|
||||
resourceUri: extensionTreeItem.resourceUri,
|
||||
tooltip: typeof extensionTreeItem.tooltip === 'string' ? extensionTreeItem.tooltip : undefined,
|
||||
command: extensionTreeItem.command ? this.commands.toInternal(extensionTreeItem.command) : undefined,
|
||||
contextValue: extensionTreeItem.contextValue,
|
||||
icon,
|
||||
iconDark: this.getDarkIconPath(extensionTreeItem) || icon,
|
||||
themeIcon: extensionTreeItem.iconPath instanceof ThemeIcon ? { id: extensionTreeItem.iconPath.id } : undefined,
|
||||
collapsibleState: isUndefinedOrNull(extensionTreeItem.collapsibleState) ? TreeItemCollapsibleState.None : extensionTreeItem.collapsibleState,
|
||||
// {{SQL CARBON EDIT}}
|
||||
payload: extensionTreeItem.payload,
|
||||
childProvider: extensionTreeItem.childProvider
|
||||
};
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
private createHandle(element: T, { id, label, resourceUri }: vscode.TreeItem, parent: TreeNode | Root, returnFirst?: boolean): TreeItemHandle {
|
||||
if (id) {
|
||||
return `${ExtHostTreeView.ID_HANDLE_PREFIX}/${id}`;
|
||||
}
|
||||
|
||||
const treeItemLabel = toTreeItemLabel(label, this.extension);
|
||||
const prefix: string = parent ? parent.item.handle : ExtHostTreeView.LABEL_HANDLE_PREFIX;
|
||||
let elementId = treeItemLabel ? treeItemLabel.label : resourceUri ? basename(resourceUri) : '';
|
||||
elementId = elementId.indexOf('/') !== -1 ? elementId.replace('/', '//') : elementId;
|
||||
const existingHandle = this.nodes.has(element) ? this.nodes.get(element)!.item.handle : undefined;
|
||||
const childrenNodes = (this.getChildrenNodes(parent) || []);
|
||||
|
||||
let handle: TreeItemHandle;
|
||||
let counter = 0;
|
||||
do {
|
||||
handle = `${prefix}/${counter}:${elementId}`;
|
||||
if (returnFirst || !this.elements.has(handle) || existingHandle === handle) {
|
||||
// Return first if asked for or
|
||||
// Return if handle does not exist or
|
||||
// Return if handle is being reused
|
||||
break;
|
||||
}
|
||||
counter++;
|
||||
} while (counter <= childrenNodes.length);
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
private getLightIconPath(extensionTreeItem: vscode.TreeItem): URI | undefined {
|
||||
if (extensionTreeItem.iconPath && !(extensionTreeItem.iconPath instanceof ThemeIcon)) {
|
||||
if (typeof extensionTreeItem.iconPath === 'string'
|
||||
|| extensionTreeItem.iconPath instanceof URI) {
|
||||
return this.getIconPath(extensionTreeItem.iconPath);
|
||||
}
|
||||
return this.getIconPath(extensionTreeItem.iconPath['light']);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private getDarkIconPath(extensionTreeItem: vscode.TreeItem): URI | undefined {
|
||||
if (extensionTreeItem.iconPath && !(extensionTreeItem.iconPath instanceof ThemeIcon) && extensionTreeItem.iconPath['dark']) {
|
||||
return this.getIconPath(extensionTreeItem.iconPath['dark']);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private getIconPath(iconPath: string | URI): URI {
|
||||
if (iconPath instanceof URI) {
|
||||
return iconPath;
|
||||
}
|
||||
return URI.file(iconPath);
|
||||
}
|
||||
|
||||
private addNodeToCache(element: T, node: TreeNode): void {
|
||||
this.elements.set(node.item.handle, element);
|
||||
this.nodes.set(element, node);
|
||||
}
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
protected updateNodeCache(element: T, newNode: TreeNode, existing: TreeNode, parentNode: TreeNode | Root): void {
|
||||
// Remove from the cache
|
||||
this.elements.delete(newNode.item.handle);
|
||||
this.nodes.delete(element);
|
||||
if (newNode.item.handle !== existing.item.handle) {
|
||||
this.elements.delete(existing.item.handle);
|
||||
}
|
||||
|
||||
// Add the new node to the cache
|
||||
this.addNodeToCache(element, newNode);
|
||||
|
||||
// Replace the node in parent's children nodes
|
||||
const childrenNodes = (this.getChildrenNodes(parentNode) || []);
|
||||
const childNode = childrenNodes.filter(c => c.item.handle === existing.item.handle)[0];
|
||||
if (childNode) {
|
||||
childrenNodes.splice(childrenNodes.indexOf(childNode), 1, newNode);
|
||||
}
|
||||
}
|
||||
|
||||
private addNodeToParentCache(node: TreeNode, parentNode: TreeNode | Root): void {
|
||||
if (parentNode) {
|
||||
if (!parentNode.children) {
|
||||
parentNode.children = [];
|
||||
}
|
||||
parentNode.children.push(node);
|
||||
} else {
|
||||
if (!this.roots) {
|
||||
this.roots = [];
|
||||
}
|
||||
this.roots.push(node);
|
||||
}
|
||||
}
|
||||
|
||||
private clearChildren(parentElement?: T): void {
|
||||
if (parentElement) {
|
||||
const node = this.nodes.get(parentElement);
|
||||
if (node) {
|
||||
if (node.children) {
|
||||
for (const child of node.children) {
|
||||
const childEleement = this.elements.get(child.item.handle);
|
||||
if (childEleement) {
|
||||
this.clear(childEleement);
|
||||
}
|
||||
}
|
||||
}
|
||||
node.children = undefined;
|
||||
}
|
||||
} else {
|
||||
this.clearAll();
|
||||
}
|
||||
}
|
||||
|
||||
private clear(element: T): void {
|
||||
const node = this.nodes.get(element);
|
||||
if (node) {
|
||||
if (node.children) {
|
||||
for (const child of node.children) {
|
||||
const childEleement = this.elements.get(child.item.handle);
|
||||
if (childEleement) {
|
||||
this.clear(childEleement);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.nodes.delete(element);
|
||||
this.elements.delete(node.item.handle);
|
||||
}
|
||||
}
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
protected clearAll(): void {
|
||||
this.roots = null;
|
||||
this.elements.clear();
|
||||
this.nodes.clear();
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.clearAll();
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,58 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import { MainContext, IMainContext, ExtHostUrlsShape, MainThreadUrlsShape } from '../common/extHost.protocol';
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import { toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
|
||||
|
||||
export class ExtHostUrls implements ExtHostUrlsShape {
|
||||
|
||||
private static HandlePool = 0;
|
||||
private readonly _proxy: MainThreadUrlsShape;
|
||||
|
||||
private handles = new Set<string>();
|
||||
private handlers = new Map<number, vscode.UriHandler>();
|
||||
|
||||
constructor(
|
||||
mainContext: IMainContext
|
||||
) {
|
||||
this._proxy = mainContext.getProxy(MainContext.MainThreadUrls);
|
||||
}
|
||||
|
||||
registerUriHandler(extensionId: ExtensionIdentifier, handler: vscode.UriHandler): vscode.Disposable {
|
||||
if (this.handles.has(ExtensionIdentifier.toKey(extensionId))) {
|
||||
throw new Error(`Protocol handler already registered for extension ${extensionId}`);
|
||||
}
|
||||
|
||||
const handle = ExtHostUrls.HandlePool++;
|
||||
this.handles.add(ExtensionIdentifier.toKey(extensionId));
|
||||
this.handlers.set(handle, handler);
|
||||
this._proxy.$registerUriHandler(handle, extensionId);
|
||||
|
||||
return toDisposable(() => {
|
||||
this.handles.delete(ExtensionIdentifier.toKey(extensionId));
|
||||
this.handlers.delete(handle);
|
||||
this._proxy.$unregisterUriHandler(handle);
|
||||
});
|
||||
}
|
||||
|
||||
$handleExternalUri(handle: number, uri: UriComponents): Promise<void> {
|
||||
const handler = this.handlers.get(handle);
|
||||
|
||||
if (!handler) {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
try {
|
||||
handler.handleUri(URI.revive(uri));
|
||||
} catch (err) {
|
||||
onUnexpectedError(err);
|
||||
}
|
||||
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
}
|
||||
@@ -1,344 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { URI } from 'vs/base/common/uri';
|
||||
import * as typeConverters from 'vs/workbench/api/node/extHostTypeConverters';
|
||||
import { EditorViewColumn } from 'vs/workbench/api/common/shared/editor';
|
||||
import * as vscode from 'vscode';
|
||||
import { ExtHostWebviewsShape, IMainContext, MainContext, MainThreadWebviewsShape, WebviewPanelHandle, WebviewPanelViewState, WebviewInsetHandle } from '../common/extHost.protocol';
|
||||
import { Disposable } from './extHostTypes';
|
||||
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
|
||||
type IconPath = URI | { light: URI, dark: URI };
|
||||
|
||||
export class ExtHostWebview implements vscode.Webview {
|
||||
private readonly _handle: WebviewPanelHandle | WebviewInsetHandle;
|
||||
private readonly _proxy: MainThreadWebviewsShape;
|
||||
private _html: string;
|
||||
private _options: vscode.WebviewOptions;
|
||||
private _isDisposed: boolean = false;
|
||||
|
||||
public readonly _onMessageEmitter = new Emitter<any>();
|
||||
public readonly onDidReceiveMessage: Event<any> = this._onMessageEmitter.event;
|
||||
|
||||
constructor(
|
||||
handle: WebviewPanelHandle | WebviewInsetHandle,
|
||||
proxy: MainThreadWebviewsShape,
|
||||
options: vscode.WebviewOptions
|
||||
) {
|
||||
this._handle = handle;
|
||||
this._proxy = proxy;
|
||||
this._options = options;
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
this._onMessageEmitter.dispose();
|
||||
}
|
||||
|
||||
public get html(): string {
|
||||
this.assertNotDisposed();
|
||||
return this._html;
|
||||
}
|
||||
|
||||
public set html(value: string) {
|
||||
this.assertNotDisposed();
|
||||
if (this._html !== value) {
|
||||
this._html = value;
|
||||
this._proxy.$setHtml(this._handle, value);
|
||||
}
|
||||
}
|
||||
|
||||
public get options(): vscode.WebviewOptions {
|
||||
this.assertNotDisposed();
|
||||
return this._options;
|
||||
}
|
||||
|
||||
public set options(newOptions: vscode.WebviewOptions) {
|
||||
this.assertNotDisposed();
|
||||
this._proxy.$setOptions(this._handle, newOptions);
|
||||
this._options = newOptions;
|
||||
}
|
||||
|
||||
public postMessage(message: any): Promise<boolean> {
|
||||
this.assertNotDisposed();
|
||||
return this._proxy.$postMessage(this._handle, message);
|
||||
}
|
||||
|
||||
private assertNotDisposed() {
|
||||
if (this._isDisposed) {
|
||||
throw new Error('Webview is disposed');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class ExtHostWebviewPanel implements vscode.WebviewPanel {
|
||||
|
||||
private readonly _handle: WebviewPanelHandle;
|
||||
private readonly _proxy: MainThreadWebviewsShape;
|
||||
private readonly _viewType: string;
|
||||
private _title: string;
|
||||
private _iconPath?: IconPath;
|
||||
|
||||
private readonly _options: vscode.WebviewPanelOptions;
|
||||
private readonly _webview: ExtHostWebview;
|
||||
private _isDisposed: boolean = false;
|
||||
private _viewColumn: vscode.ViewColumn | undefined;
|
||||
private _visible: boolean = true;
|
||||
private _active: boolean = true;
|
||||
|
||||
readonly _onDisposeEmitter = new Emitter<void>();
|
||||
public readonly onDidDispose: Event<void> = this._onDisposeEmitter.event;
|
||||
|
||||
readonly _onDidChangeViewStateEmitter = new Emitter<vscode.WebviewPanelOnDidChangeViewStateEvent>();
|
||||
public readonly onDidChangeViewState: Event<vscode.WebviewPanelOnDidChangeViewStateEvent> = this._onDidChangeViewStateEmitter.event;
|
||||
|
||||
|
||||
constructor(
|
||||
handle: WebviewPanelHandle,
|
||||
proxy: MainThreadWebviewsShape,
|
||||
viewType: string,
|
||||
title: string,
|
||||
viewColumn: vscode.ViewColumn | undefined,
|
||||
editorOptions: vscode.WebviewPanelOptions,
|
||||
webview: ExtHostWebview
|
||||
) {
|
||||
this._handle = handle;
|
||||
this._proxy = proxy;
|
||||
this._viewType = viewType;
|
||||
this._options = editorOptions;
|
||||
this._viewColumn = viewColumn;
|
||||
this._title = title;
|
||||
this._webview = webview;
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
if (this._isDisposed) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._isDisposed = true;
|
||||
this._onDisposeEmitter.fire();
|
||||
|
||||
this._proxy.$disposeWebview(this._handle);
|
||||
|
||||
this._webview.dispose();
|
||||
|
||||
this._onDisposeEmitter.dispose();
|
||||
this._onDidChangeViewStateEmitter.dispose();
|
||||
}
|
||||
|
||||
get webview() {
|
||||
this.assertNotDisposed();
|
||||
return this._webview;
|
||||
}
|
||||
|
||||
get viewType(): string {
|
||||
this.assertNotDisposed();
|
||||
return this._viewType;
|
||||
}
|
||||
|
||||
get title(): string {
|
||||
this.assertNotDisposed();
|
||||
return this._title;
|
||||
}
|
||||
|
||||
set title(value: string) {
|
||||
this.assertNotDisposed();
|
||||
if (this._title !== value) {
|
||||
this._title = value;
|
||||
this._proxy.$setTitle(this._handle, value);
|
||||
}
|
||||
}
|
||||
|
||||
get iconPath(): IconPath | undefined {
|
||||
this.assertNotDisposed();
|
||||
return this._iconPath;
|
||||
}
|
||||
|
||||
set iconPath(value: IconPath | undefined) {
|
||||
this.assertNotDisposed();
|
||||
if (this._iconPath !== value) {
|
||||
this._iconPath = value;
|
||||
|
||||
this._proxy.$setIconPath(this._handle, URI.isUri(value) ? { light: value, dark: value } : value);
|
||||
}
|
||||
}
|
||||
|
||||
get options() {
|
||||
return this._options;
|
||||
}
|
||||
|
||||
get viewColumn(): vscode.ViewColumn | undefined {
|
||||
this.assertNotDisposed();
|
||||
if (typeof this._viewColumn === 'number' && this._viewColumn < 0) {
|
||||
// We are using a symbolic view column
|
||||
// Return undefined instead to indicate that the real view column is currently unknown but will be resolved.
|
||||
return undefined;
|
||||
}
|
||||
return this._viewColumn;
|
||||
}
|
||||
|
||||
_setViewColumn(value: vscode.ViewColumn) {
|
||||
this.assertNotDisposed();
|
||||
this._viewColumn = value;
|
||||
}
|
||||
|
||||
public get active(): boolean {
|
||||
this.assertNotDisposed();
|
||||
return this._active;
|
||||
}
|
||||
|
||||
_setActive(value: boolean) {
|
||||
this.assertNotDisposed();
|
||||
this._active = value;
|
||||
}
|
||||
|
||||
public get visible(): boolean {
|
||||
this.assertNotDisposed();
|
||||
return this._visible;
|
||||
}
|
||||
|
||||
_setVisible(value: boolean) {
|
||||
this.assertNotDisposed();
|
||||
this._visible = value;
|
||||
}
|
||||
|
||||
public postMessage(message: any): Promise<boolean> {
|
||||
this.assertNotDisposed();
|
||||
return this._proxy.$postMessage(this._handle, message);
|
||||
}
|
||||
|
||||
public reveal(viewColumn?: vscode.ViewColumn, preserveFocus?: boolean): void {
|
||||
this.assertNotDisposed();
|
||||
this._proxy.$reveal(this._handle, {
|
||||
viewColumn: viewColumn ? typeConverters.ViewColumn.from(viewColumn) : undefined,
|
||||
preserveFocus: !!preserveFocus
|
||||
});
|
||||
}
|
||||
|
||||
private assertNotDisposed() {
|
||||
if (this._isDisposed) {
|
||||
throw new Error('Webview is disposed');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class ExtHostWebviews implements ExtHostWebviewsShape {
|
||||
private static webviewHandlePool = 1;
|
||||
|
||||
private static newHandle(): WebviewPanelHandle {
|
||||
return ExtHostWebviews.webviewHandlePool++ + '';
|
||||
}
|
||||
|
||||
private readonly _proxy: MainThreadWebviewsShape;
|
||||
private readonly _webviewPanels = new Map<WebviewPanelHandle, ExtHostWebviewPanel>();
|
||||
private readonly _serializers = new Map<string, vscode.WebviewPanelSerializer>();
|
||||
|
||||
constructor(
|
||||
mainContext: IMainContext
|
||||
) {
|
||||
this._proxy = mainContext.getProxy(MainContext.MainThreadWebviews);
|
||||
}
|
||||
|
||||
public createWebviewPanel(
|
||||
extension: IExtensionDescription,
|
||||
viewType: string,
|
||||
title: string,
|
||||
showOptions: vscode.ViewColumn | { viewColumn: vscode.ViewColumn, preserveFocus?: boolean },
|
||||
options: (vscode.WebviewPanelOptions & vscode.WebviewOptions) = {},
|
||||
): vscode.WebviewPanel {
|
||||
const viewColumn = typeof showOptions === 'object' ? showOptions.viewColumn : showOptions;
|
||||
const webviewShowOptions = {
|
||||
viewColumn: typeConverters.ViewColumn.from(viewColumn),
|
||||
preserveFocus: typeof showOptions === 'object' && !!showOptions.preserveFocus
|
||||
};
|
||||
|
||||
const handle = ExtHostWebviews.newHandle();
|
||||
this._proxy.$createWebviewPanel(handle, viewType, title, webviewShowOptions, options, extension.identifier, extension.extensionLocation);
|
||||
|
||||
const webview = new ExtHostWebview(handle, this._proxy, options);
|
||||
const panel = new ExtHostWebviewPanel(handle, this._proxy, viewType, title, viewColumn, options, webview);
|
||||
this._webviewPanels.set(handle, panel);
|
||||
return panel;
|
||||
}
|
||||
|
||||
public registerWebviewPanelSerializer(
|
||||
viewType: string,
|
||||
serializer: vscode.WebviewPanelSerializer
|
||||
): vscode.Disposable {
|
||||
if (this._serializers.has(viewType)) {
|
||||
throw new Error(`Serializer for '${viewType}' already registered`);
|
||||
}
|
||||
|
||||
this._serializers.set(viewType, serializer);
|
||||
this._proxy.$registerSerializer(viewType);
|
||||
|
||||
return new Disposable(() => {
|
||||
this._serializers.delete(viewType);
|
||||
this._proxy.$unregisterSerializer(viewType);
|
||||
});
|
||||
}
|
||||
|
||||
public $onMessage(
|
||||
handle: WebviewPanelHandle,
|
||||
message: any
|
||||
): void {
|
||||
const panel = this.getWebviewPanel(handle);
|
||||
if (panel) {
|
||||
panel.webview._onMessageEmitter.fire(message);
|
||||
}
|
||||
}
|
||||
|
||||
public $onDidChangeWebviewPanelViewState(
|
||||
handle: WebviewPanelHandle,
|
||||
newState: WebviewPanelViewState
|
||||
): void {
|
||||
const panel = this.getWebviewPanel(handle);
|
||||
if (!panel) {
|
||||
return;
|
||||
}
|
||||
|
||||
const viewColumn = typeConverters.ViewColumn.to(newState.position);
|
||||
if (panel.active !== newState.active || panel.visible !== newState.visible || panel.viewColumn !== viewColumn) {
|
||||
panel._setActive(newState.active);
|
||||
panel._setVisible(newState.visible);
|
||||
panel._setViewColumn(viewColumn);
|
||||
panel._onDidChangeViewStateEmitter.fire({ webviewPanel: panel });
|
||||
}
|
||||
}
|
||||
|
||||
$onDidDisposeWebviewPanel(handle: WebviewPanelHandle): Promise<void> {
|
||||
const panel = this.getWebviewPanel(handle);
|
||||
if (panel) {
|
||||
panel.dispose();
|
||||
this._webviewPanels.delete(handle);
|
||||
}
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
$deserializeWebviewPanel(
|
||||
webviewHandle: WebviewPanelHandle,
|
||||
viewType: string,
|
||||
title: string,
|
||||
state: any,
|
||||
position: EditorViewColumn,
|
||||
options: vscode.WebviewOptions & vscode.WebviewPanelOptions
|
||||
): Promise<void> {
|
||||
const serializer = this._serializers.get(viewType);
|
||||
if (!serializer) {
|
||||
return Promise.reject(new Error(`No serializer found for '${viewType}'`));
|
||||
}
|
||||
|
||||
const webview = new ExtHostWebview(webviewHandle, this._proxy, options);
|
||||
const revivedPanel = new ExtHostWebviewPanel(webviewHandle, this._proxy, viewType, title, typeof position === 'number' && position >= 0 ? typeConverters.ViewColumn.to(position) : undefined, options, webview);
|
||||
this._webviewPanels.set(webviewHandle, revivedPanel);
|
||||
return Promise.resolve(serializer.deserializeWebviewPanel(revivedPanel, state));
|
||||
}
|
||||
|
||||
private getWebviewPanel(handle: WebviewPanelHandle): ExtHostWebviewPanel | undefined {
|
||||
return this._webviewPanels.get(handle);
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { ExtHostWindowShape, MainContext, MainThreadWindowShape, IMainContext } from '../common/extHost.protocol';
|
||||
import { WindowState } from 'vscode';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { isFalsyOrWhitespace } from 'vs/base/common/strings';
|
||||
|
||||
export class ExtHostWindow implements ExtHostWindowShape {
|
||||
|
||||
private static InitialState: WindowState = {
|
||||
focused: true
|
||||
};
|
||||
|
||||
private _proxy: MainThreadWindowShape;
|
||||
|
||||
private _onDidChangeWindowState = new Emitter<WindowState>();
|
||||
readonly onDidChangeWindowState: Event<WindowState> = this._onDidChangeWindowState.event;
|
||||
|
||||
private _state = ExtHostWindow.InitialState;
|
||||
get state(): WindowState { return this._state; }
|
||||
|
||||
constructor(mainContext: IMainContext) {
|
||||
this._proxy = mainContext.getProxy(MainContext.MainThreadWindow);
|
||||
this._proxy.$getWindowVisibility().then(isFocused => this.$onDidChangeWindowFocus(isFocused));
|
||||
}
|
||||
|
||||
$onDidChangeWindowFocus(focused: boolean): void {
|
||||
if (focused === this._state.focused) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._state = { ...this._state, focused };
|
||||
this._onDidChangeWindowState.fire(this._state);
|
||||
}
|
||||
|
||||
openUri(uri: URI): Promise<boolean> {
|
||||
if (isFalsyOrWhitespace(uri.scheme)) {
|
||||
return Promise.reject('Invalid scheme - cannot be empty');
|
||||
} else if (uri.scheme === Schemas.command) {
|
||||
return Promise.reject(`Invalid scheme '${uri.scheme}'`);
|
||||
}
|
||||
return this._proxy.$openUri(uri);
|
||||
}
|
||||
}
|
||||
@@ -1,519 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { join } from 'vs/base/common/path';
|
||||
import { delta as arrayDelta, mapArrayOrNot } from 'vs/base/common/arrays';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { TernarySearchTree } from 'vs/base/common/map';
|
||||
import { Counter } from 'vs/base/common/numbers';
|
||||
import { isLinux } from 'vs/base/common/platform';
|
||||
import { basenameOrAuthority, dirname, isEqual, relativePath } from 'vs/base/common/resources';
|
||||
import { compare } from 'vs/base/common/strings';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { localize } from 'vs/nls';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { Severity } from 'vs/platform/notification/common/notification';
|
||||
import { IRawFileMatch2, resultIsMatch } from 'vs/workbench/services/search/common/search';
|
||||
import { Workspace, WorkspaceFolder } from 'vs/platform/workspace/common/workspace';
|
||||
import { Range, RelativePattern } from 'vs/workbench/api/node/extHostTypes';
|
||||
import { ITextQueryBuilderOptions } from 'vs/workbench/contrib/search/common/queryBuilder';
|
||||
import * as vscode from 'vscode';
|
||||
import { ExtHostWorkspaceShape, IWorkspaceData, MainThreadMessageServiceShape, MainThreadWorkspaceShape, IMainContext, MainContext, IStaticWorkspaceData } from '../common/extHost.protocol';
|
||||
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { Barrier } from 'vs/base/common/async';
|
||||
|
||||
export interface IExtHostWorkspaceProvider {
|
||||
getWorkspaceFolder2(uri: vscode.Uri, resolveParent?: boolean): Promise<vscode.WorkspaceFolder | undefined>;
|
||||
resolveWorkspaceFolder(uri: vscode.Uri): Promise<vscode.WorkspaceFolder | undefined>;
|
||||
getWorkspaceFolders2(): Promise<vscode.WorkspaceFolder[] | undefined>;
|
||||
resolveProxy(url: string): Promise<string | undefined>;
|
||||
}
|
||||
|
||||
function isFolderEqual(folderA: URI, folderB: URI): boolean {
|
||||
return isEqual(folderA, folderB, !isLinux);
|
||||
}
|
||||
|
||||
function compareWorkspaceFolderByUri(a: vscode.WorkspaceFolder, b: vscode.WorkspaceFolder): number {
|
||||
return isFolderEqual(a.uri, b.uri) ? 0 : compare(a.uri.toString(), b.uri.toString());
|
||||
}
|
||||
|
||||
function compareWorkspaceFolderByUriAndNameAndIndex(a: vscode.WorkspaceFolder, b: vscode.WorkspaceFolder): number {
|
||||
if (a.index !== b.index) {
|
||||
return a.index < b.index ? -1 : 1;
|
||||
}
|
||||
|
||||
return isFolderEqual(a.uri, b.uri) ? compare(a.name, b.name) : compare(a.uri.toString(), b.uri.toString());
|
||||
}
|
||||
|
||||
function delta(oldFolders: vscode.WorkspaceFolder[], newFolders: vscode.WorkspaceFolder[], compare: (a: vscode.WorkspaceFolder, b: vscode.WorkspaceFolder) => number): { removed: vscode.WorkspaceFolder[], added: vscode.WorkspaceFolder[] } {
|
||||
const oldSortedFolders = oldFolders.slice(0).sort(compare);
|
||||
const newSortedFolders = newFolders.slice(0).sort(compare);
|
||||
|
||||
return arrayDelta(oldSortedFolders, newSortedFolders, compare);
|
||||
}
|
||||
|
||||
interface MutableWorkspaceFolder extends vscode.WorkspaceFolder {
|
||||
name: string;
|
||||
index: number;
|
||||
}
|
||||
|
||||
class ExtHostWorkspaceImpl extends Workspace {
|
||||
|
||||
static toExtHostWorkspace(data: IWorkspaceData | null, previousConfirmedWorkspace?: ExtHostWorkspaceImpl, previousUnconfirmedWorkspace?: ExtHostWorkspaceImpl): { workspace: ExtHostWorkspaceImpl | null, added: vscode.WorkspaceFolder[], removed: vscode.WorkspaceFolder[] } {
|
||||
if (!data) {
|
||||
return { workspace: null, added: [], removed: [] };
|
||||
}
|
||||
|
||||
const { id, name, folders } = data;
|
||||
const newWorkspaceFolders: vscode.WorkspaceFolder[] = [];
|
||||
|
||||
// If we have an existing workspace, we try to find the folders that match our
|
||||
// data and update their properties. It could be that an extension stored them
|
||||
// for later use and we want to keep them "live" if they are still present.
|
||||
const oldWorkspace = previousConfirmedWorkspace;
|
||||
if (previousConfirmedWorkspace) {
|
||||
folders.forEach((folderData, index) => {
|
||||
const folderUri = URI.revive(folderData.uri);
|
||||
const existingFolder = ExtHostWorkspaceImpl._findFolder(previousUnconfirmedWorkspace || previousConfirmedWorkspace, folderUri);
|
||||
|
||||
if (existingFolder) {
|
||||
existingFolder.name = folderData.name;
|
||||
existingFolder.index = folderData.index;
|
||||
|
||||
newWorkspaceFolders.push(existingFolder);
|
||||
} else {
|
||||
newWorkspaceFolders.push({ uri: folderUri, name: folderData.name, index });
|
||||
}
|
||||
});
|
||||
} else {
|
||||
newWorkspaceFolders.push(...folders.map(({ uri, name, index }) => ({ uri: URI.revive(uri), name, index })));
|
||||
}
|
||||
|
||||
// make sure to restore sort order based on index
|
||||
newWorkspaceFolders.sort((f1, f2) => f1.index < f2.index ? -1 : 1);
|
||||
|
||||
const workspace = new ExtHostWorkspaceImpl(id, name, newWorkspaceFolders);
|
||||
const { added, removed } = delta(oldWorkspace ? oldWorkspace.workspaceFolders : [], workspace.workspaceFolders, compareWorkspaceFolderByUri);
|
||||
|
||||
return { workspace, added, removed };
|
||||
}
|
||||
|
||||
private static _findFolder(workspace: ExtHostWorkspaceImpl, folderUriToFind: URI): MutableWorkspaceFolder | undefined {
|
||||
for (let i = 0; i < workspace.folders.length; i++) {
|
||||
const folder = workspace.workspaceFolders[i];
|
||||
if (isFolderEqual(folder.uri, folderUriToFind)) {
|
||||
return folder;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private readonly _workspaceFolders: vscode.WorkspaceFolder[] = [];
|
||||
private readonly _structure = TernarySearchTree.forPaths<vscode.WorkspaceFolder>();
|
||||
|
||||
constructor(id: string, private _name: string, folders: vscode.WorkspaceFolder[]) {
|
||||
super(id, folders.map(f => new WorkspaceFolder(f)));
|
||||
|
||||
// setup the workspace folder data structure
|
||||
folders.forEach(folder => {
|
||||
this._workspaceFolders.push(folder);
|
||||
this._structure.set(folder.uri.toString(), folder);
|
||||
});
|
||||
}
|
||||
|
||||
get name(): string {
|
||||
return this._name;
|
||||
}
|
||||
|
||||
get workspaceFolders(): vscode.WorkspaceFolder[] {
|
||||
return this._workspaceFolders.slice(0);
|
||||
}
|
||||
|
||||
getWorkspaceFolder(uri: URI, resolveParent?: boolean): vscode.WorkspaceFolder | undefined {
|
||||
if (resolveParent && this._structure.get(uri.toString())) {
|
||||
// `uri` is a workspace folder so we check for its parent
|
||||
uri = dirname(uri);
|
||||
}
|
||||
return this._structure.findSubstr(uri.toString());
|
||||
}
|
||||
|
||||
resolveWorkspaceFolder(uri: URI): vscode.WorkspaceFolder | undefined {
|
||||
return this._structure.get(uri.toString());
|
||||
}
|
||||
}
|
||||
|
||||
export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspaceProvider {
|
||||
|
||||
private readonly _onDidChangeWorkspace = new Emitter<vscode.WorkspaceFoldersChangeEvent>();
|
||||
readonly onDidChangeWorkspace: Event<vscode.WorkspaceFoldersChangeEvent> = this._onDidChangeWorkspace.event;
|
||||
|
||||
private readonly _logService: ILogService;
|
||||
private readonly _requestIdProvider: Counter;
|
||||
private readonly _barrier: Barrier;
|
||||
|
||||
private _confirmedWorkspace?: ExtHostWorkspaceImpl;
|
||||
private _unconfirmedWorkspace?: ExtHostWorkspaceImpl;
|
||||
|
||||
private readonly _proxy: MainThreadWorkspaceShape;
|
||||
private readonly _messageService: MainThreadMessageServiceShape;
|
||||
|
||||
private readonly _activeSearchCallbacks: ((match: IRawFileMatch2) => any)[] = [];
|
||||
|
||||
constructor(
|
||||
mainContext: IMainContext,
|
||||
logService: ILogService,
|
||||
requestIdProvider: Counter,
|
||||
data?: IStaticWorkspaceData
|
||||
) {
|
||||
this._logService = logService;
|
||||
this._requestIdProvider = requestIdProvider;
|
||||
this._barrier = new Barrier();
|
||||
|
||||
this._proxy = mainContext.getProxy(MainContext.MainThreadWorkspace);
|
||||
this._messageService = mainContext.getProxy(MainContext.MainThreadMessageService);
|
||||
this._confirmedWorkspace = data ? new ExtHostWorkspaceImpl(data.id, data.name, []) : undefined;
|
||||
}
|
||||
|
||||
$initializeWorkspace(data: IWorkspaceData): void {
|
||||
this.$acceptWorkspaceData(data);
|
||||
this._barrier.open();
|
||||
}
|
||||
|
||||
waitForInitializeCall(): Promise<boolean> {
|
||||
return this._barrier.wait();
|
||||
}
|
||||
|
||||
// --- workspace ---
|
||||
|
||||
get workspace(): Workspace | undefined {
|
||||
return this._actualWorkspace;
|
||||
}
|
||||
|
||||
get name(): string | undefined {
|
||||
return this._actualWorkspace ? this._actualWorkspace.name : undefined;
|
||||
}
|
||||
|
||||
private get _actualWorkspace(): ExtHostWorkspaceImpl | undefined {
|
||||
return this._unconfirmedWorkspace || this._confirmedWorkspace;
|
||||
}
|
||||
|
||||
getWorkspaceFolders(): vscode.WorkspaceFolder[] | undefined {
|
||||
if (!this._actualWorkspace) {
|
||||
return undefined;
|
||||
}
|
||||
return this._actualWorkspace.workspaceFolders.slice(0);
|
||||
}
|
||||
|
||||
async getWorkspaceFolders2(): Promise<vscode.WorkspaceFolder[] | undefined> {
|
||||
await this._barrier.wait();
|
||||
if (!this._actualWorkspace) {
|
||||
return undefined;
|
||||
}
|
||||
return this._actualWorkspace.workspaceFolders.slice(0);
|
||||
}
|
||||
|
||||
updateWorkspaceFolders(extension: IExtensionDescription, index: number, deleteCount: number, ...workspaceFoldersToAdd: { uri: vscode.Uri, name?: string }[]): boolean {
|
||||
const validatedDistinctWorkspaceFoldersToAdd: { uri: vscode.Uri, name?: string }[] = [];
|
||||
if (Array.isArray(workspaceFoldersToAdd)) {
|
||||
workspaceFoldersToAdd.forEach(folderToAdd => {
|
||||
if (URI.isUri(folderToAdd.uri) && !validatedDistinctWorkspaceFoldersToAdd.some(f => isFolderEqual(f.uri, folderToAdd.uri))) {
|
||||
validatedDistinctWorkspaceFoldersToAdd.push({ uri: folderToAdd.uri, name: folderToAdd.name || basenameOrAuthority(folderToAdd.uri) });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!!this._unconfirmedWorkspace) {
|
||||
return false; // prevent accumulated calls without a confirmed workspace
|
||||
}
|
||||
|
||||
if ([index, deleteCount].some(i => typeof i !== 'number' || i < 0)) {
|
||||
return false; // validate numbers
|
||||
}
|
||||
|
||||
if (deleteCount === 0 && validatedDistinctWorkspaceFoldersToAdd.length === 0) {
|
||||
return false; // nothing to delete or add
|
||||
}
|
||||
|
||||
const currentWorkspaceFolders: MutableWorkspaceFolder[] = this._actualWorkspace ? this._actualWorkspace.workspaceFolders : [];
|
||||
if (index + deleteCount > currentWorkspaceFolders.length) {
|
||||
return false; // cannot delete more than we have
|
||||
}
|
||||
|
||||
// Simulate the updateWorkspaceFolders method on our data to do more validation
|
||||
const newWorkspaceFolders = currentWorkspaceFolders.slice(0);
|
||||
newWorkspaceFolders.splice(index, deleteCount, ...validatedDistinctWorkspaceFoldersToAdd.map(f => ({ uri: f.uri, name: f.name || basenameOrAuthority(f.uri), index: undefined! /* fixed later */ })));
|
||||
|
||||
for (let i = 0; i < newWorkspaceFolders.length; i++) {
|
||||
const folder = newWorkspaceFolders[i];
|
||||
if (newWorkspaceFolders.some((otherFolder, index) => index !== i && isFolderEqual(folder.uri, otherFolder.uri))) {
|
||||
return false; // cannot add the same folder multiple times
|
||||
}
|
||||
}
|
||||
|
||||
newWorkspaceFolders.forEach((f, index) => f.index = index); // fix index
|
||||
const { added, removed } = delta(currentWorkspaceFolders, newWorkspaceFolders, compareWorkspaceFolderByUriAndNameAndIndex);
|
||||
if (added.length === 0 && removed.length === 0) {
|
||||
return false; // nothing actually changed
|
||||
}
|
||||
|
||||
// Trigger on main side
|
||||
if (this._proxy) {
|
||||
const extName = extension.displayName || extension.name;
|
||||
this._proxy.$updateWorkspaceFolders(extName, index, deleteCount, validatedDistinctWorkspaceFoldersToAdd).then(undefined, error => {
|
||||
|
||||
// in case of an error, make sure to clear out the unconfirmed workspace
|
||||
// because we cannot expect the acknowledgement from the main side for this
|
||||
this._unconfirmedWorkspace = undefined;
|
||||
|
||||
// show error to user
|
||||
this._messageService.$showMessage(Severity.Error, localize('updateerror', "Extension '{0}' failed to update workspace folders: {1}", extName, error), { extension }, []);
|
||||
});
|
||||
}
|
||||
|
||||
// Try to accept directly
|
||||
this.trySetWorkspaceFolders(newWorkspaceFolders);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
getWorkspaceFolder(uri: vscode.Uri, resolveParent?: boolean): vscode.WorkspaceFolder | undefined {
|
||||
if (!this._actualWorkspace) {
|
||||
return undefined;
|
||||
}
|
||||
return this._actualWorkspace.getWorkspaceFolder(uri, resolveParent);
|
||||
}
|
||||
|
||||
async getWorkspaceFolder2(uri: vscode.Uri, resolveParent?: boolean): Promise<vscode.WorkspaceFolder | undefined> {
|
||||
await this._barrier.wait();
|
||||
if (!this._actualWorkspace) {
|
||||
return undefined;
|
||||
}
|
||||
return this._actualWorkspace.getWorkspaceFolder(uri, resolveParent);
|
||||
}
|
||||
|
||||
async resolveWorkspaceFolder(uri: vscode.Uri): Promise<vscode.WorkspaceFolder | undefined> {
|
||||
await this._barrier.wait();
|
||||
if (!this._actualWorkspace) {
|
||||
return undefined;
|
||||
}
|
||||
return this._actualWorkspace.resolveWorkspaceFolder(uri);
|
||||
}
|
||||
|
||||
getPath(): string | undefined {
|
||||
|
||||
// this is legacy from the days before having
|
||||
// multi-root and we keep it only alive if there
|
||||
// is just one workspace folder.
|
||||
if (!this._actualWorkspace) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const { folders } = this._actualWorkspace;
|
||||
if (folders.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
// #54483 @Joh Why are we still using fsPath?
|
||||
return folders[0].uri.fsPath;
|
||||
}
|
||||
|
||||
getRelativePath(pathOrUri: string | vscode.Uri, includeWorkspace?: boolean): string {
|
||||
|
||||
let resource: URI | undefined;
|
||||
let path: string = '';
|
||||
if (typeof pathOrUri === 'string') {
|
||||
resource = URI.file(pathOrUri);
|
||||
path = pathOrUri;
|
||||
} else if (typeof pathOrUri !== 'undefined') {
|
||||
resource = pathOrUri;
|
||||
path = pathOrUri.fsPath;
|
||||
}
|
||||
|
||||
if (!resource) {
|
||||
return path;
|
||||
}
|
||||
|
||||
const folder = this.getWorkspaceFolder(
|
||||
resource,
|
||||
true
|
||||
);
|
||||
|
||||
if (!folder) {
|
||||
return path;
|
||||
}
|
||||
|
||||
if (typeof includeWorkspace === 'undefined' && this._actualWorkspace) {
|
||||
includeWorkspace = this._actualWorkspace.folders.length > 1;
|
||||
}
|
||||
|
||||
let result = relativePath(folder.uri, resource);
|
||||
if (includeWorkspace && folder.name) {
|
||||
result = `${folder.name}/${result}`;
|
||||
}
|
||||
return result!;
|
||||
}
|
||||
|
||||
private trySetWorkspaceFolders(folders: vscode.WorkspaceFolder[]): void {
|
||||
|
||||
// Update directly here. The workspace is unconfirmed as long as we did not get an
|
||||
// acknowledgement from the main side (via $acceptWorkspaceData)
|
||||
if (this._actualWorkspace) {
|
||||
this._unconfirmedWorkspace = ExtHostWorkspaceImpl.toExtHostWorkspace({
|
||||
id: this._actualWorkspace.id,
|
||||
name: this._actualWorkspace.name,
|
||||
configuration: this._actualWorkspace.configuration,
|
||||
folders
|
||||
} as IWorkspaceData, this._actualWorkspace).workspace || undefined;
|
||||
}
|
||||
}
|
||||
|
||||
$acceptWorkspaceData(data: IWorkspaceData): void {
|
||||
|
||||
const { workspace, added, removed } = ExtHostWorkspaceImpl.toExtHostWorkspace(data, this._confirmedWorkspace, this._unconfirmedWorkspace);
|
||||
|
||||
// Update our workspace object. We have a confirmed workspace, so we drop our
|
||||
// unconfirmed workspace.
|
||||
this._confirmedWorkspace = workspace || undefined;
|
||||
this._unconfirmedWorkspace = undefined;
|
||||
|
||||
// Events
|
||||
this._onDidChangeWorkspace.fire(Object.freeze({
|
||||
added,
|
||||
removed
|
||||
}));
|
||||
}
|
||||
|
||||
// --- search ---
|
||||
|
||||
findFiles(include: string | RelativePattern | undefined, exclude: vscode.GlobPattern | undefined, maxResults: number | undefined, extensionId: ExtensionIdentifier, token: vscode.CancellationToken = CancellationToken.None): Promise<vscode.Uri[]> {
|
||||
this._logService.trace(`extHostWorkspace#findFiles: fileSearch, extension: ${extensionId.value}, entryPoint: findFiles`);
|
||||
|
||||
let includePattern: string | undefined;
|
||||
let includeFolder: URI | undefined;
|
||||
if (include) {
|
||||
if (typeof include === 'string') {
|
||||
includePattern = include;
|
||||
} else {
|
||||
includePattern = include.pattern;
|
||||
|
||||
// include.base must be an absolute path
|
||||
includeFolder = include.baseFolder || URI.file(include.base);
|
||||
}
|
||||
}
|
||||
|
||||
let excludePatternOrDisregardExcludes: string | false | undefined = undefined;
|
||||
if (exclude === null) {
|
||||
excludePatternOrDisregardExcludes = false;
|
||||
} else if (exclude) {
|
||||
if (typeof exclude === 'string') {
|
||||
excludePatternOrDisregardExcludes = exclude;
|
||||
} else {
|
||||
excludePatternOrDisregardExcludes = exclude.pattern;
|
||||
}
|
||||
}
|
||||
|
||||
if (token && token.isCancellationRequested) {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
|
||||
return this._proxy.$startFileSearch(includePattern, includeFolder, excludePatternOrDisregardExcludes, maxResults, token)
|
||||
.then(data => Array.isArray(data) ? data.map(URI.revive) : []);
|
||||
}
|
||||
|
||||
findTextInFiles(query: vscode.TextSearchQuery, options: vscode.FindTextInFilesOptions, callback: (result: vscode.TextSearchResult) => void, extensionId: ExtensionIdentifier, token: vscode.CancellationToken = CancellationToken.None): Promise<vscode.TextSearchComplete | undefined> {
|
||||
this._logService.trace(`extHostWorkspace#findTextInFiles: textSearch, extension: ${extensionId.value}, entryPoint: findTextInFiles`);
|
||||
|
||||
const requestId = this._requestIdProvider.getNext();
|
||||
|
||||
const globPatternToString = (pattern: vscode.GlobPattern | string) => {
|
||||
if (typeof pattern === 'string') {
|
||||
return pattern;
|
||||
}
|
||||
|
||||
return join(pattern.base, pattern.pattern);
|
||||
};
|
||||
|
||||
const previewOptions: vscode.TextSearchPreviewOptions = typeof options.previewOptions === 'undefined' ?
|
||||
{
|
||||
matchLines: 100,
|
||||
charsPerLine: 10000
|
||||
} :
|
||||
options.previewOptions;
|
||||
|
||||
const queryOptions: ITextQueryBuilderOptions = {
|
||||
ignoreSymlinks: typeof options.followSymlinks === 'boolean' ? !options.followSymlinks : undefined,
|
||||
disregardIgnoreFiles: typeof options.useIgnoreFiles === 'boolean' ? !options.useIgnoreFiles : undefined,
|
||||
disregardGlobalIgnoreFiles: typeof options.useGlobalIgnoreFiles === 'boolean' ? !options.useGlobalIgnoreFiles : undefined,
|
||||
disregardExcludeSettings: options.exclude === null,
|
||||
fileEncoding: options.encoding,
|
||||
maxResults: options.maxResults,
|
||||
previewOptions,
|
||||
afterContext: options.afterContext,
|
||||
beforeContext: options.beforeContext,
|
||||
|
||||
includePattern: options.include && globPatternToString(options.include),
|
||||
excludePattern: options.exclude ? globPatternToString(options.exclude) : undefined
|
||||
};
|
||||
|
||||
const isCanceled = false;
|
||||
|
||||
this._activeSearchCallbacks[requestId] = p => {
|
||||
if (isCanceled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const uri = URI.revive(p.resource);
|
||||
p.results!.forEach(result => {
|
||||
if (resultIsMatch(result)) {
|
||||
callback(<vscode.TextSearchMatch>{
|
||||
uri,
|
||||
preview: {
|
||||
text: result.preview.text,
|
||||
matches: mapArrayOrNot(
|
||||
result.preview.matches,
|
||||
m => new Range(m.startLineNumber, m.startColumn, m.endLineNumber, m.endColumn))
|
||||
},
|
||||
ranges: mapArrayOrNot(
|
||||
result.ranges,
|
||||
r => new Range(r.startLineNumber, r.startColumn, r.endLineNumber, r.endColumn))
|
||||
});
|
||||
} else {
|
||||
callback(<vscode.TextSearchContext>{
|
||||
uri,
|
||||
text: result.text,
|
||||
lineNumber: result.lineNumber
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
if (token.isCancellationRequested) {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
return this._proxy.$startTextSearch(query, queryOptions, requestId, token).then(result => {
|
||||
delete this._activeSearchCallbacks[requestId];
|
||||
return result;
|
||||
}, err => {
|
||||
delete this._activeSearchCallbacks[requestId];
|
||||
return Promise.reject(err);
|
||||
});
|
||||
}
|
||||
|
||||
$handleTextSearchResult(result: IRawFileMatch2, requestId: number): void {
|
||||
if (this._activeSearchCallbacks[requestId]) {
|
||||
this._activeSearchCallbacks[requestId](result);
|
||||
}
|
||||
}
|
||||
|
||||
saveAll(includeUntitled?: boolean): Promise<boolean> {
|
||||
return this._proxy.$saveAll(includeUntitled);
|
||||
}
|
||||
|
||||
resolveProxy(url: string): Promise<string | undefined> {
|
||||
return this._proxy.$resolveProxy(url);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user