mirror of
https://github.com/coder/code-server.git
synced 2026-05-06 12:31:58 +02:00
chore(vscode): update to 1.53.2
These conflicts will be resolved in the following commits. We do it this way so that PR review is possible.
This commit is contained in:
@@ -17,6 +17,7 @@ import { LanguageConfigurationFileHandler } from 'vs/workbench/contrib/codeEdito
|
||||
// --- mainThread participants
|
||||
import './mainThreadBulkEdits';
|
||||
import './mainThreadCodeInsets';
|
||||
import './mainThreadCLICommands';
|
||||
import './mainThreadClipboard';
|
||||
import './mainThreadCommands';
|
||||
import './mainThreadConfiguration';
|
||||
@@ -30,6 +31,7 @@ import './mainThreadDocuments';
|
||||
import './mainThreadDocumentsAndEditors';
|
||||
import './mainThreadEditor';
|
||||
import './mainThreadEditors';
|
||||
import './mainThreadEditorTabs';
|
||||
import './mainThreadErrors';
|
||||
import './mainThreadExtensionService';
|
||||
import './mainThreadFileSystem';
|
||||
@@ -54,6 +56,7 @@ import './mainThreadTheming';
|
||||
import './mainThreadTreeViews';
|
||||
import './mainThreadDownloadService';
|
||||
import './mainThreadUrls';
|
||||
import './mainThreadUriOpeners';
|
||||
import './mainThreadWindow';
|
||||
import './mainThreadWebviewManager';
|
||||
import './mainThreadWorkspace';
|
||||
@@ -65,6 +68,7 @@ import './mainThreadTunnelService';
|
||||
import './mainThreadAuthentication';
|
||||
import './mainThreadTimeline';
|
||||
import './mainThreadTesting';
|
||||
import './mainThreadSecretState';
|
||||
import 'vs/workbench/api/common/apiCommands';
|
||||
|
||||
export class ExtensionPoints implements IWorkbenchContribution {
|
||||
|
||||
@@ -18,9 +18,6 @@ import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteA
|
||||
import { fromNow } from 'vs/base/common/date';
|
||||
import { ActivationKind, IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { isWeb } from 'vs/base/common/platform';
|
||||
import { IEncryptionService } from 'vs/workbench/services/encryption/common/encryptionService';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { ICredentialsService } from 'vs/workbench/services/credentials/common/credentials';
|
||||
|
||||
const VSO_ALLOWED_EXTENSIONS = ['github.vscode-pull-request-github', 'github.vscode-pull-request-github-insiders', 'vscode.git', 'ms-vsonline.vsonline', 'vscode.github-browser', 'ms-vscode.github-browser', 'github.codespaces'];
|
||||
|
||||
@@ -225,10 +222,7 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu
|
||||
@INotificationService private readonly notificationService: INotificationService,
|
||||
@IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService,
|
||||
@IQuickInputService private readonly quickInputService: IQuickInputService,
|
||||
@IExtensionService private readonly extensionService: IExtensionService,
|
||||
@ICredentialsService private readonly credentialsService: ICredentialsService,
|
||||
@IEncryptionService private readonly encryptionService: IEncryptionService,
|
||||
@IProductService private readonly productService: IProductService
|
||||
@IExtensionService private readonly extensionService: IExtensionService
|
||||
) {
|
||||
super();
|
||||
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostAuthentication);
|
||||
@@ -250,10 +244,6 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu
|
||||
this._register(this.authenticationService.onDidChangeDeclaredProviders(e => {
|
||||
this._proxy.$setProviders(e);
|
||||
}));
|
||||
|
||||
this._register(this.credentialsService.onDidChangePassword(_ => {
|
||||
this._proxy.$onDidChangePassword();
|
||||
}));
|
||||
}
|
||||
|
||||
$getProviderIds(): Promise<string[]> {
|
||||
@@ -416,7 +406,7 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu
|
||||
|
||||
const remoteConnection = this.remoteAgentService.getConnection();
|
||||
const isVSO = remoteConnection !== null
|
||||
? remoteConnection.remoteAuthority.startsWith('vsonline')
|
||||
? remoteConnection.remoteAuthority.startsWith('vsonline') || remoteConnection.remoteAuthority.startsWith('codespaces')
|
||||
: isWeb;
|
||||
|
||||
if (isVSO && VSO_ALLOWED_EXTENSIONS.includes(extensionId)) {
|
||||
@@ -466,46 +456,4 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu
|
||||
this.storageService.store(`${extensionName}-${providerId}`, sessionId, StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
addAccountUsage(this.storageService, providerId, accountName, extensionId, extensionName);
|
||||
}
|
||||
|
||||
private getFullKey(extensionId: string): string {
|
||||
return `${this.productService.urlProtocol}${extensionId}`;
|
||||
}
|
||||
|
||||
async $getPassword(extensionId: string, key: string): Promise<string | undefined> {
|
||||
const fullKey = this.getFullKey(extensionId);
|
||||
const password = await this.credentialsService.getPassword(fullKey, key);
|
||||
const decrypted = password && await this.encryptionService.decrypt(password);
|
||||
|
||||
if (decrypted) {
|
||||
try {
|
||||
const value = JSON.parse(decrypted);
|
||||
if (value.extensionId === extensionId) {
|
||||
return value.content;
|
||||
}
|
||||
} catch (_) {
|
||||
throw new Error('Cannot get password');
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async $setPassword(extensionId: string, key: string, value: string): Promise<void> {
|
||||
const fullKey = this.getFullKey(extensionId);
|
||||
const toEncrypt = JSON.stringify({
|
||||
extensionId,
|
||||
content: value
|
||||
});
|
||||
const encrypted = await this.encryptionService.encrypt(toEncrypt);
|
||||
return this.credentialsService.setPassword(fullKey, key, encrypted);
|
||||
}
|
||||
|
||||
async $deletePassword(extensionId: string, key: string): Promise<void> {
|
||||
try {
|
||||
const fullKey = this.getFullKey(extensionId);
|
||||
await this.credentialsService.deletePassword(fullKey, key);
|
||||
} catch (_) {
|
||||
throw new Error('Cannot delete password');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,29 +3,9 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IBulkEditService, ResourceEdit, ResourceFileEdit, ResourceTextEdit } from 'vs/editor/browser/services/bulkEditService';
|
||||
import { IExtHostContext, IWorkspaceEditDto, WorkspaceEditType, MainThreadBulkEditsShape, MainContext } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { revive } from 'vs/base/common/marshalling';
|
||||
import { ResourceNotebookCellEdit } from 'vs/workbench/contrib/bulkEdit/browser/bulkCellEdits';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
|
||||
|
||||
function reviveWorkspaceEditDto2(data: IWorkspaceEditDto | undefined): ResourceEdit[] {
|
||||
if (!data?.edits) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const result: ResourceEdit[] = [];
|
||||
for (let edit of revive<IWorkspaceEditDto>(data).edits) {
|
||||
if (edit._type === WorkspaceEditType.File) {
|
||||
result.push(new ResourceFileEdit(edit.oldUri, edit.newUri, edit.options, edit.metadata));
|
||||
} else if (edit._type === WorkspaceEditType.Text) {
|
||||
result.push(new ResourceTextEdit(edit.resource, edit.edit, edit.modelVersionId, edit.metadata));
|
||||
} else if (edit._type === WorkspaceEditType.Cell) {
|
||||
result.push(new ResourceNotebookCellEdit(edit.resource, edit.edit, edit.notebookVersionId, edit.metadata));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService';
|
||||
import { IExtHostContext, IWorkspaceEditDto, MainThreadBulkEditsShape, MainContext } from 'vs/workbench/api/common/extHost.protocol'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
|
||||
import { reviveWorkspaceEditDto2 } from 'vs/workbench/api/browser/mainThreadEditors';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadBulkEdits)
|
||||
export class MainThreadBulkEdits implements MainThreadBulkEditsShape {
|
||||
@@ -39,12 +19,6 @@ export class MainThreadBulkEdits implements MainThreadBulkEditsShape {
|
||||
|
||||
$tryApplyWorkspaceEdit(dto: IWorkspaceEditDto, undoRedoGroupId?: number): Promise<boolean> {
|
||||
const edits = reviveWorkspaceEditDto2(dto);
|
||||
return this._bulkEditService.apply(edits, {
|
||||
// having a undoRedoGroupId means that this is a nested workspace edit,
|
||||
// e.g one from a onWill-handler and for now we need to forcefully suppress
|
||||
// refactor previewing, see: https://github.com/microsoft/vscode/issues/111873#issuecomment-738739852
|
||||
undoRedoGroupId,
|
||||
suppressPreview: typeof undoRedoGroupId === 'number' ? true : undefined
|
||||
}).then(() => true, _err => false);
|
||||
return this._bulkEditService.apply(edits, { undoRedoGroupId }).then(() => true, _err => false);
|
||||
}
|
||||
}
|
||||
|
||||
108
lib/vscode/src/vs/workbench/api/browser/mainThreadCLICommands.ts
Normal file
108
lib/vscode/src/vs/workbench/api/browser/mainThreadCLICommands.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { isString } from 'vs/base/common/types';
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import { localize } from 'vs/nls';
|
||||
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { CLIOutput, IExtensionGalleryService, IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { ExtensionManagementCLIService } from 'vs/platform/extensionManagement/common/extensionManagementCLIService';
|
||||
import { getExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
|
||||
import { ILabelService } from 'vs/platform/label/common/label';
|
||||
import { ILocalizationsService } from 'vs/platform/localizations/common/localizations';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
import { IExtensionManagementServerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
|
||||
import { canExecuteOnWorkspace } from 'vs/workbench/services/extensions/common/extensionsUtil';
|
||||
import { IExtensionManifest } from 'vs/workbench/workbench.web.api';
|
||||
|
||||
|
||||
// this class contains the commands that the CLI server is reying on
|
||||
|
||||
CommandsRegistry.registerCommand('_remoteCLI.openExternal', function (accessor: ServicesAccessor, uri: UriComponents, options: { allowTunneling?: boolean }) {
|
||||
// TODO: discuss martin, ben where to put this
|
||||
const openerService = accessor.get(IOpenerService);
|
||||
openerService.open(URI.revive(uri), { openExternal: true, allowTunneling: options?.allowTunneling === true });
|
||||
});
|
||||
|
||||
interface ManageExtensionsArgs {
|
||||
list?: { showVersions?: boolean, category?: string; };
|
||||
install?: (string | URI)[];
|
||||
uninstall?: string[];
|
||||
force?: boolean;
|
||||
}
|
||||
|
||||
CommandsRegistry.registerCommand('_remoteCLI.manageExtensions', async function (accessor: ServicesAccessor, args: ManageExtensionsArgs) {
|
||||
|
||||
const instantiationService = accessor.get(IInstantiationService);
|
||||
const extensionManagementServerService = accessor.get(IExtensionManagementServerService);
|
||||
const remoteExtensionManagementService = extensionManagementServerService.remoteExtensionManagementServer?.extensionManagementService;
|
||||
if (!remoteExtensionManagementService) {
|
||||
return;
|
||||
}
|
||||
|
||||
const cliService = instantiationService.createChild(new ServiceCollection([IExtensionManagementService, remoteExtensionManagementService])).createInstance(RemoteExtensionCLIManagementService);
|
||||
|
||||
const lines: string[] = [];
|
||||
const output = { log: lines.push.bind(lines), error: lines.push.bind(lines) };
|
||||
|
||||
if (args.list) {
|
||||
await cliService.listExtensions(!!args.list.showVersions, args.list.category, output);
|
||||
} else {
|
||||
const revive = (inputs: (string | UriComponents)[]) => inputs.map(input => isString(input) ? input : URI.revive(input));
|
||||
if (Array.isArray(args.install) && args.install.length) {
|
||||
try {
|
||||
await cliService.installExtensions(revive(args.install), [], true, !!args.force, output);
|
||||
} catch (e) {
|
||||
lines.push(e.message);
|
||||
}
|
||||
}
|
||||
if (Array.isArray(args.uninstall) && args.uninstall.length) {
|
||||
try {
|
||||
await cliService.uninstallExtensions(revive(args.uninstall), !!args.force, output);
|
||||
} catch (e) {
|
||||
lines.push(e.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
return lines.join('\n');
|
||||
});
|
||||
|
||||
class RemoteExtensionCLIManagementService extends ExtensionManagementCLIService {
|
||||
|
||||
private _location: string | undefined;
|
||||
|
||||
constructor(
|
||||
@IExtensionManagementService extensionManagementService: IExtensionManagementService,
|
||||
@IProductService private readonly productService: IProductService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@IExtensionGalleryService extensionGalleryService: IExtensionGalleryService,
|
||||
@ILocalizationsService localizationsService: ILocalizationsService,
|
||||
@ILabelService labelService: ILabelService,
|
||||
@IWorkbenchEnvironmentService envService: IWorkbenchEnvironmentService
|
||||
) {
|
||||
super(extensionManagementService, extensionGalleryService, localizationsService);
|
||||
|
||||
const remoteAuthority = envService.remoteAuthority;
|
||||
this._location = remoteAuthority ? labelService.getHostLabel(Schemas.vscodeRemote, remoteAuthority) : undefined;
|
||||
}
|
||||
|
||||
protected get location(): string | undefined {
|
||||
return this._location;
|
||||
}
|
||||
|
||||
protected validateExtensionKind(manifest: IExtensionManifest, output: CLIOutput): boolean {
|
||||
if (!canExecuteOnWorkspace(manifest, this.productService, this.configurationService)) {
|
||||
output.log(localize('cannot be installed', "Cannot install '{0}' because this extension has defined that it cannot run on the remote server.", getExtensionId(manifest.publisher, manifest.name)));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -10,22 +10,18 @@ import { IRemoteConsoleLog, log } from 'vs/base/common/console';
|
||||
import { logRemoteEntry } from 'vs/workbench/services/extensions/common/remoteConsoleUtil';
|
||||
import { parseExtensionDevOptions } from 'vs/workbench/services/extensions/common/extensionDevOptions';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IExtensionHostDebugService } from 'vs/platform/debug/common/extensionHostDebug';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadConsole)
|
||||
export class MainThreadConsole implements MainThreadConsoleShape {
|
||||
|
||||
private readonly _isExtensionDevHost: boolean;
|
||||
private readonly _isExtensionDevTestFromCli: boolean;
|
||||
|
||||
constructor(
|
||||
extHostContext: IExtHostContext,
|
||||
_extHostContext: IExtHostContext,
|
||||
@IEnvironmentService private readonly _environmentService: IEnvironmentService,
|
||||
@ILogService private readonly _logService: ILogService,
|
||||
@IExtensionHostDebugService private readonly _extensionHostDebugService: IExtensionHostDebugService,
|
||||
) {
|
||||
const devOpts = parseExtensionDevOptions(this._environmentService);
|
||||
this._isExtensionDevHost = devOpts.isExtensionDevHost;
|
||||
this._isExtensionDevTestFromCli = devOpts.isExtensionDevTestFromCli;
|
||||
}
|
||||
|
||||
@@ -43,10 +39,5 @@ export class MainThreadConsole implements MainThreadConsoleShape {
|
||||
if (this._isExtensionDevTestFromCli) {
|
||||
logRemoteEntry(this._logService, entry);
|
||||
}
|
||||
|
||||
// Broadcast to other windows if we are in development mode
|
||||
else if (this._environmentService.debugExtensionHost.debugId && (!this._environmentService.isBuilt || this._isExtensionDevHost)) {
|
||||
this._extensionHostDebugService.logToSession(this._environmentService.debugExtensionHost.debugId, entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,15 +3,15 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { multibyteAwareBtoa } from 'vs/base/browser/dom';
|
||||
import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { isPromiseCanceledError, onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { Disposable, DisposableStore, IDisposable, IReference } from 'vs/base/common/lifecycle';
|
||||
import { Disposable, DisposableStore, dispose, IDisposable, IReference } from 'vs/base/common/lifecycle';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { basename } from 'vs/base/common/path';
|
||||
import { isEqual, isEqualOrParent, toLocalResource } from 'vs/base/common/resources';
|
||||
import { multibyteAwareBtoa } from 'vs/base/browser/dom';
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import * as modes from 'vs/editor/common/modes';
|
||||
import { localize } from 'vs/nls';
|
||||
@@ -95,10 +95,7 @@ export class MainThreadCustomEditors extends Disposable implements extHostProtoc
|
||||
dispose() {
|
||||
super.dispose();
|
||||
|
||||
for (const disposable of this._editorProviders.values()) {
|
||||
disposable.dispose();
|
||||
}
|
||||
|
||||
dispose(this._editorProviders.values());
|
||||
this._editorProviders.clear();
|
||||
}
|
||||
|
||||
|
||||
@@ -327,7 +327,7 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb
|
||||
return {
|
||||
id: sessionID,
|
||||
type: session.configuration.type,
|
||||
name: session.configuration.name,
|
||||
name: session.name,
|
||||
folderUri: session.root ? session.root.uri : undefined,
|
||||
configuration: session.configuration
|
||||
};
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { ExtHostContext, IExtHostEditorTabsShape, IExtHostContext, MainContext, IEditorTabDto } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
|
||||
import { Verbosity } from 'vs/workbench/common/editor';
|
||||
import { GroupChangeKind, IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
|
||||
export interface ITabInfo {
|
||||
name: string;
|
||||
resource: URI;
|
||||
}
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadEditorTabs)
|
||||
export class MainThreadEditorTabs {
|
||||
|
||||
private static _GroupEventFilter = new Set([GroupChangeKind.EDITOR_CLOSE, GroupChangeKind.EDITOR_OPEN]);
|
||||
|
||||
private readonly _dispoables = new DisposableStore();
|
||||
private readonly _groups = new Map<IEditorGroup, IDisposable>();
|
||||
private readonly _proxy: IExtHostEditorTabsShape;
|
||||
|
||||
constructor(
|
||||
extHostContext: IExtHostContext,
|
||||
@IEditorGroupsService private readonly _editorGroupsService: IEditorGroupsService,
|
||||
) {
|
||||
|
||||
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostEditorTabs);
|
||||
|
||||
this._editorGroupsService.groups.forEach(this._subscribeToGroup, this);
|
||||
this._dispoables.add(_editorGroupsService.onDidAddGroup(this._subscribeToGroup, this));
|
||||
this._dispoables.add(_editorGroupsService.onDidRemoveGroup(e => {
|
||||
const subscription = this._groups.get(e);
|
||||
if (subscription) {
|
||||
subscription.dispose();
|
||||
this._groups.delete(e);
|
||||
this._pushEditorTabs();
|
||||
}
|
||||
}));
|
||||
this._pushEditorTabs();
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
dispose(this._groups.values());
|
||||
this._dispoables.dispose();
|
||||
}
|
||||
|
||||
private _subscribeToGroup(group: IEditorGroup) {
|
||||
this._groups.get(group)?.dispose();
|
||||
const listener = group.onDidGroupChange(e => {
|
||||
if (MainThreadEditorTabs._GroupEventFilter.has(e.kind)) {
|
||||
this._pushEditorTabs();
|
||||
}
|
||||
});
|
||||
this._groups.set(group, listener);
|
||||
}
|
||||
|
||||
private _pushEditorTabs(): void {
|
||||
const tabs: IEditorTabDto[] = [];
|
||||
for (const group of this._editorGroupsService.groups) {
|
||||
for (const editor of group.editors) {
|
||||
if (editor.isDisposed() || !editor.resource) {
|
||||
continue;
|
||||
}
|
||||
tabs.push({
|
||||
group: group.id,
|
||||
name: editor.getTitle(Verbosity.SHORT) ?? '',
|
||||
resource: editor.resource
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this._proxy.$acceptEditorTabs(tabs);
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,7 @@ import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/wo
|
||||
import { revive } from 'vs/base/common/marshalling';
|
||||
import { ResourceNotebookCellEdit } from 'vs/workbench/contrib/bulkEdit/browser/bulkCellEdits';
|
||||
|
||||
function reviveWorkspaceEditDto2(data: IWorkspaceEditDto | undefined): ResourceEdit[] {
|
||||
export function reviveWorkspaceEditDto2(data: IWorkspaceEditDto | undefined): ResourceEdit[] {
|
||||
if (!data?.edits) {
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { SerializedError } from 'vs/base/common/errors';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
|
||||
import { IExtHostContext, MainContext, MainThreadExtensionServiceShape } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { IExtensionService, ExtensionActivationError } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { IExtensionService, ExtensionActivationError, ExtensionHostKind } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { localize } from 'vs/nls';
|
||||
@@ -19,29 +19,23 @@ import { IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/com
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { ExtensionActivationReason } from 'vs/workbench/api/common/extHostExtensionActivator';
|
||||
import { ITimerService } from 'vs/workbench/services/timer/browser/timerService';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadExtensionService)
|
||||
export class MainThreadExtensionService implements MainThreadExtensionServiceShape {
|
||||
|
||||
private readonly _extensionService: IExtensionService;
|
||||
private readonly _notificationService: INotificationService;
|
||||
private readonly _extensionsWorkbenchService: IExtensionsWorkbenchService;
|
||||
private readonly _hostService: IHostService;
|
||||
private readonly _extensionEnablementService: IWorkbenchExtensionEnablementService;
|
||||
private readonly _extensionHostKind: ExtensionHostKind;
|
||||
|
||||
constructor(
|
||||
extHostContext: IExtHostContext,
|
||||
@IExtensionService extensionService: IExtensionService,
|
||||
@INotificationService notificationService: INotificationService,
|
||||
@IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService,
|
||||
@IHostService hostService: IHostService,
|
||||
@IWorkbenchExtensionEnablementService extensionEnablementService: IWorkbenchExtensionEnablementService
|
||||
@IExtensionService private readonly _extensionService: IExtensionService,
|
||||
@INotificationService private readonly _notificationService: INotificationService,
|
||||
@IExtensionsWorkbenchService private readonly _extensionsWorkbenchService: IExtensionsWorkbenchService,
|
||||
@IHostService private readonly _hostService: IHostService,
|
||||
@IWorkbenchExtensionEnablementService private readonly _extensionEnablementService: IWorkbenchExtensionEnablementService,
|
||||
@ITimerService private readonly _timerService: ITimerService,
|
||||
) {
|
||||
this._extensionService = extensionService;
|
||||
this._notificationService = notificationService;
|
||||
this._extensionsWorkbenchService = extensionsWorkbenchService;
|
||||
this._hostService = hostService;
|
||||
this._extensionEnablementService = extensionEnablementService;
|
||||
this._extensionHostKind = extHostContext.extensionHostKind;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
@@ -131,4 +125,14 @@ export class MainThreadExtensionService implements MainThreadExtensionServiceSha
|
||||
async $onExtensionHostExit(code: number): Promise<void> {
|
||||
this._extensionService._onExtensionHostExit(code);
|
||||
}
|
||||
|
||||
async $setPerformanceMarks(marks: PerformanceMark[]): Promise<void> {
|
||||
if (this._extensionHostKind === ExtensionHostKind.LocalProcess) {
|
||||
this._timerService.setPerformanceMarks('localExtHost', marks);
|
||||
} else if (this._extensionHostKind === ExtensionHostKind.LocalWebWorker) {
|
||||
this._timerService.setPerformanceMarks('workerExtHost', marks);
|
||||
} else {
|
||||
this._timerService.setPerformanceMarks('remoteExtHost', marks);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,23 +4,43 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { FileChangeType, IFileService } from 'vs/platform/files/common/files';
|
||||
import { FileChangeType, FileOperation, IFileService } from 'vs/platform/files/common/files';
|
||||
import { extHostCustomer } from 'vs/workbench/api/common/extHostCustomers';
|
||||
import { ExtHostContext, FileSystemEvents, IExtHostContext } from '../common/extHost.protocol';
|
||||
import { localize } from 'vs/nls';
|
||||
import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService';
|
||||
import { IWorkingCopyFileOperationParticipant, IWorkingCopyFileService, SourceTargetPair, IFileOperationUndoRedoInfo } from 'vs/workbench/services/workingCopy/common/workingCopyFileService';
|
||||
import { reviveWorkspaceEditDto2 } from 'vs/workbench/api/browser/mainThreadEditors';
|
||||
import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService';
|
||||
import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress';
|
||||
import { raceCancellation } from 'vs/base/common/async';
|
||||
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||
import { Action2, registerAction2 } from 'vs/platform/actions/common/actions';
|
||||
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
|
||||
@extHostCustomer
|
||||
export class MainThreadFileSystemEventService {
|
||||
|
||||
static readonly MementoKeyAdditionalEdits = `file.particpants.additionalEdits`;
|
||||
|
||||
private readonly _listener = new DisposableStore();
|
||||
|
||||
constructor(
|
||||
extHostContext: IExtHostContext,
|
||||
@IFileService fileService: IFileService,
|
||||
@IWorkingCopyFileService workingCopyFileService: IWorkingCopyFileService
|
||||
@IWorkingCopyFileService workingCopyFileService: IWorkingCopyFileService,
|
||||
@IBulkEditService bulkEditService: IBulkEditService,
|
||||
@IProgressService progressService: IProgressService,
|
||||
@IDialogService dialogService: IDialogService,
|
||||
@IStorageService storageService: IStorageService,
|
||||
@ILogService logService: ILogService,
|
||||
@IEnvironmentService envService: IEnvironmentService
|
||||
) {
|
||||
|
||||
const proxy = extHostContext.getProxy(ExtHostContext.ExtHostFileSystemEventService);
|
||||
@@ -53,14 +73,124 @@ export class MainThreadFileSystemEventService {
|
||||
}));
|
||||
|
||||
|
||||
// BEFORE file operation
|
||||
this._listener.add(workingCopyFileService.addFileOperationParticipant({
|
||||
participate: async (files, operation, undoRedoGroupId, isUndoing, _progress, timeout, token) => {
|
||||
if (!isUndoing) {
|
||||
return proxy.$onWillRunFileOperation(operation, files, undoRedoGroupId, timeout, token);
|
||||
const fileOperationParticipant = new class implements IWorkingCopyFileOperationParticipant {
|
||||
async participate(files: SourceTargetPair[], operation: FileOperation, undoInfo: IFileOperationUndoRedoInfo | undefined, timeout: number, token: CancellationToken) {
|
||||
if (undoInfo?.isUndoing) {
|
||||
return;
|
||||
}
|
||||
|
||||
const cts = new CancellationTokenSource(token);
|
||||
const timer = setTimeout(() => cts.cancel(), timeout);
|
||||
|
||||
const data = await progressService.withProgress({
|
||||
location: ProgressLocation.Notification,
|
||||
title: this._progressLabel(operation),
|
||||
cancellable: true,
|
||||
delay: Math.min(timeout / 2, 3000)
|
||||
}, () => {
|
||||
// race extension host event delivery against timeout AND user-cancel
|
||||
const onWillEvent = proxy.$onWillRunFileOperation(operation, files, timeout, token);
|
||||
return raceCancellation(onWillEvent, cts.token);
|
||||
}, () => {
|
||||
// user-cancel
|
||||
cts.cancel();
|
||||
|
||||
}).finally(() => {
|
||||
cts.dispose();
|
||||
clearTimeout(timer);
|
||||
});
|
||||
|
||||
if (!data) {
|
||||
// cancelled or no reply
|
||||
return;
|
||||
}
|
||||
|
||||
const needsConfirmation = data.edit.edits.some(edit => edit.metadata?.needsConfirmation);
|
||||
let showPreview = storageService.getBoolean(MainThreadFileSystemEventService.MementoKeyAdditionalEdits, StorageScope.GLOBAL);
|
||||
|
||||
if (envService.extensionTestsLocationURI) {
|
||||
// don't show dialog in tests
|
||||
showPreview = false;
|
||||
}
|
||||
|
||||
if (showPreview === undefined) {
|
||||
// show a user facing message
|
||||
|
||||
let message: string;
|
||||
if (data.extensionNames.length === 1) {
|
||||
if (operation === FileOperation.CREATE) {
|
||||
message = localize('ask.1.create', "Extension '{0}' wants to make refactoring changes with this file creation", data.extensionNames[0]);
|
||||
} else if (operation === FileOperation.COPY) {
|
||||
message = localize('ask.1.copy', "Extension '{0}' wants to make refactoring changes with this file copy", data.extensionNames[0]);
|
||||
} else if (operation === FileOperation.MOVE) {
|
||||
message = localize('ask.1.move', "Extension '{0}' wants to make refactoring changes with this file move", data.extensionNames[0]);
|
||||
} else /* if (operation === FileOperation.DELETE) */ {
|
||||
message = localize('ask.1.delete', "Extension '{0}' wants to make refactoring changes with this file deletion", data.extensionNames[0]);
|
||||
}
|
||||
} else {
|
||||
if (operation === FileOperation.CREATE) {
|
||||
message = localize('ask.N.create', "{0} extensions want to make refactoring changes with this file creation", data.extensionNames.length);
|
||||
} else if (operation === FileOperation.COPY) {
|
||||
message = localize('ask.N.copy', "{0} extensions want to make refactoring changes with this file copy", data.extensionNames.length);
|
||||
} else if (operation === FileOperation.MOVE) {
|
||||
message = localize('ask.N.move', "{0} extensions want to make refactoring changes with this file move", data.extensionNames.length);
|
||||
} else /* if (operation === FileOperation.DELETE) */ {
|
||||
message = localize('ask.N.delete', "{0} extensions want to make refactoring changes with this file deletion", data.extensionNames.length);
|
||||
}
|
||||
}
|
||||
|
||||
if (needsConfirmation) {
|
||||
// edit which needs confirmation -> always show dialog
|
||||
const answer = await dialogService.show(Severity.Info, message, [localize('preview', "Show Preview"), localize('cancel', "Skip Changes")], { cancelId: 1 });
|
||||
showPreview = true;
|
||||
if (answer.choice === 1) {
|
||||
// no changes wanted
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// choice
|
||||
const answer = await dialogService.show(Severity.Info, message,
|
||||
[localize('ok', "OK"), localize('preview', "Show Preview"), localize('cancel', "Skip Changes")],
|
||||
{
|
||||
cancelId: 2,
|
||||
checkbox: { label: localize('again', "Don't ask again") }
|
||||
}
|
||||
);
|
||||
if (answer.choice === 2) {
|
||||
// no changes wanted, don't persist cancel option
|
||||
return;
|
||||
}
|
||||
showPreview = answer.choice === 1;
|
||||
if (answer.checkboxChecked /* && answer.choice !== 2 */) {
|
||||
storageService.store(MainThreadFileSystemEventService.MementoKeyAdditionalEdits, showPreview, StorageScope.GLOBAL, StorageTarget.USER);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logService.info('[onWill-handler] applying additional workspace edit from extensions', data.extensionNames);
|
||||
|
||||
await bulkEditService.apply(
|
||||
reviveWorkspaceEditDto2(data.edit),
|
||||
{ undoRedoGroupId: undoInfo?.undoRedoGroupId, showPreview }
|
||||
);
|
||||
}
|
||||
|
||||
private _progressLabel(operation: FileOperation): string {
|
||||
switch (operation) {
|
||||
case FileOperation.CREATE:
|
||||
return localize('msg-create', "Running 'File Create' participants...");
|
||||
case FileOperation.MOVE:
|
||||
return localize('msg-rename', "Running 'File Rename' participants...");
|
||||
case FileOperation.COPY:
|
||||
return localize('msg-copy', "Running 'File Copy' participants...");
|
||||
case FileOperation.DELETE:
|
||||
return localize('msg-delete', "Running 'File Delete' participants...");
|
||||
}
|
||||
}
|
||||
}));
|
||||
};
|
||||
|
||||
// BEFORE file operation
|
||||
this._listener.add(workingCopyFileService.addFileOperationParticipant(fileOperationParticipant));
|
||||
|
||||
// AFTER file operation
|
||||
this._listener.add(workingCopyFileService.onDidRunWorkingCopyFileOperation(e => proxy.$onDidRunFileOperation(e.operation, e.files)));
|
||||
@@ -71,6 +201,19 @@ export class MainThreadFileSystemEventService {
|
||||
}
|
||||
}
|
||||
|
||||
registerAction2(class ResetMemento extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: 'files.participants.resetChoice',
|
||||
title: localize('label', "Reset choice for 'File operation needs preview'"),
|
||||
f1: true
|
||||
});
|
||||
}
|
||||
run(accessor: ServicesAccessor) {
|
||||
accessor.get(IStorageService).remove(MainThreadFileSystemEventService.MementoKeyAdditionalEdits, StorageScope.GLOBAL);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Registry.as<IConfigurationRegistry>(Extensions.Configuration).registerConfiguration({
|
||||
id: 'files',
|
||||
|
||||
@@ -21,7 +21,7 @@ import { Selection } from 'vs/editor/common/core/selection';
|
||||
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
|
||||
import * as callh from 'vs/workbench/contrib/callHierarchy/common/callHierarchy';
|
||||
import { mixin } from 'vs/base/common/objects';
|
||||
import { decodeSemanticTokensDto } from 'vs/workbench/api/common/shared/semanticTokensDto';
|
||||
import { decodeSemanticTokensDto } from 'vs/editor/common/services/semanticTokensDto';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadLanguageFeatures)
|
||||
export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesShape {
|
||||
@@ -497,6 +497,32 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
|
||||
}));
|
||||
}
|
||||
|
||||
// --- inline hints
|
||||
|
||||
$registerInlineHintsProvider(handle: number, selector: IDocumentFilterDto[], eventHandle: number | undefined): void {
|
||||
const provider = <modes.InlineHintsProvider>{
|
||||
provideInlineHints: async (model: ITextModel, range: EditorRange, token: CancellationToken): Promise<modes.InlineHint[] | undefined> => {
|
||||
const result = await this._proxy.$provideInlineHints(handle, model.uri, range, token);
|
||||
return result?.hints;
|
||||
}
|
||||
};
|
||||
|
||||
if (typeof eventHandle === 'number') {
|
||||
const emitter = new Emitter<void>();
|
||||
this._registrations.set(eventHandle, emitter);
|
||||
provider.onDidChangeInlineHints = emitter.event;
|
||||
}
|
||||
|
||||
this._registrations.set(handle, modes.InlineHintsProviderRegistry.register(selector, provider));
|
||||
}
|
||||
|
||||
$emitInlineHintsEvent(eventHandle: number, event?: any): void {
|
||||
const obj = this._registrations.get(eventHandle);
|
||||
if (obj instanceof Emitter) {
|
||||
obj.fire(event);
|
||||
}
|
||||
}
|
||||
|
||||
// --- links
|
||||
|
||||
$registerDocumentLinkProvider(handle: number, selector: IDocumentFilterDto[], supportsResolve: boolean): void {
|
||||
@@ -662,7 +688,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
|
||||
return {
|
||||
beforeText: MainThreadLanguageFeatures._reviveRegExp(onEnterRule.beforeText),
|
||||
afterText: onEnterRule.afterText ? MainThreadLanguageFeatures._reviveRegExp(onEnterRule.afterText) : undefined,
|
||||
oneLineAboveText: onEnterRule.oneLineAboveText ? MainThreadLanguageFeatures._reviveRegExp(onEnterRule.oneLineAboveText) : undefined,
|
||||
previousLineText: onEnterRule.previousLineText ? MainThreadLanguageFeatures._reviveRegExp(onEnterRule.previousLineText) : undefined,
|
||||
action: onEnterRule.action
|
||||
};
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ export class MainThreadMessageService implements MainThreadMessageServiceShape {
|
||||
|
||||
$showMessage(severity: Severity, message: string, options: MainThreadMessageOptions, commands: { title: string; isCloseAffordance: boolean; handle: number; }[]): Promise<number | undefined> {
|
||||
if (options.modal) {
|
||||
return this._showModalMessage(severity, message, commands);
|
||||
return this._showModalMessage(severity, message, commands, options.useCustom);
|
||||
} else {
|
||||
return this._showMessage(severity, message, commands, options.extension);
|
||||
}
|
||||
@@ -97,7 +97,7 @@ export class MainThreadMessageService implements MainThreadMessageServiceShape {
|
||||
});
|
||||
}
|
||||
|
||||
private async _showModalMessage(severity: Severity, message: string, commands: { title: string; isCloseAffordance: boolean; handle: number; }[]): Promise<number | undefined> {
|
||||
private async _showModalMessage(severity: Severity, message: string, commands: { title: string; isCloseAffordance: boolean; handle: number; }[], useCustom?: boolean): Promise<number | undefined> {
|
||||
let cancelId: number | undefined = undefined;
|
||||
|
||||
const buttons = commands.map((command, index) => {
|
||||
@@ -118,7 +118,7 @@ export class MainThreadMessageService implements MainThreadMessageServiceShape {
|
||||
cancelId = buttons.length - 1;
|
||||
}
|
||||
|
||||
const { choice } = await this._dialogService.show(severity, message, buttons, { cancelId });
|
||||
const { choice } = await this._dialogService.show(severity, message, buttons, { cancelId, useCustom });
|
||||
return choice === commands.length ? undefined : commands[choice].handle;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,8 +16,8 @@ import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { EditorActivation, ITextEditorOptions } from 'vs/platform/editor/common/editor';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
|
||||
import { viewColumnToEditorGroup } from 'vs/workbench/common/editor';
|
||||
import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
|
||||
@@ -129,12 +129,12 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo
|
||||
@IEditorGroupsService private readonly editorGroupsService: IEditorGroupsService,
|
||||
@IEditorGroupsService private readonly _editorGroupService: IEditorGroupsService,
|
||||
@IAccessibilityService private readonly accessibilityService: IAccessibilityService,
|
||||
@IQuickInputService private readonly quickInputService: IQuickInputService,
|
||||
@ILogService private readonly logService: ILogService,
|
||||
@INotebookCellStatusBarService private readonly cellStatusBarService: INotebookCellStatusBarService,
|
||||
@IWorkingCopyService private readonly _workingCopyService: IWorkingCopyService,
|
||||
@INotebookEditorModelResolverService private readonly _notebookModelResolverService: INotebookEditorModelResolverService,
|
||||
@IUriIdentityService private readonly _uriIdentityService: IUriIdentityService,
|
||||
@IInstantiationService private readonly _instantiationService: IInstantiationService,
|
||||
) {
|
||||
super();
|
||||
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostNotebook);
|
||||
@@ -646,14 +646,13 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo
|
||||
|
||||
switch (revealType) {
|
||||
case NotebookEditorRevealType.Default:
|
||||
notebookEditor.revealInView(cell);
|
||||
break;
|
||||
return notebookEditor.revealCellRangeInView(range);
|
||||
case NotebookEditorRevealType.InCenter:
|
||||
notebookEditor.revealInCenter(cell);
|
||||
break;
|
||||
return notebookEditor.revealInCenter(cell);
|
||||
case NotebookEditorRevealType.InCenterIfOutsideViewport:
|
||||
notebookEditor.revealInCenterIfOutsideViewport(cell);
|
||||
break;
|
||||
return notebookEditor.revealInCenterIfOutsideViewport(cell);
|
||||
case NotebookEditorRevealType.AtTop:
|
||||
return notebookEditor.revealInViewAtTop(cell);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -733,7 +732,7 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo
|
||||
const input = this.editorService.createEditorInput({ resource: URI.revive(resource), options: editorOptions });
|
||||
|
||||
// TODO: handle options.selection
|
||||
const editorPane = await openEditorWith(input, viewType, options, group, this.editorService, this.configurationService, this.quickInputService);
|
||||
const editorPane = await this._instantiationService.invokeFunction(openEditorWith, input, viewType, options, group);
|
||||
const notebookEditor = (editorPane as unknown as { isNotebookEditor?: boolean })?.isNotebookEditor ? (editorPane!.getControl() as INotebookEditor) : undefined;
|
||||
|
||||
if (notebookEditor) {
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
|
||||
import { ICredentialsService } from 'vs/workbench/services/credentials/common/credentials';
|
||||
import { IEncryptionService } from 'vs/workbench/services/encryption/common/encryptionService';
|
||||
import { ExtHostContext, ExtHostSecretStateShape, IExtHostContext, MainContext, MainThreadSecretStateShape } from '../common/extHost.protocol';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadSecretState)
|
||||
export class MainThreadSecretState extends Disposable implements MainThreadSecretStateShape {
|
||||
private readonly _proxy: ExtHostSecretStateShape;
|
||||
|
||||
constructor(
|
||||
extHostContext: IExtHostContext,
|
||||
@ICredentialsService private readonly credentialsService: ICredentialsService,
|
||||
@IEncryptionService private readonly encryptionService: IEncryptionService,
|
||||
@IProductService private readonly productService: IProductService
|
||||
) {
|
||||
super();
|
||||
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostSecretState);
|
||||
|
||||
this._register(this.credentialsService.onDidChangePassword(e => {
|
||||
const extensionId = e.service.substring(this.productService.urlProtocol.length);
|
||||
this._proxy.$onDidChangePassword({ extensionId, key: e.account });
|
||||
}));
|
||||
}
|
||||
|
||||
private getFullKey(extensionId: string): string {
|
||||
return `${this.productService.urlProtocol}${extensionId}`;
|
||||
}
|
||||
|
||||
async $getPassword(extensionId: string, key: string): Promise<string | undefined> {
|
||||
const fullKey = this.getFullKey(extensionId);
|
||||
const password = await this.credentialsService.getPassword(fullKey, key);
|
||||
const decrypted = password && await this.encryptionService.decrypt(password);
|
||||
|
||||
if (decrypted) {
|
||||
try {
|
||||
const value = JSON.parse(decrypted);
|
||||
if (value.extensionId === extensionId) {
|
||||
return value.content;
|
||||
}
|
||||
} catch (_) {
|
||||
throw new Error('Cannot get password');
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async $setPassword(extensionId: string, key: string, value: string): Promise<void> {
|
||||
const fullKey = this.getFullKey(extensionId);
|
||||
const toEncrypt = JSON.stringify({
|
||||
extensionId,
|
||||
content: value
|
||||
});
|
||||
const encrypted = await this.encryptionService.encrypt(toEncrypt);
|
||||
return this.credentialsService.setPassword(fullKey, key, encrypted);
|
||||
}
|
||||
|
||||
async $deletePassword(extensionId: string, key: string): Promise<void> {
|
||||
try {
|
||||
const fullKey = this.getFullKey(extensionId);
|
||||
await this.credentialsService.deletePassword(fullKey, key);
|
||||
} catch (_) {
|
||||
throw new Error('Cannot delete password');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -567,13 +567,19 @@ export class MainThreadTask implements MainThreadTaskShape {
|
||||
if (!task) {
|
||||
reject(new Error('Task not found'));
|
||||
} else {
|
||||
this._taskService.run(task).then(undefined, reason => {
|
||||
// eat the error, it has already been surfaced to the user and we don't care about it here
|
||||
});
|
||||
const result: TaskExecutionDTO = {
|
||||
id: value.id,
|
||||
task: TaskDTO.from(task)
|
||||
};
|
||||
this._taskService.run(task).then(summary => {
|
||||
// Ensure that the task execution gets cleaned up if the exit code is undefined
|
||||
// This can happen when the task has dependent tasks and one of them failed
|
||||
if ((summary?.exitCode === undefined) || (summary.exitCode !== 0)) {
|
||||
this._proxy.$OnDidEndTask(result);
|
||||
}
|
||||
}, reason => {
|
||||
// eat the error, it has already been surfaced to the user and we don't care about it here
|
||||
});
|
||||
resolve(result);
|
||||
}
|
||||
}, (_error) => {
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { DisposableStore, Disposable, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IShellLaunchConfig, ITerminalProcessExtHostProxy, ISpawnExtHostProcessRequest, ITerminalDimensions, EXT_HOST_CREATION_DELAY, IAvailableShellsRequest, IDefaultShellAndArgsRequest, IStartExtensionTerminalRequest } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { ExtHostContext, ExtHostTerminalServiceShape, MainThreadTerminalServiceShape, MainContext, IExtHostContext, IShellLaunchConfigDto, TerminalLaunchConfig, ITerminalDimensionsDto } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { IShellLaunchConfig, ITerminalProcessExtHostProxy, ISpawnExtHostProcessRequest, ITerminalDimensions, IAvailableShellsRequest, IDefaultShellAndArgsRequest, IStartExtensionTerminalRequest, ITerminalConfiguration, TERMINAL_CONFIG_SECTION } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { ExtHostContext, ExtHostTerminalServiceShape, MainThreadTerminalServiceShape, MainContext, IExtHostContext, IShellLaunchConfigDto, TerminalLaunchConfig, ITerminalDimensionsDto, TerminalIdentifier } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { StopWatch } from 'vs/base/common/stopwatch';
|
||||
@@ -16,11 +16,18 @@ import { TerminalDataBufferer } from 'vs/workbench/contrib/terminal/common/termi
|
||||
import { IEnvironmentVariableService, ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable';
|
||||
import { deserializeEnvironmentVariableCollection, serializeEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariableShared';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadTerminalService)
|
||||
export class MainThreadTerminalService implements MainThreadTerminalServiceShape {
|
||||
|
||||
private _proxy: ExtHostTerminalServiceShape;
|
||||
/**
|
||||
* Stores a map from a temporary terminal id (a UUID generated on the extension host side)
|
||||
* to a numeric terminal id (an id generated on the renderer side)
|
||||
* This comes in play only when dealing with terminals created on the extension host side
|
||||
*/
|
||||
private _extHostTerminalIds = new Map<string, number>();
|
||||
private _remoteAuthority: string | null;
|
||||
private readonly _toDispose = new DisposableStore();
|
||||
private readonly _terminalProcessProxies = new Map<number, ITerminalProcessExtHostProxy>();
|
||||
@@ -40,6 +47,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
|
||||
@IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService,
|
||||
@IInstantiationService private readonly _instantiationService: IInstantiationService,
|
||||
@IEnvironmentVariableService private readonly _environmentVariableService: IEnvironmentVariableService,
|
||||
@IConfigurationService private readonly _configurationService: IConfigurationService,
|
||||
@ILogService private readonly _logService: ILogService,
|
||||
) {
|
||||
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostTerminalService);
|
||||
@@ -47,13 +55,8 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
|
||||
|
||||
// ITerminalService listeners
|
||||
this._toDispose.add(_terminalService.onInstanceCreated((instance) => {
|
||||
// Delay this message so the TerminalInstance constructor has a chance to finish and
|
||||
// return the ID normally to the extension host. The ID that is passed here will be
|
||||
// used to register non-extension API terminals in the extension host.
|
||||
setTimeout(() => {
|
||||
this._onTerminalOpened(instance);
|
||||
this._onInstanceDimensionsChanged(instance);
|
||||
}, EXT_HOST_CREATION_DELAY);
|
||||
this._onTerminalOpened(instance);
|
||||
this._onInstanceDimensionsChanged(instance);
|
||||
}));
|
||||
|
||||
this._toDispose.add(_terminalService.onInstanceDisposed(instance => this._onTerminalDisposed(instance)));
|
||||
@@ -100,7 +103,22 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
|
||||
// when the extension host process goes down ?
|
||||
}
|
||||
|
||||
public $createTerminal(launchConfig: TerminalLaunchConfig): Promise<{ id: number, name: string }> {
|
||||
private _getTerminalId(id: TerminalIdentifier): number | undefined {
|
||||
if (typeof id === 'number') {
|
||||
return id;
|
||||
}
|
||||
return this._extHostTerminalIds.get(id);
|
||||
}
|
||||
|
||||
private _getTerminalInstance(id: TerminalIdentifier): ITerminalInstance | undefined {
|
||||
const rendererId = this._getTerminalId(id);
|
||||
if (typeof rendererId === 'number') {
|
||||
return this._terminalService.getInstanceFromId(rendererId);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public async $createTerminal(extHostTerminalId: string, launchConfig: TerminalLaunchConfig): Promise<void> {
|
||||
const shellLaunchConfig: IShellLaunchConfig = {
|
||||
name: launchConfig.name,
|
||||
executable: launchConfig.shellPath,
|
||||
@@ -112,39 +130,38 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
|
||||
strictEnv: launchConfig.strictEnv,
|
||||
hideFromUser: launchConfig.hideFromUser,
|
||||
isExtensionTerminal: launchConfig.isExtensionTerminal,
|
||||
extHostTerminalId: extHostTerminalId,
|
||||
isFeatureTerminal: launchConfig.isFeatureTerminal
|
||||
};
|
||||
const terminal = this._terminalService.createTerminal(shellLaunchConfig);
|
||||
return Promise.resolve({
|
||||
id: terminal.id,
|
||||
name: terminal.title
|
||||
});
|
||||
this._extHostTerminalIds.set(extHostTerminalId, terminal.id);
|
||||
}
|
||||
|
||||
public $show(terminalId: number, preserveFocus: boolean): void {
|
||||
const terminalInstance = this._terminalService.getInstanceFromId(terminalId);
|
||||
public $show(id: TerminalIdentifier, preserveFocus: boolean): void {
|
||||
const terminalInstance = this._getTerminalInstance(id);
|
||||
if (terminalInstance) {
|
||||
this._terminalService.setActiveInstance(terminalInstance);
|
||||
this._terminalService.showPanel(!preserveFocus);
|
||||
}
|
||||
}
|
||||
|
||||
public $hide(terminalId: number): void {
|
||||
public $hide(id: TerminalIdentifier): void {
|
||||
const rendererId = this._getTerminalId(id);
|
||||
const instance = this._terminalService.getActiveInstance();
|
||||
if (instance && instance.id === terminalId) {
|
||||
if (instance && instance.id === rendererId) {
|
||||
this._terminalService.hidePanel();
|
||||
}
|
||||
}
|
||||
|
||||
public $dispose(terminalId: number): void {
|
||||
const terminalInstance = this._terminalService.getInstanceFromId(terminalId);
|
||||
public $dispose(id: TerminalIdentifier): void {
|
||||
const terminalInstance = this._getTerminalInstance(id);
|
||||
if (terminalInstance) {
|
||||
terminalInstance.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public $sendText(terminalId: number, text: string, addNewLine: boolean): void {
|
||||
const terminalInstance = this._terminalService.getInstanceFromId(terminalId);
|
||||
public $sendText(id: TerminalIdentifier, text: string, addNewLine: boolean): void {
|
||||
const terminalInstance = this._getTerminalInstance(id);
|
||||
if (terminalInstance) {
|
||||
terminalInstance.sendText(text, addNewLine);
|
||||
}
|
||||
@@ -204,6 +221,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
|
||||
}
|
||||
|
||||
private _onTerminalOpened(terminalInstance: ITerminalInstance): void {
|
||||
const extHostTerminalId = terminalInstance.shellLaunchConfig.extHostTerminalId;
|
||||
const shellLaunchConfigDto: IShellLaunchConfigDto = {
|
||||
name: terminalInstance.shellLaunchConfig.name,
|
||||
executable: terminalInstance.shellLaunchConfig.executable,
|
||||
@@ -212,13 +230,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
|
||||
env: terminalInstance.shellLaunchConfig.env,
|
||||
hideFromUser: terminalInstance.shellLaunchConfig.hideFromUser
|
||||
};
|
||||
if (terminalInstance.title) {
|
||||
this._proxy.$acceptTerminalOpened(terminalInstance.id, terminalInstance.title, shellLaunchConfigDto);
|
||||
} else {
|
||||
terminalInstance.waitForTitle().then(title => {
|
||||
this._proxy.$acceptTerminalOpened(terminalInstance.id, title, shellLaunchConfigDto);
|
||||
});
|
||||
}
|
||||
this._proxy.$acceptTerminalOpened(terminalInstance.id, extHostTerminalId, terminalInstance.title, shellLaunchConfigDto);
|
||||
}
|
||||
|
||||
private _onTerminalProcessIdReady(terminalInstance: ITerminalInstance): void {
|
||||
@@ -249,7 +261,8 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
|
||||
executable: request.shellLaunchConfig.executable,
|
||||
args: request.shellLaunchConfig.args,
|
||||
cwd: request.shellLaunchConfig.cwd,
|
||||
env: request.shellLaunchConfig.env
|
||||
env: request.shellLaunchConfig.env,
|
||||
flowControl: this._configurationService.getValue<ITerminalConfiguration>(TERMINAL_CONFIG_SECTION).flowControl
|
||||
};
|
||||
|
||||
this._logService.trace('Spawning ext host process', { terminalId: proxy.terminalId, shellLaunchConfigDto, request });
|
||||
@@ -260,8 +273,9 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
|
||||
request.cols,
|
||||
request.rows,
|
||||
request.isWorkspaceShellAllowed
|
||||
).then(request.callback);
|
||||
).then(request.callback, request.callback);
|
||||
|
||||
proxy.onAcknowledgeDataEvent(charCount => this._proxy.$acceptProcessAckDataEvent(proxy.terminalId, charCount));
|
||||
proxy.onInput(data => this._proxy.$acceptProcessInput(proxy.terminalId, data));
|
||||
proxy.onResize(dimensions => this._proxy.$acceptProcessResize(proxy.terminalId, dimensions.cols, dimensions.rows));
|
||||
proxy.onShutdown(immediate => this._proxy.$acceptProcessShutdown(proxy.terminalId, immediate));
|
||||
@@ -294,38 +308,59 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
|
||||
}
|
||||
|
||||
public $sendProcessTitle(terminalId: number, title: string): void {
|
||||
this._getTerminalProcess(terminalId).emitTitle(title);
|
||||
const terminalProcess = this._terminalProcessProxies.get(terminalId);
|
||||
if (terminalProcess) {
|
||||
terminalProcess.emitTitle(title);
|
||||
}
|
||||
}
|
||||
|
||||
public $sendProcessData(terminalId: number, data: string): void {
|
||||
this._getTerminalProcess(terminalId).emitData(data);
|
||||
const terminalProcess = this._terminalProcessProxies.get(terminalId);
|
||||
if (terminalProcess) {
|
||||
terminalProcess.emitData(data);
|
||||
}
|
||||
}
|
||||
|
||||
public $sendProcessReady(terminalId: number, pid: number, cwd: string): void {
|
||||
this._getTerminalProcess(terminalId).emitReady(pid, cwd);
|
||||
const terminalProcess = this._terminalProcessProxies.get(terminalId);
|
||||
if (terminalProcess) {
|
||||
terminalProcess.emitReady(pid, cwd);
|
||||
}
|
||||
}
|
||||
|
||||
public $sendProcessExit(terminalId: number, exitCode: number | undefined): void {
|
||||
this._getTerminalProcess(terminalId).emitExit(exitCode);
|
||||
this._terminalProcessProxies.delete(terminalId);
|
||||
const terminalProcess = this._terminalProcessProxies.get(terminalId);
|
||||
if (terminalProcess) {
|
||||
terminalProcess.emitExit(exitCode);
|
||||
this._terminalProcessProxies.delete(terminalId);
|
||||
}
|
||||
}
|
||||
|
||||
public $sendOverrideDimensions(terminalId: number, dimensions: ITerminalDimensions | undefined): void {
|
||||
this._getTerminalProcess(terminalId).emitOverrideDimensions(dimensions);
|
||||
const terminalProcess = this._terminalProcessProxies.get(terminalId);
|
||||
if (terminalProcess) {
|
||||
terminalProcess.emitOverrideDimensions(dimensions);
|
||||
}
|
||||
}
|
||||
|
||||
public $sendProcessInitialCwd(terminalId: number, initialCwd: string): void {
|
||||
this._getTerminalProcess(terminalId).emitInitialCwd(initialCwd);
|
||||
const terminalProcess = this._terminalProcessProxies.get(terminalId);
|
||||
if (terminalProcess) {
|
||||
terminalProcess.emitInitialCwd(initialCwd);
|
||||
}
|
||||
}
|
||||
|
||||
public $sendProcessCwd(terminalId: number, cwd: string): void {
|
||||
this._getTerminalProcess(terminalId).emitCwd(cwd);
|
||||
const terminalProcess = this._terminalProcessProxies.get(terminalId);
|
||||
if (terminalProcess) {
|
||||
terminalProcess.emitCwd(cwd);
|
||||
}
|
||||
}
|
||||
|
||||
public $sendResolvedLaunchConfig(terminalId: number, shellLaunchConfig: IShellLaunchConfig): void {
|
||||
const instance = this._terminalService.getInstanceFromId(terminalId);
|
||||
if (instance) {
|
||||
this._getTerminalProcess(terminalId).emitResolvedShellLaunchConfig(shellLaunchConfig);
|
||||
this._getTerminalProcess(terminalId)?.emitResolvedShellLaunchConfig(shellLaunchConfig);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -338,7 +373,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
|
||||
sw.stop();
|
||||
sum += sw.elapsed();
|
||||
}
|
||||
this._getTerminalProcess(terminalId).emitLatency(sum / COUNT);
|
||||
this._getTerminalProcess(terminalId)?.emitLatency(sum / COUNT);
|
||||
}
|
||||
|
||||
private _isPrimaryExtHost(): boolean {
|
||||
@@ -363,10 +398,11 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
|
||||
}
|
||||
}
|
||||
|
||||
private _getTerminalProcess(terminalId: number): ITerminalProcessExtHostProxy {
|
||||
private _getTerminalProcess(terminalId: number): ITerminalProcessExtHostProxy | undefined {
|
||||
const terminal = this._terminalProcessProxies.get(terminalId);
|
||||
if (!terminal) {
|
||||
throw new Error(`Unknown terminal: ${terminalId}`);
|
||||
this._logService.error(`Unknown terminal: ${terminalId}`);
|
||||
return undefined;
|
||||
}
|
||||
return terminal;
|
||||
}
|
||||
|
||||
@@ -3,12 +3,31 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { getTestSubscriptionKey, RunTestsRequest, RunTestsResult, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection';
|
||||
import { ITestService } from 'vs/workbench/contrib/testing/common/testService';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
|
||||
import { ExtHostContext, ExtHostTestingResource, ExtHostTestingShape, IExtHostContext, MainContext, MainThreadTestingShape } from '../common/extHost.protocol';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { Disposable, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle';
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
|
||||
import { getTestSubscriptionKey, RunTestsRequest, RunTestsResult, TestDiffOpType, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection';
|
||||
import { ITestResultService } from 'vs/workbench/contrib/testing/common/testResultService';
|
||||
import { ITestService } from 'vs/workbench/contrib/testing/common/testService';
|
||||
import { ExtHostContext, ExtHostTestingResource, ExtHostTestingShape, IExtHostContext, MainContext, MainThreadTestingShape } from '../common/extHost.protocol';
|
||||
|
||||
const reviveDiff = (diff: TestsDiff) => {
|
||||
for (const entry of diff) {
|
||||
if (entry[0] === TestDiffOpType.Add || entry[0] === TestDiffOpType.Update) {
|
||||
const item = entry[1];
|
||||
if (item.item.location) {
|
||||
item.item.location.uri = URI.revive(item.item.location.uri);
|
||||
}
|
||||
|
||||
for (const message of item.item.state.messages) {
|
||||
if (message.location) {
|
||||
message.location.uri = URI.revive(message.location.uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadTesting)
|
||||
export class MainThreadTesting extends Disposable implements MainThreadTestingShape {
|
||||
@@ -18,11 +37,28 @@ export class MainThreadTesting extends Disposable implements MainThreadTestingSh
|
||||
constructor(
|
||||
extHostContext: IExtHostContext,
|
||||
@ITestService private readonly testService: ITestService,
|
||||
@ITestResultService resultService: ITestResultService,
|
||||
) {
|
||||
super();
|
||||
this.proxy = extHostContext.getProxy(ExtHostContext.ExtHostTesting);
|
||||
this._register(this.testService.onShouldSubscribe(args => this.proxy.$subscribeToTests(args.resource, args.uri)));
|
||||
this._register(this.testService.onShouldUnsubscribe(args => this.proxy.$unsubscribeFromTests(args.resource, args.uri)));
|
||||
|
||||
const testCompleteListener = this._register(new MutableDisposable());
|
||||
this._register(resultService.onNewTestResult(results => {
|
||||
testCompleteListener.value = results.onComplete(() => this.proxy.$publishTestResults({ tests: results.tests }));
|
||||
}));
|
||||
|
||||
testService.updateRootProviderCount(1);
|
||||
|
||||
const lastCompleted = resultService.results.find(r => !r.isComplete);
|
||||
if (lastCompleted) {
|
||||
this.proxy.$publishTestResults({ tests: lastCompleted.tests });
|
||||
}
|
||||
|
||||
for (const { resource, uri } of this.testService.subscriptions) {
|
||||
this.proxy.$subscribeToTests(resource, uri);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -30,7 +66,8 @@ export class MainThreadTesting extends Disposable implements MainThreadTestingSh
|
||||
*/
|
||||
public $registerTestProvider(id: string) {
|
||||
this.testService.registerTestController(id, {
|
||||
runTests: req => this.proxy.$runTestsForProvider(req),
|
||||
runTests: (req, token) => this.proxy.$runTestsForProvider(req, token),
|
||||
lookupTest: test => this.proxy.$lookupTest(test),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -64,14 +101,19 @@ export class MainThreadTesting extends Disposable implements MainThreadTestingSh
|
||||
* @inheritdoc
|
||||
*/
|
||||
public $publishDiff(resource: ExtHostTestingResource, uri: UriComponents, diff: TestsDiff): void {
|
||||
reviveDiff(diff);
|
||||
this.testService.publishDiff(resource, URI.revive(uri), diff);
|
||||
}
|
||||
|
||||
public $runTests(req: RunTestsRequest): Promise<RunTestsResult> {
|
||||
return this.testService.runTests(req);
|
||||
public $runTests(req: RunTestsRequest, token: CancellationToken): Promise<RunTestsResult> {
|
||||
return this.testService.runTests(req, token);
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
// no-op
|
||||
this.testService.updateRootProviderCount(-1);
|
||||
for (const subscription of this.testSubscriptions.values()) {
|
||||
subscription.dispose();
|
||||
}
|
||||
this.testSubscriptions.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,8 +222,8 @@ class TreeViewDataProvider implements ITreeViewDataProvider {
|
||||
const hasResolve = await this.hasResolve;
|
||||
if (elements) {
|
||||
for (const element of elements) {
|
||||
const resolvable = new ResolvableTreeItem(element, hasResolve ? () => {
|
||||
return this._proxy.$resolve(this.treeViewId, element.handle);
|
||||
const resolvable = new ResolvableTreeItem(element, hasResolve ? (token) => {
|
||||
return this._proxy.$resolve(this.treeViewId, element.handle, token);
|
||||
} : undefined);
|
||||
this.itemsMap.set(element.handle, resolvable);
|
||||
result.push(resolvable);
|
||||
|
||||
@@ -3,22 +3,31 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { MainThreadTunnelServiceShape, IExtHostContext, MainContext, ExtHostContext, ExtHostTunnelServiceShape } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { TunnelDto } from 'vs/workbench/api/common/extHostTunnelService';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
|
||||
import { IRemoteExplorerService, makeAddress } from 'vs/workbench/services/remote/common/remoteExplorerService';
|
||||
import { ITunnelProvider, ITunnelService, TunnelCreationOptions, TunnelOptions } from 'vs/platform/remote/common/tunnel';
|
||||
import { CandidatePort, IRemoteExplorerService, makeAddress } from 'vs/workbench/services/remote/common/remoteExplorerService';
|
||||
import { ITunnelProvider, ITunnelService, TunnelCreationOptions, TunnelProviderFeatures, TunnelOptions, RemoteTunnel, isPortPrivileged } from 'vs/platform/remote/common/tunnel';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import type { TunnelDescription } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
||||
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { PORT_AUTO_FORWARD_SETTING } from 'vs/workbench/contrib/remote/browser/tunnelView';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadTunnelService)
|
||||
export class MainThreadTunnelService extends Disposable implements MainThreadTunnelServiceShape {
|
||||
private readonly _proxy: ExtHostTunnelServiceShape;
|
||||
private elevateionRetry: boolean = false;
|
||||
|
||||
constructor(
|
||||
extHostContext: IExtHostContext,
|
||||
@IRemoteExplorerService private readonly remoteExplorerService: IRemoteExplorerService,
|
||||
@ITunnelService private readonly tunnelService: ITunnelService
|
||||
@ITunnelService private readonly tunnelService: ITunnelService,
|
||||
@INotificationService private readonly notificationService: INotificationService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@ILogService private readonly logService: ILogService
|
||||
) {
|
||||
super();
|
||||
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostTunnelService);
|
||||
@@ -26,14 +35,50 @@ export class MainThreadTunnelService extends Disposable implements MainThreadTun
|
||||
this._register(tunnelService.onTunnelClosed(() => this._proxy.$onDidTunnelsChange()));
|
||||
}
|
||||
|
||||
async $setCandidateFinder(): Promise<void> {
|
||||
if (this.remoteExplorerService.portsFeaturesEnabled) {
|
||||
this._proxy.$registerCandidateFinder(this.configurationService.getValue(PORT_AUTO_FORWARD_SETTING));
|
||||
} else {
|
||||
this._register(this.remoteExplorerService.onEnabledPortsFeatures(() => this._proxy.$registerCandidateFinder(this.configurationService.getValue(PORT_AUTO_FORWARD_SETTING))));
|
||||
}
|
||||
this._register(this.configurationService.onDidChangeConfiguration(async (e) => {
|
||||
if (e.affectsConfiguration(PORT_AUTO_FORWARD_SETTING)) {
|
||||
return this._proxy.$registerCandidateFinder((this.configurationService.getValue(PORT_AUTO_FORWARD_SETTING)));
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
async $openTunnel(tunnelOptions: TunnelOptions, source: string): Promise<TunnelDto | undefined> {
|
||||
const tunnel = await this.remoteExplorerService.forward(tunnelOptions.remoteAddress, tunnelOptions.localAddressPort, tunnelOptions.label, source);
|
||||
const tunnel = await this.remoteExplorerService.forward(tunnelOptions.remoteAddress, tunnelOptions.localAddressPort, tunnelOptions.label, source, false);
|
||||
if (tunnel) {
|
||||
if (!this.elevateionRetry
|
||||
&& (tunnelOptions.localAddressPort !== undefined)
|
||||
&& (tunnel.tunnelLocalPort !== undefined)
|
||||
&& isPortPrivileged(tunnelOptions.localAddressPort)
|
||||
&& (tunnel.tunnelLocalPort !== tunnelOptions.localAddressPort)
|
||||
&& this.tunnelService.canElevate) {
|
||||
|
||||
this.elevationPrompt(tunnelOptions, tunnel, source);
|
||||
}
|
||||
return TunnelDto.fromServiceTunnel(tunnel);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private async elevationPrompt(tunnelOptions: TunnelOptions, tunnel: RemoteTunnel, source: string) {
|
||||
return this.notificationService.prompt(Severity.Info,
|
||||
nls.localize('remote.tunnel.openTunnel', "The extension {0} has forwarded port {1}. You'll need to run as superuser to use port {2} locally.", source, tunnelOptions.remoteAddress.port, tunnelOptions.localAddressPort),
|
||||
[{
|
||||
label: nls.localize('remote.tunnelsView.elevationButton', "Use Port {0} as Sudo...", tunnel.tunnelRemotePort),
|
||||
run: async () => {
|
||||
this.elevateionRetry = true;
|
||||
await this.remoteExplorerService.close({ host: tunnel.tunnelRemoteHost, port: tunnel.tunnelRemotePort });
|
||||
await this.remoteExplorerService.forward(tunnelOptions.remoteAddress, tunnelOptions.localAddressPort, tunnelOptions.label, source, true);
|
||||
this.elevateionRetry = false;
|
||||
}
|
||||
}]);
|
||||
}
|
||||
|
||||
async $closeTunnel(remote: { host: string, port: number }): Promise<void> {
|
||||
return this.remoteExplorerService.close(remote);
|
||||
}
|
||||
@@ -47,27 +92,29 @@ export class MainThreadTunnelService extends Disposable implements MainThreadTun
|
||||
});
|
||||
}
|
||||
|
||||
async $onFoundNewCandidates(candidates: { host: string, port: number, detail: string }[]): Promise<void> {
|
||||
async $onFoundNewCandidates(candidates: CandidatePort[]): Promise<void> {
|
||||
this.remoteExplorerService.onFoundNewCandidates(candidates);
|
||||
}
|
||||
|
||||
async $tunnelServiceReady(): Promise<void> {
|
||||
return this.remoteExplorerService.restore();
|
||||
}
|
||||
|
||||
async $setTunnelProvider(): Promise<void> {
|
||||
async $setTunnelProvider(features: TunnelProviderFeatures): Promise<void> {
|
||||
const tunnelProvider: ITunnelProvider = {
|
||||
forwardPort: (tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions) => {
|
||||
const forward = this._proxy.$forwardPort(tunnelOptions, tunnelCreationOptions);
|
||||
if (forward) {
|
||||
return forward.then(tunnel => {
|
||||
this.logService.trace(`MainThreadTunnelService: New tunnel established by tunnel provider: ${tunnel?.remoteAddress.host}:${tunnel?.remoteAddress.port}`);
|
||||
if (!tunnel) {
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
tunnelRemotePort: tunnel.remoteAddress.port,
|
||||
tunnelRemoteHost: tunnel.remoteAddress.host,
|
||||
localAddress: typeof tunnel.localAddress === 'string' ? tunnel.localAddress : makeAddress(tunnel.localAddress.host, tunnel.localAddress.port),
|
||||
tunnelLocalPort: typeof tunnel.localAddress !== 'string' ? tunnel.localAddress.port : undefined,
|
||||
dispose: (silent?: boolean) => {
|
||||
this._proxy.$closeTunnel({ host: tunnel.remoteAddress.host, port: tunnel.remoteAddress.port }, silent);
|
||||
public: tunnel.public,
|
||||
dispose: async (silent?: boolean) => {
|
||||
this.logService.trace(`MainThreadTunnelService: Closing tunnel from tunnel provider: ${tunnel?.remoteAddress.host}:${tunnel?.remoteAddress.port}`);
|
||||
return this._proxy.$closeTunnel({ host: tunnel.remoteAddress.host, port: tunnel.remoteAddress.port }, silent);
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -75,9 +122,16 @@ export class MainThreadTunnelService extends Disposable implements MainThreadTun
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
this.tunnelService.setTunnelProvider(tunnelProvider);
|
||||
this.tunnelService.setTunnelProvider(tunnelProvider, features);
|
||||
}
|
||||
|
||||
async $setCandidateFilter(): Promise<void> {
|
||||
this.remoteExplorerService.setCandidateFilter((candidates: CandidatePort[]): Promise<CandidatePort[]> => {
|
||||
return this._proxy.$applyCandidateFilter(candidates);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
dispose(): void {
|
||||
|
||||
}
|
||||
|
||||
132
lib/vscode/src/vs/workbench/api/browser/mainThreadUriOpeners.ts
Normal file
132
lib/vscode/src/vs/workbench/api/browser/mainThreadUriOpeners.ts
Normal file
@@ -0,0 +1,132 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { isPromiseCanceledError } from 'vs/base/common/errors';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { localize } from 'vs/nls';
|
||||
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
|
||||
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { ExtHostContext, ExtHostUriOpenersShape, IExtHostContext, MainContext, MainThreadUriOpenersShape } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { defaultExternalUriOpenerId } from 'vs/workbench/contrib/externalUriOpener/common/configuration';
|
||||
import { ContributedExternalUriOpenersStore } from 'vs/workbench/contrib/externalUriOpener/common/contributedOpeners';
|
||||
import { IExternalOpenerProvider, IExternalUriOpener, IExternalUriOpenerService } from 'vs/workbench/contrib/externalUriOpener/common/externalUriOpenerService';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { extHostNamedCustomer } from '../common/extHostCustomers';
|
||||
|
||||
interface RegisteredOpenerMetadata {
|
||||
readonly schemes: ReadonlySet<string>;
|
||||
readonly extensionId: ExtensionIdentifier;
|
||||
readonly label: string;
|
||||
}
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadUriOpeners)
|
||||
export class MainThreadUriOpeners extends Disposable implements MainThreadUriOpenersShape, IExternalOpenerProvider {
|
||||
|
||||
private readonly proxy: ExtHostUriOpenersShape;
|
||||
private readonly _registeredOpeners = new Map<string, RegisteredOpenerMetadata>();
|
||||
private readonly _contributedExternalUriOpenersStore: ContributedExternalUriOpenersStore;
|
||||
|
||||
constructor(
|
||||
context: IExtHostContext,
|
||||
@IStorageService storageService: IStorageService,
|
||||
@IExternalUriOpenerService externalUriOpenerService: IExternalUriOpenerService,
|
||||
@IExtensionService private readonly extensionService: IExtensionService,
|
||||
@IOpenerService private readonly openerService: IOpenerService,
|
||||
@INotificationService private readonly notificationService: INotificationService,
|
||||
) {
|
||||
super();
|
||||
this.proxy = context.getProxy(ExtHostContext.ExtHostUriOpeners);
|
||||
|
||||
this._register(externalUriOpenerService.registerExternalOpenerProvider(this));
|
||||
|
||||
this._contributedExternalUriOpenersStore = this._register(new ContributedExternalUriOpenersStore(storageService, extensionService));
|
||||
}
|
||||
|
||||
public async *getOpeners(targetUri: URI): AsyncIterable<IExternalUriOpener> {
|
||||
|
||||
// Currently we only allow openers for http and https urls
|
||||
if (targetUri.scheme !== Schemas.http && targetUri.scheme !== Schemas.https) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.extensionService.activateByEvent(`onUriOpen:${targetUri.scheme}`);
|
||||
|
||||
for (const [id, openerMetadata] of this._registeredOpeners) {
|
||||
if (openerMetadata.schemes.has(targetUri.scheme)) {
|
||||
yield this.createOpener(id, openerMetadata);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private createOpener(id: string, metadata: RegisteredOpenerMetadata): IExternalUriOpener {
|
||||
return {
|
||||
id: id,
|
||||
label: metadata.label,
|
||||
canOpen: (uri, token) => {
|
||||
return this.proxy.$canOpenUri(id, uri, token);
|
||||
},
|
||||
openExternalUri: async (uri, ctx, token) => {
|
||||
try {
|
||||
await this.proxy.$openUri(id, { resolvedUri: uri, sourceUri: ctx.sourceUri }, token);
|
||||
} catch (e) {
|
||||
if (!isPromiseCanceledError(e)) {
|
||||
const openDefaultAction = new Action('default', localize('openerFailedUseDefault', "Open using default opener"), undefined, undefined, () => {
|
||||
return this.openerService.open(uri, {
|
||||
allowTunneling: false,
|
||||
allowContributedOpeners: defaultExternalUriOpenerId,
|
||||
});
|
||||
});
|
||||
openDefaultAction.tooltip = uri.toString();
|
||||
|
||||
this.notificationService.notify({
|
||||
severity: Severity.Error,
|
||||
message: localize('openerFailedMessage', 'Could not open uri with \'{0}\': {1}', id, e.toString()),
|
||||
actions: {
|
||||
primary: [
|
||||
openDefaultAction
|
||||
]
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async $registerUriOpener(
|
||||
id: string,
|
||||
schemes: readonly string[],
|
||||
extensionId: ExtensionIdentifier,
|
||||
label: string,
|
||||
): Promise<void> {
|
||||
if (this._registeredOpeners.has(id)) {
|
||||
throw new Error(`Opener with id '${id}' already registered`);
|
||||
}
|
||||
|
||||
this._registeredOpeners.set(id, {
|
||||
schemes: new Set(schemes),
|
||||
label,
|
||||
extensionId,
|
||||
});
|
||||
|
||||
this._contributedExternalUriOpenersStore.add(id, extensionId.value);
|
||||
}
|
||||
|
||||
async $unregisterUriOpener(id: string): Promise<void> {
|
||||
this._registeredOpeners.delete(id);
|
||||
this._contributedExternalUriOpenersStore.delete(id);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
super.dispose();
|
||||
this._registeredOpeners.clear();
|
||||
}
|
||||
}
|
||||
@@ -129,6 +129,9 @@ export class MainThreadWebviewPanels extends Disposable implements extHostProtoc
|
||||
|
||||
dispose(this._editorProviders.values());
|
||||
this._editorProviders.clear();
|
||||
|
||||
dispose(this._revivers.values());
|
||||
this._revivers.clear();
|
||||
}
|
||||
|
||||
public get webviewInputs(): Iterable<WebviewInput> { return this._webviewInputs; }
|
||||
@@ -181,7 +184,6 @@ export class MainThreadWebviewPanels extends Disposable implements extHostProtoc
|
||||
webview.setName(value);
|
||||
}
|
||||
|
||||
|
||||
public $setIconPath(handle: extHostProtocol.WebviewHandle, value: { light: UriComponents, dark: UriComponents; } | undefined): void {
|
||||
const webview = this.getWebviewInput(handle);
|
||||
webview.iconPath = reviveWebviewIcon(value);
|
||||
@@ -199,8 +201,7 @@ export class MainThreadWebviewPanels extends Disposable implements extHostProtoc
|
||||
}
|
||||
}
|
||||
|
||||
public $registerSerializer(viewType: string)
|
||||
: void {
|
||||
public $registerSerializer(viewType: string): void {
|
||||
if (this._revivers.has(viewType)) {
|
||||
throw new Error(`Reviver for ${viewType} already registered`);
|
||||
}
|
||||
@@ -216,7 +217,6 @@ export class MainThreadWebviewPanels extends Disposable implements extHostProtoc
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const handle = webviewInput.id;
|
||||
|
||||
this.addWebviewInput(handle, webviewInput);
|
||||
|
||||
@@ -78,7 +78,7 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
|
||||
private onDidClickLink(handle: extHostProtocol.WebviewHandle, link: string): void {
|
||||
const webview = this.getWebview(handle);
|
||||
if (this.isSupportedLink(webview, URI.parse(link))) {
|
||||
this._openerService.open(link, { fromUserGesture: true });
|
||||
this._openerService.open(link, { fromUserGesture: true, allowContributedOpeners: true });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -52,7 +52,11 @@ export class MainThreadWindow implements MainThreadWindowShape {
|
||||
// called with URI or transformed -> use uri
|
||||
target = uri;
|
||||
}
|
||||
return this.openerService.open(target, { openExternal: true, allowTunneling: options.allowTunneling });
|
||||
return this.openerService.open(target, {
|
||||
openExternal: true,
|
||||
allowTunneling: options.allowTunneling,
|
||||
allowContributedOpeners: options.allowContributedOpeners,
|
||||
});
|
||||
}
|
||||
|
||||
async $asExternalUri(uriComponents: UriComponents, options: IOpenUriOptions): Promise<UriComponents> {
|
||||
|
||||
@@ -6,25 +6,25 @@
|
||||
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { isPromiseCanceledError } from 'vs/base/common/errors';
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { isNative } from 'vs/base/common/platform';
|
||||
import { withNullAsUndefined } from 'vs/base/common/types';
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import { localize } from 'vs/nls';
|
||||
import { isNative } from 'vs/base/common/platform';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ILabelService } from 'vs/platform/label/common/label';
|
||||
import { IFileMatch, IPatternInfo, ISearchProgressItem, ISearchService } from 'vs/workbench/services/search/common/search';
|
||||
import { IWorkspaceContextService, WorkbenchState, IWorkspace, toWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { IRequestService } from 'vs/platform/request/common/request';
|
||||
import { IWorkspace, IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
|
||||
import { isUntitledWorkspace } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
|
||||
import { checkGlobFileExists } from 'vs/workbench/api/common/shared/workspaceContains';
|
||||
import { ITextQueryBuilderOptions, QueryBuilder } from 'vs/workbench/contrib/search/common/queryBuilder';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IFileMatch, IPatternInfo, ISearchProgressItem, ISearchService } from 'vs/workbench/services/search/common/search';
|
||||
import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/common/workspaceEditing';
|
||||
import { ExtHostContext, ExtHostWorkspaceShape, IExtHostContext, MainContext, MainThreadWorkspaceShape, IWorkspaceData, ITextSearchComplete } from '../common/extHost.protocol';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { isUntitledWorkspace } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { withNullAsUndefined } from 'vs/base/common/types';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { IRequestService } from 'vs/platform/request/common/request';
|
||||
import { checkGlobFileExists } from 'vs/workbench/api/common/shared/workspaceContains';
|
||||
import { ExtHostContext, ExtHostWorkspaceShape, IExtHostContext, ITextSearchComplete, IWorkspaceData, MainContext, MainThreadWorkspaceShape } from '../common/extHost.protocol';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadWorkspace)
|
||||
export class MainThreadWorkspace implements MainThreadWorkspaceShape {
|
||||
@@ -139,7 +139,7 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape {
|
||||
}
|
||||
|
||||
const query = this._queryBuilder.file(
|
||||
includeFolder ? [toWorkspaceFolder(includeFolder)] : workspace.folders,
|
||||
includeFolder ? [includeFolder] : workspace.folders,
|
||||
{
|
||||
maxResults: withNullAsUndefined(maxResults),
|
||||
disregardExcludeSettings: (excludePatternOrDisregardExcludes === false) || undefined,
|
||||
|
||||
@@ -3,35 +3,32 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { localize } from 'vs/nls';
|
||||
import { coalesce } from 'vs/base/common/arrays';
|
||||
import { forEach } from 'vs/base/common/collections';
|
||||
import { IJSONSchema } from 'vs/base/common/jsonSchema';
|
||||
import * as resources from 'vs/base/common/resources';
|
||||
import { ExtensionMessageCollector, ExtensionsRegistry, IExtensionPoint, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry';
|
||||
import { ViewContainer, IViewsRegistry, ITreeViewDescriptor, IViewContainersRegistry, Extensions as ViewContainerExtensions, TEST_VIEW_CONTAINER_ID, IViewDescriptor, ViewContainerLocation, testViewIcon } from 'vs/workbench/common/views';
|
||||
import { CustomTreeView, TreeViewPane } from 'vs/workbench/browser/parts/views/treeView';
|
||||
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { coalesce, } from 'vs/base/common/arrays';
|
||||
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions';
|
||||
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { VIEWLET_ID as EXPLORER } from 'vs/workbench/contrib/files/common/files';
|
||||
import { VIEWLET_ID as SCM } from 'vs/workbench/contrib/scm/common/scm';
|
||||
import { VIEWLET_ID as DEBUG } from 'vs/workbench/contrib/debug/common/debug';
|
||||
import { VIEWLET_ID as REMOTE } from 'vs/workbench/contrib/remote/browser/remoteExplorer';
|
||||
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { ViewletRegistry, Extensions as ViewletExtensions, ShowViewletAction } from 'vs/workbench/browser/viewlet';
|
||||
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
|
||||
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
|
||||
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { IWorkbenchActionRegistry, Extensions as ActionExtensions, CATEGORIES } from 'vs/workbench/common/actions';
|
||||
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
|
||||
import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
||||
import { localize } from 'vs/nls';
|
||||
import { registerAction2 } from 'vs/platform/actions/common/actions';
|
||||
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
import { WebviewViewPane } from 'vs/workbench/contrib/webviewView/browser/webviewViewPane';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
|
||||
import { CustomTreeView, TreeViewPane } from 'vs/workbench/browser/parts/views/treeView';
|
||||
import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
||||
import { Extensions as ViewletExtensions, ShowViewletAction2, ViewletRegistry } from 'vs/workbench/browser/viewlet';
|
||||
import { CATEGORIES } from 'vs/workbench/common/actions';
|
||||
import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions';
|
||||
import { Extensions as ViewContainerExtensions, ITreeViewDescriptor, IViewContainersRegistry, IViewDescriptor, IViewsRegistry, ViewContainer, ViewContainerLocation } from 'vs/workbench/common/views';
|
||||
import { VIEWLET_ID as DEBUG } from 'vs/workbench/contrib/debug/common/debug';
|
||||
import { VIEWLET_ID as EXPLORER } from 'vs/workbench/contrib/files/common/files';
|
||||
import { VIEWLET_ID as REMOTE } from 'vs/workbench/contrib/remote/browser/remoteExplorer';
|
||||
import { VIEWLET_ID as SCM } from 'vs/workbench/contrib/scm/common/scm';
|
||||
import { WebviewViewPane } from 'vs/workbench/contrib/webviewView/browser/webviewViewPane';
|
||||
import { ExtensionMessageCollector, ExtensionsRegistry, IExtensionPoint, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry';
|
||||
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
|
||||
|
||||
export interface IUserFriendlyViewsContainerDescriptor {
|
||||
id: string;
|
||||
@@ -110,7 +107,7 @@ const viewDescriptor: IJSONSchema = {
|
||||
defaultSnippets: [{ body: { id: '${1:id}', name: '${2:name}' } }],
|
||||
properties: {
|
||||
type: {
|
||||
markdownDescription: localize('vscode.extension.contributes.view.type', "Type of the the view. This can either be `tree` for a tree view based view or `webview` for a webview based view. The default is `tree`."),
|
||||
markdownDescription: localize('vscode.extension.contributes.view.type', "Type of the view. This can either be `tree` for a tree view based view or `webview` for a webview based view. The default is `tree`."),
|
||||
type: 'string',
|
||||
enum: [
|
||||
'tree',
|
||||
@@ -255,7 +252,8 @@ const viewsExtensionPoint: IExtensionPoint<ViewExtensionPointType> = ExtensionsR
|
||||
jsonSchema: viewsContribution
|
||||
});
|
||||
|
||||
const TEST_VIEW_CONTAINER_ORDER = 6;
|
||||
const CUSTOM_VIEWS_START_ORDER = 7;
|
||||
|
||||
class ViewsExtensionHandler implements IWorkbenchContribution {
|
||||
|
||||
private viewContainersRegistry: IViewContainersRegistry;
|
||||
@@ -271,7 +269,6 @@ class ViewsExtensionHandler implements IWorkbenchContribution {
|
||||
}
|
||||
|
||||
private handleAndRegisterCustomViewContainers() {
|
||||
this.registerTestViewContainer();
|
||||
viewsContainersExtensionPoint.setHandler((extensions, { added, removed }) => {
|
||||
if (removed.length) {
|
||||
this.removeCustomViewContainers(removed);
|
||||
@@ -284,7 +281,7 @@ class ViewsExtensionHandler implements IWorkbenchContribution {
|
||||
|
||||
private addCustomViewContainers(extensionPoints: readonly IExtensionPointUser<ViewContainerExtensionPointType>[], existingViewContainers: ViewContainer[]): void {
|
||||
const viewContainersRegistry = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry);
|
||||
let activityBarOrder = TEST_VIEW_CONTAINER_ORDER + viewContainersRegistry.all.filter(v => !!v.extensionId && viewContainersRegistry.getViewContainerLocation(v) === ViewContainerLocation.Sidebar).length + 1;
|
||||
let activityBarOrder = CUSTOM_VIEWS_START_ORDER + viewContainersRegistry.all.filter(v => !!v.extensionId && viewContainersRegistry.getViewContainerLocation(v) === ViewContainerLocation.Sidebar).length;
|
||||
let panelOrder = 5 + viewContainersRegistry.all.filter(v => !!v.extensionId && viewContainersRegistry.getViewContainerLocation(v) === ViewContainerLocation.Panel).length + 1;
|
||||
for (let { value, collector, description } of extensionPoints) {
|
||||
forEach(value, entry => {
|
||||
@@ -318,13 +315,6 @@ class ViewsExtensionHandler implements IWorkbenchContribution {
|
||||
}
|
||||
}
|
||||
|
||||
private registerTestViewContainer(): void {
|
||||
const title = localize('test', "Test");
|
||||
const icon = testViewIcon;
|
||||
|
||||
this.registerCustomViewContainer(TEST_VIEW_CONTAINER_ID, title, icon, TEST_VIEW_CONTAINER_ORDER, undefined, ViewContainerLocation.Sidebar);
|
||||
}
|
||||
|
||||
private isValidViewsContainer(viewsContainersDescriptors: IUserFriendlyViewsContainerDescriptor[], collector: ExtensionMessageCollector): boolean {
|
||||
if (!Array.isArray(viewsContainersDescriptors)) {
|
||||
collector.error(localize('viewcontainer requirearray', "views containers must be an array"));
|
||||
@@ -395,22 +385,15 @@ class ViewsExtensionHandler implements IWorkbenchContribution {
|
||||
}, location);
|
||||
|
||||
// Register Action to Open Viewlet
|
||||
class OpenCustomViewletAction extends ShowViewletAction {
|
||||
constructor(
|
||||
id: string, label: string,
|
||||
@IViewletService viewletService: IViewletService,
|
||||
@IEditorGroupsService editorGroupService: IEditorGroupsService,
|
||||
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService
|
||||
) {
|
||||
super(id, label, id, viewletService, editorGroupService, layoutService);
|
||||
registerAction2(class OpenCustomViewletAction extends ShowViewletAction2 {
|
||||
constructor() {
|
||||
super({ id, f1: true, title: localize('showViewlet', "Show {0}", title), category: CATEGORIES.View.value });
|
||||
}
|
||||
}
|
||||
const registry = Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions);
|
||||
registry.registerWorkbenchAction(
|
||||
SyncActionDescriptor.create(OpenCustomViewletAction, id, localize('showViewlet', "Show {0}", title)),
|
||||
`View: Show ${title}`,
|
||||
CATEGORIES.View.value
|
||||
);
|
||||
|
||||
protected viewletId() {
|
||||
return id;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return viewContainer;
|
||||
@@ -501,7 +484,8 @@ class ViewsExtensionHandler implements IWorkbenchContribution {
|
||||
originalContainerId: entry.key,
|
||||
group: item.group,
|
||||
remoteAuthority: item.remoteName || (<any>item).remoteAuthority, // TODO@roblou - delete after remote extensions are updated
|
||||
hideByDefault: initialVisibility === InitialVisibility.Hidden
|
||||
hideByDefault: initialVisibility === InitialVisibility.Hidden,
|
||||
workspace: viewContainer?.id === REMOTE ? true : undefined
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters';
|
||||
import { CommandsRegistry, ICommandService, ICommandHandler } from 'vs/platform/commands/common/commands';
|
||||
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
@@ -12,7 +12,6 @@ import { IWorkspacesService, IRecent } from 'vs/platform/workspaces/common/works
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { IViewDescriptorService, IViewsService, ViewVisibilityState } from 'vs/workbench/common/views';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// The following commands are registered on both sides separately.
|
||||
@@ -128,12 +127,6 @@ CommandsRegistry.registerCommand('_extensionTests.setLogLevel', function (access
|
||||
}
|
||||
});
|
||||
|
||||
CommandsRegistry.registerCommand('_workbench.openExternal', function (accessor: ServicesAccessor, uri: UriComponents, options: { allowTunneling?: boolean }) {
|
||||
// TODO: discuss martin, ben where to put this
|
||||
const openerService = accessor.get(IOpenerService);
|
||||
openerService.open(URI.revive(uri), options);
|
||||
});
|
||||
|
||||
|
||||
CommandsRegistry.registerCommand('_extensionTests.getLogLevel', function (accessor: ServicesAccessor) {
|
||||
const logService = accessor.get(ILogService);
|
||||
|
||||
38
lib/vscode/src/vs/workbench/api/common/exHostSecretState.ts
Normal file
38
lib/vscode/src/vs/workbench/api/common/exHostSecretState.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ExtHostSecretStateShape, MainContext, MainThreadSecretStateShape } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
||||
export class ExtHostSecretState implements ExtHostSecretStateShape {
|
||||
private _proxy: MainThreadSecretStateShape;
|
||||
private _onDidChangePassword = new Emitter<{ extensionId: string, key: string }>();
|
||||
readonly onDidChangePassword = this._onDidChangePassword.event;
|
||||
|
||||
constructor(mainContext: IExtHostRpcService) {
|
||||
this._proxy = mainContext.getProxy(MainContext.MainThreadSecretState);
|
||||
}
|
||||
|
||||
async $onDidChangePassword(e: { extensionId: string, key: string }): Promise<void> {
|
||||
this._onDidChangePassword.fire(e);
|
||||
}
|
||||
|
||||
get(extensionId: string, key: string): Promise<string | undefined> {
|
||||
return this._proxy.$getPassword(extensionId, key);
|
||||
}
|
||||
|
||||
store(extensionId: string, key: string, value: string): Promise<void> {
|
||||
return this._proxy.$setPassword(extensionId, key, value);
|
||||
}
|
||||
|
||||
delete(extensionId: string, key: string): Promise<void> {
|
||||
return this._proxy.$deletePassword(extensionId, key);
|
||||
}
|
||||
}
|
||||
|
||||
export interface IExtHostSecretState extends ExtHostSecretState { }
|
||||
export const IExtHostSecretState = createDecorator<IExtHostSecretState>('IExtHostSecretState');
|
||||
@@ -82,6 +82,9 @@ import { ExtHostWebviewPanels } from 'vs/workbench/api/common/extHostWebviewPane
|
||||
import { ExtHostBulkEdits } from 'vs/workbench/api/common/extHostBulkEdits';
|
||||
import { IExtHostFileSystemInfo } from 'vs/workbench/api/common/extHostFileSystemInfo';
|
||||
import { ExtHostTesting } from 'vs/workbench/api/common/extHostTesting';
|
||||
import { ExtHostUriOpeners } from 'vs/workbench/api/common/extHostUriOpener';
|
||||
import { IExtHostSecretState } from 'vs/workbench/api/common/exHostSecretState';
|
||||
import { ExtHostEditorTabs } from 'vs/workbench/api/common/extHostEditorTabs';
|
||||
|
||||
export interface IExtensionApiFactory {
|
||||
(extension: IExtensionDescription, registry: ExtensionDescriptionRegistry, configProvider: ExtHostConfigProvider): typeof vscode;
|
||||
@@ -107,6 +110,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
const extHostTunnelService = accessor.get(IExtHostTunnelService);
|
||||
const extHostApiDeprecation = accessor.get(IExtHostApiDeprecationService);
|
||||
const extHostWindow = accessor.get(IExtHostWindow);
|
||||
const extHostSecretState = accessor.get(IExtHostSecretState);
|
||||
|
||||
// register addressable instances
|
||||
rpcProtocol.set(ExtHostContext.ExtHostFileSystemInfo, extHostFileSystemInfo);
|
||||
@@ -117,6 +121,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
rpcProtocol.set(ExtHostContext.ExtHostStorage, extHostStorage);
|
||||
rpcProtocol.set(ExtHostContext.ExtHostTunnelService, extHostTunnelService);
|
||||
rpcProtocol.set(ExtHostContext.ExtHostWindow, extHostWindow);
|
||||
rpcProtocol.set(ExtHostContext.ExtHostSecretState, extHostSecretState);
|
||||
|
||||
// automatically create and register addressable instances
|
||||
const extHostDecorations = rpcProtocol.set(ExtHostContext.ExtHostDecorations, accessor.get(IExtHostDecorations));
|
||||
@@ -129,6 +134,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
const extHostOutputService = rpcProtocol.set(ExtHostContext.ExtHostOutputService, accessor.get(IExtHostOutputService));
|
||||
|
||||
// manually create and register addressable instances
|
||||
const extHostEditorTabs = rpcProtocol.set(ExtHostContext.ExtHostEditorTabs, new ExtHostEditorTabs());
|
||||
const extHostUrls = rpcProtocol.set(ExtHostContext.ExtHostUrls, new ExtHostUrls(rpcProtocol));
|
||||
const extHostDocuments = rpcProtocol.set(ExtHostContext.ExtHostDocuments, new ExtHostDocuments(rpcProtocol, extHostDocumentsAndEditors));
|
||||
const extHostDocumentContentProviders = rpcProtocol.set(ExtHostContext.ExtHostDocumentContentProviders, new ExtHostDocumentContentProvider(rpcProtocol, extHostDocumentsAndEditors, extHostLogService));
|
||||
@@ -154,6 +160,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
const extHostCustomEditors = rpcProtocol.set(ExtHostContext.ExtHostCustomEditors, new ExtHostCustomEditors(rpcProtocol, extHostDocuments, extensionStoragePaths, extHostWebviews, extHostWebviewPanels));
|
||||
const extHostWebviewViews = rpcProtocol.set(ExtHostContext.ExtHostWebviewViews, new ExtHostWebviewViews(rpcProtocol, extHostWebviews));
|
||||
const extHostTesting = rpcProtocol.set(ExtHostContext.ExtHostTesting, new ExtHostTesting(rpcProtocol, extHostDocumentsAndEditors, extHostWorkspace));
|
||||
const extHostUriOpeners = rpcProtocol.set(ExtHostContext.ExtHostUriOpeners, new ExtHostUriOpeners(rpcProtocol));
|
||||
|
||||
// Check that no named customers are missing
|
||||
const expected: ProxyIdentifier<any>[] = values(ExtHostContext);
|
||||
@@ -209,22 +216,14 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
get onDidChangeSessions(): Event<vscode.AuthenticationSessionsChangeEvent> {
|
||||
return extHostAuthentication.onDidChangeSessions;
|
||||
},
|
||||
registerAuthenticationProvider(provider: vscode.AuthenticationProvider): vscode.Disposable {
|
||||
registerAuthenticationProvider(id: string, label: string, provider: vscode.AuthenticationProvider, options?: vscode.AuthenticationProviderOptions): vscode.Disposable {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostAuthentication.registerAuthenticationProvider(provider);
|
||||
return extHostAuthentication.registerAuthenticationProvider(id, label, provider, options);
|
||||
},
|
||||
get onDidChangeAuthenticationProviders(): Event<vscode.AuthenticationProvidersChangeEvent> {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostAuthentication.onDidChangeAuthenticationProviders;
|
||||
},
|
||||
getProviderIds(): Thenable<ReadonlyArray<string>> {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostAuthentication.getProviderIds();
|
||||
},
|
||||
get providerIds(): string[] {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostAuthentication.providerIds;
|
||||
},
|
||||
get providers(): ReadonlyArray<vscode.AuthenticationProviderInformation> {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostAuthentication.providers;
|
||||
@@ -232,22 +231,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
logout(providerId: string, sessionId: string): Thenable<void> {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostAuthentication.logout(providerId, sessionId);
|
||||
},
|
||||
getPassword(key: string): Thenable<string | undefined> {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostAuthentication.getPassword(extension, key);
|
||||
},
|
||||
setPassword(key: string, value: string): Thenable<void> {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostAuthentication.setPassword(extension, key, value);
|
||||
},
|
||||
deletePassword(key: string): Thenable<void> {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostAuthentication.deletePassword(extension, key);
|
||||
},
|
||||
get onDidChangePassword(): Event<void> {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostAuthentication.onDidChangePassword;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -311,8 +294,11 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
get shell() {
|
||||
return extHostTerminalService.getDefaultShell(false, configProvider);
|
||||
},
|
||||
openExternal(uri: URI) {
|
||||
return extHostWindow.openUri(uri, { allowTunneling: !!initData.remote.authority });
|
||||
openExternal(uri: URI, options?: { allowContributedOpeners?: boolean | string; }) {
|
||||
return extHostWindow.openUri(uri, {
|
||||
allowTunneling: !!initData.remote.authority,
|
||||
allowContributedOpeners: options?.allowContributedOpeners,
|
||||
});
|
||||
},
|
||||
asExternalUri(uri: URI) {
|
||||
if (uri.scheme === initData.environment.appUriScheme) {
|
||||
@@ -354,6 +340,14 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostTesting.runTests(provider);
|
||||
},
|
||||
get onDidChangeTestResults() {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostTesting.onLastResultsChanged;
|
||||
},
|
||||
get testResults() {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostTesting.lastResults;
|
||||
},
|
||||
};
|
||||
|
||||
// namespace: extensions
|
||||
@@ -480,6 +474,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
getTokenInformationAtPosition(doc: vscode.TextDocument, pos: vscode.Position) {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostLanguages.tokenAtPosition(doc, pos);
|
||||
},
|
||||
registerInlineHintsProvider(selector: vscode.DocumentSelector, provider: vscode.InlineHintsProvider): vscode.Disposable {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostLanguageFeatures.registerInlineHintsProvider(extension, selector, provider);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -588,10 +586,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
id = extension.identifier.value;
|
||||
name = nls.localize('extensionLabel', "{0} (Extension)", extension.displayName || extension.name);
|
||||
alignment = alignmentOrOptions;
|
||||
priority = priority;
|
||||
}
|
||||
|
||||
return extHostStatusBar.createStatusBarEntry(id, name, alignment, priority, accessibilityInformation, extension);
|
||||
return extHostStatusBar.createStatusBarEntry(id, name, alignment, priority, accessibilityInformation);
|
||||
},
|
||||
setStatusBarMessage(text: string, timeoutOrThenable?: number | Thenable<any>): vscode.Disposable {
|
||||
return extHostStatusBar.setStatusBarMessage(text, timeoutOrThenable);
|
||||
@@ -691,6 +688,18 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
showNotebookDocument(document, options?) {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostNotebook.showNotebookDocument(document, options);
|
||||
},
|
||||
registerExternalUriOpener(id: string, opener: vscode.ExternalUriOpener, metadata: vscode.ExternalUriOpenerMetadata) {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostUriOpeners.registerExternalUriOpener(extension.identifier, id, opener, metadata);
|
||||
},
|
||||
get openEditors() {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostEditorTabs.tabs;
|
||||
},
|
||||
get onDidChangeOpenEditors() {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostEditorTabs.onDidChangeTabs;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1108,6 +1117,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
CallHierarchyIncomingCall: extHostTypes.CallHierarchyIncomingCall,
|
||||
CallHierarchyItem: extHostTypes.CallHierarchyItem,
|
||||
CallHierarchyOutgoingCall: extHostTypes.CallHierarchyOutgoingCall,
|
||||
CancellationError: errors.CancellationError,
|
||||
CancellationTokenSource: CancellationTokenSource,
|
||||
CodeAction: extHostTypes.CodeAction,
|
||||
CodeActionKind: extHostTypes.CodeActionKind,
|
||||
@@ -1148,6 +1158,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
EventEmitter: Emitter,
|
||||
ExtensionKind: extHostTypes.ExtensionKind,
|
||||
ExtensionMode: extHostTypes.ExtensionMode,
|
||||
ExternalUriOpenerPriority: extHostTypes.ExternalUriOpenerPriority,
|
||||
FileChangeType: extHostTypes.FileChangeType,
|
||||
FileDecoration: extHostTypes.FileDecoration,
|
||||
FileSystemError: extHostTypes.FileSystemError,
|
||||
@@ -1157,6 +1168,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
FunctionBreakpoint: extHostTypes.FunctionBreakpoint,
|
||||
Hover: extHostTypes.Hover,
|
||||
IndentAction: languageConfiguration.IndentAction,
|
||||
InlineHint: extHostTypes.InlineHint,
|
||||
Location: extHostTypes.Location,
|
||||
MarkdownString: extHostTypes.MarkdownString,
|
||||
OverviewRulerLane: OverviewRulerLane,
|
||||
|
||||
@@ -21,6 +21,7 @@ import { IExtHostApiDeprecationService, ExtHostApiDeprecationService, } from 'vs
|
||||
import { IExtHostWindow, ExtHostWindow } from 'vs/workbench/api/common/extHostWindow';
|
||||
import { IExtHostConsumerFileSystem, ExtHostConsumerFileSystem } from 'vs/workbench/api/common/extHostFileSystemConsumer';
|
||||
import { IExtHostFileSystemInfo, ExtHostFileSystemInfo } from 'vs/workbench/api/common/extHostFileSystemInfo';
|
||||
import { IExtHostSecretState, ExtHostSecretState } from 'vs/workbench/api/common/exHostSecretState';
|
||||
|
||||
registerSingleton(IExtensionStoragePaths, ExtensionStoragePaths);
|
||||
registerSingleton(IExtHostApiDeprecationService, ExtHostApiDeprecationService);
|
||||
@@ -39,3 +40,4 @@ registerSingleton(IExtHostTerminalService, WorkerExtHostTerminalService);
|
||||
registerSingleton(IExtHostTunnelService, ExtHostTunnelService);
|
||||
registerSingleton(IExtHostWindow, ExtHostWindow);
|
||||
registerSingleton(IExtHostWorkspace, ExtHostWorkspace);
|
||||
registerSingleton(IExtHostSecretState, ExtHostSecretState);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as performance from 'vs/base/common/performance';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { IRemoteConsoleLog } from 'vs/base/common/console';
|
||||
@@ -41,13 +42,13 @@ import { IRevealOptions, ITreeItem } from 'vs/workbench/common/views';
|
||||
import { IAdapterDescriptor, IConfig, IDebugSessionReplMode } from 'vs/workbench/contrib/debug/common/debug';
|
||||
import { ITextQueryBuilderOptions } from 'vs/workbench/contrib/search/common/queryBuilder';
|
||||
import { ITerminalDimensions, IShellLaunchConfig, ITerminalLaunchError } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { ActivationKind, ExtensionActivationError } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { ActivationKind, ExtensionActivationError, ExtensionHostKind } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { createExtHostContextProxyIdentifier as createExtId, createMainContextProxyIdentifier as createMainId, IRPCProtocol } from 'vs/workbench/services/extensions/common/proxyIdentifier';
|
||||
import * as search from 'vs/workbench/services/search/common/search';
|
||||
import { EditorGroupColumn, SaveReason } from 'vs/workbench/common/editor';
|
||||
import { ExtensionActivationReason } from 'vs/workbench/api/common/extHostExtensionActivator';
|
||||
import { TunnelDto } from 'vs/workbench/api/common/extHostTunnelService';
|
||||
import { TunnelCreationOptions, TunnelOptions } from 'vs/platform/remote/common/tunnel';
|
||||
import { TunnelCreationOptions, TunnelProviderFeatures, TunnelOptions } from 'vs/platform/remote/common/tunnel';
|
||||
import { Timeline, TimelineChangeEvent, TimelineOptions, TimelineProviderDescriptor, InternalTimelineOptions } from 'vs/workbench/contrib/timeline/common/timeline';
|
||||
import { revive } from 'vs/base/common/marshalling';
|
||||
import { IProcessedOutput, INotebookDisplayOrder, NotebookCellMetadata, NotebookDocumentMetadata, ICellEditOperation, NotebookCellsChangedEventDto, NotebookDataDto, IMainCellDto, INotebookDocumentFilter, INotebookKernelInfoDto2, TransientMetadata, INotebookCellStatusBarEntry, ICellRange, INotebookDecorationRenderOptions, INotebookExclusiveDocumentFilter } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
@@ -57,7 +58,8 @@ import { ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib
|
||||
import { DebugConfigurationProviderTriggerKind } from 'vs/workbench/api/common/extHostTypes';
|
||||
import { IAccessibilityInformation } from 'vs/platform/accessibility/common/accessibility';
|
||||
import { IExtensionIdWithVersion } from 'vs/platform/userDataSync/common/extensionsStorageSync';
|
||||
import { RunTestForProviderRequest, RunTestsRequest, RunTestsResult, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection';
|
||||
import { InternalTestItem, InternalTestResults, RunTestForProviderRequest, RunTestsRequest, RunTestsResult, TestIdWithProvider, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection';
|
||||
import { CandidatePort } from 'vs/workbench/services/remote/common/remoteExplorerService';
|
||||
|
||||
export interface IEnvironment {
|
||||
isExtensionDevelopmentDebug: boolean;
|
||||
@@ -108,7 +110,8 @@ export interface IConfigurationInitData extends IConfigurationData {
|
||||
}
|
||||
|
||||
export interface IExtHostContext extends IRPCProtocol {
|
||||
remoteAuthority: string | null;
|
||||
readonly remoteAuthority: string | null;
|
||||
readonly extensionHostKind: ExtensionHostKind;
|
||||
}
|
||||
|
||||
export interface IMainContext extends IRPCProtocol {
|
||||
@@ -174,7 +177,9 @@ export interface MainThreadAuthenticationShape extends IDisposable {
|
||||
$getSessions(providerId: string): Promise<ReadonlyArray<modes.AuthenticationSession>>;
|
||||
$login(providerId: string, scopes: string[]): Promise<modes.AuthenticationSession>;
|
||||
$logout(providerId: string, sessionId: string): Promise<void>;
|
||||
}
|
||||
|
||||
export interface MainThreadSecretStateShape extends IDisposable {
|
||||
$getPassword(extensionId: string, key: string): Promise<string | undefined>;
|
||||
$setPassword(extensionId: string, key: string, value: string): Promise<void>;
|
||||
$deletePassword(extensionId: string, key: string): Promise<void>;
|
||||
@@ -330,7 +335,7 @@ export interface IIndentationRuleDto {
|
||||
export interface IOnEnterRuleDto {
|
||||
beforeText: IRegExpDto;
|
||||
afterText?: IRegExpDto;
|
||||
oneLineAboveText?: IRegExpDto;
|
||||
previousLineText?: IRegExpDto;
|
||||
action: EnterAction;
|
||||
}
|
||||
export interface ILanguageConfigurationDto {
|
||||
@@ -397,6 +402,8 @@ export interface MainThreadLanguageFeaturesShape extends IDisposable {
|
||||
$registerDocumentRangeSemanticTokensProvider(handle: number, selector: IDocumentFilterDto[], legend: modes.SemanticTokensLegend): void;
|
||||
$registerSuggestSupport(handle: number, selector: IDocumentFilterDto[], triggerCharacters: string[], supportsResolveDetails: boolean, displayName: string): void;
|
||||
$registerSignatureHelpProvider(handle: number, selector: IDocumentFilterDto[], metadata: ISignatureHelpProviderMetadataDto): void;
|
||||
$registerInlineHintsProvider(handle: number, selector: IDocumentFilterDto[], eventHandle: number | undefined): void;
|
||||
$emitInlineHintsEvent(eventHandle: number, event?: any): void;
|
||||
$registerDocumentLinkProvider(handle: number, selector: IDocumentFilterDto[], supportsResolve: boolean): void;
|
||||
$registerDocumentColorProvider(handle: number, selector: IDocumentFilterDto[]): void;
|
||||
$registerFoldingRangeProvider(handle: number, selector: IDocumentFilterDto[], eventHandle: number | undefined): void;
|
||||
@@ -415,6 +422,7 @@ export interface MainThreadLanguagesShape extends IDisposable {
|
||||
export interface MainThreadMessageOptions {
|
||||
extension?: IExtensionDescription;
|
||||
modal?: boolean;
|
||||
useCustom?: boolean;
|
||||
}
|
||||
|
||||
export interface MainThreadMessageServiceShape extends IDisposable {
|
||||
@@ -438,6 +446,16 @@ export interface MainThreadProgressShape extends IDisposable {
|
||||
$progressEnd(handle: number): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* A terminal that is created on the extension host side is temporarily assigned
|
||||
* a UUID by the extension host that created it. Once the renderer side has assigned
|
||||
* a real numeric id, the numeric id will be used.
|
||||
*
|
||||
* All other terminals (that are not created on the extension host side) always
|
||||
* use the numeric id.
|
||||
*/
|
||||
export type TerminalIdentifier = number | string;
|
||||
|
||||
export interface TerminalLaunchConfig {
|
||||
name?: string;
|
||||
shellPath?: string;
|
||||
@@ -452,11 +470,11 @@ export interface TerminalLaunchConfig {
|
||||
}
|
||||
|
||||
export interface MainThreadTerminalServiceShape extends IDisposable {
|
||||
$createTerminal(config: TerminalLaunchConfig): Promise<{ id: number, name: string; }>;
|
||||
$dispose(terminalId: number): void;
|
||||
$hide(terminalId: number): void;
|
||||
$sendText(terminalId: number, text: string, addNewLine: boolean): void;
|
||||
$show(terminalId: number, preserveFocus: boolean): void;
|
||||
$createTerminal(extHostTerminalId: string, config: TerminalLaunchConfig): Promise<void>;
|
||||
$dispose(id: TerminalIdentifier): void;
|
||||
$hide(id: TerminalIdentifier): void;
|
||||
$sendText(id: TerminalIdentifier, text: string, addNewLine: boolean): void;
|
||||
$show(id: TerminalIdentifier, preserveFocus: boolean): void;
|
||||
$startSendingDataEvents(): void;
|
||||
$stopSendingDataEvents(): void;
|
||||
$startLinkProvider(): void;
|
||||
@@ -594,6 +612,24 @@ export interface ExtHostEditorInsetsShape {
|
||||
$onDidReceiveMessage(handle: number, message: any): void;
|
||||
}
|
||||
|
||||
//#region --- open editors model
|
||||
|
||||
export interface MainThreadEditorTabsShape extends IDisposable {
|
||||
// manage tabs: move, close, rearrange etc
|
||||
}
|
||||
|
||||
export interface IEditorTabDto {
|
||||
group: number;
|
||||
name: string;
|
||||
resource: UriComponents
|
||||
}
|
||||
|
||||
export interface IExtHostEditorTabsShape {
|
||||
$acceptEditorTabs(tabs: IEditorTabDto[]): void;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
export type WebviewHandle = string;
|
||||
|
||||
export interface WebviewPanelShowOptions {
|
||||
@@ -739,6 +775,7 @@ export enum NotebookEditorRevealType {
|
||||
Default = 0,
|
||||
InCenter = 1,
|
||||
InCenterIfOutsideViewport = 2,
|
||||
AtTop = 3
|
||||
}
|
||||
|
||||
export interface INotebookDocumentShowOptions {
|
||||
@@ -786,6 +823,16 @@ export interface ExtHostUrlsShape {
|
||||
$handleExternalUri(handle: number, uri: UriComponents): Promise<void>;
|
||||
}
|
||||
|
||||
export interface MainThreadUriOpenersShape extends IDisposable {
|
||||
$registerUriOpener(id: string, schemes: readonly string[], extensionId: ExtensionIdentifier, label: string): Promise<void>;
|
||||
$unregisterUriOpener(id: string): Promise<void>;
|
||||
}
|
||||
|
||||
export interface ExtHostUriOpenersShape {
|
||||
$canOpenUri(id: string, uri: UriComponents, token: CancellationToken): Promise<modes.ExternalUriOpenerPriority>;
|
||||
$openUri(id: string, context: { resolvedUri: UriComponents, sourceUri: UriComponents }, token: CancellationToken): Promise<void>;
|
||||
}
|
||||
|
||||
export interface ITextSearchComplete {
|
||||
limitHit?: boolean;
|
||||
}
|
||||
@@ -853,6 +900,7 @@ export interface MainThreadExtensionServiceShape extends IDisposable {
|
||||
$onExtensionActivationError(extensionId: ExtensionIdentifier, error: ExtensionActivationError): Promise<void>;
|
||||
$onExtensionRuntimeError(extensionId: ExtensionIdentifier, error: SerializedError): void;
|
||||
$onExtensionHostExit(code: number): Promise<void>;
|
||||
$setPerformanceMarks(marks: performance.PerformanceMark[]): Promise<void>;
|
||||
}
|
||||
|
||||
export interface SCMProviderFeatures {
|
||||
@@ -946,6 +994,7 @@ export interface MainThreadDebugServiceShape extends IDisposable {
|
||||
|
||||
export interface IOpenUriOptions {
|
||||
readonly allowTunneling?: boolean;
|
||||
readonly allowContributedOpeners?: boolean | string;
|
||||
}
|
||||
|
||||
export interface MainThreadWindowShape extends IDisposable {
|
||||
@@ -958,8 +1007,9 @@ export interface MainThreadTunnelServiceShape extends IDisposable {
|
||||
$openTunnel(tunnelOptions: TunnelOptions, source: string | undefined): Promise<TunnelDto | undefined>;
|
||||
$closeTunnel(remote: { host: string, port: number }): Promise<void>;
|
||||
$getTunnels(): Promise<TunnelDescription[]>;
|
||||
$setTunnelProvider(): Promise<void>;
|
||||
$tunnelServiceReady(): Promise<void>;
|
||||
$setTunnelProvider(features: TunnelProviderFeatures): Promise<void>;
|
||||
$setCandidateFinder(): Promise<void>;
|
||||
$setCandidateFilter(): Promise<void>;
|
||||
$onFoundNewCandidates(candidates: { host: string, port: number, detail: string }[]): Promise<void>;
|
||||
}
|
||||
|
||||
@@ -1052,7 +1102,7 @@ export interface ExtHostTreeViewsShape {
|
||||
$setSelection(treeViewId: string, treeItemHandles: string[]): void;
|
||||
$setVisible(treeViewId: string, visible: boolean): void;
|
||||
$hasResolve(treeViewId: string): Promise<boolean>;
|
||||
$resolve(treeViewId: string, treeItemHandle: string): Promise<ITreeItem | undefined>;
|
||||
$resolve(treeViewId: string, treeItemHandle: string, token: CancellationToken): Promise<ITreeItem | undefined>;
|
||||
}
|
||||
|
||||
export interface ExtHostWorkspaceShape {
|
||||
@@ -1094,7 +1144,10 @@ export interface ExtHostAuthenticationShape {
|
||||
$onDidChangeAuthenticationSessions(id: string, label: string, event: modes.AuthenticationSessionsChangeEvent): Promise<void>;
|
||||
$onDidChangeAuthenticationProviders(added: modes.AuthenticationProviderInformation[], removed: modes.AuthenticationProviderInformation[]): Promise<void>;
|
||||
$setProviders(providers: modes.AuthenticationProviderInformation[]): Promise<void>;
|
||||
$onDidChangePassword(): Promise<void>;
|
||||
}
|
||||
|
||||
export interface ExtHostSecretStateShape {
|
||||
$onDidChangePassword(e: { extensionId: string, key: string }): Promise<void>;
|
||||
}
|
||||
|
||||
export interface ExtHostSearchShape {
|
||||
@@ -1145,9 +1198,14 @@ export interface SourceTargetPair {
|
||||
target: UriComponents;
|
||||
}
|
||||
|
||||
export interface IWillRunFileOperationParticipation {
|
||||
edit: IWorkspaceEditDto;
|
||||
extensionNames: string[]
|
||||
}
|
||||
|
||||
export interface ExtHostFileSystemEventServiceShape {
|
||||
$onFileEvent(events: FileSystemEvents): void;
|
||||
$onWillRunFileOperation(operation: files.FileOperation, files: SourceTargetPair[], undoRedoGroupId: number | undefined, timeout: number, token: CancellationToken): Promise<any>;
|
||||
$onWillRunFileOperation(operation: files.FileOperation, files: SourceTargetPair[], timeout: number, token: CancellationToken): Promise<IWillRunFileOperationParticipation | undefined>;
|
||||
$onDidRunFileOperation(operation: files.FileOperation, files: SourceTargetPair[]): void;
|
||||
}
|
||||
|
||||
@@ -1252,6 +1310,18 @@ export interface ISignatureHelpContextDto {
|
||||
readonly activeSignatureHelp?: ISignatureHelpDto;
|
||||
}
|
||||
|
||||
export interface IInlineHintDto {
|
||||
text: string;
|
||||
range: IRange;
|
||||
hoverMessage?: string;
|
||||
whitespaceBefore?: boolean;
|
||||
whitespaceAfter?: boolean;
|
||||
}
|
||||
|
||||
export interface IInlineHintsDto {
|
||||
hints: IInlineHintDto[]
|
||||
}
|
||||
|
||||
export interface ILocationDto {
|
||||
uri: UriComponents;
|
||||
range: IRange;
|
||||
@@ -1441,6 +1511,7 @@ export interface ExtHostLanguageFeaturesShape {
|
||||
$releaseCompletionItems(handle: number, id: number): void;
|
||||
$provideSignatureHelp(handle: number, resource: UriComponents, position: IPosition, context: modes.SignatureHelpContext, token: CancellationToken): Promise<ISignatureHelpDto | undefined>;
|
||||
$releaseSignatureHelp(handle: number, id: number): void;
|
||||
$provideInlineHints(handle: number, resource: UriComponents, range: IRange, token: CancellationToken): Promise<IInlineHintsDto | undefined>
|
||||
$provideDocumentLinks(handle: number, resource: UriComponents, token: CancellationToken): Promise<ILinksListDto | undefined>;
|
||||
$resolveDocumentLink(handle: number, id: ChainedCacheId, token: CancellationToken): Promise<ILinkDto | undefined>;
|
||||
$releaseDocumentLinks(handle: number, id: number): void;
|
||||
@@ -1473,6 +1544,7 @@ export interface IShellLaunchConfigDto {
|
||||
cwd?: string | UriComponents;
|
||||
env?: { [key: string]: string | null; };
|
||||
hideFromUser?: boolean;
|
||||
flowControl?: boolean;
|
||||
}
|
||||
|
||||
export interface IShellDefinitionDto {
|
||||
@@ -1503,7 +1575,7 @@ export interface ITerminalDimensionsDto {
|
||||
|
||||
export interface ExtHostTerminalServiceShape {
|
||||
$acceptTerminalClosed(id: number, exitCode: number | undefined): void;
|
||||
$acceptTerminalOpened(id: number, name: string, shellLaunchConfig: IShellLaunchConfigDto): void;
|
||||
$acceptTerminalOpened(id: number, extHostTerminalId: string | undefined, name: string, shellLaunchConfig: IShellLaunchConfigDto): void;
|
||||
$acceptActiveTerminalChanged(id: number | null): void;
|
||||
$acceptTerminalProcessId(id: number, processId: number): void;
|
||||
$acceptTerminalProcessData(id: number, data: string): void;
|
||||
@@ -1512,6 +1584,7 @@ export interface ExtHostTerminalServiceShape {
|
||||
$acceptTerminalMaximumDimensions(id: number, cols: number, rows: number): void;
|
||||
$spawnExtHostProcess(id: number, shellLaunchConfig: IShellLaunchConfigDto, activeWorkspaceRootUri: UriComponents | undefined, cols: number, rows: number, isWorkspaceShellAllowed: boolean): Promise<ITerminalLaunchError | undefined>;
|
||||
$startExtensionTerminal(id: number, initialDimensions: ITerminalDimensionsDto | undefined): Promise<ITerminalLaunchError | undefined>;
|
||||
$acceptProcessAckDataEvent(id: number, charCount: number): void;
|
||||
$acceptProcessInput(id: number, data: string): void;
|
||||
$acceptProcessResize(id: number, cols: number, rows: number): void;
|
||||
$acceptProcessShutdown(id: number, immediate: boolean): void;
|
||||
@@ -1725,7 +1798,7 @@ export interface ExtHostNotebookShape {
|
||||
$saveNotebookAs(viewType: string, uri: UriComponents, target: UriComponents, token: CancellationToken): Promise<boolean>;
|
||||
$backup(viewType: string, uri: UriComponents, cancellation: CancellationToken): Promise<string | undefined>;
|
||||
$acceptDisplayOrder(displayOrder: INotebookDisplayOrder): void;
|
||||
$acceptNotebookActiveKernelChange(event: { uri: UriComponents, providerHandle: number | undefined, kernelId: string | undefined }): void;
|
||||
$acceptNotebookActiveKernelChange(event: { uri: UriComponents, providerHandle: number | undefined, kernelFriendlyId: string | undefined }): void;
|
||||
$onDidReceiveMessage(editorId: string, rendererId: string | undefined, message: unknown): void;
|
||||
$acceptModelChanged(uriComponents: UriComponents, event: NotebookCellsChangedEventDto, isDirty: boolean): void;
|
||||
$acceptModelSaved(uriComponents: UriComponents): void;
|
||||
@@ -1749,9 +1822,11 @@ export interface MainThreadThemingShape extends IDisposable {
|
||||
}
|
||||
|
||||
export interface ExtHostTunnelServiceShape {
|
||||
$forwardPort(tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions): Promise<TunnelDto> | undefined;
|
||||
$forwardPort(tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions): Promise<TunnelDto | undefined>;
|
||||
$closeTunnel(remote: { host: string, port: number }, silent?: boolean): Promise<void>;
|
||||
$onDidTunnelsChange(): Promise<void>;
|
||||
$registerCandidateFinder(enable: boolean): Promise<void>;
|
||||
$applyCandidateFilter(candidates: CandidatePort[]): Promise<CandidatePort[]>;
|
||||
}
|
||||
|
||||
export interface ExtHostTimelineShape {
|
||||
@@ -1764,11 +1839,12 @@ export const enum ExtHostTestingResource {
|
||||
}
|
||||
|
||||
export interface ExtHostTestingShape {
|
||||
$runTestsForProvider(req: RunTestForProviderRequest): Promise<RunTestsResult>;
|
||||
$runTestsForProvider(req: RunTestForProviderRequest, token: CancellationToken): Promise<RunTestsResult>;
|
||||
$subscribeToTests(resource: ExtHostTestingResource, uri: UriComponents): void;
|
||||
$unsubscribeFromTests(resource: ExtHostTestingResource, uri: UriComponents): void;
|
||||
|
||||
$lookupTest(test: TestIdWithProvider): Promise<InternalTestItem | undefined>;
|
||||
$acceptDiff(resource: ExtHostTestingResource, uri: UriComponents, diff: TestsDiff): void;
|
||||
$publishTestResults(results: InternalTestResults): void;
|
||||
}
|
||||
|
||||
export interface MainThreadTestingShape {
|
||||
@@ -1777,7 +1853,7 @@ export interface MainThreadTestingShape {
|
||||
$subscribeToDiffs(resource: ExtHostTestingResource, uri: UriComponents): void;
|
||||
$unsubscribeFromDiffs(resource: ExtHostTestingResource, uri: UriComponents): void;
|
||||
$publishDiff(resource: ExtHostTestingResource, uri: UriComponents, diff: TestsDiff): void;
|
||||
$runTests(req: RunTestsRequest): Promise<RunTestsResult>;
|
||||
$runTests(req: RunTestsRequest, token: CancellationToken): Promise<RunTestsResult>;
|
||||
}
|
||||
|
||||
// --- proxy identifiers
|
||||
@@ -1798,6 +1874,7 @@ export const MainContext = {
|
||||
MainThreadDocumentContentProviders: createMainId<MainThreadDocumentContentProvidersShape>('MainThreadDocumentContentProviders'),
|
||||
MainThreadTextEditors: createMainId<MainThreadTextEditorsShape>('MainThreadTextEditors'),
|
||||
MainThreadEditorInsets: createMainId<MainThreadEditorInsetsShape>('MainThreadEditorInsets'),
|
||||
MainThreadEditorTabs: createMainId<MainThreadEditorTabsShape>('MainThreadEditorTabs'),
|
||||
MainThreadErrors: createMainId<MainThreadErrorsShape>('MainThreadErrors'),
|
||||
MainThreadTreeViews: createMainId<MainThreadTreeViewsShape>('MainThreadTreeViews'),
|
||||
MainThreadDownloadService: createMainId<MainThreadDownloadServiceShape>('MainThreadDownloadService'),
|
||||
@@ -1810,6 +1887,7 @@ export const MainContext = {
|
||||
MainThreadProgress: createMainId<MainThreadProgressShape>('MainThreadProgress'),
|
||||
MainThreadQuickOpen: createMainId<MainThreadQuickOpenShape>('MainThreadQuickOpen'),
|
||||
MainThreadStatusBar: createMainId<MainThreadStatusBarShape>('MainThreadStatusBar'),
|
||||
MainThreadSecretState: createMainId<MainThreadSecretStateShape>('MainThreadSecretState'),
|
||||
MainThreadStorage: createMainId<MainThreadStorageShape>('MainThreadStorage'),
|
||||
MainThreadTelemetry: createMainId<MainThreadTelemetryShape>('MainThreadTelemetry'),
|
||||
MainThreadTerminalService: createMainId<MainThreadTerminalServiceShape>('MainThreadTerminalService'),
|
||||
@@ -1818,6 +1896,7 @@ export const MainContext = {
|
||||
MainThreadWebviewViews: createMainId<MainThreadWebviewViewsShape>('MainThreadWebviewViews'),
|
||||
MainThreadCustomEditors: createMainId<MainThreadCustomEditorsShape>('MainThreadCustomEditors'),
|
||||
MainThreadUrls: createMainId<MainThreadUrlsShape>('MainThreadUrls'),
|
||||
MainThreadUriOpeners: createMainId<MainThreadUriOpenersShape>('MainThreadUriOpeners'),
|
||||
MainThreadWorkspace: createMainId<MainThreadWorkspaceShape>('MainThreadWorkspace'),
|
||||
MainThreadFileSystem: createMainId<MainThreadFileSystemShape>('MainThreadFileSystem'),
|
||||
MainThreadExtensionService: createMainId<MainThreadExtensionServiceShape>('MainThreadExtensionService'),
|
||||
@@ -1863,10 +1942,13 @@ export const ExtHostContext = {
|
||||
ExtHostCustomEditors: createExtId<ExtHostCustomEditorsShape>('ExtHostCustomEditors'),
|
||||
ExtHostWebviewViews: createExtId<ExtHostWebviewViewsShape>('ExtHostWebviewViews'),
|
||||
ExtHostEditorInsets: createExtId<ExtHostEditorInsetsShape>('ExtHostEditorInsets'),
|
||||
ExtHostEditorTabs: createExtId<IExtHostEditorTabsShape>('ExtHostEditorTabs'),
|
||||
ExtHostProgress: createMainId<ExtHostProgressShape>('ExtHostProgress'),
|
||||
ExtHostComments: createMainId<ExtHostCommentsShape>('ExtHostComments'),
|
||||
ExtHostSecretState: createMainId<ExtHostSecretStateShape>('ExtHostSecretState'),
|
||||
ExtHostStorage: createMainId<ExtHostStorageShape>('ExtHostStorage'),
|
||||
ExtHostUrls: createExtId<ExtHostUrlsShape>('ExtHostUrls'),
|
||||
ExtHostUriOpeners: createExtId<ExtHostUriOpenersShape>('ExtHostUriOpeners'),
|
||||
ExtHostOutputService: createMainId<ExtHostOutputServiceShape>('ExtHostOutputService'),
|
||||
ExtHosLabelService: createMainId<ExtHostLabelServiceShape>('ExtHostLabelService'),
|
||||
ExtHostNotebook: createMainId<ExtHostNotebookShape>('ExtHostNotebook'),
|
||||
|
||||
@@ -20,6 +20,8 @@ import { IRange } from 'vs/editor/common/core/range';
|
||||
import { IPosition } from 'vs/editor/common/core/position';
|
||||
import { TransientMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
import { ITextEditorOptions } from 'vs/platform/editor/common/editor';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { decodeSemanticTokensDto } from 'vs/editor/common/services/semanticTokensDto';
|
||||
|
||||
//#region --- NEW world
|
||||
|
||||
@@ -176,6 +178,57 @@ const newCommands: ApiCommand[] = [
|
||||
[ApiCommandArgument.Uri, ApiCommandArgument.Number.with('linkResolveCount', 'Number of links that should be resolved, only when links are unresolved.').optional()],
|
||||
new ApiCommandResult<modes.ILink[], vscode.DocumentLink[]>('A promise that resolves to an array of DocumentLink-instances.', value => value.map(typeConverters.DocumentLink.to))
|
||||
),
|
||||
// --- semantic tokens
|
||||
new ApiCommand(
|
||||
'vscode.provideDocumentSemanticTokensLegend', '_provideDocumentSemanticTokensLegend', 'Provide semantic tokens legend for a document',
|
||||
[ApiCommandArgument.Uri],
|
||||
new ApiCommandResult<modes.SemanticTokensLegend, types.SemanticTokensLegend | undefined>('A promise that resolves to SemanticTokensLegend.', value => {
|
||||
if (!value) {
|
||||
return undefined;
|
||||
}
|
||||
return new types.SemanticTokensLegend(value.tokenTypes, value.tokenModifiers);
|
||||
})
|
||||
),
|
||||
new ApiCommand(
|
||||
'vscode.provideDocumentSemanticTokens', '_provideDocumentSemanticTokens', 'Provide semantic tokens for a document',
|
||||
[ApiCommandArgument.Uri],
|
||||
new ApiCommandResult<VSBuffer, types.SemanticTokens | undefined>('A promise that resolves to SemanticTokens.', value => {
|
||||
if (!value) {
|
||||
return undefined;
|
||||
}
|
||||
const semanticTokensDto = decodeSemanticTokensDto(value);
|
||||
if (semanticTokensDto.type !== 'full') {
|
||||
// only accepting full semantic tokens from provideDocumentSemanticTokens
|
||||
return undefined;
|
||||
}
|
||||
return new types.SemanticTokens(semanticTokensDto.data, undefined);
|
||||
})
|
||||
),
|
||||
new ApiCommand(
|
||||
'vscode.provideDocumentRangeSemanticTokensLegend', '_provideDocumentRangeSemanticTokensLegend', 'Provide semantic tokens legend for a document range',
|
||||
[ApiCommandArgument.Uri],
|
||||
new ApiCommandResult<modes.SemanticTokensLegend, types.SemanticTokensLegend | undefined>('A promise that resolves to SemanticTokensLegend.', value => {
|
||||
if (!value) {
|
||||
return undefined;
|
||||
}
|
||||
return new types.SemanticTokensLegend(value.tokenTypes, value.tokenModifiers);
|
||||
})
|
||||
),
|
||||
new ApiCommand(
|
||||
'vscode.provideDocumentRangeSemanticTokens', '_provideDocumentRangeSemanticTokens', 'Provide semantic tokens for a document range',
|
||||
[ApiCommandArgument.Uri, ApiCommandArgument.Range],
|
||||
new ApiCommandResult<VSBuffer, types.SemanticTokens | undefined>('A promise that resolves to SemanticTokens.', value => {
|
||||
if (!value) {
|
||||
return undefined;
|
||||
}
|
||||
const semanticTokensDto = decodeSemanticTokensDto(value);
|
||||
if (semanticTokensDto.type !== 'full') {
|
||||
// only accepting full semantic tokens from provideDocumentRangeSemanticTokens
|
||||
return undefined;
|
||||
}
|
||||
return new types.SemanticTokens(semanticTokensDto.data, undefined);
|
||||
})
|
||||
),
|
||||
// --- completions
|
||||
new ApiCommand(
|
||||
'vscode.executeCompletionItemProvider', '_executeCompletionItemProvider', 'Execute completion item provider.',
|
||||
@@ -271,13 +324,21 @@ const newCommands: ApiCommand[] = [
|
||||
return [];
|
||||
})
|
||||
),
|
||||
// --- inline hints
|
||||
new ApiCommand(
|
||||
'vscode.executeInlineHintProvider', '_executeInlineHintProvider', 'Execute inline hints provider',
|
||||
[ApiCommandArgument.Uri, ApiCommandArgument.Range],
|
||||
new ApiCommandResult<modes.InlineHint[], vscode.InlineHint[]>('A promise that resolves to an array of InlineHint objects', result => {
|
||||
return result.map(typeConverters.InlineHint.to);
|
||||
})
|
||||
),
|
||||
// --- notebooks
|
||||
new ApiCommand(
|
||||
'vscode.resolveNotebookContentProviders', '_resolveNotebookContentProvider', 'Resolve Notebook Content Providers',
|
||||
[
|
||||
new ApiCommandArgument<string, string>('viewType', '', v => typeof v === 'string', v => v),
|
||||
new ApiCommandArgument<string, string>('displayName', '', v => typeof v === 'string', v => v),
|
||||
new ApiCommandArgument<object, object>('options', '', v => typeof v === 'object', v => v),
|
||||
// new ApiCommandArgument<string, string>('viewType', '', v => typeof v === 'string', v => v),
|
||||
// new ApiCommandArgument<string, string>('displayName', '', v => typeof v === 'string', v => v),
|
||||
// new ApiCommandArgument<object, object>('options', '', v => typeof v === 'object', v => v),
|
||||
],
|
||||
new ApiCommandResult<{
|
||||
viewType: string;
|
||||
|
||||
@@ -15,11 +15,15 @@ interface GetSessionsRequest {
|
||||
result: Promise<vscode.AuthenticationSession | undefined>;
|
||||
}
|
||||
|
||||
interface ProviderWithMetadata {
|
||||
label: string;
|
||||
provider: vscode.AuthenticationProvider;
|
||||
options: vscode.AuthenticationProviderOptions;
|
||||
}
|
||||
|
||||
export class ExtHostAuthentication implements ExtHostAuthenticationShape {
|
||||
private _proxy: MainThreadAuthenticationShape;
|
||||
private _authenticationProviders: Map<string, vscode.AuthenticationProvider> = new Map<string, vscode.AuthenticationProvider>();
|
||||
|
||||
private _providerIds: string[] = [];
|
||||
private _authenticationProviders: Map<string, ProviderWithMetadata> = new Map<string, ProviderWithMetadata>();
|
||||
|
||||
private _providers: vscode.AuthenticationProviderInformation[] = [];
|
||||
|
||||
@@ -29,9 +33,6 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape {
|
||||
private _onDidChangeSessions = new Emitter<vscode.AuthenticationSessionsChangeEvent>();
|
||||
readonly onDidChangeSessions: Event<vscode.AuthenticationSessionsChangeEvent> = this._onDidChangeSessions.event;
|
||||
|
||||
private _onDidChangePassword = new Emitter<void>();
|
||||
readonly onDidChangePassword: Event<void> = this._onDidChangePassword.event;
|
||||
|
||||
private _inFlightRequests = new Map<string, GetSessionsRequest[]>();
|
||||
|
||||
constructor(mainContext: IMainContext) {
|
||||
@@ -43,14 +44,6 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
getProviderIds(): Promise<ReadonlyArray<string>> {
|
||||
return this._proxy.$getProviderIds();
|
||||
}
|
||||
|
||||
get providerIds(): string[] {
|
||||
return this._providerIds;
|
||||
}
|
||||
|
||||
get providers(): ReadonlyArray<vscode.AuthenticationProviderInformation> {
|
||||
return Object.freeze(this._providers.slice());
|
||||
}
|
||||
@@ -90,128 +83,120 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape {
|
||||
|
||||
private async _getSession(requestingExtension: IExtensionDescription, extensionId: string, providerId: string, scopes: string[], options: vscode.AuthenticationGetSessionOptions = {}): Promise<vscode.AuthenticationSession | undefined> {
|
||||
await this._proxy.$ensureProvider(providerId);
|
||||
const provider = this._authenticationProviders.get(providerId);
|
||||
const providerData = this._authenticationProviders.get(providerId);
|
||||
const extensionName = requestingExtension.displayName || requestingExtension.name;
|
||||
|
||||
if (!provider) {
|
||||
if (!providerData) {
|
||||
return this._proxy.$getSession(providerId, scopes, extensionId, extensionName, options);
|
||||
}
|
||||
|
||||
const orderedScopes = scopes.sort().join(' ');
|
||||
const sessions = (await provider.getSessions()).filter(session => session.scopes.slice().sort().join(' ') === orderedScopes);
|
||||
const sessions = (await providerData.provider.getSessions()).filter(session => session.scopes.slice().sort().join(' ') === orderedScopes);
|
||||
|
||||
let session: vscode.AuthenticationSession | undefined = undefined;
|
||||
if (sessions.length) {
|
||||
if (!provider.supportsMultipleAccounts) {
|
||||
if (!providerData.options.supportsMultipleAccounts) {
|
||||
session = sessions[0];
|
||||
const allowed = await this._proxy.$getSessionsPrompt(providerId, session.account.label, provider.label, extensionId, extensionName);
|
||||
const allowed = await this._proxy.$getSessionsPrompt(providerId, session.account.label, providerData.label, extensionId, extensionName);
|
||||
if (!allowed) {
|
||||
throw new Error('User did not consent to login.');
|
||||
}
|
||||
} else {
|
||||
// On renderer side, confirm consent, ask user to choose between accounts if multiple sessions are valid
|
||||
const selected = await this._proxy.$selectSession(providerId, provider.label, extensionId, extensionName, sessions, scopes, !!options.clearSessionPreference);
|
||||
const selected = await this._proxy.$selectSession(providerId, providerData.label, extensionId, extensionName, sessions, scopes, !!options.clearSessionPreference);
|
||||
session = sessions.find(session => session.id === selected.id);
|
||||
}
|
||||
|
||||
} else {
|
||||
if (options.createIfNone) {
|
||||
const isAllowed = await this._proxy.$loginPrompt(provider.label, extensionName);
|
||||
const isAllowed = await this._proxy.$loginPrompt(providerData.label, extensionName);
|
||||
if (!isAllowed) {
|
||||
throw new Error('User did not consent to login.');
|
||||
}
|
||||
|
||||
session = await provider.login(scopes);
|
||||
session = await providerData.provider.login(scopes);
|
||||
await this._proxy.$setTrustedExtensionAndAccountPreference(providerId, session.account.label, extensionId, extensionName, session.id);
|
||||
} else {
|
||||
await this._proxy.$requestNewSession(providerId, scopes, extensionId, extensionName);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
async logout(providerId: string, sessionId: string): Promise<void> {
|
||||
const provider = this._authenticationProviders.get(providerId);
|
||||
if (!provider) {
|
||||
const providerData = this._authenticationProviders.get(providerId);
|
||||
if (!providerData) {
|
||||
return this._proxy.$logout(providerId, sessionId);
|
||||
}
|
||||
|
||||
return provider.logout(sessionId);
|
||||
return providerData.provider.logout(sessionId);
|
||||
}
|
||||
|
||||
registerAuthenticationProvider(provider: vscode.AuthenticationProvider): vscode.Disposable {
|
||||
if (this._authenticationProviders.get(provider.id)) {
|
||||
throw new Error(`An authentication provider with id '${provider.id}' is already registered.`);
|
||||
registerAuthenticationProvider(id: string, label: string, provider: vscode.AuthenticationProvider, options?: vscode.AuthenticationProviderOptions): vscode.Disposable {
|
||||
if (this._authenticationProviders.get(id)) {
|
||||
throw new Error(`An authentication provider with id '${id}' is already registered.`);
|
||||
}
|
||||
|
||||
this._authenticationProviders.set(provider.id, provider);
|
||||
if (!this._providerIds.includes(provider.id)) {
|
||||
this._providerIds.push(provider.id);
|
||||
}
|
||||
this._authenticationProviders.set(id, { label, provider, options: options ?? { supportsMultipleAccounts: false } });
|
||||
|
||||
if (!this._providers.find(p => p.id === provider.id)) {
|
||||
if (!this._providers.find(p => p.id === id)) {
|
||||
this._providers.push({
|
||||
id: provider.id,
|
||||
label: provider.label
|
||||
id: id,
|
||||
label: label
|
||||
});
|
||||
}
|
||||
|
||||
const listener = provider.onDidChangeSessions(e => {
|
||||
this._proxy.$sendDidChangeSessions(provider.id, e);
|
||||
this._proxy.$sendDidChangeSessions(id, e);
|
||||
});
|
||||
|
||||
this._proxy.$registerAuthenticationProvider(provider.id, provider.label, provider.supportsMultipleAccounts);
|
||||
this._proxy.$registerAuthenticationProvider(id, label, options?.supportsMultipleAccounts ?? false);
|
||||
|
||||
return new Disposable(() => {
|
||||
listener.dispose();
|
||||
this._authenticationProviders.delete(provider.id);
|
||||
const index = this._providerIds.findIndex(id => id === provider.id);
|
||||
if (index > -1) {
|
||||
this._providerIds.splice(index);
|
||||
}
|
||||
this._authenticationProviders.delete(id);
|
||||
|
||||
const i = this._providers.findIndex(p => p.id === provider.id);
|
||||
const i = this._providers.findIndex(p => p.id === id);
|
||||
if (i > -1) {
|
||||
this._providers.splice(i);
|
||||
}
|
||||
|
||||
this._proxy.$unregisterAuthenticationProvider(provider.id);
|
||||
this._proxy.$unregisterAuthenticationProvider(id);
|
||||
});
|
||||
}
|
||||
|
||||
$login(providerId: string, scopes: string[]): Promise<modes.AuthenticationSession> {
|
||||
const authProvider = this._authenticationProviders.get(providerId);
|
||||
if (authProvider) {
|
||||
return Promise.resolve(authProvider.login(scopes));
|
||||
const providerData = this._authenticationProviders.get(providerId);
|
||||
if (providerData) {
|
||||
return Promise.resolve(providerData.provider.login(scopes));
|
||||
}
|
||||
|
||||
throw new Error(`Unable to find authentication provider with handle: ${providerId}`);
|
||||
}
|
||||
|
||||
$logout(providerId: string, sessionId: string): Promise<void> {
|
||||
const authProvider = this._authenticationProviders.get(providerId);
|
||||
if (authProvider) {
|
||||
return Promise.resolve(authProvider.logout(sessionId));
|
||||
const providerData = this._authenticationProviders.get(providerId);
|
||||
if (providerData) {
|
||||
return Promise.resolve(providerData.provider.logout(sessionId));
|
||||
}
|
||||
|
||||
throw new Error(`Unable to find authentication provider with handle: ${providerId}`);
|
||||
}
|
||||
|
||||
$getSessions(providerId: string): Promise<ReadonlyArray<modes.AuthenticationSession>> {
|
||||
const authProvider = this._authenticationProviders.get(providerId);
|
||||
if (authProvider) {
|
||||
return Promise.resolve(authProvider.getSessions());
|
||||
const providerData = this._authenticationProviders.get(providerId);
|
||||
if (providerData) {
|
||||
return Promise.resolve(providerData.provider.getSessions());
|
||||
}
|
||||
|
||||
throw new Error(`Unable to find authentication provider with handle: ${providerId}`);
|
||||
}
|
||||
|
||||
async $getSessionAccessToken(providerId: string, sessionId: string): Promise<string> {
|
||||
const authProvider = this._authenticationProviders.get(providerId);
|
||||
if (authProvider) {
|
||||
const sessions = await authProvider.getSessions();
|
||||
const providerData = this._authenticationProviders.get(providerId);
|
||||
if (providerData) {
|
||||
const sessions = await providerData.provider.getSessions();
|
||||
const session = sessions.find(session => session.id === sessionId);
|
||||
if (session) {
|
||||
return session.accessToken;
|
||||
@@ -245,23 +230,4 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape {
|
||||
this._onDidChangeAuthenticationProviders.fire({ added, removed });
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
async $onDidChangePassword(): Promise<void> {
|
||||
this._onDidChangePassword.fire();
|
||||
}
|
||||
|
||||
getPassword(requestingExtension: IExtensionDescription, key: string): Promise<string | undefined> {
|
||||
const extensionId = ExtensionIdentifier.toKey(requestingExtension.identifier);
|
||||
return this._proxy.$getPassword(extensionId, key);
|
||||
}
|
||||
|
||||
setPassword(requestingExtension: IExtensionDescription, key: string, value: string): Promise<void> {
|
||||
const extensionId = ExtensionIdentifier.toKey(requestingExtension.identifier);
|
||||
return this._proxy.$setPassword(extensionId, key, value);
|
||||
}
|
||||
|
||||
deletePassword(requestingExtension: IExtensionDescription, key: string): Promise<void> {
|
||||
const extensionId = ExtensionIdentifier.toKey(requestingExtension.identifier);
|
||||
return this._proxy.$deletePassword(extensionId, key);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ export class ExtHostCommands implements ExtHostCommandsShape {
|
||||
|
||||
const internalArgs = apiCommand.args.map((arg, i) => {
|
||||
if (!arg.validate(apiArgs[i])) {
|
||||
throw new Error(`Invalid argument '${arg.name}' when running '${apiCommand.id}', receieved: ${apiArgs[i]}`);
|
||||
throw new Error(`Invalid argument '${arg.name}' when running '${apiCommand.id}', received: ${apiArgs[i]}`);
|
||||
}
|
||||
return arg.convert(apiArgs[i]);
|
||||
});
|
||||
@@ -194,7 +194,7 @@ export class ExtHostCommands implements ExtHostCommandsShape {
|
||||
}
|
||||
}
|
||||
|
||||
private _executeContributedCommand<T>(id: string, args: any[]): Promise<T> {
|
||||
private async _executeContributedCommand<T>(id: string, args: any[]): Promise<T> {
|
||||
const command = this._commands.get(id);
|
||||
if (!command) {
|
||||
throw new Error('Unknown command');
|
||||
@@ -205,17 +205,16 @@ export class ExtHostCommands implements ExtHostCommandsShape {
|
||||
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}`));
|
||||
throw 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);
|
||||
return await callback.apply(thisArg, args);
|
||||
} catch (err) {
|
||||
this._logService.error(err, id);
|
||||
return Promise.reject(new Error(`Running the contributed command: '${id}' failed.`));
|
||||
throw new Error(`Running the contributed command: '${id}' failed.`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import * as modes from 'vs/editor/common/modes';
|
||||
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments';
|
||||
import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths';
|
||||
import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters';
|
||||
import { ExtHostWebviews, toExtensionData } from 'vs/workbench/api/common/extHostWebview';
|
||||
import { ExtHostWebviewPanels } from 'vs/workbench/api/common/extHostWebviewPanels';
|
||||
import { EditorGroupColumn } from 'vs/workbench/common/editor';
|
||||
@@ -178,7 +179,7 @@ export class ExtHostCustomEditors implements extHostProtocol.ExtHostCustomEditor
|
||||
options: { webviewOptions?: vscode.WebviewPanelOptions, supportsMultipleEditorsPerDocument?: boolean },
|
||||
): vscode.Disposable {
|
||||
const disposables = new DisposableStore();
|
||||
if ('resolveCustomTextEditor' in provider) {
|
||||
if (isCustomTextEditorProvider(provider)) {
|
||||
disposables.add(this._editorProviders.addTextProvider(viewType, extension, provider));
|
||||
this._proxy.$registerTextEditorProvider(toExtensionData(extension), viewType, options.webviewOptions || {}, {
|
||||
supportsMove: !!provider.moveCustomTextEditor,
|
||||
@@ -208,7 +209,6 @@ export class ExtHostCustomEditors implements extHostProtocol.ExtHostCustomEditor
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
async $createCustomDocument(resource: UriComponents, viewType: string, backupId: string | undefined, cancellation: CancellationToken) {
|
||||
const entry = this._editorProviders.get(viewType);
|
||||
if (!entry) {
|
||||
@@ -261,8 +261,10 @@ export class ExtHostCustomEditors implements extHostProtocol.ExtHostCustomEditor
|
||||
throw new Error(`No provider found for '${viewType}'`);
|
||||
}
|
||||
|
||||
const viewColumn = typeConverters.ViewColumn.to(position);
|
||||
|
||||
const webview = this._extHostWebview.createNewWebview(handle, options, entry.extension);
|
||||
const panel = this._extHostWebviewPanels.createNewWebviewPanel(handle, viewType, title, position, options, webview);
|
||||
const panel = this._extHostWebviewPanels.createNewWebviewPanel(handle, viewType, title, viewColumn, options, webview);
|
||||
|
||||
const revivedResource = URI.revive(resource);
|
||||
|
||||
@@ -350,7 +352,6 @@ export class ExtHostCustomEditors implements extHostProtocol.ExtHostCustomEditor
|
||||
return backup.id;
|
||||
}
|
||||
|
||||
|
||||
private getCustomDocumentEntry(viewType: string, resource: UriComponents): CustomDocumentStoreEntry {
|
||||
const entry = this._documents.get(viewType, URI.revive(resource));
|
||||
if (!entry) {
|
||||
@@ -375,6 +376,9 @@ export class ExtHostCustomEditors implements extHostProtocol.ExtHostCustomEditor
|
||||
}
|
||||
}
|
||||
|
||||
function isCustomTextEditorProvider(provider: vscode.CustomReadonlyEditorProvider<vscode.CustomDocument> | vscode.CustomTextEditorProvider): provider is vscode.CustomTextEditorProvider {
|
||||
return typeof (provider as vscode.CustomTextEditorProvider).resolveCustomTextEditor === 'function';
|
||||
}
|
||||
|
||||
function isEditEvent(e: vscode.CustomDocumentContentChangeEvent | vscode.CustomDocumentEditEvent): e is vscode.CustomDocumentEditEvent {
|
||||
return typeof (e as vscode.CustomDocumentEditEvent).undo === 'function'
|
||||
|
||||
@@ -152,7 +152,7 @@ export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, E
|
||||
|
||||
const source = <any>src;
|
||||
|
||||
if (typeof source.sourceReference === 'number') {
|
||||
if (typeof source.sourceReference === 'number' && source.sourceReference > 0) {
|
||||
// src can be retrieved via DAP's "source" request
|
||||
|
||||
let debug = `debug:${encodeURIComponent(source.path || '')}`;
|
||||
|
||||
39
lib/vscode/src/vs/workbench/api/common/extHostEditorTabs.ts
Normal file
39
lib/vscode/src/vs/workbench/api/common/extHostEditorTabs.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import type * as vscode from 'vscode';
|
||||
import { IEditorTabDto, IExtHostEditorTabsShape } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
|
||||
|
||||
export interface IEditorTab {
|
||||
name: string;
|
||||
group: number;
|
||||
resource: vscode.Uri
|
||||
}
|
||||
|
||||
export class ExtHostEditorTabs implements IExtHostEditorTabsShape {
|
||||
|
||||
private readonly _onDidChangeTabs = new Emitter<void>();
|
||||
readonly onDidChangeTabs: Event<void> = this._onDidChangeTabs.event;
|
||||
|
||||
private _tabs: IEditorTab[] = [];
|
||||
|
||||
get tabs(): readonly IEditorTab[] {
|
||||
return this._tabs;
|
||||
}
|
||||
|
||||
$acceptEditorTabs(tabs: IEditorTabDto[]): void {
|
||||
this._tabs = tabs.map(dto => {
|
||||
return {
|
||||
name: dto.name,
|
||||
group: dto.group,
|
||||
resource: URI.revive(dto.resource)
|
||||
};
|
||||
});
|
||||
this._onDidChangeTabs.fire();
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import * as path from 'vs/base/common/path';
|
||||
import * as performance from 'vs/base/common/performance';
|
||||
import { originalFSPath, joinPath } from 'vs/base/common/resources';
|
||||
import { Barrier, timeout } from 'vs/base/common/async';
|
||||
import { dispose, toDisposable, DisposableStore, Disposable } from 'vs/base/common/lifecycle';
|
||||
@@ -35,6 +36,8 @@ import { IExtHostTunnelService } from 'vs/workbench/api/common/extHostTunnelServ
|
||||
import { IExtHostTerminalService } from 'vs/workbench/api/common/extHostTerminalService';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { IExtensionActivationHost, checkActivateWorkspaceContainsExtension } from 'vs/workbench/api/common/shared/workspaceContains';
|
||||
import { ExtHostSecretState, IExtHostSecretState } from 'vs/workbench/api/common/exHostSecretState';
|
||||
import { ExtensionSecrets } from 'vs/workbench/api/common/extHostSecrets';
|
||||
|
||||
interface ITestRunner {
|
||||
/** Old test runner API, as exported from `vscode/lib/testrunner` */
|
||||
@@ -50,7 +53,7 @@ export const IHostUtils = createDecorator<IHostUtils>('IHostUtils');
|
||||
|
||||
export interface IHostUtils {
|
||||
readonly _serviceBrand: undefined;
|
||||
exit(code?: number): void;
|
||||
exit(code: number): void;
|
||||
exists(path: string): Promise<boolean>;
|
||||
realpath(path: string): Promise<string>;
|
||||
}
|
||||
@@ -94,6 +97,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
|
||||
private readonly _readyToRunExtensions: Barrier;
|
||||
protected readonly _registry: ExtensionDescriptionRegistry;
|
||||
private readonly _storage: ExtHostStorage;
|
||||
private readonly _secretState: ExtHostSecretState;
|
||||
private readonly _storagePath: IExtensionStoragePaths;
|
||||
private readonly _activator: ExtensionsActivator;
|
||||
private _extensionPathIndex: Promise<TernarySearchTree<string, IExtensionDescription>> | null;
|
||||
@@ -115,7 +119,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
|
||||
@IExtHostInitDataService initData: IExtHostInitDataService,
|
||||
@IExtensionStoragePaths storagePath: IExtensionStoragePaths,
|
||||
@IExtHostTunnelService extHostTunnelService: IExtHostTunnelService,
|
||||
@IExtHostTerminalService extHostTerminalService: IExtHostTerminalService
|
||||
@IExtHostTerminalService extHostTerminalService: IExtHostTerminalService,
|
||||
) {
|
||||
super();
|
||||
this._hostUtils = hostUtils;
|
||||
@@ -138,10 +142,12 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
|
||||
this._readyToRunExtensions = new Barrier();
|
||||
this._registry = new ExtensionDescriptionRegistry(this._initData.extensions);
|
||||
this._storage = new ExtHostStorage(this._extHostContext);
|
||||
this._secretState = new ExtHostSecretState(this._extHostContext);
|
||||
this._storagePath = storagePath;
|
||||
|
||||
this._instaService = instaService.createChild(new ServiceCollection(
|
||||
[IExtHostStorage, this._storage]
|
||||
[IExtHostStorage, this._storage],
|
||||
[IExtHostSecretState, this._secretState]
|
||||
));
|
||||
|
||||
const hostExtensions = new Set<string>();
|
||||
@@ -184,6 +190,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
|
||||
this._almostReadyToRunExtensions.open();
|
||||
|
||||
await this._extHostWorkspace.waitForInitializeCall();
|
||||
performance.mark('code/extHost/ready');
|
||||
this._readyToStartExtensionHost.open();
|
||||
|
||||
if (this._initData.autoStart) {
|
||||
@@ -362,10 +369,18 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
|
||||
|
||||
const activationTimesBuilder = new ExtensionActivationTimesBuilder(reason.startup);
|
||||
return Promise.all([
|
||||
<<<<<<< HEAD
|
||||
this._loadCommonJSModule<IExtensionModule>(joinPath(extensionDescription.extensionLocation, entryPoint), activationTimesBuilder, !extensionDescription.browser),
|
||||
=======
|
||||
this._loadCommonJSModule<IExtensionModule>(extensionDescription.identifier, joinPath(extensionDescription.extensionLocation, entryPoint), activationTimesBuilder),
|
||||
>>>>>>> 89b6e0164fa770333755b11504e19a4232b1a2d4
|
||||
this._loadExtensionContext(extensionDescription)
|
||||
]).then(values => {
|
||||
performance.mark(`code/extHost/willActivateExtension/${extensionDescription.identifier.value}`);
|
||||
return AbstractExtHostExtensionService._callActivate(this._logService, extensionDescription.identifier, values[0], values[1], activationTimesBuilder);
|
||||
}).then((activatedExtension) => {
|
||||
performance.mark(`code/extHost/didActivateExtension/${extensionDescription.identifier.value}`);
|
||||
return activatedExtension;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -373,6 +388,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
|
||||
|
||||
const globalState = new ExtensionGlobalMemento(extensionDescription, this._storage);
|
||||
const workspaceState = new ExtensionMemento(extensionDescription.identifier.value, false, this._storage);
|
||||
const secrets = new ExtensionSecrets(extensionDescription, this._secretState);
|
||||
const extensionMode = extensionDescription.isUnderDevelopment
|
||||
? (this._initData.environment.extensionTestsLocationURI ? ExtensionMode.Test : ExtensionMode.Development)
|
||||
: ExtensionMode.Production;
|
||||
@@ -388,6 +404,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
|
||||
return Object.freeze<vscode.ExtensionContext>({
|
||||
globalState,
|
||||
workspaceState,
|
||||
secrets,
|
||||
subscriptions: [],
|
||||
get extensionUri() { return extensionDescription.extensionLocation; },
|
||||
get extensionPath() { return extensionDescription.extensionLocation.fsPath; },
|
||||
@@ -456,6 +473,9 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
|
||||
}
|
||||
|
||||
private _activateAllStartupFinished(): void {
|
||||
// startup is considered finished
|
||||
this._mainThreadExtensionsProxy.$setPerformanceMarks(performance.getMarks());
|
||||
|
||||
for (const desc of this._registry.getAllExtensionDescriptions()) {
|
||||
if (desc.activationEvents) {
|
||||
for (const activationEvent of desc.activationEvents) {
|
||||
@@ -541,7 +561,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
|
||||
let testRunner: ITestRunner | INewTestRunner | undefined;
|
||||
let requireError: Error | undefined;
|
||||
try {
|
||||
testRunner = await this._loadCommonJSModule(URI.file(extensionTestsPath), new ExtensionActivationTimesBuilder(false));
|
||||
testRunner = await this._loadCommonJSModule(null, URI.file(extensionTestsPath), new ExtensionActivationTimesBuilder(false));
|
||||
} catch (error) {
|
||||
requireError = error;
|
||||
}
|
||||
@@ -586,11 +606,17 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
|
||||
}
|
||||
|
||||
private _testRunnerExit(code: number): void {
|
||||
this._logService.info(`extension host terminating: test runner requested exit with code ${code}`);
|
||||
this._logService.flush();
|
||||
|
||||
// wait at most 5000ms for the renderer to confirm our exit request and for the renderer socket to drain
|
||||
// (this is to ensure all outstanding messages reach the renderer)
|
||||
const exitPromise = this._mainThreadExtensionsProxy.$onExtensionHostExit(code);
|
||||
const drainPromise = this._extHostContext.drain();
|
||||
Promise.race([Promise.all([exitPromise, drainPromise]), timeout(5000)]).then(() => {
|
||||
this._logService.info(`exiting with code ${code}`);
|
||||
this._logService.flush();
|
||||
|
||||
this._hostUtils.exit(code);
|
||||
});
|
||||
}
|
||||
@@ -644,7 +670,9 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
|
||||
}
|
||||
|
||||
try {
|
||||
performance.mark(`code/extHost/willResolveAuthority/${authorityPrefix}`);
|
||||
const result = await resolver.resolve(remoteAuthority, { resolveAttempt });
|
||||
performance.mark(`code/extHost/didResolveAuthorityOK/${authorityPrefix}`);
|
||||
this._disposables.add(await this._extHostTunnelService.setTunnelExtensionFunctions(resolver));
|
||||
|
||||
// Split merged API result into separate authority/options
|
||||
@@ -667,6 +695,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
|
||||
}
|
||||
};
|
||||
} catch (err) {
|
||||
performance.mark(`code/extHost/didResolveAuthorityError/${authorityPrefix}`);
|
||||
if (err instanceof RemoteAuthorityResolverError) {
|
||||
return {
|
||||
type: 'error',
|
||||
@@ -754,7 +783,11 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
|
||||
|
||||
protected abstract _beforeAlmostReadyToRunExtensions(): Promise<void>;
|
||||
protected abstract _getEntryPoint(extensionDescription: IExtensionDescription): string | undefined;
|
||||
<<<<<<< HEAD
|
||||
protected abstract _loadCommonJSModule<T>(module: URI, activationTimesBuilder: ExtensionActivationTimesBuilder, isRemote?: boolean): Promise<T>;
|
||||
=======
|
||||
protected abstract _loadCommonJSModule<T>(extensionId: ExtensionIdentifier | null, module: URI, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise<T>;
|
||||
>>>>>>> 89b6e0164fa770333755b11504e19a4232b1a2d4
|
||||
public abstract $setRemoteEnvironment(env: { [key: string]: string | null }): Promise<void>;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import { IRelativePattern, parse } from 'vs/base/common/glob';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors';
|
||||
import type * as vscode from 'vscode';
|
||||
import { ExtHostFileSystemEventServiceShape, FileSystemEvents, IMainContext, MainContext, SourceTargetPair, IWorkspaceEditDto, MainThreadBulkEditsShape } from './extHost.protocol';
|
||||
import { ExtHostFileSystemEventServiceShape, FileSystemEvents, IMainContext, SourceTargetPair, IWorkspaceEditDto, IWillRunFileOperationParticipation } from './extHost.protocol';
|
||||
import * as typeConverter from './extHostTypeConverters';
|
||||
import { Disposable, WorkspaceEdit } from './extHostTypes';
|
||||
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
@@ -122,8 +122,7 @@ export class ExtHostFileSystemEventService implements ExtHostFileSystemEventServ
|
||||
constructor(
|
||||
mainContext: IMainContext,
|
||||
private readonly _logService: ILogService,
|
||||
private readonly _extHostDocumentsAndEditors: ExtHostDocumentsAndEditors,
|
||||
private readonly _mainThreadBulkEdits: MainThreadBulkEditsShape = mainContext.getProxy(MainContext.MainThreadBulkEdits)
|
||||
private readonly _extHostDocumentsAndEditors: ExtHostDocumentsAndEditors
|
||||
) {
|
||||
//
|
||||
}
|
||||
@@ -178,24 +177,21 @@ export class ExtHostFileSystemEventService implements ExtHostFileSystemEventServ
|
||||
};
|
||||
}
|
||||
|
||||
async $onWillRunFileOperation(operation: FileOperation, files: SourceTargetPair[], undoRedoGroupId: number | undefined, timeout: number, token: CancellationToken): Promise<any> {
|
||||
async $onWillRunFileOperation(operation: FileOperation, files: SourceTargetPair[], timeout: number, token: CancellationToken): Promise<IWillRunFileOperationParticipation | undefined> {
|
||||
switch (operation) {
|
||||
case FileOperation.MOVE:
|
||||
await this._fireWillEvent(this._onWillRenameFile, { files: files.map(f => ({ oldUri: URI.revive(f.source!), newUri: URI.revive(f.target) })) }, undoRedoGroupId, timeout, token);
|
||||
break;
|
||||
return await this._fireWillEvent(this._onWillRenameFile, { files: files.map(f => ({ oldUri: URI.revive(f.source!), newUri: URI.revive(f.target) })) }, timeout, token);
|
||||
case FileOperation.DELETE:
|
||||
await this._fireWillEvent(this._onWillDeleteFile, { files: files.map(f => URI.revive(f.target)) }, undoRedoGroupId, timeout, token);
|
||||
break;
|
||||
return await this._fireWillEvent(this._onWillDeleteFile, { files: files.map(f => URI.revive(f.target)) }, timeout, token);
|
||||
case FileOperation.CREATE:
|
||||
await this._fireWillEvent(this._onWillCreateFile, { files: files.map(f => URI.revive(f.target)) }, undoRedoGroupId, timeout, token);
|
||||
break;
|
||||
default:
|
||||
//ignore, dont send
|
||||
return await this._fireWillEvent(this._onWillCreateFile, { files: files.map(f => URI.revive(f.target)) }, timeout, token);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private async _fireWillEvent<E extends IWaitUntil>(emitter: AsyncEmitter<E>, data: Omit<E, 'waitUntil'>, undoRedoGroupId: number | undefined, timeout: number, token: CancellationToken): Promise<any> {
|
||||
private async _fireWillEvent<E extends IWaitUntil>(emitter: AsyncEmitter<E>, data: Omit<E, 'waitUntil'>, timeout: number, token: CancellationToken): Promise<IWillRunFileOperationParticipation | undefined> {
|
||||
|
||||
const extensionNames = new Set<string>();
|
||||
const edits: WorkspaceEdit[] = [];
|
||||
|
||||
await emitter.fireAsync(data, token, async (thenable, listener) => {
|
||||
@@ -204,25 +200,28 @@ export class ExtHostFileSystemEventService implements ExtHostFileSystemEventServ
|
||||
const result = await Promise.resolve(thenable);
|
||||
if (result instanceof WorkspaceEdit) {
|
||||
edits.push(result);
|
||||
extensionNames.add((<IExtensionListener<E>>listener).extension.displayName ?? (<IExtensionListener<E>>listener).extension.identifier.value);
|
||||
}
|
||||
|
||||
if (Date.now() - now > timeout) {
|
||||
this._logService.warn('SLOW file-participant', (<IExtensionListener<E>>listener).extension?.identifier);
|
||||
this._logService.warn('SLOW file-participant', (<IExtensionListener<E>>listener).extension.identifier);
|
||||
}
|
||||
});
|
||||
|
||||
if (token.isCancellationRequested) {
|
||||
return;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (edits.length > 0) {
|
||||
// concat all WorkspaceEdits collected via waitUntil-call and apply them in one go.
|
||||
const dto: IWorkspaceEditDto = { edits: [] };
|
||||
for (let edit of edits) {
|
||||
let { edits } = typeConverter.WorkspaceEdit.from(edit, this._extHostDocumentsAndEditors);
|
||||
dto.edits = dto.edits.concat(edits);
|
||||
}
|
||||
return this._mainThreadBulkEdits.$tryApplyWorkspaceEdit(dto, undoRedoGroupId);
|
||||
if (edits.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// concat all WorkspaceEdits collected via waitUntil-call and send them over to the renderer
|
||||
const dto: IWorkspaceEditDto = { edits: [] };
|
||||
for (let edit of edits) {
|
||||
let { edits } = typeConverter.WorkspaceEdit.from(edit, this._extHostDocumentsAndEditors);
|
||||
dto.edits = dto.edits.concat(edits);
|
||||
}
|
||||
return { edit: dto, extensionNames: Array.from(extensionNames) };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,11 +27,12 @@ import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensio
|
||||
import { IURITransformer } from 'vs/base/common/uriIpc';
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { encodeSemanticTokensDto } from 'vs/workbench/api/common/shared/semanticTokensDto';
|
||||
import { encodeSemanticTokensDto } from 'vs/editor/common/services/semanticTokensDto';
|
||||
import { IdGenerator } from 'vs/base/common/idGenerator';
|
||||
import { IExtHostApiDeprecationService } from 'vs/workbench/api/common/extHostApiDeprecationService';
|
||||
import { Cache } from './cache';
|
||||
import { StopWatch } from 'vs/base/common/stopwatch';
|
||||
import { CancellationError } from 'vs/base/common/errors';
|
||||
|
||||
// --- adapter
|
||||
|
||||
@@ -1062,6 +1063,20 @@ class SignatureHelpAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
class InlineHintsAdapter {
|
||||
constructor(
|
||||
private readonly _documents: ExtHostDocuments,
|
||||
private readonly _provider: vscode.InlineHintsProvider,
|
||||
) { }
|
||||
|
||||
provideInlineHints(resource: URI, range: IRange, token: CancellationToken): Promise<extHostProtocol.IInlineHintsDto | undefined> {
|
||||
const doc = this._documents.getDocument(resource);
|
||||
return asPromise(() => this._provider.provideInlineHints(doc, typeConvert.Range.to(range), token)).then(value => {
|
||||
return value ? { hints: value.map(typeConvert.InlineHint.from) } : undefined;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class LinkProviderAdapter {
|
||||
|
||||
private _cache = new Cache<vscode.DocumentLink>('DocumentLink');
|
||||
@@ -1320,7 +1335,7 @@ type Adapter = DocumentSymbolAdapter | CodeLensAdapter | DefinitionAdapter | Hov
|
||||
| SuggestAdapter | SignatureHelpAdapter | LinkProviderAdapter | ImplementationAdapter
|
||||
| TypeDefinitionAdapter | ColorProviderAdapter | FoldingProviderAdapter | DeclarationAdapter
|
||||
| SelectionRangeAdapter | CallHierarchyAdapter | DocumentSemanticTokensAdapter | DocumentRangeSemanticTokensAdapter | EvaluatableExpressionAdapter
|
||||
| LinkedEditingRangeAdapter;
|
||||
| LinkedEditingRangeAdapter | InlineHintsAdapter;
|
||||
|
||||
class AdapterData {
|
||||
constructor(
|
||||
@@ -1403,7 +1418,7 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF
|
||||
return ExtHostLanguageFeatures._handlePool++;
|
||||
}
|
||||
|
||||
private _withAdapter<A, R>(handle: number, ctor: { new(...args: any[]): A; }, callback: (adapter: A, extension: IExtensionDescription | undefined) => Promise<R>, fallbackValue: R): Promise<R> {
|
||||
private _withAdapter<A, R>(handle: number, ctor: { new(...args: any[]): A; }, callback: (adapter: A, extension: IExtensionDescription | undefined) => Promise<R>, fallbackValue: R, allowCancellationError: boolean = false): Promise<R> {
|
||||
const data = this._adapter.get(handle);
|
||||
if (!data) {
|
||||
return Promise.resolve(fallbackValue);
|
||||
@@ -1421,8 +1436,11 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF
|
||||
Promise.resolve(p).then(
|
||||
() => this._logService.trace(`[${extension.identifier.value}] provider DONE after ${Date.now() - t1}ms`),
|
||||
err => {
|
||||
this._logService.error(`[${extension.identifier.value}] provider FAILED`);
|
||||
this._logService.error(err);
|
||||
const isExpectedError = allowCancellationError && (err instanceof CancellationError);
|
||||
if (!isExpectedError) {
|
||||
this._logService.error(`[${extension.identifier.value}] provider FAILED`);
|
||||
this._logService.error(err);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -1711,7 +1729,7 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF
|
||||
}
|
||||
|
||||
$provideDocumentSemanticTokens(handle: number, resource: UriComponents, previousResultId: number, token: CancellationToken): Promise<VSBuffer | null> {
|
||||
return this._withAdapter(handle, DocumentSemanticTokensAdapter, adapter => adapter.provideDocumentSemanticTokens(URI.revive(resource), previousResultId, token), null);
|
||||
return this._withAdapter(handle, DocumentSemanticTokensAdapter, adapter => adapter.provideDocumentSemanticTokens(URI.revive(resource), previousResultId, token), null, true);
|
||||
}
|
||||
|
||||
$releaseDocumentSemanticTokens(handle: number, semanticColoringResultId: number): void {
|
||||
@@ -1725,7 +1743,7 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF
|
||||
}
|
||||
|
||||
$provideDocumentRangeSemanticTokens(handle: number, resource: UriComponents, range: IRange, token: CancellationToken): Promise<VSBuffer | null> {
|
||||
return this._withAdapter(handle, DocumentRangeSemanticTokensAdapter, adapter => adapter.provideDocumentRangeSemanticTokens(URI.revive(resource), range, token), null);
|
||||
return this._withAdapter(handle, DocumentRangeSemanticTokensAdapter, adapter => adapter.provideDocumentRangeSemanticTokens(URI.revive(resource), range, token), null, true);
|
||||
}
|
||||
|
||||
//#endregion
|
||||
@@ -1770,6 +1788,27 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF
|
||||
this._withAdapter(handle, SignatureHelpAdapter, adapter => adapter.releaseSignatureHelp(id), undefined);
|
||||
}
|
||||
|
||||
// --- inline hints
|
||||
|
||||
registerInlineHintsProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.InlineHintsProvider): vscode.Disposable {
|
||||
|
||||
const eventHandle = typeof provider.onDidChangeInlineHints === 'function' ? this._nextHandle() : undefined;
|
||||
const handle = this._addNewAdapter(new InlineHintsAdapter(this._documents, provider), extension);
|
||||
|
||||
this._proxy.$registerInlineHintsProvider(handle, this._transformDocumentSelector(selector), eventHandle);
|
||||
let result = this._createDisposable(handle);
|
||||
|
||||
if (eventHandle !== undefined) {
|
||||
const subscription = provider.onDidChangeInlineHints!(_ => this._proxy.$emitInlineHintsEvent(eventHandle));
|
||||
result = Disposable.from(result, subscription);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
$provideInlineHints(handle: number, resource: UriComponents, range: IRange, token: CancellationToken): Promise<extHostProtocol.IInlineHintsDto | undefined> {
|
||||
return this._withAdapter(handle, InlineHintsAdapter, adapter => adapter.provideInlineHints(URI.revive(resource), range, token), undefined);
|
||||
}
|
||||
|
||||
// --- links
|
||||
|
||||
registerDocumentLinkProvider(extension: IExtensionDescription | undefined, selector: vscode.DocumentSelector, provider: vscode.DocumentLinkProvider): vscode.Disposable {
|
||||
@@ -1882,7 +1921,7 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF
|
||||
return {
|
||||
beforeText: ExtHostLanguageFeatures._serializeRegExp(onEnterRule.beforeText),
|
||||
afterText: onEnterRule.afterText ? ExtHostLanguageFeatures._serializeRegExp(onEnterRule.afterText) : undefined,
|
||||
oneLineAboveText: onEnterRule.oneLineAboveText ? ExtHostLanguageFeatures._serializeRegExp(onEnterRule.oneLineAboveText) : undefined,
|
||||
previousLineText: onEnterRule.previousLineText ? ExtHostLanguageFeatures._serializeRegExp(onEnterRule.previousLineText) : undefined,
|
||||
action: onEnterRule.action
|
||||
};
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import type * as vscode from 'vscode';
|
||||
import { MainContext, MainThreadMessageServiceShape, MainThreadMessageOptions, IMainContext } from './extHost.protocol';
|
||||
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions';
|
||||
|
||||
function isMessageItem(item: any): item is vscode.MessageItem {
|
||||
return item && item.title;
|
||||
@@ -37,9 +38,14 @@ export class ExtHostMessageService {
|
||||
items = [optionsOrFirstItem, ...rest];
|
||||
} else {
|
||||
options.modal = optionsOrFirstItem && optionsOrFirstItem.modal;
|
||||
options.useCustom = optionsOrFirstItem && optionsOrFirstItem.useCustom;
|
||||
items = rest;
|
||||
}
|
||||
|
||||
if (options.useCustom) {
|
||||
checkProposedApiEnabled(extension);
|
||||
}
|
||||
|
||||
const commands: { title: string; isCloseAffordance: boolean; handle: number; }[] = [];
|
||||
|
||||
for (let handle = 0; handle < items.length; handle++) {
|
||||
|
||||
@@ -78,8 +78,8 @@ export interface ExtHostNotebookOutputRenderingHandler {
|
||||
}
|
||||
|
||||
export class ExtHostNotebookKernelProviderAdapter extends Disposable {
|
||||
private _kernelToId = new Map<vscode.NotebookKernel, string>();
|
||||
private _idToKernel = new Map<string, vscode.NotebookKernel>();
|
||||
private _kernelToFriendlyId = new Map<vscode.NotebookKernel, string>();
|
||||
private _friendlyIdToKernel = new Map<string, vscode.NotebookKernel>();
|
||||
constructor(
|
||||
private readonly _proxy: MainThreadNotebookShape,
|
||||
private readonly _handle: number,
|
||||
@@ -101,24 +101,25 @@ export class ExtHostNotebookKernelProviderAdapter extends Disposable {
|
||||
|
||||
const newMap = new Map<vscode.NotebookKernel, string>();
|
||||
let kernel_unique_pool = 0;
|
||||
const kernelIdCache = new Set<string>();
|
||||
const kernelFriendlyIdCache = new Set<string>();
|
||||
|
||||
const transformedData: INotebookKernelInfoDto2[] = data.map(kernel => {
|
||||
let id = this._kernelToId.get(kernel);
|
||||
if (id === undefined) {
|
||||
if (kernel.id && kernelIdCache.has(kernel.id)) {
|
||||
id = `${this._extension.identifier.value}_${kernel.id}_${kernel_unique_pool++}`;
|
||||
let friendlyId = this._kernelToFriendlyId.get(kernel);
|
||||
if (friendlyId === undefined) {
|
||||
if (kernel.id && kernelFriendlyIdCache.has(kernel.id)) {
|
||||
friendlyId = `${this._extension.identifier.value}_${kernel.id}_${kernel_unique_pool++}`;
|
||||
} else {
|
||||
id = `${this._extension.identifier.value}_${kernel.id || UUID.generateUuid()}`;
|
||||
friendlyId = `${this._extension.identifier.value}_${kernel.id || UUID.generateUuid()}`;
|
||||
}
|
||||
|
||||
this._kernelToId.set(kernel, id);
|
||||
this._kernelToFriendlyId.set(kernel, friendlyId);
|
||||
}
|
||||
|
||||
newMap.set(kernel, id);
|
||||
newMap.set(kernel, friendlyId);
|
||||
|
||||
return {
|
||||
id,
|
||||
id: kernel.id,
|
||||
friendlyId: friendlyId,
|
||||
label: kernel.label,
|
||||
extension: this._extension.identifier,
|
||||
extensionLocation: this._extension.extensionLocation,
|
||||
@@ -129,22 +130,22 @@ export class ExtHostNotebookKernelProviderAdapter extends Disposable {
|
||||
};
|
||||
});
|
||||
|
||||
this._kernelToId = newMap;
|
||||
this._kernelToFriendlyId = newMap;
|
||||
|
||||
this._idToKernel.clear();
|
||||
this._kernelToId.forEach((value, key) => {
|
||||
this._idToKernel.set(value, key);
|
||||
this._friendlyIdToKernel.clear();
|
||||
this._kernelToFriendlyId.forEach((value, key) => {
|
||||
this._friendlyIdToKernel.set(value, key);
|
||||
});
|
||||
|
||||
return transformedData;
|
||||
}
|
||||
|
||||
getKernel(kernelId: string) {
|
||||
return this._idToKernel.get(kernelId);
|
||||
getKernelByFriendlyId(kernelId: string) {
|
||||
return this._friendlyIdToKernel.get(kernelId);
|
||||
}
|
||||
|
||||
async resolveNotebook(kernelId: string, document: ExtHostNotebookDocument, webview: vscode.NotebookCommunication, token: CancellationToken) {
|
||||
const kernel = this._idToKernel.get(kernelId);
|
||||
const kernel = this._friendlyIdToKernel.get(kernelId);
|
||||
|
||||
if (kernel && this._provider.resolveKernel) {
|
||||
return this._provider.resolveKernel(kernel, document.notebookDocument, webview, token);
|
||||
@@ -152,7 +153,7 @@ export class ExtHostNotebookKernelProviderAdapter extends Disposable {
|
||||
}
|
||||
|
||||
async executeNotebook(kernelId: string, document: ExtHostNotebookDocument, cell: ExtHostCell | undefined) {
|
||||
const kernel = this._idToKernel.get(kernelId);
|
||||
const kernel = this._friendlyIdToKernel.get(kernelId);
|
||||
|
||||
if (!kernel) {
|
||||
return;
|
||||
@@ -166,7 +167,7 @@ export class ExtHostNotebookKernelProviderAdapter extends Disposable {
|
||||
}
|
||||
|
||||
async cancelNotebook(kernelId: string, document: ExtHostNotebookDocument, cell: ExtHostCell | undefined) {
|
||||
const kernel = this._idToKernel.get(kernelId);
|
||||
const kernel = this._friendlyIdToKernel.get(kernelId);
|
||||
|
||||
if (!kernel) {
|
||||
return;
|
||||
@@ -580,10 +581,10 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
|
||||
this._outputDisplayOrder = displayOrder;
|
||||
}
|
||||
|
||||
$acceptNotebookActiveKernelChange(event: { uri: UriComponents, providerHandle: number | undefined, kernelId: string | undefined; }) {
|
||||
$acceptNotebookActiveKernelChange(event: { uri: UriComponents, providerHandle: number | undefined, kernelFriendlyId: string | undefined; }) {
|
||||
if (event.providerHandle !== undefined) {
|
||||
this._withAdapter(event.providerHandle, event.uri, async (adapter, document) => {
|
||||
const kernel = event.kernelId ? adapter.getKernel(event.kernelId) : undefined;
|
||||
const kernel = event.kernelFriendlyId ? adapter.getKernelByFriendlyId(event.kernelFriendlyId) : undefined;
|
||||
this._editors.forEach(editor => {
|
||||
if (editor.editor.notebookData === document) {
|
||||
editor.editor._acceptKernel(kernel);
|
||||
|
||||
@@ -113,14 +113,17 @@ export class ExtHostCell extends Disposable {
|
||||
get cell(): vscode.NotebookCell {
|
||||
if (!this._cell) {
|
||||
const that = this;
|
||||
const document = this._extHostDocument.getDocument(this.uri)!.document;
|
||||
const data = this._extHostDocument.getDocument(this.uri);
|
||||
if (!data) {
|
||||
throw new Error(`MISSING extHostDocument for notebook cell: ${this.uri}`);
|
||||
}
|
||||
this._cell = Object.freeze({
|
||||
get index() { return that._notebook.getCellIndex(that); },
|
||||
notebook: that._notebook.notebookDocument,
|
||||
uri: that.uri,
|
||||
cellKind: this._cellData.cellKind,
|
||||
document,
|
||||
get language() { return document.languageId; },
|
||||
document: data.document,
|
||||
get language() { return data!.document.languageId; },
|
||||
get outputs() { return that._outputs; },
|
||||
set outputs(value) { that._updateOutputs(value); },
|
||||
get metadata() { return that._metadata; },
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as performance from 'vs/base/common/performance';
|
||||
import { TernarySearchTree } from 'vs/base/common/map';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { MainThreadTelemetryShape, MainContext } from 'vs/workbench/api/common/extHost.protocol';
|
||||
@@ -52,7 +53,9 @@ export abstract class RequireInterceptor {
|
||||
|
||||
this._installInterceptor();
|
||||
|
||||
performance.mark('code/extHost/willWaitForConfig');
|
||||
const configProvider = await this._extHostConfiguration.getConfigProvider();
|
||||
performance.mark('code/extHost/didWaitForConfig');
|
||||
const extensionPaths = await this._extHostExtensionService.getExtensionPathIndex();
|
||||
|
||||
this.register(new VSCodeNodeModuleFactory(this._apiFactory, extensionPaths, this._extensionRegistry, configProvider, this._logService));
|
||||
|
||||
43
lib/vscode/src/vs/workbench/api/common/extHostSecrets.ts
Normal file
43
lib/vscode/src/vs/workbench/api/common/extHostSecrets.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import type * as vscode from 'vscode';
|
||||
|
||||
import { ExtHostSecretState } from 'vs/workbench/api/common/exHostSecretState';
|
||||
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
|
||||
export class ExtensionSecrets implements vscode.SecretStorage {
|
||||
|
||||
protected readonly _id: string;
|
||||
readonly #secretState: ExtHostSecretState;
|
||||
|
||||
private _onDidChange = new Emitter<vscode.SecretStorageChangeEvent>();
|
||||
readonly onDidChange: Event<vscode.SecretStorageChangeEvent> = this._onDidChange.event;
|
||||
|
||||
|
||||
constructor(extensionDescription: IExtensionDescription, secretState: ExtHostSecretState) {
|
||||
this._id = ExtensionIdentifier.toKey(extensionDescription.identifier);
|
||||
this.#secretState = secretState;
|
||||
|
||||
this.#secretState.onDidChangePassword(e => {
|
||||
if (e.extensionId === this._id) {
|
||||
this._onDidChange.fire({ key: e.key });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
get(key: string): Promise<string | undefined> {
|
||||
return this.#secretState.get(this._id, key);
|
||||
}
|
||||
|
||||
store(key: string, value: string): Promise<void> {
|
||||
return this.#secretState.store(this._id, key, value);
|
||||
}
|
||||
|
||||
delete(key: string): Promise<void> {
|
||||
return this.#secretState.delete(this._id, key);
|
||||
}
|
||||
}
|
||||
@@ -10,8 +10,6 @@ import { MainContext, MainThreadStatusBarShape, IMainContext, ICommandDto } from
|
||||
import { localize } from 'vs/nls';
|
||||
import { CommandsConverter } from 'vs/workbench/api/common/extHostCommands';
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions';
|
||||
|
||||
export class ExtHostStatusBarEntry implements vscode.StatusBarItem {
|
||||
private static ID_GEN = 0;
|
||||
@@ -48,9 +46,8 @@ export class ExtHostStatusBarEntry implements vscode.StatusBarItem {
|
||||
private _proxy: MainThreadStatusBarShape;
|
||||
private _commands: CommandsConverter;
|
||||
private _accessibilityInformation?: vscode.AccessibilityInformation;
|
||||
private _extension?: IExtensionDescription;
|
||||
|
||||
constructor(proxy: MainThreadStatusBarShape, commands: CommandsConverter, id: string, name: string, alignment: ExtHostStatusBarAlignment = ExtHostStatusBarAlignment.Left, priority?: number, accessibilityInformation?: vscode.AccessibilityInformation, extension?: IExtensionDescription) {
|
||||
constructor(proxy: MainThreadStatusBarShape, commands: CommandsConverter, id: string, name: string, alignment: ExtHostStatusBarAlignment = ExtHostStatusBarAlignment.Left, priority?: number, accessibilityInformation?: vscode.AccessibilityInformation) {
|
||||
this._id = ExtHostStatusBarEntry.ID_GEN++;
|
||||
this._proxy = proxy;
|
||||
this._commands = commands;
|
||||
@@ -59,7 +56,6 @@ export class ExtHostStatusBarEntry implements vscode.StatusBarItem {
|
||||
this._alignment = alignment;
|
||||
this._priority = priority;
|
||||
this._accessibilityInformation = accessibilityInformation;
|
||||
this._extension = extension;
|
||||
}
|
||||
|
||||
public get id(): number {
|
||||
@@ -87,10 +83,6 @@ export class ExtHostStatusBarEntry implements vscode.StatusBarItem {
|
||||
}
|
||||
|
||||
public get backgroundColor(): ThemeColor | undefined {
|
||||
if (this._extension) {
|
||||
checkProposedApiEnabled(this._extension);
|
||||
}
|
||||
|
||||
return this._backgroundColor;
|
||||
}
|
||||
|
||||
@@ -118,10 +110,6 @@ export class ExtHostStatusBarEntry implements vscode.StatusBarItem {
|
||||
}
|
||||
|
||||
public set backgroundColor(color: ThemeColor | undefined) {
|
||||
if (this._extension) {
|
||||
checkProposedApiEnabled(this._extension);
|
||||
}
|
||||
|
||||
if (color && !ExtHostStatusBarEntry.ALLOWED_BACKGROUND_COLORS.has(color.id)) {
|
||||
color = undefined;
|
||||
}
|
||||
@@ -248,8 +236,8 @@ export class ExtHostStatusBar {
|
||||
this._statusMessage = new StatusBarMessage(this);
|
||||
}
|
||||
|
||||
createStatusBarEntry(id: string, name: string, alignment?: ExtHostStatusBarAlignment, priority?: number, accessibilityInformation?: vscode.AccessibilityInformation, extension?: IExtensionDescription): vscode.StatusBarItem {
|
||||
return new ExtHostStatusBarEntry(this._proxy, this._commands, id, name, alignment, priority, accessibilityInformation, extension);
|
||||
createStatusBarEntry(id: string, name: string, alignment?: ExtHostStatusBarAlignment, priority?: number, accessibilityInformation?: vscode.AccessibilityInformation): vscode.StatusBarItem {
|
||||
return new ExtHostStatusBarEntry(this._proxy, this._commands, id, name, alignment, priority, accessibilityInformation);
|
||||
}
|
||||
|
||||
setStatusBarMessage(text: string, timeoutOrThenable?: number | Thenable<any>): Disposable {
|
||||
|
||||
@@ -5,12 +5,11 @@
|
||||
|
||||
import type * as vscode from 'vscode';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { ExtHostTerminalServiceShape, MainContext, MainThreadTerminalServiceShape, IShellLaunchConfigDto, IShellDefinitionDto, IShellAndArgsDto, ITerminalDimensionsDto, ITerminalLinkDto } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { ExtHostTerminalServiceShape, MainContext, MainThreadTerminalServiceShape, IShellLaunchConfigDto, IShellDefinitionDto, IShellAndArgsDto, ITerminalDimensionsDto, ITerminalLinkDto, TerminalIdentifier } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { ExtHostConfigProvider } from 'vs/workbench/api/common/extHostConfiguration';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import { ITerminalChildProcess, EXT_HOST_CREATION_DELAY, ITerminalLaunchError, ITerminalDimensionsOverride } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { timeout } from 'vs/base/common/async';
|
||||
import { ITerminalChildProcess, ITerminalLaunchError, ITerminalDimensionsOverride } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
|
||||
import { TerminalDataBufferer } from 'vs/workbench/contrib/terminal/common/terminalDataBuffering';
|
||||
import { IDisposable, DisposableStore, Disposable } from 'vs/base/common/lifecycle';
|
||||
@@ -21,6 +20,7 @@ import { localize } from 'vs/nls';
|
||||
import { NotSupportedError } from 'vs/base/common/errors';
|
||||
import { serializeEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariableShared';
|
||||
import { CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
|
||||
export interface IExtHostTerminalService extends ExtHostTerminalServiceShape, IDisposable {
|
||||
|
||||
@@ -47,63 +47,8 @@ export interface IExtHostTerminalService extends ExtHostTerminalServiceShape, ID
|
||||
|
||||
export const IExtHostTerminalService = createDecorator<IExtHostTerminalService>('IExtHostTerminalService');
|
||||
|
||||
export class BaseExtHostTerminal {
|
||||
public _id: number | undefined;
|
||||
protected _idPromise: Promise<number>;
|
||||
private _idPromiseComplete: ((value: number) => any) | undefined;
|
||||
export class ExtHostTerminal implements vscode.Terminal {
|
||||
private _disposed: boolean = false;
|
||||
private _queuedRequests: ApiRequest[] = [];
|
||||
|
||||
constructor(
|
||||
protected _proxy: MainThreadTerminalServiceShape,
|
||||
id?: number
|
||||
) {
|
||||
this._idPromise = new Promise<number>(c => {
|
||||
if (id !== undefined) {
|
||||
this._id = id;
|
||||
c(id);
|
||||
} else {
|
||||
this._idPromiseComplete = c;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
if (!this._disposed) {
|
||||
this._disposed = true;
|
||||
this._queueApiRequest(this._proxy.$dispose, []);
|
||||
}
|
||||
}
|
||||
|
||||
protected _checkDisposed() {
|
||||
if (this._disposed) {
|
||||
throw new Error('Terminal has already been disposed');
|
||||
}
|
||||
}
|
||||
|
||||
protected _queueApiRequest(callback: (...args: any[]) => void, args: any[]): void {
|
||||
const request: ApiRequest = new ApiRequest(callback, args);
|
||||
if (!this._id) {
|
||||
this._queuedRequests.push(request);
|
||||
return;
|
||||
}
|
||||
request.run(this._proxy, this._id);
|
||||
}
|
||||
|
||||
public _runQueuedRequests(id: number): void {
|
||||
this._id = id;
|
||||
if (this._idPromiseComplete) {
|
||||
this._idPromiseComplete(id);
|
||||
this._idPromiseComplete = undefined;
|
||||
}
|
||||
this._queuedRequests.forEach((r) => {
|
||||
r.run(this._proxy, id);
|
||||
});
|
||||
this._queuedRequests.length = 0;
|
||||
}
|
||||
}
|
||||
|
||||
export class ExtHostTerminal extends BaseExtHostTerminal implements vscode.Terminal {
|
||||
private _pidPromise: Promise<number | undefined>;
|
||||
private _cols: number | undefined;
|
||||
private _pidPromiseComplete: ((value: number | undefined) => any) | undefined;
|
||||
@@ -113,12 +58,11 @@ export class ExtHostTerminal extends BaseExtHostTerminal implements vscode.Termi
|
||||
public isOpen: boolean = false;
|
||||
|
||||
constructor(
|
||||
proxy: MainThreadTerminalServiceShape,
|
||||
private _proxy: MainThreadTerminalServiceShape,
|
||||
public _id: TerminalIdentifier,
|
||||
private readonly _creationOptions: vscode.TerminalOptions | vscode.ExtensionTerminalOptions,
|
||||
private _name?: string,
|
||||
id?: number
|
||||
) {
|
||||
super(proxy, id);
|
||||
this._creationOptions = Object.freeze(this._creationOptions);
|
||||
this._pidPromise = new Promise<number | undefined>(c => this._pidPromiseComplete = c);
|
||||
}
|
||||
@@ -133,16 +77,35 @@ export class ExtHostTerminal extends BaseExtHostTerminal implements vscode.Termi
|
||||
hideFromUser?: boolean,
|
||||
isFeatureTerminal?: boolean
|
||||
): Promise<void> {
|
||||
const result = await this._proxy.$createTerminal({ name: this._name, shellPath, shellArgs, cwd, env, waitOnExit, strictEnv, hideFromUser, isFeatureTerminal });
|
||||
this._name = result.name;
|
||||
this._runQueuedRequests(result.id);
|
||||
if (typeof this._id !== 'string') {
|
||||
throw new Error('Terminal has already been created');
|
||||
}
|
||||
await this._proxy.$createTerminal(this._id, { name: this._name, shellPath, shellArgs, cwd, env, waitOnExit, strictEnv, hideFromUser, isFeatureTerminal });
|
||||
}
|
||||
|
||||
public async createExtensionTerminal(): Promise<number> {
|
||||
const result = await this._proxy.$createTerminal({ name: this._name, isExtensionTerminal: true });
|
||||
this._name = result.name;
|
||||
this._runQueuedRequests(result.id);
|
||||
return result.id;
|
||||
if (typeof this._id !== 'string') {
|
||||
throw new Error('Terminal has already been created');
|
||||
}
|
||||
await this._proxy.$createTerminal(this._id, { name: this._name, isExtensionTerminal: true });
|
||||
// At this point, the id has been set via `$acceptTerminalOpened`
|
||||
if (typeof this._id === 'string') {
|
||||
throw new Error('Terminal creation failed');
|
||||
}
|
||||
return this._id;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
if (!this._disposed) {
|
||||
this._disposed = true;
|
||||
this._proxy.$dispose(this._id);
|
||||
}
|
||||
}
|
||||
|
||||
private _checkDisposed() {
|
||||
if (this._disposed) {
|
||||
throw new Error('Terminal has already been disposed');
|
||||
}
|
||||
}
|
||||
|
||||
public get name(): string {
|
||||
@@ -194,17 +157,17 @@ export class ExtHostTerminal extends BaseExtHostTerminal implements vscode.Termi
|
||||
|
||||
public sendText(text: string, addNewLine: boolean = true): void {
|
||||
this._checkDisposed();
|
||||
this._queueApiRequest(this._proxy.$sendText, [text, addNewLine]);
|
||||
this._proxy.$sendText(this._id, text, addNewLine);
|
||||
}
|
||||
|
||||
public show(preserveFocus: boolean): void {
|
||||
this._checkDisposed();
|
||||
this._queueApiRequest(this._proxy.$show, [preserveFocus]);
|
||||
this._proxy.$show(this._id, preserveFocus);
|
||||
}
|
||||
|
||||
public hide(): void {
|
||||
this._checkDisposed();
|
||||
this._queueApiRequest(this._proxy.$hide, []);
|
||||
this._proxy.$hide(this._id);
|
||||
}
|
||||
|
||||
public _setProcessId(processId: number | undefined): void {
|
||||
@@ -223,20 +186,6 @@ export class ExtHostTerminal extends BaseExtHostTerminal implements vscode.Termi
|
||||
}
|
||||
}
|
||||
|
||||
class ApiRequest {
|
||||
private _callback: (...args: any[]) => void;
|
||||
private _args: any[];
|
||||
|
||||
constructor(callback: (...args: any[]) => void, args: any[]) {
|
||||
this._callback = callback;
|
||||
this._args = args;
|
||||
}
|
||||
|
||||
public run(proxy: MainThreadTerminalServiceShape, id: number) {
|
||||
this._callback.apply(proxy, [id].concat(this._args));
|
||||
}
|
||||
}
|
||||
|
||||
export class ExtHostPseudoterminal implements ITerminalChildProcess {
|
||||
private readonly _onProcessData = new Emitter<string>();
|
||||
public readonly onProcessData: Event<string> = this._onProcessData.event;
|
||||
@@ -271,6 +220,11 @@ export class ExtHostPseudoterminal implements ITerminalChildProcess {
|
||||
}
|
||||
}
|
||||
|
||||
acknowledgeDataEvent(charCount: number): void {
|
||||
// No-op, flow control is not supported in extension owned terminals. If this is ever
|
||||
// implemented it will need new pause and resume VS Code APIs.
|
||||
}
|
||||
|
||||
getInitialCwd(): Promise<string> {
|
||||
return Promise.resolve('');
|
||||
}
|
||||
@@ -296,6 +250,11 @@ export class ExtHostPseudoterminal implements ITerminalChildProcess {
|
||||
}
|
||||
|
||||
this._pty.open(initialDimensions ? initialDimensions : undefined);
|
||||
|
||||
if (this._pty.setDimensions && initialDimensions) {
|
||||
this._pty.setDimensions(initialDimensions);
|
||||
}
|
||||
|
||||
this._onProcessReady.fire({ pid: -1, cwd: '' });
|
||||
}
|
||||
}
|
||||
@@ -370,7 +329,7 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I
|
||||
public abstract $acceptWorkspacePermissionsChanged(isAllowed: boolean): void;
|
||||
|
||||
public createExtensionTerminal(options: vscode.ExtensionTerminalOptions): vscode.Terminal {
|
||||
const terminal = new ExtHostTerminal(this._proxy, options, options.name);
|
||||
const terminal = new ExtHostTerminal(this._proxy, generateUuid(), options, options.name);
|
||||
const p = new ExtHostPseudoterminal(options.pty);
|
||||
terminal.createExtensionTerminal().then(id => {
|
||||
const disposable = this._setupExtHostProcessListeners(id, p);
|
||||
@@ -381,7 +340,7 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I
|
||||
}
|
||||
|
||||
public attachPtyToTerminal(id: number, pty: vscode.Pseudoterminal): void {
|
||||
const terminal = this._getTerminalByIdEventually(id);
|
||||
const terminal = this._getTerminalById(id);
|
||||
if (!terminal) {
|
||||
throw new Error(`Cannot resolve terminal with id ${id} for virtual process`);
|
||||
}
|
||||
@@ -399,7 +358,7 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I
|
||||
}
|
||||
return;
|
||||
}
|
||||
const terminal = await this._getTerminalByIdEventually(id);
|
||||
const terminal = this._getTerminalById(id);
|
||||
if (terminal) {
|
||||
this._activeTerminal = terminal;
|
||||
if (original !== this._activeTerminal) {
|
||||
@@ -409,14 +368,14 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I
|
||||
}
|
||||
|
||||
public async $acceptTerminalProcessData(id: number, data: string): Promise<void> {
|
||||
const terminal = await this._getTerminalByIdEventually(id);
|
||||
const terminal = this._getTerminalById(id);
|
||||
if (terminal) {
|
||||
this._onDidWriteTerminalData.fire({ terminal, data });
|
||||
}
|
||||
}
|
||||
|
||||
public async $acceptTerminalDimensions(id: number, cols: number, rows: number): Promise<void> {
|
||||
const terminal = await this._getTerminalByIdEventually(id);
|
||||
const terminal = this._getTerminalById(id);
|
||||
if (terminal) {
|
||||
if (terminal.setDimensions(cols, rows)) {
|
||||
this._onDidChangeTerminalDimensions.fire({
|
||||
@@ -428,23 +387,19 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I
|
||||
}
|
||||
|
||||
public async $acceptTerminalMaximumDimensions(id: number, cols: number, rows: number): Promise<void> {
|
||||
await this._getTerminalByIdEventually(id);
|
||||
|
||||
// Extension pty terminal only - when virtual process resize fires it means that the
|
||||
// terminal's maximum dimensions changed
|
||||
this._terminalProcesses.get(id)?.resize(cols, rows);
|
||||
}
|
||||
|
||||
public async $acceptTerminalTitleChange(id: number, name: string): Promise<void> {
|
||||
await this._getTerminalByIdEventually(id);
|
||||
const extHostTerminal = this._getTerminalObjectById(this.terminals, id);
|
||||
if (extHostTerminal) {
|
||||
extHostTerminal.name = name;
|
||||
const terminal = this._getTerminalById(id);
|
||||
if (terminal) {
|
||||
terminal.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
public async $acceptTerminalClosed(id: number, exitCode: number | undefined): Promise<void> {
|
||||
await this._getTerminalByIdEventually(id);
|
||||
const index = this._getTerminalObjectIndexById(this.terminals, id);
|
||||
if (index !== null) {
|
||||
const terminal = this._terminals.splice(index, 1)[0];
|
||||
@@ -453,13 +408,17 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I
|
||||
}
|
||||
}
|
||||
|
||||
public $acceptTerminalOpened(id: number, name: string, shellLaunchConfigDto: IShellLaunchConfigDto): void {
|
||||
const index = this._getTerminalObjectIndexById(this._terminals, id);
|
||||
if (index !== null) {
|
||||
// The terminal has already been created (via createTerminal*), only fire the event
|
||||
this._onDidOpenTerminal.fire(this.terminals[index]);
|
||||
this.terminals[index].isOpen = true;
|
||||
return;
|
||||
public $acceptTerminalOpened(id: number, extHostTerminalId: string | undefined, name: string, shellLaunchConfigDto: IShellLaunchConfigDto): void {
|
||||
if (extHostTerminalId) {
|
||||
// Resolve with the renderer generated id
|
||||
const index = this._getTerminalObjectIndexById(this._terminals, extHostTerminalId);
|
||||
if (index !== null) {
|
||||
// The terminal has already been created (via createTerminal*), only fire the event
|
||||
this.terminals[index]._id = id;
|
||||
this._onDidOpenTerminal.fire(this.terminals[index]);
|
||||
this.terminals[index].isOpen = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const creationOptions: vscode.TerminalOptions = {
|
||||
@@ -470,14 +429,14 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I
|
||||
env: shellLaunchConfigDto.env,
|
||||
hideFromUser: shellLaunchConfigDto.hideFromUser
|
||||
};
|
||||
const terminal = new ExtHostTerminal(this._proxy, creationOptions, name, id);
|
||||
const terminal = new ExtHostTerminal(this._proxy, id, creationOptions, name);
|
||||
this._terminals.push(terminal);
|
||||
this._onDidOpenTerminal.fire(terminal);
|
||||
terminal.isOpen = true;
|
||||
}
|
||||
|
||||
public async $acceptTerminalProcessId(id: number, processId: number): Promise<void> {
|
||||
const terminal = await this._getTerminalByIdEventually(id);
|
||||
const terminal = this._getTerminalById(id);
|
||||
if (terminal) {
|
||||
terminal._setProcessId(processId);
|
||||
}
|
||||
@@ -486,7 +445,7 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I
|
||||
public async $startExtensionTerminal(id: number, initialDimensions: ITerminalDimensionsDto | undefined): Promise<ITerminalLaunchError | undefined> {
|
||||
// Make sure the ExtHostTerminal exists so onDidOpenTerminal has fired before we call
|
||||
// Pseudoterminal.start
|
||||
const terminal = await this._getTerminalByIdEventually(id);
|
||||
const terminal = this._getTerminalById(id);
|
||||
if (!terminal) {
|
||||
return { message: localize('launchFail.idMissingOnExtHost', "Could not find the terminal with id {0} on the extension host", id) };
|
||||
}
|
||||
@@ -539,6 +498,10 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I
|
||||
return disposables;
|
||||
}
|
||||
|
||||
public $acceptProcessAckDataEvent(id: number, charCount: number): void {
|
||||
this._terminalProcesses.get(id)?.acknowledgeDataEvent(charCount);
|
||||
}
|
||||
|
||||
public $acceptProcessInput(id: number, data: string): void {
|
||||
this._terminalProcesses.get(id)?.input(data);
|
||||
}
|
||||
@@ -670,32 +633,6 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I
|
||||
this._proxy.$sendProcessExit(id, exitCode);
|
||||
}
|
||||
|
||||
// TODO: This could be improved by using a single promise and resolve it when the terminal is ready
|
||||
private _getTerminalByIdEventually(id: number, retries: number = 5): Promise<ExtHostTerminal | undefined> {
|
||||
if (!this._getTerminalPromises[id]) {
|
||||
this._getTerminalPromises[id] = this._createGetTerminalPromise(id, retries);
|
||||
}
|
||||
return this._getTerminalPromises[id];
|
||||
}
|
||||
|
||||
private _createGetTerminalPromise(id: number, retries: number = 5): Promise<ExtHostTerminal | undefined> {
|
||||
return new Promise(c => {
|
||||
if (retries === 0) {
|
||||
c(undefined);
|
||||
return;
|
||||
}
|
||||
|
||||
const terminal = this._getTerminalById(id);
|
||||
if (terminal) {
|
||||
c(terminal);
|
||||
} else {
|
||||
// This should only be needed immediately after createTerminalRenderer is called as
|
||||
// the ExtHostTerminal has not yet been iniitalized
|
||||
timeout(EXT_HOST_CREATION_DELAY * 2).then(() => c(this._createGetTerminalPromise(id, retries - 1)));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private _getTerminalById(id: number): ExtHostTerminal | null {
|
||||
return this._getTerminalObjectById(this._terminals, id);
|
||||
}
|
||||
@@ -705,7 +642,7 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I
|
||||
return index !== null ? array[index] : null;
|
||||
}
|
||||
|
||||
private _getTerminalObjectIndexById<T extends ExtHostTerminal>(array: T[], id: number): number | null {
|
||||
private _getTerminalObjectIndexById<T extends ExtHostTerminal>(array: T[], id: TerminalIdentifier): number | null {
|
||||
let index: number | null = null;
|
||||
array.some((item, i) => {
|
||||
const thisId = item._id;
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
import { mapFind } from 'vs/base/common/arrays';
|
||||
import { disposableTimeout } from 'vs/base/common/async';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { throttle } from 'vs/base/common/decorators';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { once } from 'vs/base/common/functional';
|
||||
import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
|
||||
@@ -14,25 +13,35 @@ import { isDefined } from 'vs/base/common/types';
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { ExtHostTestingResource, ExtHostTestingShape, MainContext, MainThreadTestingShape } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { ExtHostDocumentData } from 'vs/workbench/api/common/extHostDocumentData';
|
||||
import { IExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors';
|
||||
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
|
||||
import { TestItem } from 'vs/workbench/api/common/extHostTypeConverters';
|
||||
import { Disposable, RequiredTestItem } from 'vs/workbench/api/common/extHostTypes';
|
||||
import { Disposable } from 'vs/workbench/api/common/extHostTypes';
|
||||
import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace';
|
||||
import { AbstractIncrementalTestCollection, EMPTY_TEST_RESULT, IncrementalChangeCollector, IncrementalTestCollectionItem, InternalTestItem, RunTestForProviderRequest, RunTestsResult, TestDiffOpType, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection';
|
||||
import { OwnedTestCollection, SingleUseTestCollection } from 'vs/workbench/contrib/testing/common/ownedTestCollection';
|
||||
import { AbstractIncrementalTestCollection, EMPTY_TEST_RESULT, IncrementalChangeCollector, IncrementalTestCollectionItem, InternalTestItem, InternalTestItemWithChildren, InternalTestResults, RunTestForProviderRequest, RunTestsResult, TestDiffOpType, TestIdWithProvider, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection';
|
||||
import type * as vscode from 'vscode';
|
||||
|
||||
const getTestSubscriptionKey = (resource: ExtHostTestingResource, uri: URI) => `${resource}:${uri.toString()}`;
|
||||
|
||||
export class ExtHostTesting implements ExtHostTestingShape {
|
||||
private readonly resultsChangedEmitter = new Emitter<void>();
|
||||
private readonly providers = new Map<string, vscode.TestProvider>();
|
||||
private readonly proxy: MainThreadTestingShape;
|
||||
private readonly ownedTests = new OwnedTestCollection();
|
||||
private readonly testSubscriptions = new Map<string, { collection: SingleUseTestCollection, store: IDisposable }>();
|
||||
private readonly testSubscriptions = new Map<string, {
|
||||
collection: SingleUseTestCollection;
|
||||
store: IDisposable;
|
||||
subscribeFn: (id: string, provider: vscode.TestProvider) => void;
|
||||
}>();
|
||||
|
||||
private workspaceObservers: WorkspaceFolderTestObserverFactory;
|
||||
private textDocumentObservers: TextDocumentTestObserverFactory;
|
||||
|
||||
public onLastResultsChanged = this.resultsChangedEmitter.event;
|
||||
public lastResults?: vscode.TestResults;
|
||||
|
||||
constructor(@IExtHostRpcService rpc: IExtHostRpcService, @IExtHostDocumentsAndEditors private readonly documents: IExtHostDocumentsAndEditors, @IExtHostWorkspace private readonly workspace: IExtHostWorkspace) {
|
||||
this.proxy = rpc.getProxy(MainContext.MainThreadTesting);
|
||||
this.workspaceObservers = new WorkspaceFolderTestObserverFactory(this.proxy);
|
||||
@@ -47,6 +56,14 @@ export class ExtHostTesting implements ExtHostTestingShape {
|
||||
this.providers.set(providerId, provider);
|
||||
this.proxy.$registerTestProvider(providerId);
|
||||
|
||||
// give the ext a moment to register things rather than synchronously invoking within activate()
|
||||
const toSubscribe = [...this.testSubscriptions.keys()];
|
||||
setTimeout(() => {
|
||||
for (const subscription of toSubscribe) {
|
||||
this.testSubscriptions.get(subscription)?.subscribeFn(providerId, provider);
|
||||
}
|
||||
}, 0);
|
||||
|
||||
return new Disposable(() => {
|
||||
this.providers.delete(providerId);
|
||||
this.proxy.$unregisterTestProvider(providerId);
|
||||
@@ -70,7 +87,7 @@ export class ExtHostTesting implements ExtHostTestingShape {
|
||||
/**
|
||||
* Implements vscode.test.runTests
|
||||
*/
|
||||
public async runTests(req: vscode.TestRunOptions<vscode.TestItem>) {
|
||||
public async runTests(req: vscode.TestRunOptions<vscode.TestItem>, token = CancellationToken.None) {
|
||||
await this.proxy.$runTests({
|
||||
tests: req.tests
|
||||
// Find workspace items first, then owned tests, then document tests.
|
||||
@@ -82,14 +99,27 @@ export class ExtHostTesting implements ExtHostTestingShape {
|
||||
.filter(isDefined)
|
||||
.map(item => ({ providerId: item.providerId, testId: item.id })),
|
||||
debug: req.debug
|
||||
});
|
||||
}, token);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Updates test results shown to extensions.
|
||||
* @override
|
||||
*/
|
||||
public $publishTestResults(results: InternalTestResults): void {
|
||||
const convert = (item: InternalTestItemWithChildren): vscode.RequiredTestItem =>
|
||||
({ ...TestItem.toShallow(item.item), children: item.children.map(convert) });
|
||||
|
||||
this.lastResults = { tests: results.tests.map(convert) };
|
||||
this.resultsChangedEmitter.fire();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a request to read tests for a file, or workspace.
|
||||
* @override
|
||||
*/
|
||||
public $subscribeToTests(resource: ExtHostTestingResource, uriComponents: UriComponents) {
|
||||
public async $subscribeToTests(resource: ExtHostTestingResource, uriComponents: UriComponents) {
|
||||
const uri = URI.revive(uriComponents);
|
||||
const subscriptionKey = getTestSubscriptionKey(resource, uri);
|
||||
if (this.testSubscriptions.has(subscriptionKey)) {
|
||||
@@ -98,12 +128,29 @@ export class ExtHostTesting implements ExtHostTestingShape {
|
||||
|
||||
let method: undefined | ((p: vscode.TestProvider) => vscode.TestHierarchy<vscode.TestItem> | undefined);
|
||||
if (resource === ExtHostTestingResource.TextDocument) {
|
||||
const document = this.documents.getDocument(uri);
|
||||
let document = this.documents.getDocument(uri);
|
||||
|
||||
// we can ask to subscribe to tests before the documents are populated in
|
||||
// the extension host. Try to wait.
|
||||
if (!document) {
|
||||
const store = new DisposableStore();
|
||||
document = await new Promise<ExtHostDocumentData | undefined>(resolve => {
|
||||
store.add(disposableTimeout(() => resolve(undefined), 5000));
|
||||
store.add(this.documents.onDidAddDocuments(e => {
|
||||
const data = e.find(data => data.document.uri.toString() === uri.toString());
|
||||
if (data) { resolve(data); }
|
||||
}));
|
||||
}).finally(() => store.dispose());
|
||||
}
|
||||
|
||||
if (document) {
|
||||
method = p => p.createDocumentTestHierarchy?.(document.document);
|
||||
const folder = await this.workspace.getWorkspaceFolder2(uri, false);
|
||||
method = p => p.createDocumentTestHierarchy
|
||||
? p.createDocumentTestHierarchy(document!.document)
|
||||
: this.createDefaultDocumentTestHierarchy(p, document!.document, folder);
|
||||
}
|
||||
} else {
|
||||
const folder = this.workspace.getWorkspaceFolder(uri, false);
|
||||
const folder = await this.workspace.getWorkspaceFolder2(uri, false);
|
||||
if (folder) {
|
||||
method = p => p.createWorkspaceTestHierarchy?.(folder);
|
||||
}
|
||||
@@ -113,24 +160,34 @@ export class ExtHostTesting implements ExtHostTestingShape {
|
||||
return;
|
||||
}
|
||||
|
||||
const disposable = new DisposableStore();
|
||||
const collection = disposable.add(this.ownedTests.createForHierarchy(diff => this.proxy.$publishDiff(resource, uriComponents, diff)));
|
||||
for (const [id, provider] of this.providers) {
|
||||
const subscribeFn = (id: string, provider: vscode.TestProvider) => {
|
||||
try {
|
||||
const hierarchy = method(provider);
|
||||
const hierarchy = method!(provider);
|
||||
if (!hierarchy) {
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
|
||||
collection.pushDiff([TestDiffOpType.DeltaDiscoverComplete, 1]);
|
||||
disposable.add(hierarchy);
|
||||
collection.addRoot(hierarchy.root, id);
|
||||
Promise.resolve(hierarchy.discoveredInitialTests).then(() => collection.pushDiff([TestDiffOpType.DeltaDiscoverComplete, -1]));
|
||||
hierarchy.onDidChangeTest(e => collection.onItemChange(e, id));
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
};
|
||||
|
||||
const disposable = new DisposableStore();
|
||||
const collection = disposable.add(this.ownedTests.createForHierarchy(diff => this.proxy.$publishDiff(resource, uriComponents, diff)));
|
||||
for (const [id, provider] of this.providers) {
|
||||
subscribeFn(id, provider);
|
||||
}
|
||||
|
||||
this.testSubscriptions.set(subscriptionKey, { store: disposable, collection });
|
||||
// note: we don't increment the root count initially -- this is done by the
|
||||
// main thread, incrementing once per extension host. We just push the
|
||||
// diff to signal that roots have been discovered.
|
||||
collection.pushDiff([TestDiffOpType.DeltaRootsComplete, -1]);
|
||||
this.testSubscriptions.set(subscriptionKey, { store: disposable, collection, subscribeFn });
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -162,217 +219,193 @@ export class ExtHostTesting implements ExtHostTestingShape {
|
||||
* providers to be run.
|
||||
* @override
|
||||
*/
|
||||
public async $runTestsForProvider(req: RunTestForProviderRequest): Promise<RunTestsResult> {
|
||||
public async $runTestsForProvider(req: RunTestForProviderRequest, cancellation: CancellationToken): Promise<RunTestsResult> {
|
||||
const provider = this.providers.get(req.providerId);
|
||||
if (!provider || !provider.runTests) {
|
||||
return EMPTY_TEST_RESULT;
|
||||
}
|
||||
|
||||
const tests = req.ids.map(id => this.ownedTests.getTestById(id)?.actual).filter(isDefined);
|
||||
const tests = req.ids.map(id => this.ownedTests.getTestById(id)?.actual)
|
||||
.filter(isDefined)
|
||||
// Only send the actual TestItem's to the user to run.
|
||||
.map(t => t instanceof TestItemFilteredWrapper ? t.actual : t);
|
||||
if (!tests.length) {
|
||||
return EMPTY_TEST_RESULT;
|
||||
}
|
||||
|
||||
await provider.runTests({ tests, debug: req.debug }, CancellationToken.None);
|
||||
return EMPTY_TEST_RESULT;
|
||||
}
|
||||
}
|
||||
|
||||
const keyMap: { [K in keyof Omit<RequiredTestItem, 'children'>]: null } = {
|
||||
label: null,
|
||||
location: null,
|
||||
state: null,
|
||||
debuggable: null,
|
||||
description: null,
|
||||
runnable: null
|
||||
};
|
||||
|
||||
const simpleProps = Object.keys(keyMap) as ReadonlyArray<keyof typeof keyMap>;
|
||||
|
||||
const itemEqualityComparator = (a: vscode.TestItem) => {
|
||||
const values: unknown[] = [];
|
||||
for (const prop of simpleProps) {
|
||||
values.push(a[prop]);
|
||||
}
|
||||
|
||||
return (b: vscode.TestItem) => {
|
||||
for (let i = 0; i < simpleProps.length; i++) {
|
||||
if (values[i] !== b[simpleProps[i]]) {
|
||||
return false;
|
||||
try {
|
||||
await provider.runTests({ tests, debug: req.debug }, cancellation);
|
||||
for (const { collection } of this.testSubscriptions.values()) {
|
||||
collection.flushDiff(); // ensure all states are updated
|
||||
}
|
||||
|
||||
return EMPTY_TEST_RESULT;
|
||||
} catch (e) {
|
||||
console.error(e); // so it appears to attached debuggers
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public $lookupTest(req: TestIdWithProvider): Promise<InternalTestItem | undefined> {
|
||||
const owned = this.ownedTests.getTestById(req.testId);
|
||||
if (!owned) {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
export interface OwnedCollectionTestItem extends InternalTestItem {
|
||||
actual: vscode.TestItem;
|
||||
previousChildren: Set<string>;
|
||||
previousEquals: (v: vscode.TestItem) => boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
export class OwnedTestCollection {
|
||||
protected readonly testIdToInternal = new Map<string, OwnedCollectionTestItem>();
|
||||
|
||||
/**
|
||||
* Gets test information by ID, if it was defined and still exists in this
|
||||
* extension host.
|
||||
*/
|
||||
public getTestById(id: string) {
|
||||
return this.testIdToInternal.get(id);
|
||||
const { actual, previousChildren, previousEquals, ...item } = owned;
|
||||
return Promise.resolve(item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new test collection for a specific hierarchy for a workspace
|
||||
* or document observation.
|
||||
*/
|
||||
public createForHierarchy(publishDiff: (diff: TestsDiff) => void = () => undefined) {
|
||||
return new SingleUseTestCollection(this.testIdToInternal, publishDiff);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maintains tests created and registered for a single set of hierarchies
|
||||
* for a workspace or document.
|
||||
* @private
|
||||
*/
|
||||
export class SingleUseTestCollection implements IDisposable {
|
||||
protected readonly testItemToInternal = new Map<vscode.TestItem, OwnedCollectionTestItem>();
|
||||
protected diff: TestsDiff = [];
|
||||
private disposed = false;
|
||||
|
||||
constructor(private readonly testIdToInternal: Map<string, OwnedCollectionTestItem>, private readonly publishDiff: (diff: TestsDiff) => void) { }
|
||||
|
||||
/**
|
||||
* Adds a new root node to the collection.
|
||||
*/
|
||||
public addRoot(item: vscode.TestItem, providerId: string) {
|
||||
this.addItem(item, providerId, null);
|
||||
this.throttleSendDiff();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets test information by its reference, if it was defined and still exists
|
||||
* in this extension host.
|
||||
*/
|
||||
public getTestByReference(item: vscode.TestItem) {
|
||||
return this.testItemToInternal.get(item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Should be called when an item change is fired on the test provider.
|
||||
*/
|
||||
public onItemChange(item: vscode.TestItem, providerId: string) {
|
||||
const existing = this.testItemToInternal.get(item);
|
||||
if (!existing) {
|
||||
if (!this.disposed) {
|
||||
console.warn(`Received a TestProvider.onDidChangeTest for a test that wasn't seen before as a child.`);
|
||||
}
|
||||
private createDefaultDocumentTestHierarchy(provider: vscode.TestProvider, document: vscode.TextDocument, folder: vscode.WorkspaceFolder | undefined): vscode.TestHierarchy<vscode.TestItem> | undefined {
|
||||
if (!folder) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.addItem(item, providerId, existing.parent);
|
||||
this.throttleSendDiff();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a diff of all changes that have been made, and clears the diff queue.
|
||||
*/
|
||||
public collectDiff() {
|
||||
const diff = this.diff;
|
||||
this.diff = [];
|
||||
return diff;
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
for (const item of this.testItemToInternal.values()) {
|
||||
this.testIdToInternal.delete(item.id);
|
||||
const workspaceHierarchy = provider.createWorkspaceTestHierarchy?.(folder);
|
||||
if (!workspaceHierarchy) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.testIdToInternal.clear();
|
||||
this.diff = [];
|
||||
this.disposed = true;
|
||||
}
|
||||
const onDidChangeTest = new Emitter<vscode.TestItem>();
|
||||
workspaceHierarchy.onDidChangeTest(node => {
|
||||
const wrapper = TestItemFilteredWrapper.getWrapperForTestItem(node, document);
|
||||
const previouslySeen = wrapper.hasNodeMatchingFilter;
|
||||
|
||||
protected getId(): string {
|
||||
return generateUuid();
|
||||
}
|
||||
if (previouslySeen) {
|
||||
// reset cache and get whether you can currently see the TestItem.
|
||||
wrapper.reset();
|
||||
const currentlySeen = wrapper.hasNodeMatchingFilter;
|
||||
|
||||
private addItem(actual: vscode.TestItem, providerId: string, parent: string | null) {
|
||||
let internal = this.testItemToInternal.get(actual);
|
||||
if (!internal) {
|
||||
internal = {
|
||||
actual,
|
||||
id: this.getId(),
|
||||
parent,
|
||||
item: TestItem.from(actual),
|
||||
providerId,
|
||||
previousChildren: new Set(),
|
||||
previousEquals: itemEqualityComparator(actual),
|
||||
};
|
||||
if (currentlySeen) {
|
||||
onDidChangeTest.fire(wrapper);
|
||||
return;
|
||||
}
|
||||
|
||||
this.testItemToInternal.set(actual, internal);
|
||||
this.testIdToInternal.set(internal.id, internal);
|
||||
this.diff.push([TestDiffOpType.Add, { id: internal.id, parent, providerId, item: internal.item }]);
|
||||
} else if (!internal.previousEquals(actual)) {
|
||||
internal.item = TestItem.from(actual);
|
||||
internal.previousEquals = itemEqualityComparator(actual);
|
||||
this.diff.push([TestDiffOpType.Update, { id: internal.id, parent, providerId, item: internal.item }]);
|
||||
}
|
||||
|
||||
// If there are children, track which ones are deleted
|
||||
// and recursively and/update them.
|
||||
if (actual.children) {
|
||||
const deletedChildren = internal.previousChildren;
|
||||
const currentChildren = new Set<string>();
|
||||
for (const child of actual.children) {
|
||||
const c = this.addItem(child, providerId, internal.id);
|
||||
deletedChildren.delete(c.id);
|
||||
currentChildren.add(c.id);
|
||||
// Fire the event to say that the current visible parent has changed.
|
||||
onDidChangeTest.fire(wrapper.visibleParent);
|
||||
return;
|
||||
}
|
||||
|
||||
for (const child of deletedChildren) {
|
||||
this.removeItembyId(child);
|
||||
const previousParent = wrapper.visibleParent;
|
||||
wrapper.reset();
|
||||
const currentlySeen = wrapper.hasNodeMatchingFilter;
|
||||
|
||||
// It wasn't previously seen and isn't currently seen so
|
||||
// nothing has actually changed.
|
||||
if (!currentlySeen) {
|
||||
return;
|
||||
}
|
||||
|
||||
internal.previousChildren = currentChildren;
|
||||
}
|
||||
// The test is now visible so we need to refresh the cache
|
||||
// of the previous visible parent and fire that it has changed.
|
||||
previousParent.reset();
|
||||
onDidChangeTest.fire(previousParent);
|
||||
});
|
||||
|
||||
return {
|
||||
root: TestItemFilteredWrapper.getWrapperForTestItem(workspaceHierarchy.root, document),
|
||||
dispose: () => {
|
||||
onDidChangeTest.dispose();
|
||||
TestItemFilteredWrapper.removeFilter(document);
|
||||
},
|
||||
onDidChangeTest: onDidChangeTest.event
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return internal;
|
||||
/*
|
||||
* A class which wraps a vscode.TestItem that provides the ability to filter a TestItem's children
|
||||
* to only the children that are located in a certain vscode.Uri.
|
||||
*/
|
||||
export class TestItemFilteredWrapper implements vscode.TestItem {
|
||||
private static wrapperMap = new WeakMap<vscode.TextDocument, WeakMap<vscode.TestItem, TestItemFilteredWrapper>>();
|
||||
public static removeFilter(document: vscode.TextDocument): void {
|
||||
this.wrapperMap.delete(document);
|
||||
}
|
||||
|
||||
private removeItembyId(id: string) {
|
||||
this.diff.push([TestDiffOpType.Remove, id]);
|
||||
|
||||
const queue = [this.testIdToInternal.get(id)];
|
||||
while (queue.length) {
|
||||
const item = queue.pop();
|
||||
if (!item) {
|
||||
continue;
|
||||
}
|
||||
|
||||
this.testIdToInternal.delete(item.id);
|
||||
this.testItemToInternal.delete(item.actual);
|
||||
for (const child of item.previousChildren) {
|
||||
queue.push(this.testIdToInternal.get(child));
|
||||
}
|
||||
// Wraps the TestItem specified in a TestItemFilteredWrapper and pulls from a cache if it already exists.
|
||||
public static getWrapperForTestItem(item: vscode.TestItem, filterDocument: vscode.TextDocument, parent?: TestItemFilteredWrapper): TestItemFilteredWrapper {
|
||||
let innerMap = this.wrapperMap.get(filterDocument);
|
||||
if (innerMap?.has(item)) {
|
||||
return innerMap.get(item)!;
|
||||
}
|
||||
|
||||
if (!innerMap) {
|
||||
innerMap = new WeakMap<vscode.TestItem, TestItemFilteredWrapper>();
|
||||
this.wrapperMap.set(filterDocument, innerMap);
|
||||
|
||||
}
|
||||
|
||||
const w = new TestItemFilteredWrapper(item, filterDocument, parent);
|
||||
innerMap.set(item, w);
|
||||
return w;
|
||||
}
|
||||
|
||||
@throttle(200)
|
||||
protected throttleSendDiff() {
|
||||
const diff = this.collectDiff();
|
||||
if (diff.length) {
|
||||
this.publishDiff(diff);
|
||||
public get label() {
|
||||
return this.actual.label;
|
||||
}
|
||||
|
||||
public get debuggable() {
|
||||
return this.actual.debuggable;
|
||||
}
|
||||
|
||||
public get description() {
|
||||
return this.actual.description;
|
||||
}
|
||||
|
||||
public get location() {
|
||||
return this.actual.location;
|
||||
}
|
||||
|
||||
public get runnable() {
|
||||
return this.actual.runnable;
|
||||
}
|
||||
|
||||
public get state() {
|
||||
return this.actual.state;
|
||||
}
|
||||
|
||||
public get children() {
|
||||
// We only want children that match the filter.
|
||||
return this.getWrappedChildren().filter(child => child.hasNodeMatchingFilter);
|
||||
}
|
||||
|
||||
public get visibleParent(): TestItemFilteredWrapper {
|
||||
return this.hasNodeMatchingFilter ? this : this.parent!.visibleParent;
|
||||
}
|
||||
|
||||
private matchesFilter: boolean | undefined;
|
||||
|
||||
// Determines if the TestItem matches the filter. This would be true if:
|
||||
// 1. We don't have a parent (because the root is the workspace root node)
|
||||
// 2. The URI of the current node matches the filter URI
|
||||
// 3. Some child of the current node matches the filter URI
|
||||
public get hasNodeMatchingFilter(): boolean {
|
||||
if (this.matchesFilter === undefined) {
|
||||
this.matchesFilter = !this.parent
|
||||
|| this.actual.location?.uri.toString() === this.filterDocument.uri.toString()
|
||||
|| this.getWrappedChildren().some(child => child.hasNodeMatchingFilter);
|
||||
}
|
||||
|
||||
return this.matchesFilter;
|
||||
}
|
||||
|
||||
// Reset the cache of whether or not you can see a node from a particular node
|
||||
// up to it's visible parent.
|
||||
public reset(): void {
|
||||
if (this !== this.visibleParent) {
|
||||
this.parent?.reset();
|
||||
}
|
||||
this.matchesFilter = undefined;
|
||||
}
|
||||
|
||||
|
||||
private constructor(public readonly actual: vscode.TestItem, private filterDocument: vscode.TextDocument, private readonly parent?: TestItemFilteredWrapper) {
|
||||
this.getWrappedChildren();
|
||||
}
|
||||
|
||||
private getWrappedChildren() {
|
||||
return this.actual.children?.map(t => TestItemFilteredWrapper.getWrapperForTestItem(t, this.filterDocument, this)) || [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -382,7 +415,7 @@ export class SingleUseTestCollection implements IDisposable {
|
||||
interface MirroredCollectionTestItem extends IncrementalTestCollectionItem {
|
||||
revived: vscode.TestItem;
|
||||
depth: number;
|
||||
wrapped?: vscode.TestItem;
|
||||
wrapped?: vscode.RequiredTestItem;
|
||||
}
|
||||
|
||||
class MirroredChangeCollector extends IncrementalChangeCollector<MirroredCollectionTestItem> {
|
||||
@@ -411,7 +444,7 @@ class MirroredChangeCollector extends IncrementalChangeCollector<MirroredCollect
|
||||
* @override
|
||||
*/
|
||||
public update(node: MirroredCollectionTestItem): void {
|
||||
Object.assign(node.revived, TestItem.to(node.item));
|
||||
Object.assign(node.revived, TestItem.toShallow(node.item));
|
||||
if (!this.added.has(node)) {
|
||||
this.updated.add(node);
|
||||
}
|
||||
@@ -545,8 +578,8 @@ export class MirroredTestCollection extends AbstractIncrementalTestCollection<Mi
|
||||
/**
|
||||
* Translates the item IDs to TestItems for exposure to extensions.
|
||||
*/
|
||||
public getAllAsTestItem(itemIds: Iterable<string>): vscode.TestItem[] {
|
||||
let output: vscode.TestItem[] = [];
|
||||
public getAllAsTestItem(itemIds: Iterable<string>): vscode.RequiredTestItem[] {
|
||||
let output: vscode.RequiredTestItem[] = [];
|
||||
for (const itemId of itemIds) {
|
||||
const item = this.items.get(itemId);
|
||||
if (item) {
|
||||
@@ -577,7 +610,7 @@ export class MirroredTestCollection extends AbstractIncrementalTestCollection<Mi
|
||||
* @override
|
||||
*/
|
||||
protected createItem(item: InternalTestItem, parent?: MirroredCollectionTestItem): MirroredCollectionTestItem {
|
||||
return { ...item, revived: TestItem.to(item.item), depth: parent ? parent.depth + 1 : 0, children: new Set() };
|
||||
return { ...item, revived: TestItem.toShallow(item.item), depth: parent ? parent.depth + 1 : 0, children: new Set() };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -590,9 +623,9 @@ export class MirroredTestCollection extends AbstractIncrementalTestCollection<Mi
|
||||
/**
|
||||
* Gets the public test item instance for the given mirrored record.
|
||||
*/
|
||||
public getPublicTestItem(item: MirroredCollectionTestItem): vscode.TestItem {
|
||||
public getPublicTestItem(item: MirroredCollectionTestItem): vscode.RequiredTestItem {
|
||||
if (!item.wrapped) {
|
||||
item.wrapped = new ExtHostTestItem(item, this);
|
||||
item.wrapped = new TestItemFromMirror(item, this);
|
||||
}
|
||||
|
||||
return item.wrapped;
|
||||
@@ -605,10 +638,11 @@ const getMirroredItemId = (item: vscode.TestItem) => {
|
||||
|
||||
const MirroredItemId = Symbol('MirroredItemId');
|
||||
|
||||
class ExtHostTestItem implements vscode.TestItem, RequiredTestItem {
|
||||
class TestItemFromMirror implements vscode.RequiredTestItem {
|
||||
readonly #internal: MirroredCollectionTestItem;
|
||||
readonly #collection: MirroredTestCollection;
|
||||
|
||||
public get id() { return this.#internal.revived.id!; }
|
||||
public get label() { return this.#internal.revived.label; }
|
||||
public get description() { return this.#internal.revived.description; }
|
||||
public get state() { return this.#internal.revived.state; }
|
||||
@@ -627,14 +661,18 @@ class ExtHostTestItem implements vscode.TestItem, RequiredTestItem {
|
||||
}
|
||||
|
||||
public toJSON() {
|
||||
const serialized: RequiredTestItem = {
|
||||
const serialized: vscode.RequiredTestItem & TestIdWithProvider = {
|
||||
id: this.id,
|
||||
label: this.label,
|
||||
description: this.description,
|
||||
state: this.state,
|
||||
location: this.location,
|
||||
runnable: this.runnable,
|
||||
debuggable: this.debuggable,
|
||||
children: this.children.map(c => (c as ExtHostTestItem).toJSON()),
|
||||
children: this.children.map(c => (c as TestItemFromMirror).toJSON()),
|
||||
|
||||
providerId: this.#internal.providerId,
|
||||
testId: this.#internal.id,
|
||||
};
|
||||
|
||||
return serialized;
|
||||
@@ -655,6 +693,7 @@ abstract class AbstractTestObserverFactory {
|
||||
const resourceKey = resourceUri.toString();
|
||||
const resource = this.resources.get(resourceKey) ?? this.createObserverData(resourceUri);
|
||||
|
||||
resource.pendingDeletion?.dispose();
|
||||
resource.observers++;
|
||||
|
||||
return {
|
||||
@@ -778,16 +817,9 @@ class TextDocumentTestObserverFactory extends AbstractTestObserverFactory {
|
||||
const uriString = resourceUri.toString();
|
||||
this.diffListeners.set(uriString, onDiff);
|
||||
|
||||
const disposeListener = this.documents.onDidRemoveDocuments(evt => {
|
||||
if (evt.some(delta => delta.document.uri.toString() === uriString)) {
|
||||
this.unlisten(resourceUri);
|
||||
}
|
||||
});
|
||||
|
||||
this.proxy.$subscribeToDiffs(ExtHostTestingResource.TextDocument, resourceUri);
|
||||
return new Disposable(() => {
|
||||
this.proxy.$unsubscribeFromDiffs(ExtHostTestingResource.TextDocument, resourceUri);
|
||||
disposeListener.dispose();
|
||||
this.diffListeners.delete(uriString);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'
|
||||
import { MarkdownString } from 'vs/workbench/api/common/extHostTypeConverters';
|
||||
import { IMarkdownString } from 'vs/base/common/htmlContent';
|
||||
import { CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { Command } from 'vs/editor/common/modes';
|
||||
|
||||
type TreeItemHandle = string;
|
||||
|
||||
@@ -132,12 +133,12 @@ export class ExtHostTreeViews implements ExtHostTreeViewsShape {
|
||||
return treeView.hasResolve;
|
||||
}
|
||||
|
||||
$resolve(treeViewId: string, treeItemHandle: string): Promise<ITreeItem | undefined> {
|
||||
$resolve(treeViewId: string, treeItemHandle: string, token: vscode.CancellationToken): Promise<ITreeItem | undefined> {
|
||||
const treeView = this.treeViews.get(treeViewId);
|
||||
if (!treeView) {
|
||||
throw new Error(localize('treeView.notRegistered', 'No tree view with id \'{0}\' registered.', treeViewId));
|
||||
}
|
||||
return treeView.resolveTreeItem(treeItemHandle);
|
||||
return treeView.resolveTreeItem(treeItemHandle, token);
|
||||
}
|
||||
|
||||
$setExpanded(treeViewId: string, treeItemHandle: string, expanded: boolean): void {
|
||||
@@ -184,6 +185,7 @@ interface TreeNode extends IDisposable {
|
||||
extensionItem: vscode.TreeItem;
|
||||
parent: TreeNode | Root;
|
||||
children?: TreeNode[];
|
||||
disposableStore: DisposableStore;
|
||||
}
|
||||
|
||||
class ExtHostTreeView<T> extends Disposable {
|
||||
@@ -370,7 +372,7 @@ class ExtHostTreeView<T> extends Disposable {
|
||||
return !!this.dataProvider.resolveTreeItem;
|
||||
}
|
||||
|
||||
async resolveTreeItem(treeItemHandle: string): Promise<ITreeItem | undefined> {
|
||||
async resolveTreeItem(treeItemHandle: string, token: vscode.CancellationToken): Promise<ITreeItem | undefined> {
|
||||
if (!this.dataProvider.resolveTreeItem) {
|
||||
return;
|
||||
}
|
||||
@@ -378,9 +380,10 @@ class ExtHostTreeView<T> extends Disposable {
|
||||
if (element) {
|
||||
const node = this.nodes.get(element);
|
||||
if (node) {
|
||||
const resolve = await this.dataProvider.resolveTreeItem(node.extensionItem, element) ?? node.extensionItem;
|
||||
// Resolvable elements. Currently only tooltip.
|
||||
const resolve = await this.dataProvider.resolveTreeItem(node.extensionItem, element, token) ?? node.extensionItem;
|
||||
// Resolvable elements. Currently only tooltip and command.
|
||||
node.item.tooltip = this.getTooltip(resolve.tooltip);
|
||||
node.item.command = this.getCommand(node.disposableStore, resolve.command);
|
||||
return node.item;
|
||||
}
|
||||
}
|
||||
@@ -573,8 +576,12 @@ class ExtHostTreeView<T> extends Disposable {
|
||||
return tooltip;
|
||||
}
|
||||
|
||||
private getCommand(disposable: DisposableStore, command?: vscode.Command): Command | undefined {
|
||||
return command ? this.commands.toInternal(command, disposable) : undefined;
|
||||
}
|
||||
|
||||
private createTreeNode(element: T, extensionTreeItem: vscode.TreeItem, parent: TreeNode | Root): TreeNode {
|
||||
const disposable = new DisposableStore();
|
||||
const disposableStore = new DisposableStore();
|
||||
const handle = this.createHandle(element, extensionTreeItem, parent);
|
||||
const icon = this.getLightIconPath(extensionTreeItem);
|
||||
const item: ITreeItem = {
|
||||
@@ -584,7 +591,7 @@ class ExtHostTreeView<T> extends Disposable {
|
||||
description: extensionTreeItem.description,
|
||||
resourceUri: extensionTreeItem.resourceUri,
|
||||
tooltip: this.getTooltip(extensionTreeItem.tooltip),
|
||||
command: extensionTreeItem.command ? this.commands.toInternal(extensionTreeItem.command, disposable) : undefined,
|
||||
command: this.getCommand(disposableStore, extensionTreeItem.command),
|
||||
contextValue: extensionTreeItem.contextValue,
|
||||
icon,
|
||||
iconDark: this.getDarkIconPath(extensionTreeItem) || icon,
|
||||
@@ -598,7 +605,8 @@ class ExtHostTreeView<T> extends Disposable {
|
||||
extensionItem: extensionTreeItem,
|
||||
parent,
|
||||
children: undefined,
|
||||
dispose(): void { disposable.dispose(); }
|
||||
disposableStore,
|
||||
dispose(): void { disposableStore.dispose(); }
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ExtHostTunnelServiceShape, MainContext, MainThreadTunnelServiceShape } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { ExtHostTunnelServiceShape } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import * as vscode from 'vscode';
|
||||
import { RemoteTunnel, TunnelCreationOptions, TunnelOptions } from 'vs/platform/remote/common/tunnel';
|
||||
@@ -11,18 +11,27 @@ import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
|
||||
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { CandidatePort } from 'vs/workbench/services/remote/common/remoteExplorerService';
|
||||
|
||||
export interface TunnelDto {
|
||||
remoteAddress: { port: number, host: string };
|
||||
localAddress: { port: number, host: string } | string;
|
||||
public: boolean;
|
||||
}
|
||||
|
||||
export namespace TunnelDto {
|
||||
export function fromApiTunnel(tunnel: vscode.Tunnel): TunnelDto {
|
||||
return { remoteAddress: tunnel.remoteAddress, localAddress: tunnel.localAddress };
|
||||
return { remoteAddress: tunnel.remoteAddress, localAddress: tunnel.localAddress, public: !!tunnel.public };
|
||||
}
|
||||
export function fromServiceTunnel(tunnel: RemoteTunnel): TunnelDto {
|
||||
return { remoteAddress: { host: tunnel.tunnelRemoteHost, port: tunnel.tunnelRemotePort }, localAddress: tunnel.localAddress };
|
||||
return {
|
||||
remoteAddress: {
|
||||
host: tunnel.tunnelRemoteHost,
|
||||
port: tunnel.tunnelRemotePort
|
||||
},
|
||||
localAddress: tunnel.localAddress,
|
||||
public: tunnel.public
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,12 +53,13 @@ export const IExtHostTunnelService = createDecorator<IExtHostTunnelService>('IEx
|
||||
export class ExtHostTunnelService implements IExtHostTunnelService {
|
||||
declare readonly _serviceBrand: undefined;
|
||||
onDidChangeTunnels: vscode.Event<void> = (new Emitter<void>()).event;
|
||||
private readonly _proxy: MainThreadTunnelServiceShape;
|
||||
|
||||
constructor(
|
||||
@IExtHostRpcService extHostRpc: IExtHostRpcService,
|
||||
) {
|
||||
this._proxy = extHostRpc.getProxy(MainContext.MainThreadTunnelService);
|
||||
}
|
||||
async $applyCandidateFilter(candidates: CandidatePort[]): Promise<CandidatePort[]> {
|
||||
return candidates;
|
||||
}
|
||||
|
||||
async openTunnel(extension: IExtensionDescription, forward: TunnelOptions): Promise<vscode.Tunnel | undefined> {
|
||||
@@ -59,10 +69,10 @@ export class ExtHostTunnelService implements IExtHostTunnelService {
|
||||
return [];
|
||||
}
|
||||
async setTunnelExtensionFunctions(provider: vscode.RemoteAuthorityResolver | undefined): Promise<IDisposable> {
|
||||
await this._proxy.$tunnelServiceReady();
|
||||
return { dispose: () => { } };
|
||||
}
|
||||
$forwardPort(tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions): Promise<TunnelDto> | undefined { return undefined; }
|
||||
async $forwardPort(tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions): Promise<TunnelDto | undefined> { return undefined; }
|
||||
async $closeTunnel(remote: { host: string, port: number }): Promise<void> { }
|
||||
async $onDidTunnelsChange(): Promise<void> { }
|
||||
async $registerCandidateFinder(): Promise<void> { }
|
||||
}
|
||||
|
||||
@@ -1015,6 +1015,29 @@ export namespace SignatureHelp {
|
||||
}
|
||||
}
|
||||
|
||||
export namespace InlineHint {
|
||||
|
||||
export function from(hint: vscode.InlineHint): modes.InlineHint {
|
||||
return {
|
||||
text: hint.text,
|
||||
range: Range.from(hint.range),
|
||||
description: hint.description && MarkdownString.fromStrict(hint.description),
|
||||
whitespaceBefore: hint.whitespaceBefore,
|
||||
whitespaceAfter: hint.whitespaceAfter
|
||||
};
|
||||
}
|
||||
|
||||
export function to(hint: modes.InlineHint): vscode.InlineHint {
|
||||
return new types.InlineHint(
|
||||
hint.text,
|
||||
Range.to(hint.range),
|
||||
htmlContent.isMarkdownString(hint.description) ? MarkdownString.to(hint.description) : hint.description,
|
||||
hint.whitespaceBefore,
|
||||
hint.whitespaceAfter
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export namespace DocumentLink {
|
||||
|
||||
export function from(link: vscode.DocumentLink): modes.ILink {
|
||||
@@ -1389,19 +1412,21 @@ export namespace TestState {
|
||||
|
||||
|
||||
export namespace TestItem {
|
||||
export function from(item: vscode.TestItem): ITestItem {
|
||||
export function from(item: vscode.TestItem, parentExtId?: string): ITestItem {
|
||||
return {
|
||||
extId: item.id ?? (parentExtId ? `${parentExtId}\0${item.label}` : item.label),
|
||||
label: item.label,
|
||||
location: item.location ? location.from(item.location) : undefined,
|
||||
debuggable: item.debuggable,
|
||||
debuggable: item.debuggable ?? false,
|
||||
description: item.description,
|
||||
runnable: item.runnable,
|
||||
runnable: item.runnable ?? true,
|
||||
state: TestState.from(item.state),
|
||||
};
|
||||
}
|
||||
|
||||
export function to(item: ITestItem): vscode.TestItem {
|
||||
export function toShallow(item: ITestItem): Omit<vscode.RequiredTestItem, 'children'> {
|
||||
return {
|
||||
id: item.extId,
|
||||
label: item.label,
|
||||
location: item.location && location.to({
|
||||
range: item.location.range,
|
||||
|
||||
@@ -13,7 +13,7 @@ import { URI } from 'vs/base/common/uri';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { FileSystemProviderErrorCode, markAsFileSystemProviderError } from 'vs/platform/files/common/files';
|
||||
import { RemoteAuthorityResolverErrorCode } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
||||
import { addIdToOutput, CellEditType, ICellEditOperation, IDisplayOutput } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
import { addIdToOutput, CellEditType, ICellEditOperation, IDisplayOutput, notebookDocumentMetadataDefaults } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
import type * as vscode from 'vscode';
|
||||
|
||||
function es5ClassCompat(target: Function): any {
|
||||
@@ -638,7 +638,7 @@ export class WorkspaceEdit implements vscode.WorkspaceEdit {
|
||||
// --- notebook
|
||||
|
||||
replaceNotebookMetadata(uri: URI, value: vscode.NotebookDocumentMetadata, metadata?: vscode.WorkspaceEditEntryMetadata): void {
|
||||
this._edits.push({ _type: FileEditType.Cell, metadata, uri, notebookMetadata: value });
|
||||
this._edits.push({ _type: FileEditType.Cell, metadata, uri, edit: { editType: CellEditType.DocumentMetadata, metadata: { ...notebookDocumentMetadataDefaults, ...value } }, notebookMetadata: value });
|
||||
}
|
||||
|
||||
replaceNotebookCells(uri: URI, start: number, end: number, cells: vscode.NotebookCellData[], metadata?: vscode.WorkspaceEditEntryMetadata): void {
|
||||
@@ -1373,6 +1373,23 @@ export enum SignatureHelpTriggerKind {
|
||||
ContentChange = 3,
|
||||
}
|
||||
|
||||
@es5ClassCompat
|
||||
export class InlineHint {
|
||||
text: string;
|
||||
range: Range;
|
||||
description?: string | vscode.MarkdownString;
|
||||
whitespaceBefore?: boolean;
|
||||
whitespaceAfter?: boolean;
|
||||
|
||||
constructor(text: string, range: Range, description?: string | vscode.MarkdownString, whitespaceBefore?: boolean, whitespaceAfter?: boolean) {
|
||||
this.text = text;
|
||||
this.range = range;
|
||||
this.description = description;
|
||||
this.whitespaceBefore = whitespaceBefore;
|
||||
this.whitespaceAfter = whitespaceAfter;
|
||||
}
|
||||
}
|
||||
|
||||
export enum CompletionTriggerKind {
|
||||
Invoke = 0,
|
||||
TriggerCharacter = 1,
|
||||
@@ -2298,9 +2315,6 @@ export class FunctionBreakpoint extends Breakpoint {
|
||||
|
||||
constructor(functionName: string, enabled?: boolean, condition?: string, hitCondition?: string, logMessage?: string) {
|
||||
super(enabled, condition, hitCondition, logMessage);
|
||||
if (!functionName) {
|
||||
throw illegalArgument('functionName');
|
||||
}
|
||||
this.functionName = functionName;
|
||||
}
|
||||
}
|
||||
@@ -2858,7 +2872,8 @@ export enum NotebookCellStatusBarAlignment {
|
||||
export enum NotebookEditorRevealType {
|
||||
Default = 0,
|
||||
InCenter = 1,
|
||||
InCenterIfOutsideViewport = 2
|
||||
InCenterIfOutsideViewport = 2,
|
||||
AtTop = 3
|
||||
}
|
||||
|
||||
|
||||
@@ -2924,11 +2939,12 @@ export class LinkedEditingRanges {
|
||||
//#region Testing
|
||||
export enum TestRunState {
|
||||
Unset = 0,
|
||||
Running = 1,
|
||||
Passed = 2,
|
||||
Failed = 3,
|
||||
Skipped = 4,
|
||||
Errored = 5
|
||||
Queued = 1,
|
||||
Running = 2,
|
||||
Passed = 3,
|
||||
Failed = 4,
|
||||
Skipped = 5,
|
||||
Errored = 6
|
||||
}
|
||||
|
||||
export enum TestMessageSeverity {
|
||||
@@ -2963,15 +2979,15 @@ export class TestState {
|
||||
}
|
||||
}
|
||||
|
||||
type AllowedUndefined = 'description' | 'location';
|
||||
|
||||
/**
|
||||
* Test item without any optional properties. Only some properties are
|
||||
* permitted to be undefined, but they must still exist.
|
||||
*/
|
||||
export type RequiredTestItem = {
|
||||
[K in keyof Required<vscode.TestItem>]: K extends AllowedUndefined ? vscode.TestItem[K] : Required<vscode.TestItem>[K]
|
||||
};
|
||||
export type RequiredTestItem = vscode.RequiredTestItem;
|
||||
|
||||
export type TestItem = vscode.TestItem;
|
||||
|
||||
//#endregion
|
||||
|
||||
export enum ExternalUriOpenerPriority {
|
||||
None = 0,
|
||||
Option = 1,
|
||||
Default = 2,
|
||||
Preferred = 3,
|
||||
}
|
||||
|
||||
74
lib/vscode/src/vs/workbench/api/common/extHostUriOpener.ts
Normal file
74
lib/vscode/src/vs/workbench/api/common/extHostUriOpener.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import * as modes from 'vs/editor/common/modes';
|
||||
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
|
||||
import type * as vscode from 'vscode';
|
||||
import { ExtHostUriOpenersShape, IMainContext, MainContext, MainThreadUriOpenersShape } from './extHost.protocol';
|
||||
|
||||
|
||||
export class ExtHostUriOpeners implements ExtHostUriOpenersShape {
|
||||
|
||||
private static readonly supportedSchemes = new Set<string>([Schemas.http, Schemas.https]);
|
||||
|
||||
private readonly _proxy: MainThreadUriOpenersShape;
|
||||
|
||||
private readonly _openers = new Map<string, vscode.ExternalUriOpener>();
|
||||
|
||||
constructor(
|
||||
mainContext: IMainContext,
|
||||
) {
|
||||
this._proxy = mainContext.getProxy(MainContext.MainThreadUriOpeners);
|
||||
}
|
||||
|
||||
registerExternalUriOpener(
|
||||
extensionId: ExtensionIdentifier,
|
||||
id: string,
|
||||
opener: vscode.ExternalUriOpener,
|
||||
metadata: vscode.ExternalUriOpenerMetadata,
|
||||
): vscode.Disposable {
|
||||
if (this._openers.has(id)) {
|
||||
throw new Error(`Opener with id '${id}' already registered`);
|
||||
}
|
||||
|
||||
const invalidScheme = metadata.schemes.find(scheme => !ExtHostUriOpeners.supportedSchemes.has(scheme));
|
||||
if (invalidScheme) {
|
||||
throw new Error(`Scheme '${invalidScheme}' is not supported. Only http and https are currently supported.`);
|
||||
}
|
||||
|
||||
this._openers.set(id, opener);
|
||||
this._proxy.$registerUriOpener(id, metadata.schemes, extensionId, metadata.label);
|
||||
|
||||
return toDisposable(() => {
|
||||
this._openers.delete(id);
|
||||
this._proxy.$unregisterUriOpener(id);
|
||||
});
|
||||
}
|
||||
|
||||
async $canOpenUri(id: string, uriComponents: UriComponents, token: CancellationToken): Promise<modes.ExternalUriOpenerPriority> {
|
||||
const opener = this._openers.get(id);
|
||||
if (!opener) {
|
||||
throw new Error(`Unknown opener with id: ${id}`);
|
||||
}
|
||||
|
||||
const uri = URI.revive(uriComponents);
|
||||
return opener.canOpenExternalUri(uri, token);
|
||||
}
|
||||
|
||||
async $openUri(id: string, context: { resolvedUri: UriComponents, sourceUri: UriComponents }, token: CancellationToken): Promise<void> {
|
||||
const opener = this._openers.get(id);
|
||||
if (!opener) {
|
||||
throw new Error(`Unknown opener id: '${id}'`);
|
||||
}
|
||||
|
||||
return opener.openExternalUri(URI.revive(context.resolvedUri), {
|
||||
sourceUri: URI.revive(context.sourceUri)
|
||||
}, token);
|
||||
}
|
||||
}
|
||||
@@ -287,8 +287,8 @@ export class ExtHostWebviewPanels implements extHostProtocol.ExtHostWebviewPanel
|
||||
await serializer.deserializeWebviewPanel(revivedPanel, state);
|
||||
}
|
||||
|
||||
public createNewWebviewPanel(webviewHandle: string, viewType: string, title: string, position: number, options: modes.IWebviewOptions & modes.IWebviewPanelOptions, webview: ExtHostWebview) {
|
||||
const panel = new ExtHostWebviewPanel(webviewHandle, this._proxy, viewType, title, typeof position === 'number' && position >= 0 ? typeConverters.ViewColumn.to(position) : undefined, options, webview);
|
||||
public createNewWebviewPanel(webviewHandle: string, viewType: string, title: string, position: vscode.ViewColumn, options: modes.IWebviewOptions & modes.IWebviewPanelOptions, webview: ExtHostWebview) {
|
||||
const panel = new ExtHostWebviewPanel(webviewHandle, this._proxy, viewType, title, position, options, webview);
|
||||
this._webviewPanels.set(webviewHandle, panel);
|
||||
return panel;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { TernarySearchTree } from 'vs/base/common/map';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { Counter } from 'vs/base/common/numbers';
|
||||
import { basename, basenameOrAuthority, dirname, isEqual, relativePath } from 'vs/base/common/resources';
|
||||
import { basename, basenameOrAuthority, dirname, ExtUri, relativePath } from 'vs/base/common/resources';
|
||||
import { compare } from 'vs/base/common/strings';
|
||||
import { withUndefinedAsNull } from 'vs/base/common/types';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
@@ -37,27 +37,27 @@ export interface IExtHostWorkspaceProvider {
|
||||
resolveProxy(url: string): Promise<string | undefined>;
|
||||
}
|
||||
|
||||
function isFolderEqual(folderA: URI, folderB: URI): boolean {
|
||||
return isEqual(folderA, folderB);
|
||||
function isFolderEqual(folderA: URI, folderB: URI, extHostFileSystemInfo: IExtHostFileSystemInfo): boolean {
|
||||
return new ExtUri(uri => ignorePathCasing(uri, extHostFileSystemInfo)).isEqual(folderA, folderB);
|
||||
}
|
||||
|
||||
function compareWorkspaceFolderByUri(a: vscode.WorkspaceFolder, b: vscode.WorkspaceFolder): number {
|
||||
return isFolderEqual(a.uri, b.uri) ? 0 : compare(a.uri.toString(), b.uri.toString());
|
||||
function compareWorkspaceFolderByUri(a: vscode.WorkspaceFolder, b: vscode.WorkspaceFolder, extHostFileSystemInfo: IExtHostFileSystemInfo): number {
|
||||
return isFolderEqual(a.uri, b.uri, extHostFileSystemInfo) ? 0 : compare(a.uri.toString(), b.uri.toString());
|
||||
}
|
||||
|
||||
function compareWorkspaceFolderByUriAndNameAndIndex(a: vscode.WorkspaceFolder, b: vscode.WorkspaceFolder): number {
|
||||
function compareWorkspaceFolderByUriAndNameAndIndex(a: vscode.WorkspaceFolder, b: vscode.WorkspaceFolder, extHostFileSystemInfo: IExtHostFileSystemInfo): 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());
|
||||
return isFolderEqual(a.uri, b.uri, extHostFileSystemInfo) ? 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);
|
||||
function delta(oldFolders: vscode.WorkspaceFolder[], newFolders: vscode.WorkspaceFolder[], compare: (a: vscode.WorkspaceFolder, b: vscode.WorkspaceFolder, extHostFileSystemInfo: IExtHostFileSystemInfo) => number, extHostFileSystemInfo: IExtHostFileSystemInfo): { removed: vscode.WorkspaceFolder[], added: vscode.WorkspaceFolder[] } {
|
||||
const oldSortedFolders = oldFolders.slice(0).sort((a, b) => compare(a, b, extHostFileSystemInfo));
|
||||
const newSortedFolders = newFolders.slice(0).sort((a, b) => compare(a, b, extHostFileSystemInfo));
|
||||
|
||||
return arrayDelta(oldSortedFolders, newSortedFolders, compare);
|
||||
return arrayDelta(oldSortedFolders, newSortedFolders, (a, b) => compare(a, b, extHostFileSystemInfo));
|
||||
}
|
||||
|
||||
function ignorePathCasing(uri: URI, extHostFileSystemInfo: IExtHostFileSystemInfo): boolean {
|
||||
@@ -87,7 +87,7 @@ class ExtHostWorkspaceImpl extends Workspace {
|
||||
if (previousConfirmedWorkspace) {
|
||||
folders.forEach((folderData, index) => {
|
||||
const folderUri = URI.revive(folderData.uri);
|
||||
const existingFolder = ExtHostWorkspaceImpl._findFolder(previousUnconfirmedWorkspace || previousConfirmedWorkspace, folderUri);
|
||||
const existingFolder = ExtHostWorkspaceImpl._findFolder(previousUnconfirmedWorkspace || previousConfirmedWorkspace, folderUri, extHostFileSystemInfo);
|
||||
|
||||
if (existingFolder) {
|
||||
existingFolder.name = folderData.name;
|
||||
@@ -106,15 +106,15 @@ class ExtHostWorkspaceImpl extends Workspace {
|
||||
newWorkspaceFolders.sort((f1, f2) => f1.index < f2.index ? -1 : 1);
|
||||
|
||||
const workspace = new ExtHostWorkspaceImpl(id, name, newWorkspaceFolders, configuration ? URI.revive(configuration) : null, !!isUntitled, uri => ignorePathCasing(uri, extHostFileSystemInfo));
|
||||
const { added, removed } = delta(oldWorkspace ? oldWorkspace.workspaceFolders : [], workspace.workspaceFolders, compareWorkspaceFolderByUri);
|
||||
const { added, removed } = delta(oldWorkspace ? oldWorkspace.workspaceFolders : [], workspace.workspaceFolders, compareWorkspaceFolderByUri, extHostFileSystemInfo);
|
||||
|
||||
return { workspace, added, removed };
|
||||
}
|
||||
|
||||
private static _findFolder(workspace: ExtHostWorkspaceImpl, folderUriToFind: URI): MutableWorkspaceFolder | undefined {
|
||||
private static _findFolder(workspace: ExtHostWorkspaceImpl, folderUriToFind: URI, extHostFileSystemInfo: IExtHostFileSystemInfo): MutableWorkspaceFolder | undefined {
|
||||
for (let i = 0; i < workspace.folders.length; i++) {
|
||||
const folder = workspace.workspaceFolders[i];
|
||||
if (isFolderEqual(folder.uri, folderUriToFind)) {
|
||||
if (isFolderEqual(folder.uri, folderUriToFind, extHostFileSystemInfo)) {
|
||||
return folder;
|
||||
}
|
||||
}
|
||||
@@ -254,7 +254,7 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac
|
||||
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))) {
|
||||
if (URI.isUri(folderToAdd.uri) && !validatedDistinctWorkspaceFoldersToAdd.some(f => isFolderEqual(f.uri, folderToAdd.uri, this._extHostFileSystemInfo))) {
|
||||
validatedDistinctWorkspaceFoldersToAdd.push({ uri: folderToAdd.uri, name: folderToAdd.name || basenameOrAuthority(folderToAdd.uri) });
|
||||
}
|
||||
});
|
||||
@@ -283,13 +283,13 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac
|
||||
|
||||
for (let i = 0; i < newWorkspaceFolders.length; i++) {
|
||||
const folder = newWorkspaceFolders[i];
|
||||
if (newWorkspaceFolders.some((otherFolder, index) => index !== i && isFolderEqual(folder.uri, otherFolder.uri))) {
|
||||
if (newWorkspaceFolders.some((otherFolder, index) => index !== i && isFolderEqual(folder.uri, otherFolder.uri, this._extHostFileSystemInfo))) {
|
||||
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);
|
||||
const { added, removed } = delta(currentWorkspaceFolders, newWorkspaceFolders, compareWorkspaceFolderByUriAndNameAndIndex, this._extHostFileSystemInfo);
|
||||
if (added.length === 0 && removed.length === 0) {
|
||||
return false; // nothing actually changed
|
||||
}
|
||||
|
||||
@@ -1,152 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
|
||||
export interface IFullSemanticTokensDto {
|
||||
id: number;
|
||||
type: 'full';
|
||||
data: Uint32Array;
|
||||
}
|
||||
|
||||
export interface IDeltaSemanticTokensDto {
|
||||
id: number;
|
||||
type: 'delta';
|
||||
deltas: { start: number; deleteCount: number; data?: Uint32Array; }[];
|
||||
}
|
||||
|
||||
export type ISemanticTokensDto = IFullSemanticTokensDto | IDeltaSemanticTokensDto;
|
||||
|
||||
const enum EncodedSemanticTokensType {
|
||||
Full = 1,
|
||||
Delta = 2
|
||||
}
|
||||
|
||||
function reverseEndianness(arr: Uint8Array): void {
|
||||
for (let i = 0, len = arr.length; i < len; i += 4) {
|
||||
// flip bytes 0<->3 and 1<->2
|
||||
const b0 = arr[i + 0];
|
||||
const b1 = arr[i + 1];
|
||||
const b2 = arr[i + 2];
|
||||
const b3 = arr[i + 3];
|
||||
arr[i + 0] = b3;
|
||||
arr[i + 1] = b2;
|
||||
arr[i + 2] = b1;
|
||||
arr[i + 3] = b0;
|
||||
}
|
||||
}
|
||||
|
||||
function toLittleEndianBuffer(arr: Uint32Array): VSBuffer {
|
||||
const uint8Arr = new Uint8Array(arr.buffer, arr.byteOffset, arr.length * 4);
|
||||
if (!platform.isLittleEndian()) {
|
||||
// the byte order must be changed
|
||||
reverseEndianness(uint8Arr);
|
||||
}
|
||||
return VSBuffer.wrap(uint8Arr);
|
||||
}
|
||||
|
||||
function fromLittleEndianBuffer(buff: VSBuffer): Uint32Array {
|
||||
const uint8Arr = buff.buffer;
|
||||
if (!platform.isLittleEndian()) {
|
||||
// the byte order must be changed
|
||||
reverseEndianness(uint8Arr);
|
||||
}
|
||||
if (uint8Arr.byteOffset % 4 === 0) {
|
||||
return new Uint32Array(uint8Arr.buffer, uint8Arr.byteOffset, uint8Arr.length / 4);
|
||||
} else {
|
||||
// unaligned memory access doesn't work on all platforms
|
||||
const data = new Uint8Array(uint8Arr.byteLength);
|
||||
data.set(uint8Arr);
|
||||
return new Uint32Array(data.buffer, data.byteOffset, data.length / 4);
|
||||
}
|
||||
}
|
||||
|
||||
export function encodeSemanticTokensDto(semanticTokens: ISemanticTokensDto): VSBuffer {
|
||||
const dest = new Uint32Array(encodeSemanticTokensDtoSize(semanticTokens));
|
||||
let offset = 0;
|
||||
dest[offset++] = semanticTokens.id;
|
||||
if (semanticTokens.type === 'full') {
|
||||
dest[offset++] = EncodedSemanticTokensType.Full;
|
||||
dest[offset++] = semanticTokens.data.length;
|
||||
dest.set(semanticTokens.data, offset); offset += semanticTokens.data.length;
|
||||
} else {
|
||||
dest[offset++] = EncodedSemanticTokensType.Delta;
|
||||
dest[offset++] = semanticTokens.deltas.length;
|
||||
for (const delta of semanticTokens.deltas) {
|
||||
dest[offset++] = delta.start;
|
||||
dest[offset++] = delta.deleteCount;
|
||||
if (delta.data) {
|
||||
dest[offset++] = delta.data.length;
|
||||
dest.set(delta.data, offset); offset += delta.data.length;
|
||||
} else {
|
||||
dest[offset++] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
return toLittleEndianBuffer(dest);
|
||||
}
|
||||
|
||||
function encodeSemanticTokensDtoSize(semanticTokens: ISemanticTokensDto): number {
|
||||
let result = 0;
|
||||
result += (
|
||||
+ 1 // id
|
||||
+ 1 // type
|
||||
);
|
||||
if (semanticTokens.type === 'full') {
|
||||
result += (
|
||||
+ 1 // data length
|
||||
+ semanticTokens.data.length
|
||||
);
|
||||
} else {
|
||||
result += (
|
||||
+ 1 // delta count
|
||||
);
|
||||
result += (
|
||||
+ 1 // start
|
||||
+ 1 // deleteCount
|
||||
+ 1 // data length
|
||||
) * semanticTokens.deltas.length;
|
||||
for (const delta of semanticTokens.deltas) {
|
||||
if (delta.data) {
|
||||
result += delta.data.length;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function decodeSemanticTokensDto(_buff: VSBuffer): ISemanticTokensDto {
|
||||
const src = fromLittleEndianBuffer(_buff);
|
||||
let offset = 0;
|
||||
const id = src[offset++];
|
||||
const type: EncodedSemanticTokensType = src[offset++];
|
||||
if (type === EncodedSemanticTokensType.Full) {
|
||||
const length = src[offset++];
|
||||
const data = src.subarray(offset, offset + length); offset += length;
|
||||
return {
|
||||
id: id,
|
||||
type: 'full',
|
||||
data: data
|
||||
};
|
||||
}
|
||||
const deltaCount = src[offset++];
|
||||
let deltas: { start: number; deleteCount: number; data?: Uint32Array; }[] = [];
|
||||
for (let i = 0; i < deltaCount; i++) {
|
||||
const start = src[offset++];
|
||||
const deleteCount = src[offset++];
|
||||
const length = src[offset++];
|
||||
let data: Uint32Array | undefined;
|
||||
if (length > 0) {
|
||||
data = src.subarray(offset, offset + length); offset += length;
|
||||
}
|
||||
deltas[i] = { start, deleteCount, data };
|
||||
}
|
||||
return {
|
||||
id: id,
|
||||
type: 'delta',
|
||||
deltas: deltas
|
||||
};
|
||||
}
|
||||
@@ -119,8 +119,7 @@ export function checkGlobFileExists(
|
||||
const queryBuilder = instantiationService.createInstance(QueryBuilder);
|
||||
const query = queryBuilder.file(folders.map(folder => toWorkspaceFolder(URI.revive(folder))), {
|
||||
_reason: 'checkExists',
|
||||
includePattern: includes.join(', '),
|
||||
expandPatterns: true,
|
||||
includePattern: includes,
|
||||
exists: true
|
||||
});
|
||||
|
||||
|
||||
@@ -41,7 +41,15 @@ export interface RunCommandPipeArgs {
|
||||
args: any[];
|
||||
}
|
||||
|
||||
export type PipeCommand = OpenCommandPipeArgs | StatusPipeArgs | RunCommandPipeArgs | OpenExternalCommandPipeArgs;
|
||||
export interface ExtensionManagementPipeArgs {
|
||||
type: 'extensionManagement';
|
||||
list?: { showVersions?: boolean, category?: string; };
|
||||
install?: string[];
|
||||
uninstall?: string[];
|
||||
force?: boolean;
|
||||
}
|
||||
|
||||
export type PipeCommand = OpenCommandPipeArgs | StatusPipeArgs | RunCommandPipeArgs | OpenExternalCommandPipeArgs | ExtensionManagementPipeArgs;
|
||||
|
||||
export interface ICommandsExecuter {
|
||||
executeCommand<T>(id: string, ...args: any[]): Promise<T>;
|
||||
@@ -102,6 +110,10 @@ export class CLIServerBase {
|
||||
this.runCommand(data, res)
|
||||
.catch(this.logService.error);
|
||||
break;
|
||||
case 'extensionManagement':
|
||||
this.manageExtensions(data, res)
|
||||
.catch(this.logService.error);
|
||||
break;
|
||||
default:
|
||||
res.writeHead(404);
|
||||
res.write(`Unknown message type: ${data.type}`, err => {
|
||||
@@ -150,14 +162,34 @@ export class CLIServerBase {
|
||||
res.end();
|
||||
}
|
||||
|
||||
private openExternal(data: OpenExternalCommandPipeArgs, res: http.ServerResponse) {
|
||||
private async openExternal(data: OpenExternalCommandPipeArgs, res: http.ServerResponse) {
|
||||
for (const uri of data.uris) {
|
||||
this._commands.executeCommand('_workbench.openExternal', URI.parse(uri), { allowTunneling: true });
|
||||
await this._commands.executeCommand('_remoteCLI.openExternal', URI.parse(uri), { allowTunneling: true });
|
||||
}
|
||||
res.writeHead(200);
|
||||
res.end();
|
||||
}
|
||||
|
||||
private async manageExtensions(data: ExtensionManagementPipeArgs, res: http.ServerResponse) {
|
||||
console.log('server: manageExtensions');
|
||||
try {
|
||||
const toExtOrVSIX = (inputs: string[] | undefined) => inputs?.map(input => /\.vsix$/i.test(input) ? URI.parse(input) : input);
|
||||
const commandArgs = {
|
||||
list: data.list,
|
||||
install: toExtOrVSIX(data.install),
|
||||
uninstall: toExtOrVSIX(data.uninstall),
|
||||
force: data.force
|
||||
};
|
||||
const output = await this._commands.executeCommand('_remoteCLI.manageExtensions', commandArgs, { allowTunneling: true });
|
||||
res.writeHead(200);
|
||||
res.write(output);
|
||||
} catch (e) {
|
||||
res.writeHead(500);
|
||||
res.write(String(e));
|
||||
}
|
||||
res.end();
|
||||
}
|
||||
|
||||
private async getStatus(data: StatusPipeArgs, res: http.ServerResponse) {
|
||||
try {
|
||||
const status = await this._commands.executeCommand('_issues.getSystemStatus');
|
||||
|
||||
@@ -104,7 +104,7 @@ export class ExtHostDebugService extends ExtHostDebugServiceBase {
|
||||
cwdForPrepareCommand = args.cwd;
|
||||
}
|
||||
|
||||
terminal.show();
|
||||
terminal.show(true);
|
||||
|
||||
const shellProcessId = await terminal.processId;
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as performance from 'vs/base/common/performance';
|
||||
import { createApiFactoryAndRegisterActors } from 'vs/workbench/api/common/extHost.api.impl';
|
||||
import { RequireInterceptor } from 'vs/workbench/api/common/extHostRequireInterceptor';
|
||||
import { MainContext } from 'vs/workbench/api/common/extHost.protocol';
|
||||
@@ -13,7 +14,7 @@ import { ExtHostDownloadService } from 'vs/workbench/api/node/extHostDownloadSer
|
||||
import { CLIServer } from 'vs/workbench/api/node/extHostCLIServer';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { ExtensionRuntime } from 'vs/workbench/api/common/extHostTypes';
|
||||
|
||||
class NodeModuleRequireInterceptor extends RequireInterceptor {
|
||||
@@ -62,10 +63,12 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService {
|
||||
// Module loading tricks
|
||||
const interceptor = this._instaService.createInstance(NodeModuleRequireInterceptor, extensionApiFactory, this._registry);
|
||||
await interceptor.install();
|
||||
performance.mark('code/extHost/didInitAPI');
|
||||
|
||||
// Do this when extension service exists, but extensions are not being activated yet.
|
||||
const configProvider = await this._extHostConfiguration.getConfigProvider();
|
||||
await connectProxyResolver(this._extHostWorkspace, configProvider, this, this._logService, this._mainThreadTelemetryProxy, this._initData);
|
||||
performance.mark('code/extHost/didInitProxyResolver');
|
||||
|
||||
// Use IPC messages to forward console-calls, note that the console is
|
||||
// already patched to use`process.send()`
|
||||
@@ -84,7 +87,7 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService {
|
||||
return extensionDescription.main;
|
||||
}
|
||||
|
||||
protected _loadCommonJSModule<T>(module: URI, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise<T> {
|
||||
protected _loadCommonJSModule<T>(extensionId: ExtensionIdentifier | null, module: URI, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise<T> {
|
||||
if (module.scheme !== Schemas.file) {
|
||||
throw new Error(`Cannot load URI: '${module}', must be of file-scheme`);
|
||||
}
|
||||
@@ -93,10 +96,16 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService {
|
||||
this._logService.info(`ExtensionService#loadCommonJSModule ${module.toString(true)}`);
|
||||
this._logService.flush();
|
||||
try {
|
||||
if (extensionId) {
|
||||
performance.mark(`code/extHost/willLoadExtensionCode/${extensionId.value}`);
|
||||
}
|
||||
r = require.__$__nodeRequire<T>(module.fsPath);
|
||||
} catch (e) {
|
||||
return Promise.reject(e);
|
||||
} finally {
|
||||
if (extensionId) {
|
||||
performance.mark(`code/extHost/didLoadExtensionCode/${extensionId.value}`);
|
||||
}
|
||||
activationTimesBuilder.codeLoadingStop();
|
||||
}
|
||||
return Promise.resolve(r);
|
||||
|
||||
@@ -50,11 +50,12 @@ export class ExtHostTask extends ExtHostTaskBase {
|
||||
}
|
||||
|
||||
public async executeTask(extension: IExtensionDescription, task: vscode.Task): Promise<vscode.TaskExecution> {
|
||||
if (!task.execution) {
|
||||
const tTask = (task as types.Task);
|
||||
|
||||
if (!task.execution && (tTask._id === undefined)) {
|
||||
throw new Error('Tasks to execute must include an execution');
|
||||
}
|
||||
|
||||
const tTask = (task as types.Task);
|
||||
// We have a preserved ID. So the task didn't change.
|
||||
if (tTask._id !== undefined) {
|
||||
// Always get the task execution first to prevent timing issues when retrieving it later
|
||||
@@ -121,7 +122,7 @@ export class ExtHostTask extends ExtHostTaskBase {
|
||||
private async getVariableResolver(workspaceFolders: vscode.WorkspaceFolder[]): Promise<ExtHostVariableResolverService> {
|
||||
if (this._variableResolver === undefined) {
|
||||
const configProvider = await this._configurationService.getConfigProvider();
|
||||
this._variableResolver = new ExtHostVariableResolverService(workspaceFolders, this._editorService, configProvider, process.env as IProcessEnvironment);
|
||||
this._variableResolver = new ExtHostVariableResolverService(workspaceFolders, this._editorService, configProvider, process.env as IProcessEnvironment, this.workspaceService);
|
||||
}
|
||||
return this._variableResolver;
|
||||
}
|
||||
|
||||
@@ -17,13 +17,15 @@ import { ExtHostWorkspace, IExtHostWorkspace } from 'vs/workbench/api/common/ext
|
||||
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
|
||||
import { ExtHostVariableResolverService } from 'vs/workbench/api/common/extHostDebugService';
|
||||
import { ExtHostDocumentsAndEditors, IExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors';
|
||||
import { getSystemShell, detectAvailableShells } from 'vs/workbench/contrib/terminal/node/terminal';
|
||||
import { detectAvailableShells } from 'vs/workbench/contrib/terminal/node/terminal';
|
||||
import { getMainProcessParentEnv } from 'vs/workbench/contrib/terminal/node/terminalEnvironment';
|
||||
import { BaseExtHostTerminalService, ExtHostTerminal } from 'vs/workbench/api/common/extHostTerminalService';
|
||||
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
|
||||
import { MergedEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariableCollection';
|
||||
import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService';
|
||||
import { withNullAsUndefined } from 'vs/base/common/types';
|
||||
import { getSystemShell, getSystemShellSync } from 'vs/base/node/shell';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
|
||||
export class ExtHostTerminalService extends BaseExtHostTerminalService {
|
||||
|
||||
@@ -32,6 +34,7 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService {
|
||||
|
||||
// TODO: Pull this from main side
|
||||
private _isWorkspaceShellAllowed: boolean = false;
|
||||
private _defaultShell: string | undefined;
|
||||
|
||||
constructor(
|
||||
@IExtHostRpcService extHostRpc: IExtHostRpcService,
|
||||
@@ -42,20 +45,26 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService {
|
||||
@IExtHostInitDataService private _extHostInitDataService: IExtHostInitDataService
|
||||
) {
|
||||
super(true, extHostRpc);
|
||||
|
||||
// Getting the SystemShell is an async operation, however, the ExtHost terminal service is mostly synchronous
|
||||
// and the API `vscode.env.shell` is also synchronous. The default shell _should_ be set when extensions are
|
||||
// starting up but if not, we run getSystemShellSync below which gets a sane default.
|
||||
getSystemShell(platform.platform).then(s => this._defaultShell = s);
|
||||
|
||||
this._updateLastActiveWorkspace();
|
||||
this._updateVariableResolver();
|
||||
this._registerListeners();
|
||||
}
|
||||
|
||||
public createTerminal(name?: string, shellPath?: string, shellArgs?: string[] | string): vscode.Terminal {
|
||||
const terminal = new ExtHostTerminal(this._proxy, { name, shellPath, shellArgs }, name);
|
||||
const terminal = new ExtHostTerminal(this._proxy, generateUuid(), { name, shellPath, shellArgs }, name);
|
||||
this._terminals.push(terminal);
|
||||
terminal.create(shellPath, shellArgs);
|
||||
return terminal;
|
||||
}
|
||||
|
||||
public createTerminalFromOptions(options: vscode.TerminalOptions, isFeatureTerminal?: boolean): vscode.Terminal {
|
||||
const terminal = new ExtHostTerminal(this._proxy, options, options.name);
|
||||
const terminal = new ExtHostTerminal(this._proxy, generateUuid(), options, options.name);
|
||||
this._terminals.push(terminal);
|
||||
terminal.create(
|
||||
withNullAsUndefined(options.shellPath),
|
||||
@@ -76,10 +85,11 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService {
|
||||
.inspect<string | string[]>(key.substr(key.lastIndexOf('.') + 1));
|
||||
return this._apiInspectConfigToPlain<string | string[]>(setting);
|
||||
};
|
||||
|
||||
return terminalEnvironment.getDefaultShell(
|
||||
fetchSetting,
|
||||
this._isWorkspaceShellAllowed,
|
||||
getSystemShell(platform.platform),
|
||||
this._defaultShell ?? getSystemShellSync(platform.platform),
|
||||
process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432'),
|
||||
process.env.windir,
|
||||
terminalEnvironment.createVariableResolver(this._lastActiveWorkspace, this._variableResolver),
|
||||
@@ -139,7 +149,8 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService {
|
||||
executable: shellLaunchConfigDto.executable,
|
||||
args: shellLaunchConfigDto.args,
|
||||
cwd: typeof shellLaunchConfigDto.cwd === 'string' ? shellLaunchConfigDto.cwd : URI.revive(shellLaunchConfigDto.cwd),
|
||||
env: shellLaunchConfigDto.env
|
||||
env: shellLaunchConfigDto.env,
|
||||
flowControl: shellLaunchConfigDto.flowControl
|
||||
};
|
||||
|
||||
// Merge in shell and args from settings
|
||||
|
||||
@@ -12,12 +12,16 @@ import { URI } from 'vs/base/common/uri';
|
||||
import { exec } from 'child_process';
|
||||
import * as resources from 'vs/base/common/resources';
|
||||
import * as fs from 'fs';
|
||||
import * as pfs from 'vs/base/node/pfs';
|
||||
import { isLinux } from 'vs/base/common/platform';
|
||||
import { IExtHostTunnelService, TunnelDto } from 'vs/workbench/api/common/extHostTunnelService';
|
||||
import { asPromise } from 'vs/base/common/async';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { TunnelOptions, TunnelCreationOptions } from 'vs/platform/remote/common/tunnel';
|
||||
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { promisify } from 'util';
|
||||
import { MovingAverage } from 'vs/base/common/numbers';
|
||||
import { CandidatePort } from 'vs/workbench/services/remote/common/remoteExplorerService';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
|
||||
class ExtensionTunnel implements vscode.Tunnel {
|
||||
private _onDispose: Emitter<void> = new Emitter();
|
||||
@@ -26,14 +30,106 @@ class ExtensionTunnel implements vscode.Tunnel {
|
||||
constructor(
|
||||
public readonly remoteAddress: { port: number, host: string },
|
||||
public readonly localAddress: { port: number, host: string } | string,
|
||||
private readonly _dispose: () => void) { }
|
||||
private readonly _dispose: () => Promise<void>) { }
|
||||
|
||||
dispose(): void {
|
||||
dispose(): Promise<void> {
|
||||
this._onDispose.fire();
|
||||
this._dispose();
|
||||
return this._dispose();
|
||||
}
|
||||
}
|
||||
|
||||
export function getSockets(stdout: string): { pid: number, socket: number }[] {
|
||||
const lines = stdout.trim().split('\n');
|
||||
const mapped: { pid: number, socket: number }[] = [];
|
||||
lines.forEach(line => {
|
||||
const match = /\/proc\/(\d+)\/fd\/\d+ -> socket:\[(\d+)\]/.exec(line)!;
|
||||
if (match && match.length >= 3) {
|
||||
mapped.push({
|
||||
pid: parseInt(match[1], 10),
|
||||
socket: parseInt(match[2], 10)
|
||||
});
|
||||
}
|
||||
});
|
||||
return mapped;
|
||||
}
|
||||
|
||||
export function loadListeningPorts(...stdouts: string[]): { socket: number, ip: string, port: number }[] {
|
||||
const table = ([] as Record<string, string>[]).concat(...stdouts.map(loadConnectionTable));
|
||||
return [
|
||||
...new Map(
|
||||
table.filter(row => row.st === '0A')
|
||||
.map(row => {
|
||||
const address = row.local_address.split(':');
|
||||
return {
|
||||
socket: parseInt(row.inode, 10),
|
||||
ip: parseIpAddress(address[0]),
|
||||
port: parseInt(address[1], 16)
|
||||
};
|
||||
}).map(port => [port.ip + ':' + port.port, port])
|
||||
).values()
|
||||
];
|
||||
}
|
||||
|
||||
export function parseIpAddress(hex: string): string {
|
||||
let result = '';
|
||||
if (hex.length === 8) {
|
||||
for (let i = hex.length - 2; i >= 0; i -= 2) {
|
||||
result += parseInt(hex.substr(i, 2), 16);
|
||||
if (i !== 0) {
|
||||
result += '.';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (let i = hex.length - 4; i >= 0; i -= 4) {
|
||||
result += parseInt(hex.substr(i, 4), 16).toString(16);
|
||||
if (i !== 0) {
|
||||
result += ':';
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function loadConnectionTable(stdout: string): Record<string, string>[] {
|
||||
const lines = stdout.trim().split('\n');
|
||||
const names = lines.shift()!.trim().split(/\s+/)
|
||||
.filter(name => name !== 'rx_queue' && name !== 'tm->when');
|
||||
const table = lines.map(line => line.trim().split(/\s+/).reduce((obj, value, i) => {
|
||||
obj[names[i] || i] = value;
|
||||
return obj;
|
||||
}, {} as Record<string, string>));
|
||||
return table;
|
||||
}
|
||||
|
||||
function knownExcludeCmdline(command: string): boolean {
|
||||
return !!command.match(/.*\.vscode-server-[a-zA-Z]+\/bin.*/)
|
||||
|| (command.indexOf('out/vs/server/main.js') !== -1)
|
||||
|| (command.indexOf('_productName=VSCode') !== -1);
|
||||
}
|
||||
|
||||
export async function findPorts(tcp: string, tcp6: string, procSockets: string, processes: { pid: number, cwd: string, cmd: string }[]): Promise<CandidatePort[]> {
|
||||
const connections: { socket: number, ip: string, port: number }[] = loadListeningPorts(tcp, tcp6);
|
||||
const sockets = getSockets(procSockets);
|
||||
|
||||
const socketMap = sockets.reduce((m, socket) => {
|
||||
m[socket.socket] = socket;
|
||||
return m;
|
||||
}, {} as Record<string, typeof sockets[0]>);
|
||||
const processMap = processes.reduce((m, process) => {
|
||||
m[process.pid] = process;
|
||||
return m;
|
||||
}, {} as Record<string, typeof processes[0]>);
|
||||
|
||||
const ports: CandidatePort[] = [];
|
||||
connections.filter((connection => socketMap[connection.socket])).forEach(({ socket, ip, port }) => {
|
||||
const command = processMap[socketMap[socket].pid].cmd;
|
||||
if (!knownExcludeCmdline(command)) {
|
||||
ports.push({ host: ip, port, detail: processMap[socketMap[socket].pid].cmd, pid: socketMap[socket].pid });
|
||||
}
|
||||
});
|
||||
return ports;
|
||||
}
|
||||
|
||||
export class ExtHostTunnelService extends Disposable implements IExtHostTunnelService {
|
||||
readonly _serviceBrand: undefined;
|
||||
private readonly _proxy: MainThreadTunnelServiceShape;
|
||||
@@ -42,15 +138,17 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
|
||||
private _extensionTunnels: Map<string, Map<number, { tunnel: vscode.Tunnel, disposeListener: IDisposable }>> = new Map();
|
||||
private _onDidChangeTunnels: Emitter<void> = new Emitter<void>();
|
||||
onDidChangeTunnels: vscode.Event<void> = this._onDidChangeTunnels.event;
|
||||
private _candidateFindingEnabled: boolean = false;
|
||||
|
||||
constructor(
|
||||
@IExtHostRpcService extHostRpc: IExtHostRpcService,
|
||||
@IExtHostInitDataService initData: IExtHostInitDataService
|
||||
@IExtHostInitDataService initData: IExtHostInitDataService,
|
||||
@ILogService private readonly logService: ILogService
|
||||
) {
|
||||
super();
|
||||
this._proxy = extHostRpc.getProxy(MainContext.MainThreadTunnelService);
|
||||
if (initData.remote.isRemote && initData.remote.authority) {
|
||||
this.registerCandidateFinder();
|
||||
if (isLinux && initData.remote.isRemote && initData.remote.authority) {
|
||||
this._proxy.$setCandidateFinder();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,18 +168,30 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
|
||||
return this._proxy.$getTunnels();
|
||||
}
|
||||
|
||||
registerCandidateFinder(): void {
|
||||
// Every two seconds, scan to see if the candidate ports have changed;
|
||||
if (isLinux) {
|
||||
let oldPorts: { host: string, port: number, detail: string }[] | undefined = undefined;
|
||||
setInterval(async () => {
|
||||
const newPorts = await this.findCandidatePorts();
|
||||
if (!oldPorts || (JSON.stringify(oldPorts) !== JSON.stringify(newPorts))) {
|
||||
oldPorts = newPorts;
|
||||
this._proxy.$onFoundNewCandidates(oldPorts.filter(async (candidate) => await this._showCandidatePort(candidate.host, candidate.port, candidate.detail)));
|
||||
return;
|
||||
}
|
||||
}, 2000);
|
||||
private calculateDelay(movingAverage: number) {
|
||||
// Some local testing indicated that the moving average might be between 50-100 ms.
|
||||
return Math.max(movingAverage * 20, 2000);
|
||||
}
|
||||
|
||||
async $registerCandidateFinder(enable: boolean): Promise<void> {
|
||||
if (enable && this._candidateFindingEnabled) {
|
||||
// already enabled
|
||||
return;
|
||||
}
|
||||
this._candidateFindingEnabled = enable;
|
||||
// Regularly scan to see if the candidate ports have changed.
|
||||
let movingAverage = new MovingAverage();
|
||||
let oldPorts: { host: string, port: number, detail: string }[] | undefined = undefined;
|
||||
while (this._candidateFindingEnabled) {
|
||||
const startTime = new Date().getTime();
|
||||
const newPorts = await this.findCandidatePorts();
|
||||
const timeTaken = new Date().getTime() - startTime;
|
||||
movingAverage.update(timeTaken);
|
||||
if (!oldPorts || (JSON.stringify(oldPorts) !== JSON.stringify(newPorts))) {
|
||||
oldPorts = newPorts;
|
||||
await this._proxy.$onFoundNewCandidates(oldPorts);
|
||||
}
|
||||
await (new Promise<void>(resolve => setTimeout(() => resolve(), this.calculateDelay(movingAverage.value))));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,15 +199,18 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
|
||||
if (provider) {
|
||||
if (provider.showCandidatePort) {
|
||||
this._showCandidatePort = provider.showCandidatePort;
|
||||
await this._proxy.$setCandidateFilter();
|
||||
}
|
||||
if (provider.tunnelFactory) {
|
||||
this._forwardPortProvider = provider.tunnelFactory;
|
||||
await this._proxy.$setTunnelProvider();
|
||||
await this._proxy.$setTunnelProvider(provider.tunnelFeatures ?? {
|
||||
elevation: false,
|
||||
public: false
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this._forwardPortProvider = undefined;
|
||||
}
|
||||
await this._proxy.$tunnelServiceReady();
|
||||
return toDisposable(() => {
|
||||
this._forwardPortProvider = undefined;
|
||||
});
|
||||
@@ -110,7 +223,7 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
|
||||
if (silent) {
|
||||
hostMap.get(remote.port)!.disposeListener.dispose();
|
||||
}
|
||||
hostMap.get(remote.port)!.tunnel.dispose();
|
||||
await hostMap.get(remote.port)!.tunnel.dispose();
|
||||
hostMap.delete(remote.port);
|
||||
}
|
||||
}
|
||||
@@ -120,31 +233,42 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
|
||||
this._onDidChangeTunnels.fire();
|
||||
}
|
||||
|
||||
$forwardPort(tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions): Promise<TunnelDto> | undefined {
|
||||
async $forwardPort(tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions): Promise<TunnelDto | undefined> {
|
||||
if (this._forwardPortProvider) {
|
||||
const providedPort = this._forwardPortProvider(tunnelOptions, tunnelCreationOptions);
|
||||
if (providedPort !== undefined) {
|
||||
return asPromise(() => providedPort).then(tunnel => {
|
||||
try {
|
||||
this.logService.trace('$forwardPort: Getting tunnel from provider.');
|
||||
const providedPort = this._forwardPortProvider(tunnelOptions, tunnelCreationOptions);
|
||||
this.logService.trace('$forwardPort: Got tunnel promise from provider.');
|
||||
if (providedPort !== undefined) {
|
||||
const tunnel = await providedPort;
|
||||
this.logService.trace('$forwardPort: Successfully awaited tunnel from provider.');
|
||||
if (!this._extensionTunnels.has(tunnelOptions.remoteAddress.host)) {
|
||||
this._extensionTunnels.set(tunnelOptions.remoteAddress.host, new Map());
|
||||
}
|
||||
const disposeListener = this._register(tunnel.onDidDispose(() => this._proxy.$closeTunnel(tunnel.remoteAddress)));
|
||||
this._extensionTunnels.get(tunnelOptions.remoteAddress.host)!.set(tunnelOptions.remoteAddress.port, { tunnel, disposeListener });
|
||||
return Promise.resolve(TunnelDto.fromApiTunnel(tunnel));
|
||||
});
|
||||
return TunnelDto.fromApiTunnel(tunnel);
|
||||
} else {
|
||||
this.logService.trace('$forwardPort: Tunnel is undefined');
|
||||
}
|
||||
} catch (e) {
|
||||
this.logService.trace('$forwardPort: tunnel provider error');
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async $applyCandidateFilter(candidates: CandidatePort[]): Promise<CandidatePort[]> {
|
||||
const filter = await Promise.all(candidates.map(candidate => this._showCandidatePort(candidate.host, candidate.port, candidate.detail)));
|
||||
return candidates.filter((candidate, index) => filter[index]);
|
||||
}
|
||||
|
||||
async findCandidatePorts(): Promise<{ host: string, port: number, detail: string }[]> {
|
||||
const ports: { host: string, port: number, detail: string }[] = [];
|
||||
async findCandidatePorts(): Promise<CandidatePort[]> {
|
||||
let tcp: string = '';
|
||||
let tcp6: string = '';
|
||||
try {
|
||||
tcp = fs.readFileSync('/proc/net/tcp', 'utf8');
|
||||
tcp6 = fs.readFileSync('/proc/net/tcp6', 'utf8');
|
||||
tcp = await pfs.readFile('/proc/net/tcp', 'utf8');
|
||||
tcp6 = await pfs.readFile('/proc/net/tcp6', 'utf8');
|
||||
} catch (e) {
|
||||
// File reading error. No additional handling needed.
|
||||
}
|
||||
@@ -154,105 +278,24 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
|
||||
});
|
||||
}));
|
||||
|
||||
const procChildren = fs.readdirSync('/proc');
|
||||
const processes: { pid: number, cwd: string, cmd: string }[] = [];
|
||||
const procChildren = await pfs.readdir('/proc');
|
||||
const processes: {
|
||||
pid: number, cwd: string, cmd: string
|
||||
}[] = [];
|
||||
for (let childName of procChildren) {
|
||||
try {
|
||||
const pid: number = Number(childName);
|
||||
const childUri = resources.joinPath(URI.file('/proc'), childName);
|
||||
const childStat = fs.statSync(childUri.fsPath);
|
||||
const childStat = await pfs.stat(childUri.fsPath);
|
||||
if (childStat.isDirectory() && !isNaN(pid)) {
|
||||
const cwd = fs.readlinkSync(resources.joinPath(childUri, 'cwd').fsPath);
|
||||
const cmd = fs.readFileSync(resources.joinPath(childUri, 'cmdline').fsPath, 'utf8');
|
||||
const cwd = await promisify(fs.readlink)(resources.joinPath(childUri, 'cwd').fsPath);
|
||||
const cmd = await pfs.readFile(resources.joinPath(childUri, 'cmdline').fsPath, 'utf8');
|
||||
processes.push({ pid, cwd, cmd });
|
||||
}
|
||||
} catch (e) {
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
const connections: { socket: number, ip: string, port: number }[] = this.loadListeningPorts(tcp, tcp6);
|
||||
const sockets = this.getSockets(procSockets);
|
||||
|
||||
const socketMap = sockets.reduce((m, socket) => {
|
||||
m[socket.socket] = socket;
|
||||
return m;
|
||||
}, {} as Record<string, typeof sockets[0]>);
|
||||
const processMap = processes.reduce((m, process) => {
|
||||
m[process.pid] = process;
|
||||
return m;
|
||||
}, {} as Record<string, typeof processes[0]>);
|
||||
|
||||
connections.filter((connection => socketMap[connection.socket])).forEach(({ socket, ip, port }) => {
|
||||
const command = processMap[socketMap[socket].pid].cmd;
|
||||
if (!command.match(/.*\.vscode-server-[a-zA-Z]+\/bin.*/) && (command.indexOf('out/vs/server/main.js') === -1)) {
|
||||
ports.push({ host: ip, port, detail: processMap[socketMap[socket].pid].cmd });
|
||||
}
|
||||
});
|
||||
|
||||
return ports;
|
||||
}
|
||||
|
||||
private getSockets(stdout: string): { pid: number, socket: number }[] {
|
||||
const lines = stdout.trim().split('\n');
|
||||
const mapped: { pid: number, socket: number }[] = [];
|
||||
lines.forEach(line => {
|
||||
const match = /\/proc\/(\d+)\/fd\/\d+ -> socket:\[(\d+)\]/.exec(line)!;
|
||||
if (match && match.length >= 3) {
|
||||
mapped.push({
|
||||
pid: parseInt(match[1], 10),
|
||||
socket: parseInt(match[2], 10)
|
||||
});
|
||||
}
|
||||
});
|
||||
return mapped;
|
||||
}
|
||||
|
||||
private loadListeningPorts(...stdouts: string[]): { socket: number, ip: string, port: number }[] {
|
||||
const table = ([] as Record<string, string>[]).concat(...stdouts.map(this.loadConnectionTable));
|
||||
return [
|
||||
...new Map(
|
||||
table.filter(row => row.st === '0A')
|
||||
.map(row => {
|
||||
const address = row.local_address.split(':');
|
||||
return {
|
||||
socket: parseInt(row.inode, 10),
|
||||
ip: this.parseIpAddress(address[0]),
|
||||
port: parseInt(address[1], 16)
|
||||
};
|
||||
}).map(port => [port.ip + ':' + port.port, port])
|
||||
).values()
|
||||
];
|
||||
}
|
||||
|
||||
private parseIpAddress(hex: string): string {
|
||||
let result = '';
|
||||
if (hex.length === 8) {
|
||||
for (let i = hex.length - 2; i >= 0; i -= 2) {
|
||||
result += parseInt(hex.substr(i, 2), 16);
|
||||
if (i !== 0) {
|
||||
result += '.';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (let i = hex.length - 4; i >= 0; i -= 4) {
|
||||
result += parseInt(hex.substr(i, 4), 16).toString(16);
|
||||
if (i !== 0) {
|
||||
result += ':';
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private loadConnectionTable(stdout: string): Record<string, string>[] {
|
||||
const lines = stdout.trim().split('\n');
|
||||
const names = lines.shift()!.trim().split(/\s+/)
|
||||
.filter(name => name !== 'rx_queue' && name !== 'tm->when');
|
||||
const table = lines.map(line => line.trim().split(/\s+/).reduce((obj, value, i) => {
|
||||
obj[names[i] || i] = value;
|
||||
return obj;
|
||||
}, {} as Record<string, string>));
|
||||
return table;
|
||||
return findPorts(tcp, tcp6, procSockets, processes);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,10 +8,38 @@ import { ExtensionActivationTimesBuilder } from 'vs/workbench/api/common/extHost
|
||||
import { AbstractExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensionService';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { RequireInterceptor } from 'vs/workbench/api/common/extHostRequireInterceptor';
|
||||
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { ExtensionRuntime } from 'vs/workbench/api/common/extHostTypes';
|
||||
import { timeout } from 'vs/base/common/async';
|
||||
|
||||
namespace TrustedFunction {
|
||||
|
||||
// workaround a chrome issue not allowing to create new functions
|
||||
// see https://github.com/w3c/webappsec-trusted-types/wiki/Trusted-Types-for-function-constructor
|
||||
const ttpTrustedFunction = self.trustedTypes?.createPolicy('TrustedFunctionWorkaround', {
|
||||
createScript: (_, ...args: string[]) => {
|
||||
args.forEach((arg) => {
|
||||
if (!self.trustedTypes?.isScript(arg)) {
|
||||
throw new Error('TrustedScripts only, please');
|
||||
}
|
||||
});
|
||||
// NOTE: This is insecure without parsing the arguments and body,
|
||||
// Malicious inputs can escape the function body and execute immediately!
|
||||
const fnArgs = args.slice(0, -1).join(',');
|
||||
const fnBody = args.pop()!.toString();
|
||||
const body = `(function anonymous(${fnArgs}) {\n${fnBody}\n})`;
|
||||
return body;
|
||||
}
|
||||
});
|
||||
|
||||
export function create(...args: string[]): Function {
|
||||
if (!ttpTrustedFunction) {
|
||||
return new Function(...args);
|
||||
}
|
||||
return self.eval(ttpTrustedFunction.createScript('', ...args) as unknown as string);
|
||||
}
|
||||
}
|
||||
|
||||
class WorkerRequireInterceptor extends RequireInterceptor {
|
||||
|
||||
_installInterceptor() { }
|
||||
@@ -35,6 +63,8 @@ class WorkerRequireInterceptor extends RequireInterceptor {
|
||||
export class ExtHostExtensionService extends AbstractExtHostExtensionService {
|
||||
readonly extensionRuntime = ExtensionRuntime.Webworker;
|
||||
|
||||
private static _ttpExtensionScripts = self.trustedTypes?.createPolicy('ExtensionScripts', { createScript: source => source });
|
||||
|
||||
private _fakeModules?: WorkerRequireInterceptor;
|
||||
|
||||
protected async _beforeAlmostReadyToRunExtensions(): Promise<void> {
|
||||
@@ -42,6 +72,8 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService {
|
||||
const apiFactory = this._instaService.invokeFunction(createApiFactoryAndRegisterActors);
|
||||
this._fakeModules = this._instaService.createInstance(WorkerRequireInterceptor, apiFactory, this._registry);
|
||||
await this._fakeModules.install();
|
||||
performance.mark('code/extHost/didInitAPI');
|
||||
|
||||
await this._waitForDebuggerAttachment();
|
||||
}
|
||||
|
||||
@@ -49,10 +81,16 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService {
|
||||
return extensionDescription.browser;
|
||||
}
|
||||
|
||||
protected async _loadCommonJSModule<T>(module: URI, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise<T> {
|
||||
protected async _loadCommonJSModule<T>(extensionId: ExtensionIdentifier | null, module: URI, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise<T> {
|
||||
|
||||
module = module.with({ path: ensureSuffix(module.path, '.js') });
|
||||
if (extensionId) {
|
||||
performance.mark(`code/extHost/willFetchExtensionCode/${extensionId.value}`);
|
||||
}
|
||||
const response = await fetch(module.toString(true));
|
||||
if (extensionId) {
|
||||
performance.mark(`code/extHost/didFetchExtensionCode/${extensionId.value}`);
|
||||
}
|
||||
|
||||
if (response.status !== 200) {
|
||||
throw new Error(response.statusText);
|
||||
@@ -63,7 +101,25 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService {
|
||||
// Here we append #vscode-extension to serve as a marker, such that source maps
|
||||
// can be adjusted for the extra wrapping function.
|
||||
const sourceURL = `${module.toString(true)}#vscode-extension`;
|
||||
const initFn = new Function('module', 'exports', 'require', `${source}\n//# sourceURL=${sourceURL}`);
|
||||
const fullSource = `${source}\n//# sourceURL=${sourceURL}`;
|
||||
let initFn: Function;
|
||||
try {
|
||||
initFn = TrustedFunction.create(
|
||||
ExtHostExtensionService._ttpExtensionScripts?.createScript('module') as unknown as string ?? 'module',
|
||||
ExtHostExtensionService._ttpExtensionScripts?.createScript('exports') as unknown as string ?? 'exports',
|
||||
ExtHostExtensionService._ttpExtensionScripts?.createScript('require') as unknown as string ?? 'require',
|
||||
ExtHostExtensionService._ttpExtensionScripts?.createScript(fullSource) as unknown as string ?? fullSource
|
||||
);
|
||||
} catch (err) {
|
||||
if (extensionId) {
|
||||
console.error(`Loading code for extension ${extensionId.value} failed: ${err.message}`);
|
||||
} else {
|
||||
console.error(`Loading code failed: ${err.message}`);
|
||||
}
|
||||
console.error(`${module.toString(true)}${typeof err.line === 'number' ? ` line ${err.line}` : ''}${typeof err.column === 'number' ? ` column ${err.column}` : ''}`);
|
||||
console.error(err);
|
||||
throw err;
|
||||
}
|
||||
|
||||
// define commonjs globals: `module`, `exports`, and `require`
|
||||
const _exports = {};
|
||||
@@ -78,9 +134,15 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService {
|
||||
|
||||
try {
|
||||
activationTimesBuilder.codeLoadingStart();
|
||||
if (extensionId) {
|
||||
performance.mark(`code/extHost/willLoadExtensionCode/${extensionId.value}`);
|
||||
}
|
||||
initFn(_module, _exports, _require);
|
||||
return <T>(_module.exports !== _exports ? _module.exports : _exports);
|
||||
} finally {
|
||||
if (extensionId) {
|
||||
performance.mark(`code/extHost/didLoadExtensionCode/${extensionId.value}`);
|
||||
}
|
||||
activationTimesBuilder.codeLoadingStop();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user