mirror of
https://github.com/coder/code-server.git
synced 2026-05-08 13:27:25 +02:00
chore(vscode): update to 1.55.2
This commit is contained in:
@@ -15,12 +15,11 @@ import { getExtensionId } from 'vs/platform/extensionManagement/common/extension
|
||||
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 { ExtensionKindController } from 'vs/workbench/services/extensions/common/extensionsUtil';
|
||||
import { IExtensionManifest } from 'vs/workbench/workbench.web.api';
|
||||
|
||||
|
||||
@@ -78,19 +77,22 @@ class RemoteExtensionCLIManagementService extends ExtensionManagementCLIService
|
||||
|
||||
private _location: string | undefined;
|
||||
|
||||
private readonly _extensionKindController: ExtensionKindController;
|
||||
|
||||
constructor(
|
||||
@IExtensionManagementService extensionManagementService: IExtensionManagementService,
|
||||
@IProductService private readonly productService: IProductService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@IProductService productService: IProductService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IExtensionGalleryService extensionGalleryService: IExtensionGalleryService,
|
||||
@ILocalizationsService localizationsService: ILocalizationsService,
|
||||
@ILabelService labelService: ILabelService,
|
||||
@IWorkbenchEnvironmentService envService: IWorkbenchEnvironmentService
|
||||
) {
|
||||
super(extensionManagementService, extensionGalleryService, localizationsService);
|
||||
super(extensionManagementService, extensionGalleryService);
|
||||
|
||||
const remoteAuthority = envService.remoteAuthority;
|
||||
this._location = remoteAuthority ? labelService.getHostLabel(Schemas.vscodeRemote, remoteAuthority) : undefined;
|
||||
|
||||
this._extensionKindController = new ExtensionKindController(productService, configurationService);
|
||||
}
|
||||
|
||||
protected get location(): string | undefined {
|
||||
@@ -98,7 +100,7 @@ class RemoteExtensionCLIManagementService extends ExtensionManagementCLIService
|
||||
}
|
||||
|
||||
protected validateExtensionKind(manifest: IExtensionManifest, output: CLIOutput): boolean {
|
||||
if (!canExecuteOnWorkspace(manifest, this.productService, this.configurationService)) {
|
||||
if (!this._extensionKindController.canExecuteOnWorkspace(manifest)) {
|
||||
output.log(localize('cannot be installed', "Cannot install the '{0}' extension because it is declared to not run in this setup.", getExtensionId(manifest.publisher, manifest.name)));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -3,16 +3,16 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { UriComponents, URI } from 'vs/base/common/uri';
|
||||
import * as modes from 'vs/editor/common/modes';
|
||||
import { MainContext, MainThreadEditorInsetsShape, IExtHostContext, ExtHostEditorInsetsShape, ExtHostContext } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { extHostNamedCustomer } from '../common/extHostCustomers';
|
||||
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
|
||||
import { IWebviewService, WebviewElement } from 'vs/workbench/contrib/webview/browser/webview';
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { IActiveCodeEditor, IViewZone } from 'vs/editor/browser/editorBrowser';
|
||||
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
|
||||
import { isEqual } from 'vs/base/common/resources';
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import { IActiveCodeEditor, IViewZone } from 'vs/editor/browser/editorBrowser';
|
||||
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
|
||||
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
|
||||
import { reviveWebviewContentOptions } from 'vs/workbench/api/browser/mainThreadWebviews';
|
||||
import { ExtHostContext, ExtHostEditorInsetsShape, IExtHostContext, IWebviewOptions, MainContext, MainThreadEditorInsetsShape } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { IWebviewService, WebviewElement } from 'vs/workbench/contrib/webview/browser/webview';
|
||||
import { extHostNamedCustomer } from '../common/extHostCustomers';
|
||||
|
||||
// todo@jrieken move these things back into something like contrib/insets
|
||||
class EditorWebviewZone implements IViewZone {
|
||||
@@ -70,7 +70,7 @@ export class MainThreadEditorInsets implements MainThreadEditorInsetsShape {
|
||||
this._disposables.dispose();
|
||||
}
|
||||
|
||||
async $createEditorInset(handle: number, id: string, uri: UriComponents, line: number, height: number, options: modes.IWebviewOptions, extensionId: ExtensionIdentifier, extensionLocation: UriComponents): Promise<void> {
|
||||
async $createEditorInset(handle: number, id: string, uri: UriComponents, line: number, height: number, options: IWebviewOptions, extensionId: ExtensionIdentifier, extensionLocation: UriComponents): Promise<void> {
|
||||
|
||||
let editor: IActiveCodeEditor | undefined;
|
||||
id = id.substr(0, id.indexOf(',')); //todo@jrieken HACK
|
||||
@@ -91,10 +91,7 @@ export class MainThreadEditorInsets implements MainThreadEditorInsetsShape {
|
||||
|
||||
const webview = this._webviewService.createWebviewElement('' + handle, {
|
||||
enableFindWidget: false,
|
||||
}, {
|
||||
allowScripts: options.enableScripts,
|
||||
localResourceRoots: options.localResourceRoots ? options.localResourceRoots.map(uri => URI.revive(uri)) : undefined
|
||||
}, { id: extensionId, location: URI.revive(extensionLocation) });
|
||||
}, reviveWebviewContentOptions(options), { id: extensionId, location: URI.revive(extensionLocation) });
|
||||
|
||||
const webviewZone = new EditorWebviewZone(editor, line, height, webview);
|
||||
|
||||
@@ -117,7 +114,6 @@ export class MainThreadEditorInsets implements MainThreadEditorInsetsShape {
|
||||
const inset = this.getInset(handle);
|
||||
this._insets.delete(handle);
|
||||
inset.dispose();
|
||||
|
||||
}
|
||||
|
||||
$setHtml(handle: number, value: string): void {
|
||||
@@ -125,12 +121,9 @@ export class MainThreadEditorInsets implements MainThreadEditorInsetsShape {
|
||||
inset.webview.html = value;
|
||||
}
|
||||
|
||||
$setOptions(handle: number, options: modes.IWebviewOptions): void {
|
||||
$setOptions(handle: number, options: IWebviewOptions): void {
|
||||
const inset = this.getInset(handle);
|
||||
inset.webview.contentOptions = {
|
||||
...options,
|
||||
localResourceRoots: options.localResourceRoots?.map(components => URI.from(components)),
|
||||
};
|
||||
inset.webview.contentOptions = reviveWebviewContentOptions(options);
|
||||
}
|
||||
|
||||
async $postMessage(handle: number, value: any): Promise<boolean> {
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { multibyteAwareBtoa } from 'vs/base/browser/dom';
|
||||
import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async';
|
||||
import { CancelablePromise, createCancelablePromise, timeout } from 'vs/base/common/async';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { isPromiseCanceledError, onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
@@ -13,10 +14,9 @@ import { Schemas } from 'vs/base/common/network';
|
||||
import { basename } from 'vs/base/common/path';
|
||||
import { isEqual, isEqualOrParent, toLocalResource } from 'vs/base/common/resources';
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import * as modes from 'vs/editor/common/modes';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { FileChangesEvent, FileChangeType, FileSystemProviderCapabilities, IFileService } from 'vs/platform/files/common/files';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ILabelService } from 'vs/platform/label/common/label';
|
||||
import { IUndoRedoService, UndoRedoElementType } from 'vs/platform/undoRedo/common/undoRedo';
|
||||
@@ -99,11 +99,11 @@ export class MainThreadCustomEditors extends Disposable implements extHostProtoc
|
||||
this._editorProviders.clear();
|
||||
}
|
||||
|
||||
public $registerTextEditorProvider(extensionData: extHostProtocol.WebviewExtensionDescription, viewType: string, options: modes.IWebviewPanelOptions, capabilities: extHostProtocol.CustomTextEditorCapabilities): void {
|
||||
public $registerTextEditorProvider(extensionData: extHostProtocol.WebviewExtensionDescription, viewType: string, options: extHostProtocol.IWebviewPanelOptions, capabilities: extHostProtocol.CustomTextEditorCapabilities): void {
|
||||
this.registerEditorProvider(CustomEditorModelType.Text, reviveWebviewExtension(extensionData), viewType, options, capabilities, true);
|
||||
}
|
||||
|
||||
public $registerCustomEditorProvider(extensionData: extHostProtocol.WebviewExtensionDescription, viewType: string, options: modes.IWebviewPanelOptions, supportsMultipleEditorsPerDocument: boolean): void {
|
||||
public $registerCustomEditorProvider(extensionData: extHostProtocol.WebviewExtensionDescription, viewType: string, options: extHostProtocol.IWebviewPanelOptions, supportsMultipleEditorsPerDocument: boolean): void {
|
||||
this.registerEditorProvider(CustomEditorModelType.Custom, reviveWebviewExtension(extensionData), viewType, options, {}, supportsMultipleEditorsPerDocument);
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ export class MainThreadCustomEditors extends Disposable implements extHostProtoc
|
||||
modelType: CustomEditorModelType,
|
||||
extension: WebviewExtensionDescription,
|
||||
viewType: string,
|
||||
options: modes.IWebviewPanelOptions,
|
||||
options: extHostProtocol.IWebviewPanelOptions,
|
||||
capabilities: extHostProtocol.CustomTextEditorCapabilities,
|
||||
supportsMultipleEditorsPerDocument: boolean,
|
||||
): void {
|
||||
@@ -176,7 +176,11 @@ export class MainThreadCustomEditors extends Disposable implements extHostProtoc
|
||||
}
|
||||
|
||||
try {
|
||||
await this._proxyCustomEditors.$resolveWebviewEditor(resource, handle, viewType, webviewInput.getTitle(), editorGroupToViewColumn(this._editorGroupService, webviewInput.group || 0), webviewInput.webview.options, cancellation);
|
||||
await this._proxyCustomEditors.$resolveWebviewEditor(resource, handle, viewType, {
|
||||
title: webviewInput.getTitle(),
|
||||
webviewOptions: webviewInput.webview.contentOptions,
|
||||
panelOptions: webviewInput.webview.options,
|
||||
}, editorGroupToViewColumn(this._editorGroupService, webviewInput.group || 0), cancellation);
|
||||
} catch (error) {
|
||||
onUnexpectedError(error);
|
||||
webviewInput.webview.html = this.mainThreadWebview.getWebviewResolvedFailedContent(viewType);
|
||||
@@ -274,6 +278,8 @@ namespace HotExitState {
|
||||
|
||||
class MainThreadCustomEditorModel extends Disposable implements ICustomEditorModel, IWorkingCopy {
|
||||
|
||||
#isDisposed = false;
|
||||
|
||||
private _fromBackup: boolean = false;
|
||||
private _hotExitState: HotExitState.State = HotExitState.Allowed;
|
||||
private _backupId: string | undefined;
|
||||
@@ -282,9 +288,13 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod
|
||||
private _savePoint: number = -1;
|
||||
private readonly _edits: Array<number> = [];
|
||||
private _isDirtyFromContentChange = false;
|
||||
private _inOrphaned = false;
|
||||
|
||||
private _ongoingSave?: CancelablePromise<void>;
|
||||
|
||||
private readonly _onDidChangeOrphaned = this._register(new Emitter<void>());
|
||||
public readonly onDidChangeOrphaned = this._onDidChangeOrphaned.event;
|
||||
|
||||
public static async create(
|
||||
instantiationService: IInstantiationService,
|
||||
proxy: extHostProtocol.ExtHostCustomEditorsShape,
|
||||
@@ -294,8 +304,13 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod
|
||||
getEditors: () => CustomEditorInput[],
|
||||
cancellation: CancellationToken,
|
||||
_backupFileService: IBackupFileService,
|
||||
) {
|
||||
const { editable } = await proxy.$createCustomDocument(resource, viewType, options.backupId, cancellation);
|
||||
): Promise<MainThreadCustomEditorModel> {
|
||||
const editors = getEditors();
|
||||
let untitledDocumentData: VSBuffer | undefined;
|
||||
if (editors.length !== 0) {
|
||||
untitledDocumentData = editors[0].untitledDocumentData;
|
||||
}
|
||||
const { editable } = await proxy.$createCustomDocument(resource, viewType, options.backupId, untitledDocumentData, cancellation);
|
||||
return instantiationService.createInstance(MainThreadCustomEditorModel, proxy, viewType, resource, !!options.backupId, editable, getEditors);
|
||||
}
|
||||
|
||||
@@ -321,6 +336,8 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod
|
||||
if (_editable) {
|
||||
this._register(workingCopyService.registerWorkingCopy(this));
|
||||
}
|
||||
|
||||
this._register(_fileService.onDidFilesChange(e => this.onDidFilesChange(e)));
|
||||
}
|
||||
|
||||
get editorResource() {
|
||||
@@ -328,10 +345,14 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.#isDisposed = true;
|
||||
|
||||
if (this._editable) {
|
||||
this._undoService.removeElements(this._editorResource);
|
||||
}
|
||||
|
||||
this._proxy.$disposeCustomDocument(this._editorResource, this._viewType);
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@@ -371,6 +392,10 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod
|
||||
return this._fromBackup;
|
||||
}
|
||||
|
||||
public isOrphaned(): boolean {
|
||||
return this._inOrphaned;
|
||||
}
|
||||
|
||||
private isUntitled() {
|
||||
return this._editorResource.scheme === Schemas.untitled;
|
||||
}
|
||||
@@ -383,8 +408,64 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod
|
||||
|
||||
//#endregion
|
||||
|
||||
public isReadonly() {
|
||||
return !this._editable;
|
||||
private async onDidFilesChange(e: FileChangesEvent): Promise<void> {
|
||||
let fileEventImpactsModel = false;
|
||||
let newInOrphanModeGuess: boolean | undefined;
|
||||
|
||||
// If we are currently orphaned, we check if the model file was added back
|
||||
if (this._inOrphaned) {
|
||||
const modelFileAdded = e.contains(this.editorResource, FileChangeType.ADDED);
|
||||
if (modelFileAdded) {
|
||||
newInOrphanModeGuess = false;
|
||||
fileEventImpactsModel = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise we check if the model file was deleted
|
||||
else {
|
||||
const modelFileDeleted = e.contains(this.editorResource, FileChangeType.DELETED);
|
||||
if (modelFileDeleted) {
|
||||
newInOrphanModeGuess = true;
|
||||
fileEventImpactsModel = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (fileEventImpactsModel && this._inOrphaned !== newInOrphanModeGuess) {
|
||||
let newInOrphanModeValidated: boolean = false;
|
||||
if (newInOrphanModeGuess) {
|
||||
// We have received reports of users seeing delete events even though the file still
|
||||
// exists (network shares issue: https://github.com/microsoft/vscode/issues/13665).
|
||||
// Since we do not want to mark the model as orphaned, we have to check if the
|
||||
// file is really gone and not just a faulty file event.
|
||||
await timeout(100);
|
||||
|
||||
if (this.#isDisposed) {
|
||||
newInOrphanModeValidated = true;
|
||||
} else {
|
||||
const exists = await this._fileService.exists(this.editorResource);
|
||||
newInOrphanModeValidated = !exists;
|
||||
}
|
||||
}
|
||||
|
||||
if (this._inOrphaned !== newInOrphanModeValidated && !this.#isDisposed) {
|
||||
this.setOrphaned(newInOrphanModeValidated);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private setOrphaned(orphaned: boolean): void {
|
||||
if (this._inOrphaned !== orphaned) {
|
||||
this._inOrphaned = orphaned;
|
||||
this._onDidChangeOrphaned.fire();
|
||||
}
|
||||
}
|
||||
|
||||
public isEditable(): boolean {
|
||||
return this._editable;
|
||||
}
|
||||
|
||||
public isOnReadonlyFileSystem(): boolean {
|
||||
return this._fileService.hasCapability(this.editorResource, FileSystemProviderCapabilities.Readonly);
|
||||
}
|
||||
|
||||
public get viewType() {
|
||||
|
||||
@@ -142,7 +142,7 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb
|
||||
} else if (dto.type === 'function') {
|
||||
this.debugService.addFunctionBreakpoint(dto.functionName, dto.id);
|
||||
} else if (dto.type === 'data') {
|
||||
this.debugService.addDataBreakpoint(dto.label, dto.dataId, dto.canPersist, dto.accessTypes);
|
||||
this.debugService.addDataBreakpoint(dto.label, dto.dataId, dto.canPersist, dto.accessTypes, dto.accessType);
|
||||
}
|
||||
}
|
||||
return Promise.resolve();
|
||||
|
||||
@@ -202,12 +202,12 @@ export class MainThreadDocuments extends Disposable implements MainThreadDocumen
|
||||
}
|
||||
|
||||
private _onModelModeChanged(event: { model: ITextModel; oldModeId: string; }): void {
|
||||
let { model, oldModeId } = event;
|
||||
let { model } = event;
|
||||
const modelUrl = model.uri;
|
||||
if (!this._modelIsSynced.has(modelUrl.toString())) {
|
||||
return;
|
||||
}
|
||||
this._proxy.$acceptModelModeChanged(model.uri, oldModeId, model.getLanguageIdentifier().language);
|
||||
this._proxy.$acceptModelModeChanged(model.uri, model.getLanguageIdentifier().language);
|
||||
}
|
||||
|
||||
private _onModelRemoved(modelUrl: URI): void {
|
||||
|
||||
@@ -15,7 +15,7 @@ import { Action } from 'vs/base/common/actions';
|
||||
import { IWorkbenchExtensionEnablementService, EnablementState } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
|
||||
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { IHostService } from 'vs/workbench/services/host/browser/host';
|
||||
import { IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions';
|
||||
import { IExtension, IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { ExtensionActivationReason } from 'vs/workbench/api/common/extHostExtensionActivator';
|
||||
@@ -106,26 +106,26 @@ export class MainThreadExtensionService implements MainThreadExtensionServiceSha
|
||||
|
||||
private async _handleMissingNotInstalledDependency(extension: IExtensionDescription, missingDependency: string): Promise<void> {
|
||||
const extName = extension.displayName || extension.name;
|
||||
const dependencyExtension = (await this._extensionsWorkbenchService.queryGallery({ names: [missingDependency] }, CancellationToken.None)).firstPage[0];
|
||||
let dependencyExtension: IExtension | null = null;
|
||||
try {
|
||||
dependencyExtension = (await this._extensionsWorkbenchService.queryGallery({ names: [missingDependency] }, CancellationToken.None)).firstPage[0];
|
||||
} catch (err) {
|
||||
}
|
||||
if (dependencyExtension) {
|
||||
this._notificationService.notify({
|
||||
severity: Severity.Error,
|
||||
message: localize('uninstalledDep', "Cannot activate the '{0}' extension because it depends on the '{1}' extension, which is not installed. Would you like to install the extension and reload the window?", extName, dependencyExtension.displayName),
|
||||
actions: {
|
||||
primary: [new Action('install', localize('install missing dep', "Install and Reload"), '', true,
|
||||
() => this._extensionsWorkbenchService.install(dependencyExtension)
|
||||
() => this._extensionsWorkbenchService.install(dependencyExtension!)
|
||||
.then(() => this._hostService.reload(), e => this._notificationService.error(e)))]
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this._notificationService.error(localize('unknownDep', "Cannot activate the '{0}' extension because it depends on an unknown '{1}' extension .", extName, missingDependency));
|
||||
this._notificationService.error(localize('unknownDep', "Cannot activate the '{0}' extension because it depends on an unknown '{1}' extension.", extName, missingDependency));
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
@@ -66,9 +66,12 @@ export class MainThreadMessageService implements MainThreadMessageServiceShape {
|
||||
primaryActions.push(new MessageItemAction('_extension_message_handle_' + command.handle, command.title, command.handle));
|
||||
});
|
||||
|
||||
let source: string | undefined;
|
||||
let source: string | { label: string, id: string } | undefined;
|
||||
if (extension) {
|
||||
source = nls.localize('extensionSource', "{0} (Extension)", extension.displayName || extension.name);
|
||||
source = {
|
||||
label: nls.localize('extensionSource', "{0} (Extension)", extension.displayName || extension.name),
|
||||
id: extension.identifier.value
|
||||
};
|
||||
}
|
||||
|
||||
if (!source) {
|
||||
|
||||
@@ -3,56 +3,54 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { diffMaps, diffSets } from 'vs/base/common/collections';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { IRelativePattern } from 'vs/base/common/glob';
|
||||
import { combinedDisposable, Disposable, DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { ResourceMap } from 'vs/base/common/map';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { isEqual } from 'vs/base/common/resources';
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { EditorActivation, ITextEditorOptions, EditorOverride } from 'vs/platform/editor/common/editor';
|
||||
import { EditorActivation, EditorOverride } from 'vs/platform/editor/common/editor';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { BoundModelReferenceCollection } from 'vs/workbench/api/browser/mainThreadDocuments';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
|
||||
import { viewColumnToEditorGroup } from 'vs/workbench/common/editor';
|
||||
import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
|
||||
import { getNotebookEditorFromEditorPane, IActiveNotebookEditor, INotebookEditor, NotebookEditorOptions } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
|
||||
import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/browser/notebookEditorInput';
|
||||
import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/notebookEditorService';
|
||||
import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel';
|
||||
import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel';
|
||||
import { INotebookCellStatusBarService } from 'vs/workbench/contrib/notebook/common/notebookCellStatusBarService';
|
||||
import { ICellEditOperation, ICellRange, IEditor, IMainCellDto, INotebookDecorationRenderOptions, INotebookDocumentFilter, INotebookExclusiveDocumentFilter, INotebookKernel, NotebookCellsChangeType, TransientMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
import { ICellEditOperation, ICellRange, IImmediateCellEditOperation, IMainCellDto, INotebookDecorationRenderOptions, INotebookDocumentFilter, INotebookExclusiveDocumentFilter, INotebookKernel, NotebookCellsChangeType, NotebookDataDto, TransientMetadata, TransientOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
import { INotebookEditorModelResolverService } from 'vs/workbench/contrib/notebook/common/notebookEditorModelResolverService';
|
||||
import { IMainNotebookController, INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';
|
||||
import { IEditorGroup, IEditorGroupsService, preferredSideBySideGroupDirection } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity';
|
||||
import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService';
|
||||
import { ExtHostContext, ExtHostNotebookShape, IExtHostContext, INotebookCellStatusBarEntryDto, INotebookDocumentsAndEditorsDelta, INotebookDocumentShowOptions, INotebookEditorAddData, INotebookModelAddedData, MainContext, MainThreadNotebookShape, NotebookEditorRevealType, NotebookExtensionDescription } from '../common/extHost.protocol';
|
||||
|
||||
class DocumentAndEditorState {
|
||||
static compute(before: DocumentAndEditorState | undefined, after: DocumentAndEditorState): INotebookDocumentsAndEditorsDelta {
|
||||
class NotebookAndEditorState {
|
||||
static compute(before: NotebookAndEditorState | undefined, after: NotebookAndEditorState): INotebookDocumentsAndEditorsDelta {
|
||||
if (!before) {
|
||||
return {
|
||||
addedDocuments: [...after.documents].map(DocumentAndEditorState._asModelAddData),
|
||||
addedEditors: [...after.textEditors.values()].map(DocumentAndEditorState._asEditorAddData),
|
||||
addedDocuments: [...after.documents].map(NotebookAndEditorState._asModelAddData),
|
||||
addedEditors: [...after.textEditors.values()].map(NotebookAndEditorState._asEditorAddData),
|
||||
visibleEditors: [...after.visibleEditors].map(editor => editor[0])
|
||||
};
|
||||
}
|
||||
const documentDelta = diffSets(before.documents, after.documents);
|
||||
const editorDelta = diffMaps(before.textEditors, after.textEditors);
|
||||
const addedAPIEditors = editorDelta.added.map(DocumentAndEditorState._asEditorAddData);
|
||||
const addedAPIEditors = editorDelta.added.map(NotebookAndEditorState._asEditorAddData);
|
||||
|
||||
const removedAPIEditors = editorDelta.removed.map(removed => removed.getId());
|
||||
|
||||
// const oldActiveEditor = before.activeEditor !== after.activeEditor ? before.activeEditor : undefined;
|
||||
const newActiveEditor = before.activeEditor !== after.activeEditor ? after.activeEditor : undefined;
|
||||
|
||||
const visibleEditorDelta = diffMaps(before.visibleEditors, after.visibleEditors);
|
||||
|
||||
return {
|
||||
addedDocuments: documentDelta.added.map(DocumentAndEditorState._asModelAddData),
|
||||
addedDocuments: documentDelta.added.map(NotebookAndEditorState._asModelAddData),
|
||||
removedDocuments: documentDelta.removed.map(e => e.uri),
|
||||
addedEditors: addedAPIEditors,
|
||||
removedEditors: removedAPIEditors,
|
||||
@@ -65,9 +63,9 @@ class DocumentAndEditorState {
|
||||
|
||||
constructor(
|
||||
readonly documents: Set<NotebookTextModel>,
|
||||
readonly textEditors: Map<string, IEditor>,
|
||||
readonly textEditors: Map<string, IActiveNotebookEditor>,
|
||||
readonly activeEditor: string | null | undefined,
|
||||
readonly visibleEditors: Map<string, IEditor>
|
||||
readonly visibleEditors: Map<string, IActiveNotebookEditor>
|
||||
) {
|
||||
//
|
||||
}
|
||||
@@ -87,71 +85,72 @@ class DocumentAndEditorState {
|
||||
cellKind: cell.cellKind,
|
||||
outputs: cell.outputs,
|
||||
metadata: cell.metadata
|
||||
})),
|
||||
contentOptions: e.transientOptions,
|
||||
}))
|
||||
};
|
||||
}
|
||||
|
||||
private static _asEditorAddData(add: IEditor): INotebookEditorAddData {
|
||||
private static _asEditorAddData(add: IActiveNotebookEditor): INotebookEditorAddData {
|
||||
return {
|
||||
id: add.getId(),
|
||||
documentUri: add.uri!,
|
||||
documentUri: add.viewModel.uri,
|
||||
selections: add.getSelections(),
|
||||
visibleRanges: add.visibleRanges
|
||||
visibleRanges: add.visibleRanges,
|
||||
viewColumn: undefined
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadNotebook)
|
||||
export class MainThreadNotebooks extends Disposable implements MainThreadNotebookShape {
|
||||
export class MainThreadNotebooks implements MainThreadNotebookShape {
|
||||
|
||||
private readonly _disposables = new DisposableStore();
|
||||
|
||||
private readonly _proxy: ExtHostNotebookShape;
|
||||
private readonly _notebookProviders = new Map<string, { controller: IMainNotebookController, disposable: IDisposable }>();
|
||||
private readonly _notebookSerializer = new Map<number, IDisposable>();
|
||||
private readonly _notebookKernelProviders = new Map<number, { extension: NotebookExtensionDescription, emitter: Emitter<URI | undefined>, provider: IDisposable }>();
|
||||
private readonly _toDisposeOnEditorRemove = new Map<string, IDisposable>();
|
||||
private readonly _editorEventListenersMapping: Map<string, DisposableStore> = new Map();
|
||||
private readonly _documentEventListenersMapping: ResourceMap<DisposableStore> = new ResourceMap();
|
||||
private readonly _cellStatusBarEntries: Map<number, IDisposable> = new Map();
|
||||
private readonly _editorEventListenersMapping = new Map<string, DisposableStore>();
|
||||
private readonly _documentEventListenersMapping = new ResourceMap<DisposableStore>();
|
||||
private readonly _cellStatusBarEntries = new Map<number, IDisposable>();
|
||||
private readonly _modelReferenceCollection: BoundModelReferenceCollection;
|
||||
|
||||
private _currentState?: DocumentAndEditorState;
|
||||
private _currentState?: NotebookAndEditorState;
|
||||
|
||||
constructor(
|
||||
extHostContext: IExtHostContext,
|
||||
@IInstantiationService private readonly _instantiationService: IInstantiationService,
|
||||
@IWorkingCopyService private readonly _workingCopyService: IWorkingCopyService,
|
||||
@INotebookService private _notebookService: INotebookService,
|
||||
@IConfigurationService private readonly _configurationService: IConfigurationService,
|
||||
@INotebookService private readonly _notebookService: INotebookService,
|
||||
@INotebookEditorService private readonly _notebookEditorService: INotebookEditorService,
|
||||
@IEditorService private readonly _editorService: IEditorService,
|
||||
@IEditorGroupsService private readonly _editorGroupsService: IEditorGroupsService,
|
||||
@ILogService private readonly _logService: ILogService,
|
||||
@INotebookCellStatusBarService private readonly _cellStatusBarService: INotebookCellStatusBarService,
|
||||
@INotebookEditorModelResolverService private readonly _notebookModelResolverService: INotebookEditorModelResolverService,
|
||||
@INotebookEditorModelResolverService private readonly _notebookEditorModelResolverService: INotebookEditorModelResolverService,
|
||||
@IUriIdentityService private readonly _uriIdentityService: IUriIdentityService
|
||||
) {
|
||||
super();
|
||||
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostNotebook);
|
||||
this._modelReferenceCollection = new BoundModelReferenceCollection(this._uriIdentityService.extUri);
|
||||
this.registerListeners();
|
||||
this._registerListeners();
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
super.dispose();
|
||||
this._disposables.dispose();
|
||||
|
||||
this._modelReferenceCollection.dispose();
|
||||
|
||||
// remove all notebook providers
|
||||
for (let item of this._notebookProviders.values()) {
|
||||
for (const item of this._notebookProviders.values()) {
|
||||
item.disposable.dispose();
|
||||
}
|
||||
|
||||
// remove all kernel providers
|
||||
for (let item of this._notebookKernelProviders.values()) {
|
||||
for (const item of this._notebookKernelProviders.values()) {
|
||||
item.emitter.dispose();
|
||||
item.provider.dispose();
|
||||
}
|
||||
dispose(this._notebookSerializer.values());
|
||||
dispose(this._editorEventListenersMapping.values());
|
||||
dispose(this._documentEventListenersMapping.values());
|
||||
dispose(this._toDisposeOnEditorRemove.values());
|
||||
dispose(this._cellStatusBarEntries.values());
|
||||
}
|
||||
|
||||
@@ -160,51 +159,27 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo
|
||||
if (!textModel) {
|
||||
return false;
|
||||
}
|
||||
return textModel.applyEdits(modelVersionId, cellEdits, true, undefined, () => undefined, undefined);
|
||||
if (textModel.versionId !== modelVersionId) {
|
||||
return false;
|
||||
}
|
||||
return textModel.applyEdits(cellEdits, true, undefined, () => undefined, undefined);
|
||||
}
|
||||
|
||||
private _isDeltaEmpty(delta: INotebookDocumentsAndEditorsDelta) {
|
||||
if (delta.addedDocuments !== undefined && delta.addedDocuments.length > 0) {
|
||||
return false;
|
||||
async $applyEdits(resource: UriComponents, cellEdits: IImmediateCellEditOperation[], computeUndoRedo = true): Promise<void> {
|
||||
const textModel = this._notebookService.getNotebookTextModel(URI.from(resource));
|
||||
if (!textModel) {
|
||||
throw new Error(`Can't apply edits to unknown notebook model: ${resource}`);
|
||||
}
|
||||
|
||||
if (delta.removedDocuments !== undefined && delta.removedDocuments.length > 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (delta.addedEditors !== undefined && delta.addedEditors.length > 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (delta.removedEditors !== undefined && delta.removedEditors.length > 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (delta.visibleEditors !== undefined && delta.visibleEditors.length > 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (delta.newActiveEditor !== undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
textModel.applyEdits(cellEdits, true, undefined, () => undefined, undefined, computeUndoRedo);
|
||||
}
|
||||
|
||||
private _emitDelta(delta: INotebookDocumentsAndEditorsDelta) {
|
||||
if (this._isDeltaEmpty(delta)) {
|
||||
return;
|
||||
}
|
||||
|
||||
return this._proxy.$acceptDocumentAndEditorsDelta(delta);
|
||||
}
|
||||
|
||||
registerListeners() {
|
||||
private _registerListeners(): void {
|
||||
|
||||
// forward changes to dirty state
|
||||
// todo@rebornix todo@mjbvz this seem way too complicated... is there an easy way to
|
||||
// the actual resource from a working copy?
|
||||
this._register(this._workingCopyService.onDidChangeDirty(e => {
|
||||
this._disposables.add(this._workingCopyService.onDidChangeDirty(e => {
|
||||
if (e.resource.scheme !== Schemas.vscodeNotebook) {
|
||||
return;
|
||||
}
|
||||
@@ -216,16 +191,13 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo
|
||||
}
|
||||
}));
|
||||
|
||||
this._notebookService.listNotebookEditors().forEach((e) => {
|
||||
this._addNotebookEditor(e);
|
||||
});
|
||||
|
||||
this._register(this._notebookService.onDidChangeActiveEditor(e => {
|
||||
this._disposables.add(this._editorService.onDidActiveEditorChange(e => {
|
||||
this._updateState();
|
||||
}));
|
||||
|
||||
this._register(this._notebookService.onDidChangeVisibleEditors(e => {
|
||||
if (this._notebookProviders.size > 0) {
|
||||
this._disposables.add(this._editorService.onDidVisibleEditorsChange(e => {
|
||||
if (this._notebookProviders.size > 0) { // TODO@rebornix propably wrong, what about providers from another host
|
||||
if (!this._currentState) {
|
||||
// no current state means we didn't even create editors in ext host yet.
|
||||
return;
|
||||
@@ -236,38 +208,49 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo
|
||||
}
|
||||
}));
|
||||
|
||||
const notebookEditorAddedHandler = (editor: IEditor) => {
|
||||
if (!this._editorEventListenersMapping.has(editor.getId())) {
|
||||
const disposableStore = new DisposableStore();
|
||||
disposableStore.add(editor.onDidChangeVisibleRanges(() => {
|
||||
this._proxy.$acceptEditorPropertiesChanged(editor.getId(), { visibleRanges: { ranges: editor.visibleRanges }, selections: null });
|
||||
}));
|
||||
|
||||
disposableStore.add(editor.onDidChangeSelection(() => {
|
||||
this._proxy.$acceptEditorPropertiesChanged(editor.getId(), { visibleRanges: null, selections: { selections: editor.getSelections() } });
|
||||
}));
|
||||
|
||||
this._editorEventListenersMapping.set(editor.getId(), disposableStore);
|
||||
const handleNotebookEditorAdded = (editor: INotebookEditor) => {
|
||||
if (this._editorEventListenersMapping.has(editor.getId())) {
|
||||
//todo@jrieken a bug when this happens?
|
||||
return;
|
||||
}
|
||||
const disposableStore = new DisposableStore();
|
||||
disposableStore.add(editor.onDidChangeVisibleRanges(() => {
|
||||
this._proxy.$acceptEditorPropertiesChanged(editor.getId(), { visibleRanges: { ranges: editor.visibleRanges } });
|
||||
}));
|
||||
|
||||
disposableStore.add(editor.onDidChangeSelection(() => {
|
||||
this._proxy.$acceptEditorPropertiesChanged(editor.getId(), { selections: { selections: editor.getSelections() } });
|
||||
}));
|
||||
|
||||
disposableStore.add(editor.onDidChangeKernel(() => {
|
||||
if (!editor.hasModel()) {
|
||||
return;
|
||||
}
|
||||
this._proxy.$acceptNotebookActiveKernelChange({
|
||||
uri: editor.viewModel.uri,
|
||||
providerHandle: editor.activeKernel?.providerHandle,
|
||||
kernelFriendlyId: editor.activeKernel?.friendlyId
|
||||
});
|
||||
}));
|
||||
|
||||
disposableStore.add(editor.onDidChangeModel(() => this._updateState()));
|
||||
disposableStore.add(editor.onDidFocusEditorWidget(() => this._updateState(editor)));
|
||||
|
||||
this._editorEventListenersMapping.set(editor.getId(), disposableStore);
|
||||
|
||||
const activeNotebookEditor = getNotebookEditorFromEditorPane(this._editorService.activeEditorPane);
|
||||
this._updateState(activeNotebookEditor);
|
||||
};
|
||||
|
||||
this._register(this._notebookService.onNotebookEditorAdd(editor => {
|
||||
notebookEditorAddedHandler(editor);
|
||||
this._addNotebookEditor(editor);
|
||||
this._notebookEditorService.listNotebookEditors().forEach(handleNotebookEditorAdded);
|
||||
this._disposables.add(this._notebookEditorService.onDidAddNotebookEditor(handleNotebookEditorAdded));
|
||||
|
||||
this._disposables.add(this._notebookEditorService.onDidRemoveNotebookEditor(editor => {
|
||||
this._editorEventListenersMapping.get(editor.getId())?.dispose();
|
||||
this._editorEventListenersMapping.delete(editor.getId());
|
||||
this._updateState();
|
||||
}));
|
||||
|
||||
this._register(this._notebookService.onNotebookEditorsRemove(editors => {
|
||||
this._removeNotebookEditor(editors);
|
||||
|
||||
editors.forEach(editor => {
|
||||
this._editorEventListenersMapping.get(editor.getId())?.dispose();
|
||||
this._editorEventListenersMapping.delete(editor.getId());
|
||||
});
|
||||
}));
|
||||
|
||||
this._notebookService.listNotebookEditors().forEach(editor => {
|
||||
notebookEditorAddedHandler(editor);
|
||||
});
|
||||
|
||||
const cellToDto = (cell: NotebookCellTextModel): IMainCellDto => {
|
||||
return {
|
||||
@@ -283,157 +266,136 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo
|
||||
};
|
||||
|
||||
|
||||
const notebookDocumentAddedHandler = (textModel: NotebookTextModel) => {
|
||||
if (!this._documentEventListenersMapping.has(textModel.uri)) {
|
||||
const disposableStore = new DisposableStore();
|
||||
disposableStore.add(textModel!.onDidChangeContent(event => {
|
||||
const dto = event.rawEvents.map(e => {
|
||||
const data =
|
||||
e.kind === NotebookCellsChangeType.ModelChange || e.kind === NotebookCellsChangeType.Initialize
|
||||
? {
|
||||
kind: e.kind,
|
||||
versionId: event.versionId,
|
||||
changes: e.changes.map(diff => [diff[0], diff[1], diff[2].map(cell => cellToDto(cell as NotebookCellTextModel))] as [number, number, IMainCellDto[]])
|
||||
}
|
||||
: (
|
||||
e.kind === NotebookCellsChangeType.Move
|
||||
? {
|
||||
kind: e.kind,
|
||||
index: e.index,
|
||||
length: e.length,
|
||||
newIdx: e.newIdx,
|
||||
versionId: event.versionId,
|
||||
cells: e.cells.map(cell => cellToDto(cell as NotebookCellTextModel))
|
||||
}
|
||||
: e
|
||||
);
|
||||
|
||||
return data;
|
||||
});
|
||||
|
||||
/**
|
||||
* TODO@rebornix, @jrieken
|
||||
* When a document is modified, it will trigger onDidChangeContent events.
|
||||
* The first event listener is this one, which doesn't know if the text model is dirty or not. It can ask `workingCopyService` but get the wrong result
|
||||
* The second event listener is `NotebookEditorModel`, which will then set `isDirty` to `true`.
|
||||
* Since `e.transient` decides if the model should be dirty or not, we will use the same logic here.
|
||||
*/
|
||||
const hasNonTransientEvent = event.rawEvents.find(e => !e.transient);
|
||||
this._proxy.$acceptModelChanged(textModel.uri, {
|
||||
rawEvents: dto,
|
||||
versionId: event.versionId
|
||||
}, !!hasNonTransientEvent);
|
||||
|
||||
const hasDocumentMetadataChangeEvent = event.rawEvents.find(e => e.kind === NotebookCellsChangeType.ChangeDocumentMetadata);
|
||||
if (!!hasDocumentMetadataChangeEvent) {
|
||||
this._proxy.$acceptDocumentPropertiesChanged(textModel.uri, { metadata: textModel.metadata });
|
||||
}
|
||||
}));
|
||||
this._documentEventListenersMapping.set(textModel!.uri, disposableStore);
|
||||
const handleNotebookDocumentAdded = (textModel: NotebookTextModel) => {
|
||||
if (this._documentEventListenersMapping.has(textModel.uri)) {
|
||||
//todo@jrieken a bug when this happens?
|
||||
return;
|
||||
}
|
||||
const disposableStore = new DisposableStore();
|
||||
disposableStore.add(textModel!.onDidChangeContent(event => {
|
||||
const dto = event.rawEvents.map(e => {
|
||||
const data =
|
||||
e.kind === NotebookCellsChangeType.ModelChange || e.kind === NotebookCellsChangeType.Initialize
|
||||
? {
|
||||
kind: e.kind,
|
||||
versionId: event.versionId,
|
||||
changes: e.changes.map(diff => [diff[0], diff[1], diff[2].map(cell => cellToDto(cell as NotebookCellTextModel))] as [number, number, IMainCellDto[]])
|
||||
}
|
||||
: (
|
||||
e.kind === NotebookCellsChangeType.Move
|
||||
? {
|
||||
kind: e.kind,
|
||||
index: e.index,
|
||||
length: e.length,
|
||||
newIdx: e.newIdx,
|
||||
versionId: event.versionId,
|
||||
cells: e.cells.map(cell => cellToDto(cell as NotebookCellTextModel))
|
||||
}
|
||||
: e
|
||||
);
|
||||
|
||||
return data;
|
||||
});
|
||||
|
||||
/**
|
||||
* TODO@rebornix, @jrieken
|
||||
* When a document is modified, it will trigger onDidChangeContent events.
|
||||
* The first event listener is this one, which doesn't know if the text model is dirty or not. It can ask `workingCopyService` but get the wrong result
|
||||
* The second event listener is `NotebookEditorModel`, which will then set `isDirty` to `true`.
|
||||
* Since `e.transient` decides if the model should be dirty or not, we will use the same logic here.
|
||||
*/
|
||||
const hasNonTransientEvent = event.rawEvents.find(e => !e.transient);
|
||||
this._proxy.$acceptModelChanged(textModel.uri, {
|
||||
rawEvents: dto,
|
||||
versionId: event.versionId
|
||||
}, !!hasNonTransientEvent);
|
||||
|
||||
const hasDocumentMetadataChangeEvent = event.rawEvents.find(e => e.kind === NotebookCellsChangeType.ChangeDocumentMetadata);
|
||||
if (!!hasDocumentMetadataChangeEvent) {
|
||||
this._proxy.$acceptDocumentPropertiesChanged(textModel.uri, { metadata: textModel.metadata });
|
||||
}
|
||||
}));
|
||||
this._documentEventListenersMapping.set(textModel!.uri, disposableStore);
|
||||
};
|
||||
|
||||
this._notebookService.listNotebookDocuments().forEach(notebookDocumentAddedHandler);
|
||||
this._register(this._notebookService.onDidAddNotebookDocument(document => {
|
||||
notebookDocumentAddedHandler(document);
|
||||
this._notebookService.listNotebookDocuments().forEach(handleNotebookDocumentAdded);
|
||||
this._disposables.add(this._notebookService.onDidAddNotebookDocument(document => {
|
||||
handleNotebookDocumentAdded(document);
|
||||
this._updateState();
|
||||
}));
|
||||
|
||||
this._register(this._notebookService.onDidRemoveNotebookDocument(uri => {
|
||||
this._disposables.add(this._notebookService.onDidRemoveNotebookDocument(uri => {
|
||||
this._documentEventListenersMapping.get(uri)?.dispose();
|
||||
this._documentEventListenersMapping.delete(uri);
|
||||
this._updateState();
|
||||
}));
|
||||
|
||||
this._register(this._notebookService.onDidChangeNotebookActiveKernel(e => {
|
||||
this._disposables.add(this._notebookService.onDidChangeNotebookActiveKernel(e => {
|
||||
this._proxy.$acceptNotebookActiveKernelChange(e);
|
||||
}));
|
||||
|
||||
this._register(this._notebookService.onNotebookDocumentSaved(e => {
|
||||
this._disposables.add(this._notebookEditorModelResolverService.onDidSaveNotebook(e => {
|
||||
this._proxy.$acceptModelSaved(e);
|
||||
}));
|
||||
|
||||
const activeEditorPane = this._editorService.activeEditorPane as any | undefined;
|
||||
const notebookEditor = activeEditorPane?.isNotebookEditor ? activeEditorPane.getControl() : undefined;
|
||||
const notebookEditor = getNotebookEditorFromEditorPane(this._editorService.activeEditorPane);
|
||||
this._updateState(notebookEditor);
|
||||
}
|
||||
|
||||
private _addNotebookEditor(e: IEditor) {
|
||||
this._toDisposeOnEditorRemove.set(e.getId(), combinedDisposable(
|
||||
e.onDidChangeModel(() => this._updateState()),
|
||||
e.onDidFocusEditorWidget(() => {
|
||||
this._updateState(e);
|
||||
}),
|
||||
));
|
||||
private _updateState(focusedNotebookEditor?: INotebookEditor): void {
|
||||
|
||||
const activeEditorPane = this._editorService.activeEditorPane as any | undefined;
|
||||
const notebookEditor = activeEditorPane?.isNotebookEditor ? activeEditorPane.getControl() : undefined;
|
||||
this._updateState(notebookEditor);
|
||||
}
|
||||
const activeNotebookEditor = getNotebookEditorFromEditorPane(this._editorService.activeEditorPane);
|
||||
let activeEditor = activeNotebookEditor?.hasModel() ? activeNotebookEditor.getId() : null;
|
||||
|
||||
private _removeNotebookEditor(editors: IEditor[]) {
|
||||
editors.forEach(e => {
|
||||
const sub = this._toDisposeOnEditorRemove.get(e.getId());
|
||||
if (sub) {
|
||||
this._toDisposeOnEditorRemove.delete(e.getId());
|
||||
sub.dispose();
|
||||
const editors = new Map<string, IActiveNotebookEditor>();
|
||||
const visibleEditorsMap = new Map<string, IActiveNotebookEditor>();
|
||||
|
||||
for (const editor of this._notebookEditorService.listNotebookEditors()) {
|
||||
if (editor.hasModel()) {
|
||||
editors.set(editor.getId(), editor);
|
||||
}
|
||||
});
|
||||
|
||||
this._updateState();
|
||||
}
|
||||
|
||||
private async _updateState(focusedNotebookEditor?: IEditor) {
|
||||
let activeEditor: string | null = null;
|
||||
|
||||
const activeEditorPane = this._editorService.activeEditorPane as any | undefined;
|
||||
if (activeEditorPane?.isNotebookEditor) {
|
||||
const notebookEditor = (activeEditorPane.getControl() as INotebookEditor);
|
||||
activeEditor = notebookEditor && notebookEditor.hasModel() ? notebookEditor!.getId() : null;
|
||||
}
|
||||
|
||||
const documentEditorsMap = new Map<string, IEditor>();
|
||||
|
||||
const editors = new Map<string, IEditor>();
|
||||
this._notebookService.listNotebookEditors().forEach(editor => {
|
||||
if (editor.textModel) {
|
||||
editors.set(editor.getId(), editor);
|
||||
documentEditorsMap.set(editor.textModel.uri.toString(), editor);
|
||||
this._editorService.visibleEditorPanes.forEach(editorPane => {
|
||||
const notebookEditor = getNotebookEditorFromEditorPane(editorPane);
|
||||
if (notebookEditor?.hasModel() && editors.has(notebookEditor.getId())) {
|
||||
visibleEditorsMap.set(notebookEditor.getId(), notebookEditor);
|
||||
}
|
||||
});
|
||||
|
||||
const visibleEditorsMap = new Map<string, IEditor>();
|
||||
this._editorService.visibleEditorPanes.forEach(editor => {
|
||||
if ((editor as any).isNotebookEditor) {
|
||||
const nbEditorWidget = (editor as any).getControl() as INotebookEditor;
|
||||
if (nbEditorWidget && editors.has(nbEditorWidget.getId())) {
|
||||
visibleEditorsMap.set(nbEditorWidget.getId(), nbEditorWidget);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const documents = new Set<NotebookTextModel>();
|
||||
this._notebookService.listNotebookDocuments().forEach(document => {
|
||||
documents.add(document);
|
||||
});
|
||||
|
||||
if (!activeEditor && focusedNotebookEditor && focusedNotebookEditor.textModel) {
|
||||
if (!activeEditor && focusedNotebookEditor?.textModel) {
|
||||
activeEditor = focusedNotebookEditor.getId();
|
||||
}
|
||||
|
||||
// editors always have view model attached, which means there is already a document in exthost.
|
||||
const newState = new DocumentAndEditorState(documents, editors, activeEditor, visibleEditorsMap);
|
||||
const delta = DocumentAndEditorState.compute(this._currentState, newState);
|
||||
// const isEmptyChange = (!delta.addedDocuments || delta.addedDocuments.length === 0)
|
||||
// && (!delta.removedDocuments || delta.removedDocuments.length === 0)
|
||||
// && (!delta.addedEditors || delta.addedEditors.length === 0)
|
||||
// && (!delta.removedEditors || delta.removedEditors.length === 0)
|
||||
// && (delta.newActiveEditor === undefined)
|
||||
const newState = new NotebookAndEditorState(new Set(this._notebookService.listNotebookDocuments()), editors, activeEditor, visibleEditorsMap);
|
||||
const delta = NotebookAndEditorState.compute(this._currentState, newState);
|
||||
|
||||
// if (!isEmptyChange) {
|
||||
this._currentState = newState;
|
||||
this._emitDelta(delta);
|
||||
// }
|
||||
if (!this._isDeltaEmpty(delta)) {
|
||||
return this._proxy.$acceptDocumentAndEditorsDelta(delta);
|
||||
}
|
||||
}
|
||||
|
||||
private _isDeltaEmpty(delta: INotebookDocumentsAndEditorsDelta): boolean {
|
||||
if (delta.addedDocuments !== undefined && delta.addedDocuments.length > 0) {
|
||||
return false;
|
||||
}
|
||||
if (delta.removedDocuments !== undefined && delta.removedDocuments.length > 0) {
|
||||
return false;
|
||||
}
|
||||
if (delta.addedEditors !== undefined && delta.addedEditors.length > 0) {
|
||||
return false;
|
||||
}
|
||||
if (delta.removedEditors !== undefined && delta.removedEditors.length > 0) {
|
||||
return false;
|
||||
}
|
||||
if (delta.visibleEditors !== undefined && delta.visibleEditors.length > 0) {
|
||||
return false;
|
||||
}
|
||||
if (delta.newActiveEditor !== undefined) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
async $registerNotebookProvider(extension: NotebookExtensionDescription, viewType: string, options: {
|
||||
@@ -452,8 +414,8 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo
|
||||
contentOptions.transientOutputs = newOptions.transientOutputs;
|
||||
},
|
||||
viewOptions: options.viewOptions,
|
||||
openNotebook: async (viewType: string, uri: URI, backupId?: string) => {
|
||||
const data = await this._proxy.$openNotebook(viewType, uri, backupId);
|
||||
open: async (uri: URI, backupId: string | undefined, untitledDocumentData: VSBuffer | undefined, token: CancellationToken) => {
|
||||
const data = await this._proxy.$openNotebook(viewType, uri, backupId, untitledDocumentData, token);
|
||||
return {
|
||||
data,
|
||||
transientOptions: contentOptions
|
||||
@@ -501,6 +463,24 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo
|
||||
}
|
||||
}
|
||||
|
||||
$registerNotebookSerializer(handle: number, extension: NotebookExtensionDescription, viewType: string, options: TransientOptions): void {
|
||||
const registration = this._notebookService.registerNotebookSerializer(viewType, extension, {
|
||||
options,
|
||||
dataToNotebook: (data: VSBuffer): Promise<NotebookDataDto> => {
|
||||
return this._proxy.$dataToNotebook(handle, data);
|
||||
},
|
||||
notebookToData: (data: NotebookDataDto): Promise<VSBuffer> => {
|
||||
return this._proxy.$notebookToData(handle, data);
|
||||
}
|
||||
});
|
||||
this._notebookSerializer.set(handle, registration);
|
||||
}
|
||||
|
||||
$unregisterNotebookSerializer(handle: number): void {
|
||||
this._notebookSerializer.get(handle)?.dispose();
|
||||
this._notebookSerializer.delete(handle);
|
||||
}
|
||||
|
||||
async $registerNotebookKernelProvider(extension: NotebookExtensionDescription, handle: number, documentFilter: INotebookDocumentFilter): Promise<void> {
|
||||
const emitter = new Emitter<URI | undefined>();
|
||||
const that = this;
|
||||
@@ -526,17 +506,18 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo
|
||||
isPreferred: dto.isPreferred,
|
||||
preloads: dto.preloads?.map(u => URI.revive(u)),
|
||||
supportedLanguages: dto.supportedLanguages,
|
||||
implementsInterrupt: dto.implementsInterrupt,
|
||||
resolve: (uri: URI, editorId: string, token: CancellationToken): Promise<void> => {
|
||||
this._logService.debug('MainthreadNotebooks.resolveNotebookKernel', uri.path, dto.friendlyId);
|
||||
return this._proxy.$resolveNotebookKernel(handle, editorId, uri, dto.friendlyId, token);
|
||||
},
|
||||
executeNotebookCell: (uri: URI, cellHandle: number | undefined): Promise<void> => {
|
||||
this._logService.debug('MainthreadNotebooks.executeNotebookCell', uri.path, dto.friendlyId, cellHandle);
|
||||
return this._proxy.$executeNotebookKernelFromProvider(handle, uri, dto.friendlyId, cellHandle);
|
||||
executeNotebookCellsRequest: (uri: URI, cellRanges: ICellRange[]): Promise<void> => {
|
||||
this._logService.debug('MainthreadNotebooks.executeNotebookCell', uri.path, dto.friendlyId, cellRanges);
|
||||
return this._proxy.$executeNotebookKernelFromProvider(handle, uri, dto.friendlyId, cellRanges);
|
||||
},
|
||||
cancelNotebookCell: (uri: URI, cellHandle: number | undefined): Promise<void> => {
|
||||
this._logService.debug('MainthreadNotebooks.cancelNotebookCell', uri.path, dto.friendlyId, cellHandle);
|
||||
return this._proxy.$cancelNotebookKernelFromProvider(handle, uri, dto.friendlyId, cellHandle);
|
||||
cancelNotebookCellExecution: (uri: URI, cellRanges: ICellRange[]): Promise<void> => {
|
||||
this._logService.debug('MainthreadNotebooks.cancelNotebookCellExecution', uri.path, dto.friendlyId, cellRanges);
|
||||
return this._proxy.$cancelNotebookCellExecution(handle, uri, dto.friendlyId, cellRanges);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -563,55 +544,53 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo
|
||||
entry?.emitter.fire(uriComponents ? URI.revive(uriComponents) : undefined);
|
||||
}
|
||||
|
||||
async $postMessage(editorId: string, forRendererId: string | undefined, value: any): Promise<boolean> {
|
||||
const editor = this._notebookService.getNotebookEditor(editorId) as INotebookEditor | undefined;
|
||||
if (editor?.isNotebookEditor) {
|
||||
editor.postMessage(forRendererId, value);
|
||||
return true;
|
||||
async $postMessage(id: string, forRendererId: string | undefined, value: any): Promise<boolean> {
|
||||
const editor = this._notebookEditorService.getNotebookEditor(id);
|
||||
if (!editor) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
editor.postMessage(forRendererId, value);
|
||||
return true;
|
||||
}
|
||||
|
||||
async $tryRevealRange(id: string, range: ICellRange, revealType: NotebookEditorRevealType) {
|
||||
const editor = this._notebookService.listNotebookEditors().find(editor => editor.getId() === id);
|
||||
if (editor && editor.isNotebookEditor) {
|
||||
const notebookEditor = editor as INotebookEditor;
|
||||
if (!notebookEditor.hasModel()) {
|
||||
return;
|
||||
}
|
||||
const viewModel = notebookEditor.viewModel;
|
||||
const cell = viewModel.viewCells[range.start];
|
||||
if (!cell) {
|
||||
return;
|
||||
}
|
||||
async $tryRevealRange(id: string, range: ICellRange, revealType: NotebookEditorRevealType): Promise<void> {
|
||||
const editor = this._notebookEditorService.getNotebookEditor(id);
|
||||
if (!editor) {
|
||||
return;
|
||||
}
|
||||
const notebookEditor = editor as INotebookEditor;
|
||||
if (!notebookEditor.hasModel()) {
|
||||
return;
|
||||
}
|
||||
const viewModel = notebookEditor.viewModel;
|
||||
const cell = viewModel.viewCells[range.start];
|
||||
if (!cell) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (revealType) {
|
||||
case NotebookEditorRevealType.Default:
|
||||
return notebookEditor.revealCellRangeInView(range);
|
||||
case NotebookEditorRevealType.InCenter:
|
||||
return notebookEditor.revealInCenter(cell);
|
||||
case NotebookEditorRevealType.InCenterIfOutsideViewport:
|
||||
return notebookEditor.revealInCenterIfOutsideViewport(cell);
|
||||
case NotebookEditorRevealType.AtTop:
|
||||
return notebookEditor.revealInViewAtTop(cell);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
switch (revealType) {
|
||||
case NotebookEditorRevealType.Default:
|
||||
return notebookEditor.revealCellRangeInView(range);
|
||||
case NotebookEditorRevealType.InCenter:
|
||||
return notebookEditor.revealInCenter(cell);
|
||||
case NotebookEditorRevealType.InCenterIfOutsideViewport:
|
||||
return notebookEditor.revealInCenterIfOutsideViewport(cell);
|
||||
case NotebookEditorRevealType.AtTop:
|
||||
return notebookEditor.revealInViewAtTop(cell);
|
||||
}
|
||||
}
|
||||
|
||||
$registerNotebookEditorDecorationType(key: string, options: INotebookDecorationRenderOptions) {
|
||||
this._notebookService.registerEditorDecorationType(key, options);
|
||||
$registerNotebookEditorDecorationType(key: string, options: INotebookDecorationRenderOptions): void {
|
||||
this._notebookEditorService.registerEditorDecorationType(key, options);
|
||||
}
|
||||
|
||||
$removeNotebookEditorDecorationType(key: string) {
|
||||
this._notebookService.removeEditorDecorationType(key);
|
||||
$removeNotebookEditorDecorationType(key: string): void {
|
||||
this._notebookEditorService.removeEditorDecorationType(key);
|
||||
}
|
||||
|
||||
$trySetDecorations(id: string, range: ICellRange, key: string) {
|
||||
const editor = this._notebookService.listNotebookEditors().find(editor => editor.getId() === id);
|
||||
if (editor && editor.isNotebookEditor) {
|
||||
$trySetDecorations(id: string, range: ICellRange, key: string): void {
|
||||
const editor = this._notebookEditorService.getNotebookEditor(id);
|
||||
if (editor) {
|
||||
const notebookEditor = editor as INotebookEditor;
|
||||
notebookEditor.setEditorDecorations(key, range);
|
||||
}
|
||||
@@ -634,25 +613,25 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo
|
||||
}
|
||||
|
||||
|
||||
async $tryOpenDocument(uriComponents: UriComponents, viewType?: string): Promise<URI> {
|
||||
async $tryOpenDocument(uriComponents: UriComponents): Promise<URI> {
|
||||
const uri = URI.revive(uriComponents);
|
||||
const ref = await this._notebookModelResolverService.resolve(uri, viewType);
|
||||
const ref = await this._notebookEditorModelResolverService.resolve(uri, undefined);
|
||||
this._modelReferenceCollection.add(uri, ref);
|
||||
|
||||
return uri;
|
||||
}
|
||||
|
||||
async $trySaveDocument(uriComponents: UriComponents) {
|
||||
const uri = URI.revive(uriComponents);
|
||||
|
||||
const ref = await this._notebookModelResolverService.resolve(uri);
|
||||
const ref = await this._notebookEditorModelResolverService.resolve(uri);
|
||||
const saveResult = await ref.object.save();
|
||||
ref.dispose();
|
||||
return saveResult;
|
||||
}
|
||||
|
||||
async $tryShowNotebookDocument(resource: UriComponents, viewType: string, options: INotebookDocumentShowOptions): Promise<string> {
|
||||
const editorOptions: ITextEditorOptions = {
|
||||
const editorOptions = new NotebookEditorOptions({
|
||||
cellSelections: options.selection && [options.selection],
|
||||
preserveFocus: options.preserveFocus,
|
||||
pinned: options.pinned,
|
||||
// selection: options.selection,
|
||||
@@ -660,36 +639,13 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo
|
||||
// but make sure to restore the editor to fix https://github.com/microsoft/vscode/issues/79633
|
||||
activation: options.preserveFocus ? EditorActivation.RESTORE : undefined,
|
||||
override: EditorOverride.DISABLED,
|
||||
};
|
||||
});
|
||||
|
||||
const columnArg = viewColumnToEditorGroup(this._editorGroupsService, options.position);
|
||||
|
||||
let group: IEditorGroup | undefined = undefined;
|
||||
|
||||
if (columnArg === SIDE_GROUP) {
|
||||
const direction = preferredSideBySideGroupDirection(this._configurationService);
|
||||
|
||||
let neighbourGroup = this._editorGroupsService.findGroup({ direction });
|
||||
if (!neighbourGroup) {
|
||||
neighbourGroup = this._editorGroupsService.addGroup(this._editorGroupsService.activeGroup, direction);
|
||||
}
|
||||
group = neighbourGroup;
|
||||
} else {
|
||||
group = this._editorGroupsService.getGroup(viewColumnToEditorGroup(this._editorGroupsService, columnArg)) ?? this._editorGroupsService.activeGroup;
|
||||
}
|
||||
|
||||
const input = this._editorService.createEditorInput({ resource: URI.revive(resource), options: editorOptions });
|
||||
|
||||
// TODO: handle options.selection
|
||||
const editorPane = await this._editorService.openEditor(input, { ...options, override: viewType }, group);
|
||||
const notebookEditor = (editorPane as unknown as { isNotebookEditor?: boolean })?.isNotebookEditor ? (editorPane!.getControl() as INotebookEditor) : undefined;
|
||||
const input = NotebookEditorInput.create(this._instantiationService, URI.revive(resource), viewType);
|
||||
const editorPane = await this._editorService.openEditor(input, editorOptions, options.position);
|
||||
const notebookEditor = getNotebookEditorFromEditorPane(editorPane);
|
||||
|
||||
if (notebookEditor) {
|
||||
if (notebookEditor.viewModel && options.selection && notebookEditor.viewModel.viewCells[options.selection.start]) {
|
||||
const focusedCell = notebookEditor.viewModel.viewCells[options.selection.start];
|
||||
notebookEditor.revealInCenterIfOutsideViewport(focusedCell);
|
||||
notebookEditor.focusElement(focusedCell);
|
||||
}
|
||||
return notebookEditor.getId();
|
||||
} else {
|
||||
throw new Error(`Notebook Editor creation failure for documenet ${resource}`);
|
||||
|
||||
@@ -89,6 +89,7 @@ export class MainThreadQuickOpen implements MainThreadQuickOpenShape {
|
||||
const inputOptions: IInputOptions = Object.create(null);
|
||||
|
||||
if (options) {
|
||||
inputOptions.title = options.title;
|
||||
inputOptions.password = options.password;
|
||||
inputOptions.placeHolder = options.placeHolder;
|
||||
inputOptions.valueSelection = options.valueSelection;
|
||||
|
||||
@@ -242,6 +242,7 @@ class MainThreadSCMProvider implements ISCMProvider {
|
||||
|
||||
delete this._groupsByHandle[handle];
|
||||
this.groups.splice(this.groups.elements.indexOf(group), 1);
|
||||
this._onDidChangeResources.fire();
|
||||
}
|
||||
|
||||
async getOriginalResource(uri: URI): Promise<URI | null> {
|
||||
|
||||
@@ -15,7 +15,7 @@ import { IAccessibilityInformation } from 'vs/platform/accessibility/common/acce
|
||||
export class MainThreadStatusBar implements MainThreadStatusBarShape {
|
||||
|
||||
private readonly entries: Map<number, { accessor: IStatusbarEntryAccessor, alignment: MainThreadStatusBarAlignment, priority: number }> = new Map();
|
||||
static readonly CODICON_REGEXP = /\$\((.*?)\)/g;
|
||||
private static readonly CODICON_REGEXP = /\$\((.*?)\)/g;
|
||||
|
||||
constructor(
|
||||
_extHostContext: IExtHostContext,
|
||||
|
||||
@@ -4,24 +4,46 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { MainThreadTelemetryShape, MainContext, IExtHostContext } from '../common/extHost.protocol';
|
||||
import { MainThreadTelemetryShape, MainContext, IExtHostContext, ExtHostTelemetryShape, ExtHostContext } from '../common/extHost.protocol';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
|
||||
import { ClassifiedEvent, StrictPropertyCheck, GDPRClassification } from 'vs/platform/telemetry/common/gdprTypings';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadTelemetry)
|
||||
export class MainThreadTelemetry implements MainThreadTelemetryShape {
|
||||
export class MainThreadTelemetry extends Disposable implements MainThreadTelemetryShape {
|
||||
private readonly _proxy: ExtHostTelemetryShape;
|
||||
|
||||
private static readonly _name = 'pluginHostTelemetry';
|
||||
|
||||
constructor(
|
||||
extHostContext: IExtHostContext,
|
||||
@ITelemetryService private readonly _telemetryService: ITelemetryService
|
||||
@ITelemetryService private readonly _telemetryService: ITelemetryService,
|
||||
@IConfigurationService private readonly _configurationService: IConfigurationService,
|
||||
@IEnvironmentService private readonly _environmenService: IEnvironmentService,
|
||||
) {
|
||||
//
|
||||
super();
|
||||
|
||||
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostTelemetry);
|
||||
|
||||
if (!this._environmenService.disableTelemetry) {
|
||||
this._register(this._configurationService.onDidChangeConfiguration(e => {
|
||||
if (e.affectedKeys.includes('telemetry.enableTelemetry')) {
|
||||
this._proxy.$onDidChangeTelemetryEnabled(this.telemetryEnabled);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
this._proxy.$initializeTelemetryEnabled(this.telemetryEnabled);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
//
|
||||
private get telemetryEnabled(): boolean {
|
||||
if (this._environmenService.disableTelemetry) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !!this._configurationService.getValue('telemetry.enableTelemetry');
|
||||
}
|
||||
|
||||
$publicLog(eventName: string, data: any = Object.create(null)): void {
|
||||
|
||||
@@ -3,21 +3,21 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { DisposableStore, Disposable, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { ITerminalProcessExtHostProxy, ISpawnExtHostProcessRequest, 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 { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { StopWatch } from 'vs/base/common/stopwatch';
|
||||
import { ITerminalInstanceService, ITerminalService, ITerminalInstance, ITerminalExternalLinkProvider, ITerminalLink } from 'vs/workbench/contrib/terminal/browser/terminal';
|
||||
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { TerminalDataBufferer } from 'vs/platform/terminal/common/terminalDataBuffering';
|
||||
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';
|
||||
import { IEnvironmentVariableService, ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable';
|
||||
import { IShellLaunchConfig, ITerminalDimensions } from 'vs/platform/terminal/common/terminal';
|
||||
import { TerminalDataBufferer } from 'vs/platform/terminal/common/terminalDataBuffering';
|
||||
import { ExtHostContext, ExtHostTerminalServiceShape, IExtHostContext, IShellLaunchConfigDto, ITerminalDimensionsDto, MainContext, MainThreadTerminalServiceShape, TerminalIdentifier, TerminalLaunchConfig } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
|
||||
import { ITerminalExternalLinkProvider, ITerminalInstance, ITerminalInstanceService, ITerminalLink, ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal';
|
||||
import { IEnvironmentVariableService, ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable';
|
||||
import { deserializeEnvironmentVariableCollection, serializeEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariableShared';
|
||||
import { IAvailableProfilesRequest as IAvailableProfilesRequest, IDefaultShellAndArgsRequest, IStartExtensionTerminalRequest, ITerminalProcessExtHostProxy } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { ExtensionHostKind } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadTerminalService)
|
||||
export class MainThreadTerminalService implements MainThreadTerminalServiceShape {
|
||||
@@ -33,6 +33,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
|
||||
private readonly _toDispose = new DisposableStore();
|
||||
private readonly _terminalProcessProxies = new Map<number, ITerminalProcessExtHostProxy>();
|
||||
private _dataEventTracker: TerminalDataEventTracker | undefined;
|
||||
private _extHostKind: ExtensionHostKind;
|
||||
/**
|
||||
* A single shared terminal link provider for the exthost. When an ext registers a link
|
||||
* provider, this is registered with the terminal on the renderer side and all links are
|
||||
@@ -48,7 +49,6 @@ 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);
|
||||
@@ -60,16 +60,17 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
|
||||
this._onInstanceDimensionsChanged(instance);
|
||||
}));
|
||||
|
||||
this._extHostKind = extHostContext.extensionHostKind;
|
||||
|
||||
this._toDispose.add(_terminalService.onInstanceDisposed(instance => this._onTerminalDisposed(instance)));
|
||||
this._toDispose.add(_terminalService.onInstanceProcessIdReady(instance => this._onTerminalProcessIdReady(instance)));
|
||||
this._toDispose.add(_terminalService.onInstanceDimensionsChanged(instance => this._onInstanceDimensionsChanged(instance)));
|
||||
this._toDispose.add(_terminalService.onInstanceMaximumDimensionsChanged(instance => this._onInstanceMaximumDimensionsChanged(instance)));
|
||||
this._toDispose.add(_terminalService.onInstanceRequestSpawnExtHostProcess(request => this._onRequestSpawnExtHostProcess(request)));
|
||||
this._toDispose.add(_terminalService.onInstanceRequestStartExtensionTerminal(e => this._onRequestStartExtensionTerminal(e)));
|
||||
this._toDispose.add(_terminalService.onActiveInstanceChanged(instance => this._onActiveTerminalChanged(instance ? instance.id : null)));
|
||||
this._toDispose.add(_terminalService.onInstanceTitleChanged(instance => instance && this._onTitleChanged(instance.id, instance.title)));
|
||||
this._toDispose.add(_terminalService.onActiveInstanceChanged(instance => this._onActiveTerminalChanged(instance ? instance.instanceId : null)));
|
||||
this._toDispose.add(_terminalService.onInstanceTitleChanged(instance => instance && this._onTitleChanged(instance.instanceId, instance.title)));
|
||||
this._toDispose.add(_terminalService.configHelper.onWorkspacePermissionsChanged(isAllowed => this._onWorkspacePermissionsChanged(isAllowed)));
|
||||
this._toDispose.add(_terminalService.onRequestAvailableShells(e => this._onRequestAvailableShells(e)));
|
||||
this._toDispose.add(_terminalService.onRequestAvailableProfiles(e => this._onRequestAvailableProfiles(e)));
|
||||
|
||||
// ITerminalInstanceService listeners
|
||||
if (terminalInstanceService.onRequestDefaultShellAndArgs) {
|
||||
@@ -83,7 +84,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
|
||||
});
|
||||
const activeInstance = this._terminalService.getActiveInstance();
|
||||
if (activeInstance) {
|
||||
this._proxy.$acceptActiveTerminalChanged(activeInstance.id);
|
||||
this._proxy.$acceptActiveTerminalChanged(activeInstance.instanceId);
|
||||
}
|
||||
if (this._environmentVariableService.collections.size > 0) {
|
||||
const collectionAsArray = [...this._environmentVariableService.collections.entries()];
|
||||
@@ -130,13 +131,13 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
|
||||
env: launchConfig.env,
|
||||
strictEnv: launchConfig.strictEnv,
|
||||
hideFromUser: launchConfig.hideFromUser,
|
||||
isExtensionTerminal: launchConfig.isExtensionTerminal,
|
||||
isExtensionOwnedTerminal: launchConfig.isExtensionOwnedTerminal,
|
||||
isExtensionCustomPtyTerminal: launchConfig.isExtensionCustomPtyTerminal,
|
||||
extHostTerminalId: extHostTerminalId,
|
||||
isFeatureTerminal: launchConfig.isFeatureTerminal
|
||||
isFeatureTerminal: launchConfig.isFeatureTerminal,
|
||||
isExtensionOwnedTerminal: launchConfig.isExtensionOwnedTerminal
|
||||
};
|
||||
const terminal = this._terminalService.createTerminal(shellLaunchConfig);
|
||||
this._extHostTerminalIds.set(extHostTerminalId, terminal.id);
|
||||
this._extHostTerminalIds.set(extHostTerminalId, terminal.instanceId);
|
||||
}
|
||||
|
||||
public $show(id: TerminalIdentifier, preserveFocus: boolean): void {
|
||||
@@ -150,7 +151,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
|
||||
public $hide(id: TerminalIdentifier): void {
|
||||
const rendererId = this._getTerminalId(id);
|
||||
const instance = this._terminalService.getActiveInstance();
|
||||
if (instance && instance.id === rendererId) {
|
||||
if (instance && instance.instanceId === rendererId) {
|
||||
this._terminalService.hidePanel();
|
||||
}
|
||||
}
|
||||
@@ -176,7 +177,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
|
||||
});
|
||||
// Send initial events if they exist
|
||||
this._terminalService.terminalInstances.forEach(t => {
|
||||
t.initialDataEvents?.forEach(d => this._onTerminalData(t.id, d));
|
||||
t.initialDataEvents?.forEach(d => this._onTerminalData(t.instanceId, d));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -219,7 +220,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
|
||||
}
|
||||
|
||||
private _onTerminalDisposed(terminalInstance: ITerminalInstance): void {
|
||||
this._proxy.$acceptTerminalClosed(terminalInstance.id, terminalInstance.exitCode);
|
||||
this._proxy.$acceptTerminalClosed(terminalInstance.instanceId, terminalInstance.exitCode);
|
||||
}
|
||||
|
||||
private _onTerminalOpened(terminalInstance: ITerminalInstance): void {
|
||||
@@ -232,63 +233,28 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
|
||||
env: terminalInstance.shellLaunchConfig.env,
|
||||
hideFromUser: terminalInstance.shellLaunchConfig.hideFromUser
|
||||
};
|
||||
this._proxy.$acceptTerminalOpened(terminalInstance.id, extHostTerminalId, terminalInstance.title, shellLaunchConfigDto);
|
||||
this._proxy.$acceptTerminalOpened(terminalInstance.instanceId, extHostTerminalId, terminalInstance.title, shellLaunchConfigDto);
|
||||
}
|
||||
|
||||
private _onTerminalProcessIdReady(terminalInstance: ITerminalInstance): void {
|
||||
if (terminalInstance.processId === undefined) {
|
||||
return;
|
||||
}
|
||||
this._proxy.$acceptTerminalProcessId(terminalInstance.id, terminalInstance.processId);
|
||||
this._proxy.$acceptTerminalProcessId(terminalInstance.instanceId, terminalInstance.processId);
|
||||
}
|
||||
|
||||
private _onInstanceDimensionsChanged(instance: ITerminalInstance): void {
|
||||
this._proxy.$acceptTerminalDimensions(instance.id, instance.cols, instance.rows);
|
||||
this._proxy.$acceptTerminalDimensions(instance.instanceId, instance.cols, instance.rows);
|
||||
}
|
||||
|
||||
private _onInstanceMaximumDimensionsChanged(instance: ITerminalInstance): void {
|
||||
this._proxy.$acceptTerminalMaximumDimensions(instance.id, instance.maxCols, instance.maxRows);
|
||||
this._proxy.$acceptTerminalMaximumDimensions(instance.instanceId, instance.maxCols, instance.maxRows);
|
||||
}
|
||||
|
||||
private _onRequestSpawnExtHostProcess(request: ISpawnExtHostProcessRequest): void {
|
||||
// Only allow processes on remote ext hosts
|
||||
if (!this._remoteAuthority) {
|
||||
return;
|
||||
}
|
||||
|
||||
const proxy = request.proxy;
|
||||
this._terminalProcessProxies.set(proxy.terminalId, proxy);
|
||||
const shellLaunchConfigDto: IShellLaunchConfigDto = {
|
||||
name: request.shellLaunchConfig.name,
|
||||
executable: request.shellLaunchConfig.executable,
|
||||
args: request.shellLaunchConfig.args,
|
||||
cwd: request.shellLaunchConfig.cwd,
|
||||
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 });
|
||||
this._proxy.$spawnExtHostProcess(
|
||||
proxy.terminalId,
|
||||
shellLaunchConfigDto,
|
||||
request.activeWorkspaceRootUri,
|
||||
request.cols,
|
||||
request.rows,
|
||||
request.isWorkspaceShellAllowed
|
||||
).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));
|
||||
proxy.onRequestCwd(() => this._proxy.$acceptProcessRequestCwd(proxy.terminalId));
|
||||
proxy.onRequestInitialCwd(() => this._proxy.$acceptProcessRequestInitialCwd(proxy.terminalId));
|
||||
proxy.onRequestLatency(() => this._onRequestLatency(proxy.terminalId));
|
||||
}
|
||||
|
||||
private _onRequestStartExtensionTerminal(request: IStartExtensionTerminalRequest): void {
|
||||
const proxy = request.proxy;
|
||||
this._terminalProcessProxies.set(proxy.terminalId, proxy);
|
||||
this._terminalProcessProxies.set(proxy.instanceId, proxy);
|
||||
|
||||
// Note that onReisze is not being listened to here as it needs to fire when max dimensions
|
||||
// change, excluding the dimension override
|
||||
@@ -298,15 +264,15 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
|
||||
} : undefined;
|
||||
|
||||
this._proxy.$startExtensionTerminal(
|
||||
proxy.terminalId,
|
||||
proxy.instanceId,
|
||||
initialDimensions
|
||||
).then(request.callback);
|
||||
|
||||
proxy.onInput(data => this._proxy.$acceptProcessInput(proxy.terminalId, data));
|
||||
proxy.onShutdown(immediate => this._proxy.$acceptProcessShutdown(proxy.terminalId, immediate));
|
||||
proxy.onRequestCwd(() => this._proxy.$acceptProcessRequestCwd(proxy.terminalId));
|
||||
proxy.onRequestInitialCwd(() => this._proxy.$acceptProcessRequestInitialCwd(proxy.terminalId));
|
||||
proxy.onRequestLatency(() => this._onRequestLatency(proxy.terminalId));
|
||||
proxy.onInput(data => this._proxy.$acceptProcessInput(proxy.instanceId, data));
|
||||
proxy.onShutdown(immediate => this._proxy.$acceptProcessShutdown(proxy.instanceId, immediate));
|
||||
proxy.onRequestCwd(() => this._proxy.$acceptProcessRequestCwd(proxy.instanceId));
|
||||
proxy.onRequestInitialCwd(() => this._proxy.$acceptProcessRequestInitialCwd(proxy.instanceId));
|
||||
proxy.onRequestLatency(() => this._onRequestLatency(proxy.instanceId));
|
||||
}
|
||||
|
||||
public $sendProcessTitle(terminalId: number, title: string): void {
|
||||
@@ -387,9 +353,9 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
|
||||
return true;
|
||||
}
|
||||
|
||||
private async _onRequestAvailableShells(req: IAvailableShellsRequest): Promise<void> {
|
||||
if (this._isPrimaryExtHost()) {
|
||||
req.callback(await this._proxy.$getAvailableShells());
|
||||
private async _onRequestAvailableProfiles(req: IAvailableProfilesRequest): Promise<void> {
|
||||
if (this._isPrimaryExtHost() && this._extHostKind !== ExtensionHostKind.LocalWebWorker) {
|
||||
req.callback(await this._proxy.$getAvailableProfiles(req.quickLaunchOnly));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -439,12 +405,12 @@ class TerminalDataEventTracker extends Disposable {
|
||||
|
||||
this._terminalService.terminalInstances.forEach(instance => this._registerInstance(instance));
|
||||
this._register(this._terminalService.onInstanceCreated(instance => this._registerInstance(instance)));
|
||||
this._register(this._terminalService.onInstanceDisposed(instance => this._bufferer.stopBuffering(instance.id)));
|
||||
this._register(this._terminalService.onInstanceDisposed(instance => this._bufferer.stopBuffering(instance.instanceId)));
|
||||
}
|
||||
|
||||
private _registerInstance(instance: ITerminalInstance): void {
|
||||
// Buffer data events to reduce the amount of messages going to the extension host
|
||||
this._register(this._bufferer.startBuffering(instance.id, instance.onData));
|
||||
this._register(this._bufferer.startBuffering(instance.instanceId, instance.onData));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -456,13 +422,13 @@ class ExtensionTerminalLinkProvider implements ITerminalExternalLinkProvider {
|
||||
|
||||
async provideLinks(instance: ITerminalInstance, line: string): Promise<ITerminalLink[] | undefined> {
|
||||
const proxy = this._proxy;
|
||||
const extHostLinks = await proxy.$provideLinks(instance.id, line);
|
||||
const extHostLinks = await proxy.$provideLinks(instance.instanceId, line);
|
||||
return extHostLinks.map(dto => ({
|
||||
id: dto.id,
|
||||
startIndex: dto.startIndex,
|
||||
length: dto.length,
|
||||
label: dto.label,
|
||||
activate: () => proxy.$activateLink(instance.id, dto.id)
|
||||
activate: () => proxy.$activateLink(instance.instanceId, dto.id)
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,23 +11,25 @@ import { Range } from 'vs/editor/common/core/range';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
|
||||
import { getTestSubscriptionKey, ISerializedTestResults, ITestState, RunTestsRequest, TestDiffOpType, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection';
|
||||
import { HydratedTestResult, ITestResultService, LiveTestResult } from 'vs/workbench/contrib/testing/common/testResultService';
|
||||
import { ITestService } from 'vs/workbench/contrib/testing/common/testService';
|
||||
import { ITestRootProvider, 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);
|
||||
item.item.location.range = Range.lift(item.item.location.range);
|
||||
if (item.item?.uri) {
|
||||
item.item.uri = URI.revive(item.item.uri);
|
||||
}
|
||||
if (item.item?.range) {
|
||||
item.item.range = Range.lift(item.item.range);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadTesting)
|
||||
export class MainThreadTesting extends Disposable implements MainThreadTestingShape {
|
||||
export class MainThreadTesting extends Disposable implements MainThreadTestingShape, ITestRootProvider {
|
||||
private readonly proxy: ExtHostTestingShape;
|
||||
private readonly testSubscriptions = new Map<string, IDisposable>();
|
||||
private readonly testProviderRegistrations = new Map<string, IDisposable>();
|
||||
@@ -56,7 +58,7 @@ export class MainThreadTesting extends Disposable implements MainThreadTestingSh
|
||||
}
|
||||
}));
|
||||
|
||||
testService.updateRootProviderCount(1);
|
||||
this._register(testService.registerRootProvider(this));
|
||||
|
||||
for (const { resource, uri } of this.testService.subscriptions) {
|
||||
this.proxy.$subscribeToTests(resource, uri);
|
||||
@@ -66,25 +68,14 @@ export class MainThreadTesting extends Disposable implements MainThreadTestingSh
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
$publishExtensionProvidedResults(results: ISerializedTestResults, persist: boolean): void {
|
||||
public $publishExtensionProvidedResults(results: ISerializedTestResults, persist: boolean): void {
|
||||
this.resultService.push(new HydratedTestResult(results, persist));
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
$retireTest(extId: string): void {
|
||||
for (const result of this.resultService.results) {
|
||||
if (result instanceof LiveTestResult) {
|
||||
result.retire(extId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
$updateTestStateInRun(runId: string, testId: string, state: ITestState): void {
|
||||
public $updateTestStateInRun(runId: string, testId: string, state: ITestState): void {
|
||||
const r = this.resultService.getResult(runId);
|
||||
if (r && r instanceof LiveTestResult) {
|
||||
for (const message of state.messages) {
|
||||
@@ -105,6 +96,7 @@ export class MainThreadTesting extends Disposable implements MainThreadTestingSh
|
||||
const disposable = this.testService.registerTestController(id, {
|
||||
runTests: (req, token) => this.proxy.$runTestsForProvider(req, token),
|
||||
lookupTest: test => this.proxy.$lookupTest(test),
|
||||
expandTest: (src, levels) => this.proxy.$expandTest(src, isFinite(levels) ? levels : -1),
|
||||
});
|
||||
|
||||
this.testProviderRegistrations.set(id, disposable);
|
||||
@@ -121,7 +113,7 @@ export class MainThreadTesting extends Disposable implements MainThreadTestingSh
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
$subscribeToDiffs(resource: ExtHostTestingResource, uriComponents: UriComponents): void {
|
||||
public $subscribeToDiffs(resource: ExtHostTestingResource, uriComponents: UriComponents): void {
|
||||
const uri = URI.revive(uriComponents);
|
||||
const disposable = this.testService.subscribeToDiffs(resource, uri,
|
||||
diff => this.proxy.$acceptDiff(resource, uriComponents, diff));
|
||||
@@ -152,7 +144,6 @@ export class MainThreadTesting extends Disposable implements MainThreadTestingSh
|
||||
|
||||
public dispose() {
|
||||
super.dispose();
|
||||
this.testService.updateRootProviderCount(-1);
|
||||
for (const subscription of this.testSubscriptions.values()) {
|
||||
subscription.dispose();
|
||||
}
|
||||
|
||||
@@ -4,22 +4,24 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { MainThreadTunnelServiceShape, IExtHostContext, MainContext, ExtHostContext, ExtHostTunnelServiceShape, CandidatePortSource } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { MainThreadTunnelServiceShape, IExtHostContext, MainContext, ExtHostContext, ExtHostTunnelServiceShape, CandidatePortSource, PortAttributesProviderSelector } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { TunnelDto } from 'vs/workbench/api/common/extHostTunnelService';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
|
||||
import { CandidatePort, IRemoteExplorerService, makeAddress, PORT_AUTO_FORWARD_SETTING, PORT_AUTO_SOURCE_SETTING, PORT_AUTO_SOURCE_SETTING_OUTPUT } from 'vs/workbench/services/remote/common/remoteExplorerService';
|
||||
import { ITunnelProvider, ITunnelService, TunnelCreationOptions, TunnelProviderFeatures, TunnelOptions, RemoteTunnel, isPortPrivileged } from 'vs/platform/remote/common/tunnel';
|
||||
import { CandidatePort, IRemoteExplorerService, makeAddress, PORT_AUTO_FORWARD_SETTING, PORT_AUTO_SOURCE_SETTING, PORT_AUTO_SOURCE_SETTING_OUTPUT, PORT_AUTO_SOURCE_SETTING_PROCESS } from 'vs/workbench/services/remote/common/remoteExplorerService';
|
||||
import { ITunnelProvider, ITunnelService, TunnelCreationOptions, TunnelProviderFeatures, TunnelOptions, RemoteTunnel, isPortPrivileged, ProvidedPortAttributes, PortAttributesProvider } 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 { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadTunnelService)
|
||||
export class MainThreadTunnelService extends Disposable implements MainThreadTunnelServiceShape {
|
||||
export class MainThreadTunnelService extends Disposable implements MainThreadTunnelServiceShape, PortAttributesProvider {
|
||||
private readonly _proxy: ExtHostTunnelServiceShape;
|
||||
private elevateionRetry: boolean = false;
|
||||
private portsAttributesProviders: Map<number, PortAttributesProviderSelector> = new Map();
|
||||
|
||||
constructor(
|
||||
extHostContext: IExtHostContext,
|
||||
@@ -36,20 +38,57 @@ export class MainThreadTunnelService extends Disposable implements MainThreadTun
|
||||
this._register(tunnelService.onTunnelClosed(() => this._proxy.$onDidTunnelsChange()));
|
||||
}
|
||||
|
||||
private processFindingEnabled(): boolean {
|
||||
return (!!this.configurationService.getValue(PORT_AUTO_FORWARD_SETTING)) && (this.configurationService.getValue(PORT_AUTO_SOURCE_SETTING) === PORT_AUTO_SOURCE_SETTING_PROCESS);
|
||||
}
|
||||
|
||||
async $setRemoteTunnelService(processId: number): Promise<void> {
|
||||
this.remoteExplorerService.namedProcesses.set(processId, 'Code Extension Host');
|
||||
if (this.remoteExplorerService.portsFeaturesEnabled) {
|
||||
this._proxy.$registerCandidateFinder(this.configurationService.getValue(PORT_AUTO_FORWARD_SETTING));
|
||||
this._proxy.$registerCandidateFinder(this.processFindingEnabled());
|
||||
} 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)));
|
||||
if (e.affectsConfiguration(PORT_AUTO_FORWARD_SETTING) || e.affectsConfiguration(PORT_AUTO_SOURCE_SETTING)) {
|
||||
return this._proxy.$registerCandidateFinder(this.processFindingEnabled());
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private _alreadyRegistered: boolean = false;
|
||||
async $registerPortsAttributesProvider(selector: PortAttributesProviderSelector, providerHandle: number): Promise<void> {
|
||||
this.portsAttributesProviders.set(providerHandle, selector);
|
||||
if (!this._alreadyRegistered) {
|
||||
this.remoteExplorerService.tunnelModel.addAttributesProvider(this);
|
||||
this._alreadyRegistered = true;
|
||||
}
|
||||
}
|
||||
|
||||
async $unregisterPortsAttributesProvider(providerHandle: number): Promise<void> {
|
||||
this.portsAttributesProviders.delete(providerHandle);
|
||||
}
|
||||
|
||||
async providePortAttributes(ports: number[], pid: number | undefined, commandLine: string | undefined, token: CancellationToken): Promise<ProvidedPortAttributes[]> {
|
||||
if (this.portsAttributesProviders.size === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Check all the selectors to make sure it's worth going to the extension host.
|
||||
const appropriateHandles = Array.from(this.portsAttributesProviders.entries()).filter(entry => {
|
||||
const selector = entry[1];
|
||||
const portRange = selector.portRange;
|
||||
const portInRange = portRange ? ports.some(port => portRange[0] <= port && port < portRange[1]) : true;
|
||||
const pidMatches = !selector.pid || (selector.pid === pid);
|
||||
return portInRange || pidMatches;
|
||||
}).map(entry => entry[0]);
|
||||
|
||||
if (appropriateHandles.length === 0) {
|
||||
return [];
|
||||
}
|
||||
return this._proxy.$providePortAttributes(appropriateHandles, ports, pid, commandLine, token);
|
||||
}
|
||||
|
||||
async $openTunnel(tunnelOptions: TunnelOptions, source: string): Promise<TunnelDto | undefined> {
|
||||
const tunnel = await this.remoteExplorerService.forward(tunnelOptions.remoteAddress, tunnelOptions.localAddressPort, tunnelOptions.label, source, false);
|
||||
if (tunnel) {
|
||||
@@ -102,26 +141,23 @@ export class MainThreadTunnelService extends Disposable implements MainThreadTun
|
||||
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 forward.then(tunnel => {
|
||||
this.logService.trace(`ForwardedPorts: (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,
|
||||
public: tunnel.public,
|
||||
dispose: async (silent?: boolean) => {
|
||||
this.logService.trace(`ForwardedPorts: (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);
|
||||
}
|
||||
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,
|
||||
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);
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
});
|
||||
}
|
||||
};
|
||||
this.tunnelService.setTunnelProvider(tunnelProvider, features);
|
||||
|
||||
@@ -7,13 +7,14 @@ import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { Disposable, DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { MainThreadWebviews, reviveWebviewExtension, reviveWebviewOptions } from 'vs/workbench/api/browser/mainThreadWebviews';
|
||||
import { MainThreadWebviews, reviveWebviewContentOptions, reviveWebviewExtension } from 'vs/workbench/api/browser/mainThreadWebviews';
|
||||
import * as extHostProtocol from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { editorGroupToViewColumn, EditorGroupColumn, IEditorInput, viewColumnToEditorGroup } from 'vs/workbench/common/editor';
|
||||
import { EditorGroupColumn, editorGroupToViewColumn, IEditorInput, viewColumnToEditorGroup } from 'vs/workbench/common/editor';
|
||||
import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput';
|
||||
import { WebviewIcons } from 'vs/workbench/contrib/webview/browser/webview';
|
||||
import { WebviewOptions } from 'vs/workbench/contrib/webview/browser/webview';
|
||||
import { WebviewInput } from 'vs/workbench/contrib/webviewPanel/browser/webviewEditorInput';
|
||||
import { ICreateWebViewShowOptions, IWebviewWorkbenchService, WebviewInputOptions } from 'vs/workbench/contrib/webviewPanel/browser/webviewWorkbenchService';
|
||||
import { WebviewIcons } from 'vs/workbench/contrib/webviewPanel/browser/webviewIconManager';
|
||||
import { ICreateWebViewShowOptions, IWebviewWorkbenchService } from 'vs/workbench/contrib/webviewPanel/browser/webviewWorkbenchService';
|
||||
import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
@@ -151,9 +152,12 @@ export class MainThreadWebviewPanels extends Disposable implements extHostProtoc
|
||||
extensionData: extHostProtocol.WebviewExtensionDescription,
|
||||
handle: extHostProtocol.WebviewHandle,
|
||||
viewType: string,
|
||||
title: string,
|
||||
initData: {
|
||||
title: string;
|
||||
webviewOptions: extHostProtocol.IWebviewOptions;
|
||||
panelOptions: extHostProtocol.IWebviewPanelOptions;
|
||||
},
|
||||
showOptions: { viewColumn?: EditorGroupColumn, preserveFocus?: boolean; },
|
||||
options: WebviewInputOptions
|
||||
): void {
|
||||
const mainThreadShowOptions: ICreateWebViewShowOptions = Object.create(null);
|
||||
if (showOptions) {
|
||||
@@ -163,7 +167,7 @@ export class MainThreadWebviewPanels extends Disposable implements extHostProtoc
|
||||
|
||||
const extension = reviveWebviewExtension(extensionData);
|
||||
|
||||
const webview = this._webviewWorkbenchService.createWebview(handle, this.webviewPanelViewType.fromExternal(viewType), title, mainThreadShowOptions, reviveWebviewOptions(options), extension);
|
||||
const webview = this._webviewWorkbenchService.createWebview(handle, this.webviewPanelViewType.fromExternal(viewType), initData.title, mainThreadShowOptions, reviveWebviewOptions(initData.panelOptions), reviveWebviewContentOptions(initData.webviewOptions), extension);
|
||||
this.addWebviewInput(handle, webview);
|
||||
|
||||
/* __GDPR__
|
||||
@@ -231,7 +235,12 @@ export class MainThreadWebviewPanels extends Disposable implements extHostProtoc
|
||||
}
|
||||
|
||||
try {
|
||||
await this._proxy.$deserializeWebviewPanel(handle, viewType, webviewInput.getTitle(), state, editorGroupToViewColumn(this._editorGroupService, webviewInput.group || 0), webviewInput.webview.options);
|
||||
await this._proxy.$deserializeWebviewPanel(handle, viewType, {
|
||||
title: webviewInput.getTitle(),
|
||||
state,
|
||||
panelOptions: webviewInput.webview.options,
|
||||
webviewOptions: webviewInput.webview.contentOptions,
|
||||
}, editorGroupToViewColumn(this._editorGroupService, webviewInput.group || 0));
|
||||
} catch (error) {
|
||||
onUnexpectedError(error);
|
||||
webviewInput.webview.html = this._mainThreadWebviews.getWebviewResolvedFailedContent(viewType);
|
||||
@@ -324,7 +333,6 @@ export class MainThreadWebviewPanels extends Disposable implements extHostProtoc
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function reviveWebviewIcon(
|
||||
value: { light: UriComponents, dark: UriComponents; } | undefined
|
||||
): WebviewIcons | undefined {
|
||||
@@ -333,3 +341,9 @@ function reviveWebviewIcon(
|
||||
: undefined;
|
||||
}
|
||||
|
||||
function reviveWebviewOptions(panelOptions: extHostProtocol.IWebviewPanelOptions): WebviewOptions {
|
||||
return {
|
||||
enableFindWidget: panelOptions.enableFindWidget,
|
||||
retainContextWhenHidden: panelOptions.retainContextWhenHidden,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -8,14 +8,12 @@ import { Schemas } from 'vs/base/common/network';
|
||||
import { isWeb } from 'vs/base/common/platform';
|
||||
import { escape } from 'vs/base/common/strings';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IWebviewOptions } from 'vs/editor/common/modes';
|
||||
import { localize } from 'vs/nls';
|
||||
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import * as extHostProtocol from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { Webview, WebviewExtensionDescription, WebviewOverlay } from 'vs/workbench/contrib/webview/browser/webview';
|
||||
import { WebviewInputOptions } from 'vs/workbench/contrib/webviewPanel/browser/webviewWorkbenchService';
|
||||
import { Webview, WebviewContentOptions, WebviewExtensionDescription, WebviewOverlay } from 'vs/workbench/contrib/webview/browser/webview';
|
||||
|
||||
export class MainThreadWebviews extends Disposable implements extHostProtocol.MainThreadWebviewsShape {
|
||||
|
||||
@@ -42,6 +40,10 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
|
||||
}
|
||||
|
||||
public addWebview(handle: extHostProtocol.WebviewHandle, webview: WebviewOverlay): void {
|
||||
if (this._webviews.has(handle)) {
|
||||
throw new Error('Webview already registered');
|
||||
}
|
||||
|
||||
this._webviews.set(handle, webview);
|
||||
this.hookupWebviewEventDelegate(handle, webview);
|
||||
}
|
||||
@@ -51,9 +53,9 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
|
||||
webview.html = value;
|
||||
}
|
||||
|
||||
public $setOptions(handle: extHostProtocol.WebviewHandle, options: IWebviewOptions): void {
|
||||
public $setOptions(handle: extHostProtocol.WebviewHandle, options: extHostProtocol.IWebviewOptions): void {
|
||||
const webview = this.getWebview(handle);
|
||||
webview.contentOptions = reviveWebviewOptions(options);
|
||||
webview.contentOptions = reviveWebviewContentOptions(options);
|
||||
}
|
||||
|
||||
public async $postMessage(handle: extHostProtocol.WebviewHandle, message: any): Promise<boolean> {
|
||||
@@ -116,10 +118,11 @@ export function reviveWebviewExtension(extensionData: extHostProtocol.WebviewExt
|
||||
return { id: extensionData.id, location: URI.revive(extensionData.location) };
|
||||
}
|
||||
|
||||
export function reviveWebviewOptions(options: IWebviewOptions): WebviewInputOptions {
|
||||
export function reviveWebviewContentOptions(webviewOptions: extHostProtocol.IWebviewOptions): WebviewContentOptions {
|
||||
return {
|
||||
...options,
|
||||
allowScripts: options.enableScripts,
|
||||
localResourceRoots: Array.isArray(options.localResourceRoots) ? options.localResourceRoots.map(r => URI.revive(r)) : undefined,
|
||||
allowScripts: webviewOptions.enableScripts,
|
||||
enableCommandUris: webviewOptions.enableCommandUris,
|
||||
localResourceRoots: Array.isArray(webviewOptions.localResourceRoots) ? webviewOptions.localResourceRoots.map(r => URI.revive(r)) : undefined,
|
||||
portMapping: webviewOptions.portMapping,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
|
||||
import { ILabelService } from 'vs/platform/label/common/label';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { IRequestService } from 'vs/platform/request/common/request';
|
||||
import { WorkspaceTrustStateChangeEvent, IWorkspaceTrustService, WorkspaceTrustState } from 'vs/platform/workspace/common/workspaceTrust';
|
||||
import { WorkspaceTrustStateChangeEvent, IWorkspaceTrustService, WorkspaceTrustRequestOptions, WorkspaceTrustState } from 'vs/platform/workspace/common/workspaceTrust';
|
||||
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';
|
||||
@@ -208,8 +208,8 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape {
|
||||
|
||||
// --- trust ---
|
||||
|
||||
$requireWorkspaceTrust(modal?: boolean): Promise<WorkspaceTrustState> {
|
||||
return this._workspaceTrustService.requireWorkspaceTrust({ modal: modal ?? true });
|
||||
$requireWorkspaceTrust(options?: WorkspaceTrustRequestOptions): Promise<WorkspaceTrustState> {
|
||||
return this._workspaceTrustService.requireWorkspaceTrust(options);
|
||||
}
|
||||
|
||||
private getWorkspaceTrustState(): WorkspaceTrustState {
|
||||
|
||||
@@ -32,7 +32,11 @@ function adjustHandler(handler: (executor: ICommandsExecutor, ...args: any[]) =>
|
||||
|
||||
interface INewWindowAPICommandOptions {
|
||||
reuseWindow?: boolean;
|
||||
remoteAuthority?: string;
|
||||
/**
|
||||
* If set, defines the remoteAuthority of the new window. `null` will open a local window.
|
||||
* If not set, defaults to remoteAuthority of the current window.
|
||||
*/
|
||||
remoteAuthority?: string | null;
|
||||
}
|
||||
|
||||
export class NewWindowAPICommand {
|
||||
@@ -95,6 +99,7 @@ interface RecentEntry {
|
||||
uri: URI;
|
||||
type: 'workspace' | 'folder' | 'file';
|
||||
label?: string;
|
||||
remoteAuthority?: string;
|
||||
}
|
||||
|
||||
CommandsRegistry.registerCommand('_workbench.addToRecentlyOpened', async function (accessor: ServicesAccessor, recentEntry: RecentEntry) {
|
||||
@@ -102,13 +107,14 @@ CommandsRegistry.registerCommand('_workbench.addToRecentlyOpened', async functio
|
||||
let recent: IRecent | undefined = undefined;
|
||||
const uri = recentEntry.uri;
|
||||
const label = recentEntry.label;
|
||||
const remoteAuthority = recentEntry.remoteAuthority;
|
||||
if (recentEntry.type === 'workspace') {
|
||||
const workspace = await workspacesService.getWorkspaceIdentifier(uri);
|
||||
recent = { workspace, label };
|
||||
recent = { workspace, label, remoteAuthority };
|
||||
} else if (recentEntry.type === 'folder') {
|
||||
recent = { folderUri: uri, label };
|
||||
recent = { folderUri: uri, label, remoteAuthority };
|
||||
} else {
|
||||
recent = { fileUri: uri, label };
|
||||
recent = { fileUri: uri, label, remoteAuthority };
|
||||
}
|
||||
return workspacesService.addRecentlyOpened([recent]);
|
||||
});
|
||||
|
||||
@@ -7,7 +7,6 @@ import * as nls from 'vs/nls';
|
||||
import { CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import * as errors from 'vs/base/common/errors';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import * as path from 'vs/base/common/path';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { TextEditorCursorStyle } from 'vs/editor/common/config/editorOptions';
|
||||
@@ -27,7 +26,7 @@ import { ExtHostDocumentContentProvider } from 'vs/workbench/api/common/extHostD
|
||||
import { ExtHostDocumentSaveParticipant } from 'vs/workbench/api/common/extHostDocumentSaveParticipant';
|
||||
import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments';
|
||||
import { IExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors';
|
||||
import { IExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensionService';
|
||||
import { Extension, IExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensionService';
|
||||
import { ExtHostFileSystem } from 'vs/workbench/api/common/extHostFileSystem';
|
||||
import { ExtHostFileSystemEventService } from 'vs/workbench/api/common/extHostFileSystemEventService';
|
||||
import { ExtHostLanguageFeatures } from 'vs/workbench/api/common/extHostLanguageFeatures';
|
||||
@@ -52,8 +51,7 @@ import { throwProposedApiError, checkProposedApiEnabled, checkRequiresWorkspaceT
|
||||
import { ProxyIdentifier } from 'vs/workbench/services/extensions/common/proxyIdentifier';
|
||||
import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry';
|
||||
import type * as vscode from 'vscode';
|
||||
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { originalFSPath } from 'vs/base/common/resources';
|
||||
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { values } from 'vs/base/common/collections';
|
||||
import { ExtHostEditorInsets } from 'vs/workbench/api/common/extHostCodeInsets';
|
||||
import { ExtHostLabelService } from 'vs/workbench/api/common/extHostLabelService';
|
||||
@@ -85,6 +83,7 @@ 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';
|
||||
import { IExtHostTelemetry } from 'vs/workbench/api/common/extHostTelemetry';
|
||||
|
||||
export interface IExtensionApiFactory {
|
||||
(extension: IExtensionDescription, registry: ExtensionDescriptionRegistry, configProvider: ExtHostConfigProvider): typeof vscode;
|
||||
@@ -101,6 +100,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
const extHostConsumerFileSystem = accessor.get(IExtHostConsumerFileSystem);
|
||||
const extensionService = accessor.get(IExtHostExtensionService);
|
||||
const extHostWorkspace = accessor.get(IExtHostWorkspace);
|
||||
const extHostTelemetry = accessor.get(IExtHostTelemetry);
|
||||
const extHostConfiguration = accessor.get(IExtHostConfiguration);
|
||||
const uriTransformer = accessor.get(IURITransformerService);
|
||||
const rpcProtocol = accessor.get(IExtHostRpcService);
|
||||
@@ -122,6 +122,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
rpcProtocol.set(ExtHostContext.ExtHostTunnelService, extHostTunnelService);
|
||||
rpcProtocol.set(ExtHostContext.ExtHostWindow, extHostWindow);
|
||||
rpcProtocol.set(ExtHostContext.ExtHostSecretState, extHostSecretState);
|
||||
rpcProtocol.set(ExtHostContext.ExtHostTelemetry, extHostTelemetry);
|
||||
|
||||
// automatically create and register addressable instances
|
||||
const extHostDecorations = rpcProtocol.set(ExtHostContext.ExtHostDecorations, accessor.get(IExtHostDecorations));
|
||||
@@ -139,7 +140,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
const extHostDocuments = rpcProtocol.set(ExtHostContext.ExtHostDocuments, new ExtHostDocuments(rpcProtocol, extHostDocumentsAndEditors));
|
||||
const extHostDocumentContentProviders = rpcProtocol.set(ExtHostContext.ExtHostDocumentContentProviders, new ExtHostDocumentContentProvider(rpcProtocol, extHostDocumentsAndEditors, extHostLogService));
|
||||
const extHostDocumentSaveParticipant = rpcProtocol.set(ExtHostContext.ExtHostDocumentSaveParticipant, new ExtHostDocumentSaveParticipant(extHostLogService, extHostDocuments, rpcProtocol.getProxy(MainContext.MainThreadBulkEdits)));
|
||||
const extHostNotebook = rpcProtocol.set(ExtHostContext.ExtHostNotebook, new ExtHostNotebookController(rpcProtocol, extHostCommands, extHostDocumentsAndEditors, initData.environment, extHostLogService, extensionStoragePaths));
|
||||
const extHostNotebook = rpcProtocol.set(ExtHostContext.ExtHostNotebook, new ExtHostNotebookController(rpcProtocol, extHostCommands, extHostDocumentsAndEditors, extHostDocuments, initData.environment, extHostLogService, extensionStoragePaths));
|
||||
const extHostEditors = rpcProtocol.set(ExtHostContext.ExtHostEditors, new ExtHostEditors(rpcProtocol, extHostDocumentsAndEditors));
|
||||
const extHostTreeViews = rpcProtocol.set(ExtHostContext.ExtHostTreeViews, new ExtHostTreeViews(rpcProtocol.getProxy(MainContext.MainThreadTreeViews), extHostCommands, extHostLogService));
|
||||
const extHostEditorInsets = rpcProtocol.set(ExtHostContext.ExtHostEditorInsets, new ExtHostEditorInsets(rpcProtocol.getProxy(MainContext.MainThreadEditorInsets), extHostEditors, initData.environment));
|
||||
@@ -167,7 +168,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
rpcProtocol.assertRegistered(expected);
|
||||
|
||||
// Other instances
|
||||
const extHostBulkEdits = new ExtHostBulkEdits(rpcProtocol, extHostDocumentsAndEditors, extHostNotebook);
|
||||
const extHostBulkEdits = new ExtHostBulkEdits(rpcProtocol, extHostDocumentsAndEditors);
|
||||
const extHostClipboard = new ExtHostClipboard(rpcProtocol);
|
||||
const extHostMessageService = new ExtHostMessageService(rpcProtocol, extHostLogService);
|
||||
const extHostDialogs = new ExtHostDialogs(rpcProtocol);
|
||||
@@ -292,6 +293,16 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
get shell() {
|
||||
return extHostTerminalService.getDefaultShell(false, configProvider);
|
||||
},
|
||||
get isTelemetryEnabled() {
|
||||
return extHostTelemetry.getTelemetryEnabled();
|
||||
},
|
||||
get onDidChangeTelemetryEnabled(): Event<boolean> {
|
||||
return extHostTelemetry.onDidChangeTelemetryEnabled;
|
||||
},
|
||||
get isNewAppInstall() {
|
||||
const installAge = Date.now() - new Date(initData.telemetryInfo.firstSessionDate).getTime();
|
||||
return isNaN(installAge) ? false : installAge < 1000 * 60 * 60 * 24; // install age is less than a day
|
||||
},
|
||||
openExternal(uri: URI, options?: { allowContributedOpeners?: boolean | string; }) {
|
||||
return extHostWindow.openUri(uri, {
|
||||
allowTunneling: !!initData.remote.authority,
|
||||
@@ -354,14 +365,14 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
|
||||
// namespace: extensions
|
||||
const extensions: typeof vscode.extensions = {
|
||||
getExtension(extensionId: string): Extension<any> | undefined {
|
||||
getExtension(extensionId: string): vscode.Extension<any> | undefined {
|
||||
const desc = extensionRegistry.getExtensionDescription(extensionId);
|
||||
if (desc) {
|
||||
return new Extension(extensionService, extension.identifier, desc, extensionKind);
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
get all(): Extension<any>[] {
|
||||
get all(): vscode.Extension<any>[] {
|
||||
return extensionRegistry.getAllExtensionDescriptions().map((desc) => new Extension(extensionService, extension.identifier, desc, extensionKind));
|
||||
},
|
||||
get onDidChange() {
|
||||
@@ -414,7 +425,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
return extHostLanguageFeatures.registerEvaluatableExpressionProvider(extension, checkSelector(selector), provider, extension.identifier);
|
||||
},
|
||||
registerInlineValuesProvider(selector: vscode.DocumentSelector, provider: vscode.InlineValuesProvider): vscode.Disposable {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostLanguageFeatures.registerInlineValuesProvider(extension, checkSelector(selector), provider, extension.identifier);
|
||||
},
|
||||
registerDocumentHighlightProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentHighlightProvider): vscode.Disposable {
|
||||
@@ -691,9 +701,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostNotebook.onDidChangeNotebookEditorVisibleRanges(listener, thisArgs, disposables);
|
||||
},
|
||||
showNotebookDocument(document, options?) {
|
||||
showNotebookDocument(uriOrDocument, options?) {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostNotebook.showNotebookDocument(document, options);
|
||||
return extHostNotebook.showNotebookDocument(uriOrDocument, options);
|
||||
},
|
||||
registerExternalUriOpener(id: string, opener: vscode.ExternalUriOpener, metadata: vscode.ExternalUriOpenerMetadata) {
|
||||
checkProposedApiEnabled(extension);
|
||||
@@ -890,6 +900,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostTunnelService.onDidChangeTunnels(listener, thisArg, disposables);
|
||||
},
|
||||
registerPortAttributesProvider: (portSelector: { pid?: number, portRange?: [number, number] }, provider: vscode.PortAttributesProvider) => {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostTunnelService.registerPortsAttributesProvider(portSelector, provider);
|
||||
},
|
||||
registerTimelineProvider: (scheme: string | string[], provider: vscode.TimelineProvider) => {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostTimeline.registerTimelineProvider(scheme, provider, extension.identifier, extHostCommands.converter);
|
||||
@@ -899,10 +913,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
checkRequiresWorkspaceTrust(extension);
|
||||
return extHostWorkspace.trustState;
|
||||
},
|
||||
requireWorkspaceTrust: (modal?: boolean) => {
|
||||
requireWorkspaceTrust: (options?: vscode.WorkspaceTrustRequestOptions) => {
|
||||
checkProposedApiEnabled(extension);
|
||||
checkRequiresWorkspaceTrust(extension);
|
||||
return extHostWorkspace.requireWorkspaceTrust(modal);
|
||||
return extHostWorkspace.requireWorkspaceTrust(options);
|
||||
},
|
||||
onDidChangeWorkspaceTrustState: (listener, thisArgs?, disposables?) => {
|
||||
return extHostWorkspace.onDidChangeWorkspaceTrustState(listener, thisArgs, disposables);
|
||||
@@ -1012,18 +1026,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
};
|
||||
|
||||
// namespace: notebook
|
||||
const notebook: (typeof vscode.notebook & {
|
||||
// to ensure that notebook extensions not break before they update APIs.
|
||||
visibleNotebookEditors: vscode.NotebookEditor[];
|
||||
onDidChangeVisibleNotebookEditors: Event<vscode.NotebookEditor[]>;
|
||||
activeNotebookEditor: vscode.NotebookEditor | undefined;
|
||||
onDidChangeActiveNotebookEditor: Event<vscode.NotebookEditor | undefined>;
|
||||
onDidChangeNotebookEditorSelection: Event<vscode.NotebookEditorSelectionChangeEvent>;
|
||||
onDidChangeNotebookEditorVisibleRanges: Event<vscode.NotebookEditorVisibleRangesChangeEvent>;
|
||||
}) = {
|
||||
openNotebookDocument: (uriComponents, viewType) => {
|
||||
const notebook: typeof vscode.notebook = {
|
||||
openNotebookDocument: (uriComponents) => {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostNotebook.openNotebookDocument(uriComponents, viewType);
|
||||
return extHostNotebook.openNotebookDocument(uriComponents);
|
||||
},
|
||||
get onDidOpenNotebookDocument(): Event<vscode.NotebookDocument> {
|
||||
checkProposedApiEnabled(extension);
|
||||
@@ -1041,18 +1047,14 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostNotebook.notebookDocuments.map(d => d.notebookDocument);
|
||||
},
|
||||
get visibleNotebookEditors(): vscode.NotebookEditor[] {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostNotebook.visibleNotebookEditors;
|
||||
},
|
||||
get onDidChangeVisibleNotebookEditors() {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostNotebook.onDidChangeVisibleNotebookEditors;
|
||||
},
|
||||
get onDidChangeActiveNotebookKernel() {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostNotebook.onDidChangeActiveNotebookKernel;
|
||||
},
|
||||
registerNotebookSerializer(viewType, serializer, options) {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostNotebook.registerNotebookSerializer(extension, viewType, serializer, options);
|
||||
},
|
||||
registerNotebookContentProvider: (viewType: string, provider: vscode.NotebookContentProvider, options?: {
|
||||
transientOutputs: boolean;
|
||||
transientMetadata: { [K in keyof vscode.NotebookCellMetadata]?: boolean }
|
||||
@@ -1068,14 +1070,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostNotebook.createNotebookEditorDecorationType(options);
|
||||
},
|
||||
get activeNotebookEditor(): vscode.NotebookEditor | undefined {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostNotebook.activeNotebookEditor;
|
||||
},
|
||||
onDidChangeActiveNotebookEditor(listener, thisArgs?, disposables?) {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostNotebook.onDidChangeActiveNotebookEditor(listener, thisArgs, disposables);
|
||||
},
|
||||
onDidChangeNotebookDocumentMetadata(listener, thisArgs?, disposables?) {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostNotebook.onDidChangeNotebookDocumentMetadata(listener, thisArgs, disposables);
|
||||
@@ -1084,22 +1078,14 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostNotebook.onDidChangeNotebookCells(listener, thisArgs, disposables);
|
||||
},
|
||||
onDidChangeNotebookEditorSelection(listener, thisArgs?, disposables?) {
|
||||
onDidChangeCellExecutionState(listener, thisArgs?, disposables?) {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostNotebook.onDidChangeNotebookEditorSelection(listener, thisArgs, disposables);
|
||||
},
|
||||
onDidChangeNotebookEditorVisibleRanges(listener, thisArgs?, disposables?) {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostNotebook.onDidChangeNotebookEditorVisibleRanges(listener, thisArgs, disposables);
|
||||
return extHostNotebook.onDidChangeNotebookCellExecutionState(listener, thisArgs, disposables);
|
||||
},
|
||||
onDidChangeCellOutputs(listener, thisArgs?, disposables?) {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostNotebook.onDidChangeCellOutputs(listener, thisArgs, disposables);
|
||||
},
|
||||
onDidChangeCellLanguage(listener, thisArgs?, disposables?) {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostNotebook.onDidChangeCellLanguage(listener, thisArgs, disposables);
|
||||
},
|
||||
onDidChangeCellMetadata(listener, thisArgs?, disposables?) {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostNotebook.onDidChangeCellMetadata(listener, thisArgs, disposables);
|
||||
@@ -1111,6 +1097,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
createCellStatusBarItem(cell: vscode.NotebookCell, alignment?: vscode.NotebookCellStatusBarAlignment, priority?: number): vscode.NotebookCellStatusBarItem {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostNotebook.createNotebookCellStatusBarItemInternal(cell, alignment, priority);
|
||||
},
|
||||
createNotebookCellExecutionTask(uri: vscode.Uri, index: number, kernelId: string): vscode.NotebookCellExecutionTask | undefined {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostNotebook.createNotebookCellExecution(uri, index, kernelId);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1140,7 +1130,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
CandidatePortSource: CandidatePortSource,
|
||||
CodeAction: extHostTypes.CodeAction,
|
||||
CodeActionKind: extHostTypes.CodeActionKind,
|
||||
CodeActionTrigger: extHostTypes.CodeActionTrigger,
|
||||
CodeActionTriggerKind: extHostTypes.CodeActionTriggerKind,
|
||||
CodeLens: extHostTypes.CodeLens,
|
||||
Color: extHostTypes.Color,
|
||||
ColorInformation: extHostTypes.ColorInformation,
|
||||
@@ -1194,6 +1184,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
MarkdownString: extHostTypes.MarkdownString,
|
||||
OverviewRulerLane: OverviewRulerLane,
|
||||
ParameterInformation: extHostTypes.ParameterInformation,
|
||||
PortAutoForwardAction: extHostTypes.PortAutoForwardAction,
|
||||
Position: extHostTypes.Position,
|
||||
ProcessExecution: extHostTypes.ProcessExecution,
|
||||
ProgressLocation: extHostTypes.ProgressLocation,
|
||||
@@ -1239,124 +1230,31 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
ViewColumn: extHostTypes.ViewColumn,
|
||||
WorkspaceEdit: extHostTypes.WorkspaceEdit,
|
||||
// proposed api types
|
||||
get InlineHint() {
|
||||
return extHostTypes.InlineHint;
|
||||
},
|
||||
get InlineHintKind() {
|
||||
return extHostTypes.InlineHintKind;
|
||||
},
|
||||
get RemoteAuthorityResolverError() {
|
||||
// checkProposedApiEnabled(extension);
|
||||
return extHostTypes.RemoteAuthorityResolverError;
|
||||
},
|
||||
get ResolvedAuthority() {
|
||||
// checkProposedApiEnabled(extension);
|
||||
return extHostTypes.ResolvedAuthority;
|
||||
},
|
||||
get SourceControlInputBoxValidationType() {
|
||||
// checkProposedApiEnabled(extension);
|
||||
return extHostTypes.SourceControlInputBoxValidationType;
|
||||
},
|
||||
get ExtensionRuntime() {
|
||||
// checkProposedApiEnabled(extension);
|
||||
return extHostTypes.ExtensionRuntime;
|
||||
},
|
||||
get TimelineItem() {
|
||||
// checkProposedApiEnabled(extension);
|
||||
return extHostTypes.TimelineItem;
|
||||
},
|
||||
get NotebookCellRange() {
|
||||
return extHostTypes.NotebookCellRange;
|
||||
},
|
||||
get NotebookCellKind() {
|
||||
// checkProposedApiEnabled(extension);
|
||||
return extHostTypes.NotebookCellKind;
|
||||
},
|
||||
get NotebookCellRunState() {
|
||||
// checkProposedApiEnabled(extension);
|
||||
return extHostTypes.NotebookCellRunState;
|
||||
},
|
||||
get NotebookDocumentMetadata() {
|
||||
return extHostTypes.NotebookDocumentMetadata;
|
||||
},
|
||||
get NotebookCellMetadata() {
|
||||
return extHostTypes.NotebookCellMetadata;
|
||||
},
|
||||
get NotebookRunState() {
|
||||
// checkProposedApiEnabled(extension);
|
||||
return extHostTypes.NotebookRunState;
|
||||
},
|
||||
get NotebookCellStatusBarAlignment() {
|
||||
// checkProposedApiEnabled(extension);
|
||||
return extHostTypes.NotebookCellStatusBarAlignment;
|
||||
},
|
||||
get NotebookEditorRevealType() {
|
||||
// checkProposedApiEnabled(extension);
|
||||
return extHostTypes.NotebookEditorRevealType;
|
||||
},
|
||||
get NotebookCellOutput() {
|
||||
// checkProposedApiEnabled(extension);
|
||||
return extHostTypes.NotebookCellOutput;
|
||||
},
|
||||
get NotebookCellOutputItem() {
|
||||
// checkProposedApiEnabled(extension);
|
||||
return extHostTypes.NotebookCellOutputItem;
|
||||
},
|
||||
get LinkedEditingRanges() {
|
||||
// checkProposedApiEnabled(extension);
|
||||
return extHostTypes.LinkedEditingRanges;
|
||||
},
|
||||
get TestRunState() {
|
||||
// checkProposedApiEnabled(extension);
|
||||
return extHostTypes.TestRunState;
|
||||
},
|
||||
get TestMessageSeverity() {
|
||||
// checkProposedApiEnabled(extension);
|
||||
return extHostTypes.TestMessageSeverity;
|
||||
},
|
||||
get WorkspaceTrustState() {
|
||||
// checkProposedApiEnabled(extension);
|
||||
return extHostTypes.WorkspaceTrustState;
|
||||
}
|
||||
InlineHint: extHostTypes.InlineHint,
|
||||
InlineHintKind: extHostTypes.InlineHintKind,
|
||||
RemoteAuthorityResolverError: extHostTypes.RemoteAuthorityResolverError,
|
||||
ResolvedAuthority: extHostTypes.ResolvedAuthority,
|
||||
SourceControlInputBoxValidationType: extHostTypes.SourceControlInputBoxValidationType,
|
||||
ExtensionRuntime: extHostTypes.ExtensionRuntime,
|
||||
TimelineItem: extHostTypes.TimelineItem,
|
||||
NotebookCellRange: extHostTypes.NotebookCellRange,
|
||||
NotebookCellKind: extHostTypes.NotebookCellKind,
|
||||
NotebookCellExecutionState: extHostTypes.NotebookCellExecutionState,
|
||||
NotebookDocumentMetadata: extHostTypes.NotebookDocumentMetadata,
|
||||
NotebookCellMetadata: extHostTypes.NotebookCellMetadata,
|
||||
NotebookCellData: extHostTypes.NotebookCellData,
|
||||
NotebookData: extHostTypes.NotebookData,
|
||||
NotebookCellStatusBarAlignment: extHostTypes.NotebookCellStatusBarAlignment,
|
||||
NotebookEditorRevealType: extHostTypes.NotebookEditorRevealType,
|
||||
NotebookCellOutput: extHostTypes.NotebookCellOutput,
|
||||
NotebookCellOutputItem: extHostTypes.NotebookCellOutputItem,
|
||||
LinkedEditingRanges: extHostTypes.LinkedEditingRanges,
|
||||
TestItem: extHostTypes.TestItem,
|
||||
TestState: extHostTypes.TestState,
|
||||
TestResult: extHostTypes.TestResult,
|
||||
TestMessage: extHostTypes.TestMessage,
|
||||
TestMessageSeverity: extHostTypes.TestMessageSeverity,
|
||||
WorkspaceTrustState: extHostTypes.WorkspaceTrustState
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
class Extension<T> implements vscode.Extension<T> {
|
||||
|
||||
#extensionService: IExtHostExtensionService;
|
||||
#originExtensionId: ExtensionIdentifier;
|
||||
#identifier: ExtensionIdentifier;
|
||||
|
||||
readonly id: string;
|
||||
readonly extensionUri: URI;
|
||||
readonly extensionPath: string;
|
||||
readonly packageJSON: IExtensionDescription;
|
||||
readonly extensionKind: vscode.ExtensionKind;
|
||||
|
||||
constructor(extensionService: IExtHostExtensionService, originExtensionId: ExtensionIdentifier, description: IExtensionDescription, kind: extHostTypes.ExtensionKind) {
|
||||
this.#extensionService = extensionService;
|
||||
this.#originExtensionId = originExtensionId;
|
||||
this.#identifier = description.identifier;
|
||||
this.id = description.identifier.value;
|
||||
this.extensionUri = description.extensionLocation;
|
||||
this.extensionPath = path.normalize(originalFSPath(description.extensionLocation));
|
||||
this.packageJSON = description;
|
||||
this.extensionKind = kind;
|
||||
}
|
||||
|
||||
get isActive(): boolean {
|
||||
return this.#extensionService.isActivated(this.#identifier);
|
||||
}
|
||||
|
||||
get exports(): T {
|
||||
if (this.packageJSON.api === 'none') {
|
||||
return undefined!; // Strict nulloverride - Public api
|
||||
}
|
||||
return <T>this.#extensionService.getExtensionExports(this.#identifier);
|
||||
}
|
||||
|
||||
activate(): Thenable<T> {
|
||||
return this.#extensionService.activateByIdWithErrors(this.#identifier, { startup: false, extensionId: this.#originExtensionId, activationEvent: 'api' }).then(() => this.exports);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import { IExtHostWindow, ExtHostWindow } from 'vs/workbench/api/common/extHostWi
|
||||
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';
|
||||
import { ExtHostTelemetry, IExtHostTelemetry } from 'vs/workbench/api/common/extHostTelemetry';
|
||||
|
||||
registerSingleton(IExtensionStoragePaths, ExtensionStoragePaths);
|
||||
registerSingleton(IExtHostApiDeprecationService, ExtHostApiDeprecationService);
|
||||
@@ -41,3 +42,4 @@ registerSingleton(IExtHostTunnelService, ExtHostTunnelService);
|
||||
registerSingleton(IExtHostWindow, ExtHostWindow);
|
||||
registerSingleton(IExtHostWorkspace, ExtHostWorkspace);
|
||||
registerSingleton(IExtHostSecretState, ExtHostSecretState);
|
||||
registerSingleton(IExtHostTelemetry, ExtHostTelemetry);
|
||||
|
||||
@@ -47,20 +47,21 @@ 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, TunnelProviderFeatures, TunnelOptions } from 'vs/platform/remote/common/tunnel';
|
||||
import { TunnelCreationOptions, TunnelProviderFeatures, TunnelOptions, ProvidedPortAttributes } 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 { NotebookCellMetadata, NotebookDocumentMetadata, ICellEditOperation, NotebookCellsChangedEventDto, NotebookDataDto, IMainCellDto, INotebookDocumentFilter, TransientMetadata, INotebookCellStatusBarEntry, ICellRange, INotebookDecorationRenderOptions, INotebookExclusiveDocumentFilter, IOutputDto } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
import { NotebookCellMetadata, NotebookDocumentMetadata, ICellEditOperation, NotebookCellsChangedEventDto, NotebookDataDto, IMainCellDto, INotebookDocumentFilter, TransientMetadata, INotebookCellStatusBarEntry, ICellRange, INotebookDecorationRenderOptions, INotebookExclusiveDocumentFilter, IOutputDto, TransientOptions, IImmediateCellEditOperation } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
import { CallHierarchyItem } from 'vs/workbench/contrib/callHierarchy/common/callHierarchy';
|
||||
import { Dto } from 'vs/base/common/types';
|
||||
import { DebugConfigurationProviderTriggerKind, WorkspaceTrustState } from 'vs/workbench/api/common/extHostTypes';
|
||||
import { IAccessibilityInformation } from 'vs/platform/accessibility/common/accessibility';
|
||||
import { IExtensionIdWithVersion } from 'vs/platform/userDataSync/common/extensionsStorageSync';
|
||||
import { InternalTestItem, ITestState, RunTestForProviderRequest, RunTestsRequest, TestIdWithProvider, TestsDiff, ISerializedTestResults } from 'vs/workbench/contrib/testing/common/testCollection';
|
||||
import { InternalTestItem, ITestState, RunTestForProviderRequest, RunTestsRequest, TestIdWithSrc, TestsDiff, ISerializedTestResults } from 'vs/workbench/contrib/testing/common/testCollection';
|
||||
import { CandidatePort } from 'vs/workbench/services/remote/common/remoteExplorerService';
|
||||
import { WorkspaceTrustStateChangeEvent } from 'vs/platform/workspace/common/workspaceTrust';
|
||||
import { WorkspaceTrustRequestOptions, WorkspaceTrustStateChangeEvent } from 'vs/platform/workspace/common/workspaceTrust';
|
||||
import { ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable';
|
||||
import { IShellLaunchConfig, ITerminalDimensions, ITerminalLaunchError } from 'vs/platform/terminal/common/terminal';
|
||||
import { ITerminalProfile } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
|
||||
export interface IEnvironment {
|
||||
isExtensionDevelopmentDebug: boolean;
|
||||
@@ -457,7 +458,7 @@ export interface TerminalLaunchConfig {
|
||||
waitOnExit?: boolean;
|
||||
strictEnv?: boolean;
|
||||
hideFromUser?: boolean;
|
||||
isExtensionTerminal?: boolean;
|
||||
isExtensionCustomPtyTerminal?: boolean;
|
||||
isFeatureTerminal?: boolean;
|
||||
isExtensionOwnedTerminal?: boolean;
|
||||
}
|
||||
@@ -504,6 +505,8 @@ export interface BaseTransferQuickInput {
|
||||
|
||||
id: number;
|
||||
|
||||
title?: string;
|
||||
|
||||
type?: 'quickPick' | 'inputBox';
|
||||
|
||||
enabled?: boolean;
|
||||
@@ -558,6 +561,7 @@ export interface TransferInputBox extends BaseTransferQuickInput {
|
||||
}
|
||||
|
||||
export interface IInputBoxOptions {
|
||||
title?: string;
|
||||
value?: string;
|
||||
valueSelection?: [number, number];
|
||||
prompt?: string;
|
||||
@@ -592,11 +596,11 @@ export interface MainThreadTelemetryShape extends IDisposable {
|
||||
}
|
||||
|
||||
export interface MainThreadEditorInsetsShape extends IDisposable {
|
||||
$createEditorInset(handle: number, id: string, uri: UriComponents, line: number, height: number, options: modes.IWebviewOptions, extensionId: ExtensionIdentifier, extensionLocation: UriComponents): Promise<void>;
|
||||
$createEditorInset(handle: number, id: string, uri: UriComponents, line: number, height: number, options: IWebviewOptions, extensionId: ExtensionIdentifier, extensionLocation: UriComponents): Promise<void>;
|
||||
$disposeEditorInset(handle: number): void;
|
||||
|
||||
$setHtml(handle: number, value: string): void;
|
||||
$setOptions(handle: number, options: modes.IWebviewOptions): void;
|
||||
$setOptions(handle: number, options: IWebviewOptions): void;
|
||||
$postMessage(handle: number, value: any): Promise<boolean>;
|
||||
}
|
||||
|
||||
@@ -646,18 +650,45 @@ export enum WebviewEditorCapabilities {
|
||||
SupportsHotExit,
|
||||
}
|
||||
|
||||
export interface IWebviewPortMapping {
|
||||
readonly webviewPort: number;
|
||||
readonly extensionHostPort: number;
|
||||
}
|
||||
|
||||
export interface IWebviewOptions {
|
||||
readonly enableScripts?: boolean;
|
||||
readonly enableCommandUris?: boolean;
|
||||
readonly localResourceRoots?: ReadonlyArray<UriComponents>;
|
||||
readonly portMapping?: ReadonlyArray<IWebviewPortMapping>;
|
||||
}
|
||||
|
||||
export interface IWebviewPanelOptions {
|
||||
readonly enableFindWidget?: boolean;
|
||||
readonly retainContextWhenHidden?: boolean;
|
||||
}
|
||||
|
||||
export interface CustomTextEditorCapabilities {
|
||||
readonly supportsMove?: boolean;
|
||||
}
|
||||
|
||||
export interface MainThreadWebviewsShape extends IDisposable {
|
||||
$setHtml(handle: WebviewHandle, value: string): void;
|
||||
$setOptions(handle: WebviewHandle, options: modes.IWebviewOptions): void;
|
||||
$setOptions(handle: WebviewHandle, options: IWebviewOptions): void;
|
||||
$postMessage(handle: WebviewHandle, value: any): Promise<boolean>
|
||||
}
|
||||
|
||||
export interface MainThreadWebviewPanelsShape extends IDisposable {
|
||||
$createWebviewPanel(extension: WebviewExtensionDescription, handle: WebviewHandle, viewType: string, title: string, showOptions: WebviewPanelShowOptions, options: modes.IWebviewPanelOptions & modes.IWebviewOptions): void;
|
||||
$createWebviewPanel(
|
||||
extension: WebviewExtensionDescription,
|
||||
handle: WebviewHandle,
|
||||
viewType: string,
|
||||
initData: {
|
||||
title: string;
|
||||
webviewOptions: IWebviewOptions;
|
||||
panelOptions: IWebviewPanelOptions;
|
||||
},
|
||||
showOptions: WebviewPanelShowOptions,
|
||||
): void;
|
||||
$disposeWebview(handle: WebviewHandle): void;
|
||||
$reveal(handle: WebviewHandle, showOptions: WebviewPanelShowOptions): void;
|
||||
$setTitle(handle: WebviewHandle, value: string): void;
|
||||
@@ -668,8 +699,8 @@ export interface MainThreadWebviewPanelsShape extends IDisposable {
|
||||
}
|
||||
|
||||
export interface MainThreadCustomEditorsShape extends IDisposable {
|
||||
$registerTextEditorProvider(extension: WebviewExtensionDescription, viewType: string, options: modes.IWebviewPanelOptions, capabilities: CustomTextEditorCapabilities): void;
|
||||
$registerCustomEditorProvider(extension: WebviewExtensionDescription, viewType: string, options: modes.IWebviewPanelOptions, supportsMultipleEditorsPerDocument: boolean): void;
|
||||
$registerTextEditorProvider(extension: WebviewExtensionDescription, viewType: string, options: IWebviewPanelOptions, capabilities: CustomTextEditorCapabilities): void;
|
||||
$registerCustomEditorProvider(extension: WebviewExtensionDescription, viewType: string, options: IWebviewPanelOptions, supportsMultipleEditorsPerDocument: boolean): void;
|
||||
$unregisterEditorProvider(viewType: string): void;
|
||||
|
||||
$onDidEdit(resource: UriComponents, viewType: string, editId: number, label: string | undefined): void;
|
||||
@@ -702,12 +733,33 @@ export interface ExtHostWebviewsShape {
|
||||
export interface ExtHostWebviewPanelsShape {
|
||||
$onDidChangeWebviewPanelViewStates(newState: WebviewPanelViewStateData): void;
|
||||
$onDidDisposeWebviewPanel(handle: WebviewHandle): Promise<void>;
|
||||
$deserializeWebviewPanel(newWebviewHandle: WebviewHandle, viewType: string, title: string, state: any, position: EditorGroupColumn, options: modes.IWebviewOptions & modes.IWebviewPanelOptions): Promise<void>;
|
||||
$deserializeWebviewPanel(
|
||||
newWebviewHandle: WebviewHandle,
|
||||
viewType: string,
|
||||
initData: {
|
||||
title: string;
|
||||
state: any;
|
||||
webviewOptions: IWebviewOptions;
|
||||
panelOptions: IWebviewPanelOptions;
|
||||
},
|
||||
position: EditorGroupColumn,
|
||||
): Promise<void>;
|
||||
}
|
||||
|
||||
export interface ExtHostCustomEditorsShape {
|
||||
$resolveWebviewEditor(resource: UriComponents, newWebviewHandle: WebviewHandle, viewType: string, title: string, position: EditorGroupColumn, options: modes.IWebviewOptions & modes.IWebviewPanelOptions, cancellation: CancellationToken): Promise<void>;
|
||||
$createCustomDocument(resource: UriComponents, viewType: string, backupId: string | undefined, cancellation: CancellationToken): Promise<{ editable: boolean }>;
|
||||
$resolveWebviewEditor(
|
||||
resource: UriComponents,
|
||||
newWebviewHandle: WebviewHandle,
|
||||
viewType: string,
|
||||
initData: {
|
||||
title: string;
|
||||
webviewOptions: IWebviewOptions;
|
||||
panelOptions: IWebviewPanelOptions;
|
||||
},
|
||||
position: EditorGroupColumn,
|
||||
cancellation: CancellationToken
|
||||
): Promise<void>;
|
||||
$createCustomDocument(resource: UriComponents, viewType: string, backupId: string | undefined, untitledDocumentData: VSBuffer | undefined, cancellation: CancellationToken): Promise<{ editable: boolean }>;
|
||||
$disposeCustomDocument(resource: UriComponents, viewType: string): Promise<void>;
|
||||
|
||||
$undo(resource: UriComponents, viewType: string, editId: number, isDirty: boolean): Promise<void>;
|
||||
@@ -788,14 +840,19 @@ export interface MainThreadNotebookShape extends IDisposable {
|
||||
}): Promise<void>;
|
||||
$updateNotebookProviderOptions(viewType: string, options?: { transientOutputs: boolean; transientMetadata: TransientMetadata; }): Promise<void>;
|
||||
$unregisterNotebookProvider(viewType: string): Promise<void>;
|
||||
|
||||
$registerNotebookSerializer(handle: number, extension: NotebookExtensionDescription, viewType: string, options: TransientOptions): void;
|
||||
$unregisterNotebookSerializer(handle: number): void;
|
||||
|
||||
$registerNotebookKernelProvider(extension: NotebookExtensionDescription, handle: number, documentFilter: INotebookDocumentFilter): Promise<void>;
|
||||
$unregisterNotebookKernelProvider(handle: number): Promise<void>;
|
||||
$onNotebookKernelChange(handle: number, uri: UriComponents | undefined): void;
|
||||
$trySaveDocument(uri: UriComponents): Promise<boolean>;
|
||||
$tryApplyEdits(viewType: string, resource: UriComponents, modelVersionId: number, edits: ICellEditOperation[]): Promise<boolean>;
|
||||
$applyEdits(resource: UriComponents, edits: IImmediateCellEditOperation[], computeUndoRedo?: boolean): Promise<void>;
|
||||
$postMessage(editorId: string, forRendererId: string | undefined, value: any): Promise<boolean>;
|
||||
$setStatusBarEntry(id: number, statusBarEntry: INotebookCellStatusBarEntryDto): Promise<void>;
|
||||
$tryOpenDocument(uriComponents: UriComponents, viewType?: string): Promise<URI>;
|
||||
$tryOpenDocument(uriComponents: UriComponents): Promise<UriComponents>;
|
||||
$tryShowNotebookDocument(uriComponents: UriComponents, viewType: string, options: INotebookDocumentShowOptions): Promise<string>;
|
||||
$tryRevealRange(id: string, range: ICellRange, revealType: NotebookEditorRevealType): Promise<void>;
|
||||
$registerNotebookEditorDecorationType(key: string, options: INotebookDecorationRenderOptions): void;
|
||||
@@ -834,7 +891,7 @@ export interface MainThreadWorkspaceShape extends IDisposable {
|
||||
$saveAll(includeUntitled?: boolean): Promise<boolean>;
|
||||
$updateWorkspaceFolders(extensionName: string, index: number, deleteCount: number, workspaceFoldersToAdd: { uri: UriComponents, name?: string; }[]): Promise<void>;
|
||||
$resolveProxy(url: string): Promise<string | undefined>;
|
||||
$requireWorkspaceTrust(modal?: boolean): Promise<WorkspaceTrustState>
|
||||
$requireWorkspaceTrust(options?: WorkspaceTrustRequestOptions): Promise<WorkspaceTrustState>;
|
||||
}
|
||||
|
||||
export interface IFileChangeDto {
|
||||
@@ -890,7 +947,6 @@ export interface MainThreadExtensionServiceShape extends IDisposable {
|
||||
$onDidActivateExtension(extensionId: ExtensionIdentifier, codeLoadingTime: number, activateCallTime: number, activateResolvedTime: number, activationReason: ExtensionActivationReason): void;
|
||||
$onExtensionActivationError(extensionId: ExtensionIdentifier, error: ExtensionActivationError): Promise<void>;
|
||||
$onExtensionRuntimeError(extensionId: ExtensionIdentifier, error: SerializedError): void;
|
||||
$onExtensionHostExit(code: number): Promise<void>;
|
||||
$setPerformanceMarks(marks: performance.PerformanceMark[]): Promise<void>;
|
||||
}
|
||||
|
||||
@@ -1000,6 +1056,11 @@ export enum CandidatePortSource {
|
||||
Output = 2
|
||||
}
|
||||
|
||||
export interface PortAttributesProviderSelector {
|
||||
pid?: number;
|
||||
portRange?: [number, number];
|
||||
}
|
||||
|
||||
export interface MainThreadTunnelServiceShape extends IDisposable {
|
||||
$openTunnel(tunnelOptions: TunnelOptions, source: string | undefined): Promise<TunnelDto | undefined>;
|
||||
$closeTunnel(remote: { host: string, port: number }): Promise<void>;
|
||||
@@ -1007,8 +1068,10 @@ export interface MainThreadTunnelServiceShape extends IDisposable {
|
||||
$setTunnelProvider(features: TunnelProviderFeatures): Promise<void>;
|
||||
$setRemoteTunnelService(processId: number): Promise<void>;
|
||||
$setCandidateFilter(): Promise<void>;
|
||||
$onFoundNewCandidates(candidates: { host: string, port: number, detail: string }[]): Promise<void>;
|
||||
$onFoundNewCandidates(candidates: CandidatePort[]): Promise<void>;
|
||||
$setCandidatePortSource(source: CandidatePortSource): Promise<void>;
|
||||
$registerPortsAttributesProvider(selector: PortAttributesProviderSelector, providerHandle: number): Promise<void>;
|
||||
$unregisterPortsAttributesProvider(providerHandle: number): Promise<void>;
|
||||
}
|
||||
|
||||
export interface MainThreadTimelineShape extends IDisposable {
|
||||
@@ -1046,7 +1109,7 @@ export interface IModelAddedData {
|
||||
isDirty: boolean;
|
||||
}
|
||||
export interface ExtHostDocumentsShape {
|
||||
$acceptModelModeChanged(strURL: UriComponents, oldModeId: string, newModeId: string): void;
|
||||
$acceptModelModeChanged(strURL: UriComponents, newModeId: string): void;
|
||||
$acceptModelSaved(strURL: UriComponents): void;
|
||||
$acceptDirtyStateChanged(strURL: UriComponents, isDirty: boolean): void;
|
||||
$acceptModelChanged(strURL: UriComponents, e: IModelChangedEvent, isDirty: boolean): void;
|
||||
@@ -1173,6 +1236,8 @@ export type IResolveAuthorityResult = IResolveAuthorityErrorResult | IResolveAut
|
||||
export interface ExtHostExtensionServiceShape {
|
||||
$resolveAuthority(remoteAuthority: string, resolveAttempt: number): Promise<IResolveAuthorityResult>;
|
||||
$startExtensionHost(enabledExtensionIds: ExtensionIdentifier[]): Promise<void>;
|
||||
$extensionTestsExecute(): Promise<number>;
|
||||
$extensionTestsExit(code: number): Promise<void>;
|
||||
$activateByEvent(activationEvent: string, activationKind: ActivationKind): Promise<void>;
|
||||
$activate(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise<boolean>;
|
||||
$setRemoteEnvironment(env: { [key: string]: string | null; }): Promise<void>;
|
||||
@@ -1383,9 +1448,6 @@ export interface IWorkspaceCellEditDto {
|
||||
|
||||
export interface IWorkspaceEditDto {
|
||||
edits: Array<IWorkspaceFileEditDto | IWorkspaceTextEditDto | IWorkspaceCellEditDto>;
|
||||
|
||||
// todo@jrieken reject should go into rename
|
||||
rejectReason?: string;
|
||||
}
|
||||
|
||||
export function reviveWorkspaceEditDto(data: IWorkspaceEditDto | undefined): modes.WorkspaceEdit {
|
||||
@@ -1478,6 +1540,7 @@ export interface ILinkedEditingRangesDto {
|
||||
}
|
||||
|
||||
export interface IInlineValueContextDto {
|
||||
frameId: number;
|
||||
stoppedLocation: IRange;
|
||||
}
|
||||
|
||||
@@ -1505,7 +1568,7 @@ export interface ExtHostLanguageFeaturesShape {
|
||||
$provideWorkspaceSymbols(handle: number, search: string, token: CancellationToken): Promise<IWorkspaceSymbolsDto>;
|
||||
$resolveWorkspaceSymbol(handle: number, symbol: IWorkspaceSymbolDto, token: CancellationToken): Promise<IWorkspaceSymbolDto | undefined>;
|
||||
$releaseWorkspaceSymbols(handle: number, id: number): void;
|
||||
$provideRenameEdits(handle: number, resource: UriComponents, position: IPosition, newName: string, token: CancellationToken): Promise<IWorkspaceEditDto | undefined>;
|
||||
$provideRenameEdits(handle: number, resource: UriComponents, position: IPosition, newName: string, token: CancellationToken): Promise<IWorkspaceEditDto & { rejectReason?: string } | undefined>;
|
||||
$resolveRenameLocation(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<modes.RenameLocation | undefined>;
|
||||
$provideDocumentSemanticTokens(handle: number, resource: UriComponents, previousResultId: number, token: CancellationToken): Promise<VSBuffer | null>;
|
||||
$releaseDocumentSemanticTokens(handle: number, semanticColoringResultId: number): void;
|
||||
@@ -1541,6 +1604,11 @@ export interface ExtHostQuickOpenShape {
|
||||
$onDidHide(sessionId: number): void;
|
||||
}
|
||||
|
||||
export interface ExtHostTelemetryShape {
|
||||
$initializeTelemetryEnabled(enabled: boolean): void;
|
||||
$onDidChangeTelemetryEnabled(enabled: boolean): void;
|
||||
}
|
||||
|
||||
export interface IShellLaunchConfigDto {
|
||||
name?: string;
|
||||
executable?: string;
|
||||
@@ -1548,12 +1616,6 @@ export interface IShellLaunchConfigDto {
|
||||
cwd?: string | UriComponents;
|
||||
env?: { [key: string]: string | null; };
|
||||
hideFromUser?: boolean;
|
||||
flowControl?: boolean;
|
||||
}
|
||||
|
||||
export interface IShellDefinitionDto {
|
||||
label: string;
|
||||
path: string;
|
||||
}
|
||||
|
||||
export interface IShellAndArgsDto {
|
||||
@@ -1586,7 +1648,6 @@ export interface ExtHostTerminalServiceShape {
|
||||
$acceptTerminalTitleChange(id: number, name: string): void;
|
||||
$acceptTerminalDimensions(id: number, cols: number, rows: number): void;
|
||||
$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;
|
||||
@@ -1596,7 +1657,8 @@ export interface ExtHostTerminalServiceShape {
|
||||
$acceptProcessRequestCwd(id: number): void;
|
||||
$acceptProcessRequestLatency(id: number): number;
|
||||
$acceptWorkspacePermissionsChanged(isAllowed: boolean): void;
|
||||
$getAvailableShells(): Promise<IShellDefinitionDto[]>;
|
||||
// TODO: Change quickLaunchOnly to "includeAutoDetected" or something similar
|
||||
$getAvailableProfiles(quickLaunchOnly: boolean): Promise<ITerminalProfile[]>;
|
||||
$getDefaultShellAndArgs(useAutomationShell: boolean): Promise<IShellAndArgsDto>;
|
||||
$provideLinks(id: number, line: string): Promise<ITerminalLinkDto[]>;
|
||||
$activateLink(id: number, linkId: number): void;
|
||||
@@ -1644,6 +1706,7 @@ export interface IDataBreakpointDto extends IBreakpointDto {
|
||||
canPersist: boolean;
|
||||
label: string;
|
||||
accessTypes?: DebugProtocol.DataBreakpointAccessType[];
|
||||
accessType: DebugProtocol.DataBreakpointAccessType;
|
||||
}
|
||||
|
||||
export interface ISourceBreakpointDto extends IBreakpointDto {
|
||||
@@ -1751,12 +1814,12 @@ export interface INotebookVisibleRangesEvent {
|
||||
}
|
||||
|
||||
export interface INotebookEditorPropertiesChangeData {
|
||||
visibleRanges: INotebookVisibleRangesEvent | null;
|
||||
selections: INotebookSelectionChangeEvent | null;
|
||||
visibleRanges?: INotebookVisibleRangesEvent;
|
||||
selections?: INotebookSelectionChangeEvent;
|
||||
}
|
||||
|
||||
export interface INotebookDocumentPropertiesChangeData {
|
||||
metadata: NotebookDocumentMetadata | null;
|
||||
metadata?: NotebookDocumentMetadata;
|
||||
}
|
||||
|
||||
export interface INotebookModelAddedData {
|
||||
@@ -1765,7 +1828,6 @@ export interface INotebookModelAddedData {
|
||||
cells: IMainCellDto[],
|
||||
viewType: string;
|
||||
metadata?: NotebookDocumentMetadata;
|
||||
contentOptions: { transientOutputs: boolean; transientMetadata: TransientMetadata; }
|
||||
}
|
||||
|
||||
export interface INotebookEditorAddData {
|
||||
@@ -1773,6 +1835,7 @@ export interface INotebookEditorAddData {
|
||||
documentUri: UriComponents;
|
||||
selections: ICellRange[];
|
||||
visibleRanges: ICellRange[];
|
||||
viewColumn?: number
|
||||
}
|
||||
|
||||
export interface INotebookDocumentsAndEditorsDelta {
|
||||
@@ -1796,6 +1859,7 @@ export interface INotebookKernelInfoDto2 {
|
||||
isPreferred?: boolean;
|
||||
preloads?: UriComponents[];
|
||||
supportedLanguages?: string[]
|
||||
implementsInterrupt?: boolean;
|
||||
}
|
||||
|
||||
export interface ExtHostNotebookShape {
|
||||
@@ -1803,13 +1867,18 @@ export interface ExtHostNotebookShape {
|
||||
$acceptNotebookActiveKernelChange(event: { uri: UriComponents, providerHandle: number | undefined, kernelFriendlyId: string | undefined }): void;
|
||||
$provideNotebookKernels(handle: number, uri: UriComponents, token: CancellationToken): Promise<INotebookKernelInfoDto2[]>;
|
||||
$resolveNotebookKernel(handle: number, editorId: string, uri: UriComponents, kernelId: string, token: CancellationToken): Promise<void>;
|
||||
$executeNotebookKernelFromProvider(handle: number, uri: UriComponents, kernelId: string, cellHandle: number | undefined): Promise<void>;
|
||||
$cancelNotebookKernelFromProvider(handle: number, uri: UriComponents, kernelId: string, cellHandle: number | undefined): Promise<void>;
|
||||
$executeNotebookKernelFromProvider(handle: number, uri: UriComponents, kernelId: string, cellRanges: ICellRange[]): Promise<void>;
|
||||
$cancelNotebookCellExecution(handle: number, uri: UriComponents, kernelId: string, cellRange: ICellRange[]): Promise<void>;
|
||||
$onDidReceiveMessage(editorId: string, rendererId: string | undefined, message: unknown): void;
|
||||
$openNotebook(viewType: string, uri: UriComponents, backupId?: string): Promise<NotebookDataDto>;
|
||||
|
||||
$openNotebook(viewType: string, uri: UriComponents, backupId: string | undefined, untitledDocumentData: VSBuffer | undefined, token: CancellationToken): Promise<NotebookDataDto>;
|
||||
$saveNotebook(viewType: string, uri: UriComponents, token: CancellationToken): Promise<boolean>;
|
||||
$saveNotebookAs(viewType: string, uri: UriComponents, target: UriComponents, token: CancellationToken): Promise<boolean>;
|
||||
$backupNotebook(viewType: string, uri: UriComponents, cancellation: CancellationToken): Promise<string>;
|
||||
|
||||
$dataToNotebook(handle: number, data: VSBuffer): Promise<NotebookDataDto>;
|
||||
$notebookToData(handle: number, data: NotebookDataDto): Promise<VSBuffer>;
|
||||
|
||||
$acceptModelChanged(uriComponents: UriComponents, event: NotebookCellsChangedEventDto, isDirty: boolean): void;
|
||||
$acceptDirtyStateChanged(uriComponents: UriComponents, isDirty: boolean): void;
|
||||
$acceptModelSaved(uriComponents: UriComponents): void;
|
||||
@@ -1835,6 +1904,7 @@ export interface ExtHostTunnelServiceShape {
|
||||
$onDidTunnelsChange(): Promise<void>;
|
||||
$registerCandidateFinder(enable: boolean): Promise<void>;
|
||||
$applyCandidateFilter(candidates: CandidatePort[]): Promise<CandidatePort[]>;
|
||||
$providePortAttributes(handles: number[], ports: number[], pid: number | undefined, commandline: string | undefined, cancellationToken: CancellationToken): Promise<ProvidedPortAttributes[]>;
|
||||
}
|
||||
|
||||
export interface ExtHostTimelineShape {
|
||||
@@ -1850,9 +1920,10 @@ export interface ExtHostTestingShape {
|
||||
$runTestsForProvider(req: RunTestForProviderRequest, token: CancellationToken): Promise<void>;
|
||||
$subscribeToTests(resource: ExtHostTestingResource, uri: UriComponents): void;
|
||||
$unsubscribeFromTests(resource: ExtHostTestingResource, uri: UriComponents): void;
|
||||
$lookupTest(test: TestIdWithProvider): Promise<InternalTestItem | undefined>;
|
||||
$lookupTest(test: TestIdWithSrc): Promise<InternalTestItem | undefined>;
|
||||
$acceptDiff(resource: ExtHostTestingResource, uri: UriComponents, diff: TestsDiff): void;
|
||||
$publishTestResults(results: ISerializedTestResults[]): void;
|
||||
$expandTest(src: TestIdWithSrc, levels: number): Promise<void>;
|
||||
}
|
||||
|
||||
export interface MainThreadTestingShape {
|
||||
@@ -1864,7 +1935,6 @@ export interface MainThreadTestingShape {
|
||||
$updateTestStateInRun(runId: string, testId: string, state: ITestState): void;
|
||||
$runTests(req: RunTestsRequest, token: CancellationToken): Promise<string>;
|
||||
$publishExtensionProvidedResults(results: ISerializedTestResults, persist: boolean): void;
|
||||
$retireTest(extId: string): void;
|
||||
}
|
||||
|
||||
// --- proxy identifiers
|
||||
@@ -1968,4 +2038,5 @@ export const ExtHostContext = {
|
||||
ExtHostAuthentication: createMainId<ExtHostAuthenticationShape>('ExtHostAuthentication'),
|
||||
ExtHostTimeline: createMainId<ExtHostTimelineShape>('ExtHostTimeline'),
|
||||
ExtHostTesting: createMainId<ExtHostTestingShape>('ExtHostTesting'),
|
||||
ExtHostTelemetry: createMainId<ExtHostTelemetryShape>('ExtHostTelemetry'),
|
||||
};
|
||||
|
||||
@@ -162,7 +162,7 @@ const newCommands: ApiCommand[] = [
|
||||
new ApiCommand(
|
||||
'vscode.executeDocumentRenameProvider', '_executeDocumentRenameProvider', 'Execute rename provider.',
|
||||
[ApiCommandArgument.Uri, ApiCommandArgument.Position, ApiCommandArgument.String.with('newName', 'The new symbol name')],
|
||||
new ApiCommandResult<IWorkspaceEditDto, types.WorkspaceEdit | undefined>('A promise that resolves to a WorkspaceEdit.', value => {
|
||||
new ApiCommandResult<IWorkspaceEditDto & { rejectReason?: string }, types.WorkspaceEdit | undefined>('A promise that resolves to a WorkspaceEdit.', value => {
|
||||
if (!value) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
import { MainContext, MainThreadBulkEditsShape } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors';
|
||||
import { ExtHostNotebookController } from 'vs/workbench/api/common/extHostNotebook';
|
||||
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
|
||||
import { WorkspaceEdit } from 'vs/workbench/api/common/extHostTypeConverters';
|
||||
import type * as vscode from 'vscode';
|
||||
@@ -17,13 +16,12 @@ export class ExtHostBulkEdits {
|
||||
constructor(
|
||||
@IExtHostRpcService extHostRpc: IExtHostRpcService,
|
||||
private readonly _extHostDocumentsAndEditors: ExtHostDocumentsAndEditors,
|
||||
private readonly _extHostNotebooks: ExtHostNotebookController,
|
||||
) {
|
||||
this._proxy = extHostRpc.getProxy(MainContext.MainThreadBulkEdits);
|
||||
}
|
||||
|
||||
applyWorkspaceEdit(edit: vscode.WorkspaceEdit): Promise<boolean> {
|
||||
const dto = WorkspaceEdit.from(edit, this._extHostDocumentsAndEditors, this._extHostNotebooks);
|
||||
const dto = WorkspaceEdit.from(edit, this._extHostDocumentsAndEditors);
|
||||
return this._proxy.$tryApplyWorkspaceEdit(dto);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { hash } from 'vs/base/common/hash';
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { joinPath } from 'vs/base/common/resources';
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
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';
|
||||
@@ -209,7 +209,7 @@ export class ExtHostCustomEditors implements extHostProtocol.ExtHostCustomEditor
|
||||
}));
|
||||
}
|
||||
|
||||
async $createCustomDocument(resource: UriComponents, viewType: string, backupId: string | undefined, cancellation: CancellationToken) {
|
||||
async $createCustomDocument(resource: UriComponents, viewType: string, backupId: string | undefined, untitledDocumentData: VSBuffer | undefined, cancellation: CancellationToken) {
|
||||
const entry = this._editorProviders.get(viewType);
|
||||
if (!entry) {
|
||||
throw new Error(`No provider found for '${viewType}'`);
|
||||
@@ -220,7 +220,7 @@ export class ExtHostCustomEditors implements extHostProtocol.ExtHostCustomEditor
|
||||
}
|
||||
|
||||
const revivedResource = URI.revive(resource);
|
||||
const document = await entry.provider.openCustomDocument(revivedResource, { backupId }, cancellation);
|
||||
const document = await entry.provider.openCustomDocument(revivedResource, { backupId, untitledDocumentData: untitledDocumentData?.buffer }, cancellation);
|
||||
|
||||
let storageRoot: URI | undefined;
|
||||
if (this.supportEditing(entry.provider) && this._extensionStoragePaths) {
|
||||
@@ -251,9 +251,12 @@ export class ExtHostCustomEditors implements extHostProtocol.ExtHostCustomEditor
|
||||
resource: UriComponents,
|
||||
handle: extHostProtocol.WebviewHandle,
|
||||
viewType: string,
|
||||
title: string,
|
||||
initData: {
|
||||
title: string;
|
||||
webviewOptions: extHostProtocol.IWebviewOptions;
|
||||
panelOptions: extHostProtocol.IWebviewPanelOptions;
|
||||
},
|
||||
position: EditorGroupColumn,
|
||||
options: modes.IWebviewOptions & modes.IWebviewPanelOptions,
|
||||
cancellation: CancellationToken,
|
||||
): Promise<void> {
|
||||
const entry = this._editorProviders.get(viewType);
|
||||
@@ -263,8 +266,8 @@ export class ExtHostCustomEditors implements extHostProtocol.ExtHostCustomEditor
|
||||
|
||||
const viewColumn = typeConverters.ViewColumn.to(position);
|
||||
|
||||
const webview = this._extHostWebview.createNewWebview(handle, options, entry.extension);
|
||||
const panel = this._extHostWebviewPanels.createNewWebviewPanel(handle, viewType, title, viewColumn, options, webview);
|
||||
const webview = this._extHostWebview.createNewWebview(handle, initData.webviewOptions, entry.extension);
|
||||
const panel = this._extHostWebviewPanels.createNewWebviewPanel(handle, viewType, initData.title, viewColumn, initData.panelOptions, webview);
|
||||
|
||||
const revivedResource = URI.revive(resource);
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ import type * as vscode from 'vscode';
|
||||
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { withNullAsUndefined } from 'vs/base/common/types';
|
||||
import { IProcessEnvironment } from 'vs/base/common/platform';
|
||||
import * as process from 'vs/base/common/process';
|
||||
|
||||
export const IExtHostDebugService = createDecorator<IExtHostDebugService>('IExtHostDebugService');
|
||||
|
||||
@@ -930,7 +930,7 @@ export class ExtHostDebugConsole {
|
||||
|
||||
export class ExtHostVariableResolverService extends AbstractVariableResolverService {
|
||||
|
||||
constructor(folders: vscode.WorkspaceFolder[], editorService: ExtHostDocumentsAndEditors | undefined, configurationService: ExtHostConfigProvider, env?: IProcessEnvironment, workspaceService?: IExtHostWorkspace) {
|
||||
constructor(folders: vscode.WorkspaceFolder[], editorService: ExtHostDocumentsAndEditors | undefined, configurationService: ExtHostConfigProvider, workspaceService?: IExtHostWorkspace) {
|
||||
super({
|
||||
getFolderUri: (folderName: string): URI | undefined => {
|
||||
const found = folders.filter(f => f.name === folderName);
|
||||
@@ -946,10 +946,10 @@ export class ExtHostVariableResolverService extends AbstractVariableResolverServ
|
||||
return configurationService.getConfiguration(undefined, folderUri).get<string>(section);
|
||||
},
|
||||
getAppRoot: (): string | undefined => {
|
||||
return env ? env['VSCODE_CWD'] : undefined;
|
||||
return process.cwd();
|
||||
},
|
||||
getExecPath: (): string | undefined => {
|
||||
return env ? env['VSCODE_EXEC_PATH'] : undefined;
|
||||
return process.env['VSCODE_EXEC_PATH'];
|
||||
},
|
||||
getFilePath: (): string | undefined => {
|
||||
if (editorService) {
|
||||
@@ -990,7 +990,7 @@ export class ExtHostVariableResolverService extends AbstractVariableResolverServ
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}, undefined, env);
|
||||
}, undefined, process.env);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ import type * as vscode from 'vscode';
|
||||
import { MainContext, MainThreadDiagnosticsShape, ExtHostDiagnosticsShape, IMainContext } from './extHost.protocol';
|
||||
import { DiagnosticSeverity } from './extHostTypes';
|
||||
import * as converter from './extHostTypeConverters';
|
||||
import { mergeSort } from 'vs/base/common/arrays';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { ResourceMap } from 'vs/base/common/map';
|
||||
@@ -78,7 +77,7 @@ export class DiagnosticCollection implements vscode.DiagnosticCollection {
|
||||
let lastUri: vscode.Uri | undefined;
|
||||
|
||||
// ensure stable-sort
|
||||
first = mergeSort([...first], DiagnosticCollection._compareIndexedTuplesByUri);
|
||||
first = [...first].sort(DiagnosticCollection._compareIndexedTuplesByUri);
|
||||
|
||||
for (const tuple of first) {
|
||||
const [uri, diagnostics] = tuple;
|
||||
|
||||
@@ -102,7 +102,7 @@ export class ExtHostDocuments implements ExtHostDocumentsShape {
|
||||
return this._proxy.$tryCreateDocument(options).then(data => URI.revive(data));
|
||||
}
|
||||
|
||||
public $acceptModelModeChanged(uriComponents: UriComponents, oldModeId: string, newModeId: string): void {
|
||||
public $acceptModelModeChanged(uriComponents: UriComponents, newModeId: string): void {
|
||||
const uri = URI.revive(uriComponents);
|
||||
const data = this._documentsAndEditors.getDocument(uri);
|
||||
if (!data) {
|
||||
|
||||
@@ -25,7 +25,7 @@ import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensio
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { ExtensionGlobalMemento, ExtensionMemento } from 'vs/workbench/api/common/extHostMemento';
|
||||
import { RemoteAuthorityResolverError, ExtensionMode, ExtensionRuntime } from 'vs/workbench/api/common/extHostTypes';
|
||||
import { RemoteAuthorityResolverError, ExtensionKind, ExtensionMode, ExtensionRuntime } from 'vs/workbench/api/common/extHostTypes';
|
||||
import { ResolvedAuthority, ResolvedOptions, RemoteAuthorityResolverErrorCode, IRemoteConnectionData } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
||||
import { IInstantiationService, createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService';
|
||||
@@ -95,6 +95,8 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
|
||||
private readonly _almostReadyToRunExtensions: Barrier;
|
||||
private readonly _readyToStartExtensionHost: Barrier;
|
||||
private readonly _readyToRunExtensions: Barrier;
|
||||
private readonly _eagerExtensionsActivated: Barrier;
|
||||
|
||||
protected readonly _registry: ExtensionDescriptionRegistry;
|
||||
private readonly _storage: ExtHostStorage;
|
||||
private readonly _secretState: ExtHostSecretState;
|
||||
@@ -140,6 +142,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
|
||||
this._almostReadyToRunExtensions = new Barrier();
|
||||
this._readyToStartExtensionHost = new Barrier();
|
||||
this._readyToRunExtensions = new Barrier();
|
||||
this._eagerExtensionsActivated = new Barrier();
|
||||
this._registry = new ExtensionDescriptionRegistry(this._initData.extensions);
|
||||
this._storage = new ExtHostStorage(this._extHostContext);
|
||||
this._secretState = new ExtHostSecretState(this._extHostContext);
|
||||
@@ -388,7 +391,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
|
||||
const extensionMode = extensionDescription.isUnderDevelopment
|
||||
? (this._initData.environment.extensionTestsLocationURI ? ExtensionMode.Test : ExtensionMode.Development)
|
||||
: ExtensionMode.Production;
|
||||
const installAge = Date.now() - new Date(this._initData.telemetryInfo.firstSessionDate).getTime();
|
||||
const extensionKind = this._initData.remote.isRemote ? ExtensionKind.Workspace : ExtensionKind.UI;
|
||||
|
||||
this._logService.trace(`ExtensionService#loadExtensionContext ${extensionDescription.identifier.value}`);
|
||||
|
||||
@@ -398,6 +401,8 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
|
||||
this._storagePath.whenReady
|
||||
]).then(() => {
|
||||
const that = this;
|
||||
let extension: vscode.Extension<any> | undefined;
|
||||
|
||||
return Object.freeze<vscode.ExtensionContext>({
|
||||
globalState,
|
||||
workspaceState,
|
||||
@@ -413,22 +418,16 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
|
||||
get storageUri() { return that._storagePath.workspaceValue(extensionDescription); },
|
||||
get globalStorageUri() { return that._storagePath.globalValue(extensionDescription); },
|
||||
get extensionMode() { return extensionMode; },
|
||||
get extensionId() {
|
||||
checkProposedApiEnabled(extensionDescription);
|
||||
return extensionDescription.identifier.value;
|
||||
},
|
||||
get extensionVersion() {
|
||||
checkProposedApiEnabled(extensionDescription);
|
||||
return extensionDescription.version;
|
||||
get extension() {
|
||||
if (extension === undefined) {
|
||||
extension = new Extension(that, extensionDescription.identifier, extensionDescription, extensionKind);
|
||||
}
|
||||
return extension;
|
||||
},
|
||||
get extensionRuntime() {
|
||||
checkProposedApiEnabled(extensionDescription);
|
||||
return that.extensionRuntime;
|
||||
},
|
||||
get isNewInstall() {
|
||||
checkProposedApiEnabled(extensionDescription);
|
||||
return isNaN(installAge) ? false : installAge < 1000 * 60 * 60 * 24; // installAge is less than a day;
|
||||
},
|
||||
get environmentVariableCollection() { return that._extHostTerminalService.getEnvironmentVariableCollection(extensionDescription); }
|
||||
});
|
||||
});
|
||||
@@ -550,84 +549,61 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
|
||||
);
|
||||
}
|
||||
|
||||
private _handleExtensionTests(): Promise<void> {
|
||||
return this._doHandleExtensionTests().then(undefined, error => {
|
||||
public async $extensionTestsExecute(): Promise<number> {
|
||||
await this._eagerExtensionsActivated.wait();
|
||||
try {
|
||||
return this._doHandleExtensionTests();
|
||||
} catch (error) {
|
||||
console.error(error); // ensure any error message makes it onto the console
|
||||
|
||||
return Promise.reject(error);
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private async _doHandleExtensionTests(): Promise<void> {
|
||||
private async _doHandleExtensionTests(): Promise<number> {
|
||||
const { extensionDevelopmentLocationURI, extensionTestsLocationURI } = this._initData.environment;
|
||||
if (!(extensionDevelopmentLocationURI && extensionTestsLocationURI && extensionTestsLocationURI.scheme === Schemas.file)) {
|
||||
return Promise.resolve(undefined);
|
||||
if (!extensionDevelopmentLocationURI || !extensionTestsLocationURI || extensionTestsLocationURI.scheme !== Schemas.file) {
|
||||
throw new Error(nls.localize('extensionTestError1', "Cannot load test runner."));
|
||||
}
|
||||
|
||||
const extensionTestsPath = originalFSPath(extensionTestsLocationURI);
|
||||
|
||||
// Require the test runner via node require from the provided path
|
||||
let testRunner: ITestRunner | INewTestRunner | undefined;
|
||||
let requireError: Error | undefined;
|
||||
try {
|
||||
testRunner = await this._loadCommonJSModule(null, URI.file(extensionTestsPath), new ExtensionActivationTimesBuilder(false));
|
||||
} catch (error) {
|
||||
requireError = error;
|
||||
const testRunner: ITestRunner | INewTestRunner | undefined = await this._loadCommonJSModule(null, extensionTestsLocationURI, new ExtensionActivationTimesBuilder(false));
|
||||
|
||||
if (!testRunner || typeof testRunner.run !== 'function') {
|
||||
throw new Error(nls.localize('extensionTestError', "Path {0} does not point to a valid extension test runner.", extensionTestsPath));
|
||||
}
|
||||
|
||||
// Execute the runner if it follows the old `run` spec
|
||||
if (testRunner && typeof testRunner.run === 'function') {
|
||||
return new Promise<void>((c, e) => {
|
||||
const oldTestRunnerCallback = (error: Error, failures: number | undefined) => {
|
||||
if (error) {
|
||||
e(error.toString());
|
||||
} else {
|
||||
c(undefined);
|
||||
}
|
||||
|
||||
// after tests have run, we shutdown the host
|
||||
this._testRunnerExit(error || (typeof failures === 'number' && failures > 0) ? 1 /* ERROR */ : 0 /* OK */);
|
||||
};
|
||||
|
||||
const runResult = testRunner!.run(extensionTestsPath, oldTestRunnerCallback);
|
||||
|
||||
// Using the new API `run(): Promise<void>`
|
||||
if (runResult && runResult.then) {
|
||||
runResult
|
||||
.then(() => {
|
||||
c();
|
||||
this._testRunnerExit(0);
|
||||
})
|
||||
.catch((err: Error) => {
|
||||
e(err.toString());
|
||||
this._testRunnerExit(1);
|
||||
});
|
||||
return new Promise<number>((resolve, reject) => {
|
||||
const oldTestRunnerCallback = (error: Error, failures: number | undefined) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve((typeof failures === 'number' && failures > 0) ? 1 /* ERROR */ : 0 /* OK */);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Otherwise make sure to shutdown anyway even in case of an error
|
||||
else {
|
||||
this._testRunnerExit(1 /* ERROR */);
|
||||
}
|
||||
const runResult = testRunner.run(extensionTestsPath, oldTestRunnerCallback);
|
||||
|
||||
return Promise.reject(new Error(requireError ? requireError.toString() : nls.localize('extensionTestError', "Path {0} does not point to a valid extension test runner.", extensionTestsPath)));
|
||||
// Using the new API `run(): Promise<void>`
|
||||
if (runResult && runResult.then) {
|
||||
runResult
|
||||
.then(() => {
|
||||
resolve(0);
|
||||
})
|
||||
.catch((err: Error) => {
|
||||
reject(err.toString());
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private _testRunnerExit(code: number): void {
|
||||
public async $extensionTestsExit(code: number): Promise<void> {
|
||||
this._logService.info(`extension host terminating: test runner requested exit with code ${code}`);
|
||||
this._logService.info(`exiting 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);
|
||||
});
|
||||
this._hostUtils.exit(code);
|
||||
}
|
||||
|
||||
private _startExtensionHost(): Promise<void> {
|
||||
@@ -639,8 +615,8 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
|
||||
return this._readyToStartExtensionHost.wait()
|
||||
.then(() => this._readyToRunExtensions.open())
|
||||
.then(() => this._handleEagerExtensions())
|
||||
.then(() => this._handleExtensionTests())
|
||||
.then(() => {
|
||||
this._eagerExtensionsActivated.open();
|
||||
this._logService.info(`eager extensions activated`);
|
||||
});
|
||||
}
|
||||
@@ -840,3 +816,42 @@ export interface IExtHostExtensionService extends AbstractExtHostExtensionServic
|
||||
onDidChangeRemoteConnectionData: Event<void>;
|
||||
getRemoteConnectionData(): IRemoteConnectionData | null;
|
||||
}
|
||||
|
||||
export class Extension<T> implements vscode.Extension<T> {
|
||||
|
||||
#extensionService: IExtHostExtensionService;
|
||||
#originExtensionId: ExtensionIdentifier;
|
||||
#identifier: ExtensionIdentifier;
|
||||
|
||||
readonly id: string;
|
||||
readonly extensionUri: URI;
|
||||
readonly extensionPath: string;
|
||||
readonly packageJSON: IExtensionDescription;
|
||||
readonly extensionKind: vscode.ExtensionKind;
|
||||
|
||||
constructor(extensionService: IExtHostExtensionService, originExtensionId: ExtensionIdentifier, description: IExtensionDescription, kind: ExtensionKind) {
|
||||
this.#extensionService = extensionService;
|
||||
this.#originExtensionId = originExtensionId;
|
||||
this.#identifier = description.identifier;
|
||||
this.id = description.identifier.value;
|
||||
this.extensionUri = description.extensionLocation;
|
||||
this.extensionPath = path.normalize(originalFSPath(description.extensionLocation));
|
||||
this.packageJSON = description;
|
||||
this.extensionKind = kind;
|
||||
}
|
||||
|
||||
get isActive(): boolean {
|
||||
return this.#extensionService.isActivated(this.#identifier);
|
||||
}
|
||||
|
||||
get exports(): T {
|
||||
if (this.packageJSON.api === 'none') {
|
||||
return undefined!; // Strict nulloverride - Public api
|
||||
}
|
||||
return <T>this.#extensionService.getExtensionExports(this.#identifier);
|
||||
}
|
||||
|
||||
activate(): Thenable<T> {
|
||||
return this.#extensionService.activateByIdWithErrors(this.#identifier, { startup: false, extensionId: this.#originExtensionId, activationEvent: 'api' }).then(() => this.exports);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -412,7 +412,8 @@ class CodeActionAdapter {
|
||||
|
||||
const codeActionContext: vscode.CodeActionContext = {
|
||||
diagnostics: allDiagnostics,
|
||||
only: context.only ? new CodeActionKind(context.only) : undefined
|
||||
only: context.only ? new CodeActionKind(context.only) : undefined,
|
||||
triggerKind: typeConvert.CodeActionTriggerKind.to(context.trigger),
|
||||
};
|
||||
|
||||
return asPromise(() => this._provider.provideCodeActions(doc, ran, codeActionContext, token)).then((commandsOrActions): extHostProtocol.ICodeActionListDto | undefined => {
|
||||
|
||||
@@ -5,11 +5,11 @@
|
||||
|
||||
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import * as UUID from 'vs/base/common/uuid';
|
||||
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { ExtHostNotebookShape, ICommandDto, IMainContext, IModelAddedData, INotebookDocumentPropertiesChangeData, INotebookDocumentsAndEditorsDelta, INotebookDocumentShowOptions, INotebookEditorPropertiesChangeData, INotebookKernelInfoDto2, MainContext, MainThreadNotebookShape } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { ExtHostNotebookShape, ICommandDto, IMainContext, IModelAddedData, INotebookDocumentPropertiesChangeData, INotebookDocumentsAndEditorsDelta, INotebookDocumentShowOptions, INotebookEditorAddData, INotebookEditorPropertiesChangeData, INotebookKernelInfoDto2, MainContext, MainThreadNotebookShape } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { CommandsConverter, ExtHostCommands } from 'vs/workbench/api/common/extHostCommands';
|
||||
import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors';
|
||||
@@ -17,7 +17,7 @@ import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePa
|
||||
import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters';
|
||||
import * as extHostTypes from 'vs/workbench/api/common/extHostTypes';
|
||||
import { asWebviewUri, WebviewInitData } from 'vs/workbench/api/common/shared/webview';
|
||||
import { CellStatusbarAlignment, CellUri, ICellRange, INotebookCellStatusBarEntry, INotebookExclusiveDocumentFilter, NotebookCellMetadata, NotebookCellsChangedEventDto, NotebookCellsChangeType, NotebookDataDto, notebookDocumentMetadataDefaults } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
import { CellEditType, CellStatusbarAlignment, CellUri, ICellRange, INotebookCellStatusBarEntry, INotebookExclusiveDocumentFilter, NotebookCellMetadata, NotebookCellExecutionState, NotebookCellsChangedEventDto, NotebookCellsChangeType, NotebookDataDto, NullablePartialNotebookCellMetadata, IImmediateCellEditOperation } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
import * as vscode from 'vscode';
|
||||
import { ResourceMap } from 'vs/base/common/map';
|
||||
import { ExtHostCell, ExtHostNotebookDocument } from './extHostNotebookDocument';
|
||||
@@ -25,7 +25,9 @@ import { ExtHostNotebookEditor } from './extHostNotebookEditor';
|
||||
import { IdGenerator } from 'vs/base/common/idGenerator';
|
||||
import { IRelativePattern } from 'vs/base/common/glob';
|
||||
import { assertIsDefined } from 'vs/base/common/types';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { hash } from 'vs/base/common/hash';
|
||||
import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments';
|
||||
|
||||
class ExtHostWebviewCommWrapper extends Disposable {
|
||||
private readonly _onDidReceiveDocumentMessage = new Emitter<any>();
|
||||
@@ -123,7 +125,8 @@ export class ExtHostNotebookKernelProviderAdapter extends Disposable {
|
||||
detail: kernel.detail,
|
||||
isPreferred: kernel.isPreferred,
|
||||
preloads: kernel.preloads,
|
||||
supportedLanguages: kernel.supportedLanguages
|
||||
supportedLanguages: kernel.supportedLanguages,
|
||||
implementsInterrupt: !!kernel.interrupt
|
||||
};
|
||||
});
|
||||
|
||||
@@ -149,42 +152,25 @@ export class ExtHostNotebookKernelProviderAdapter extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
async executeNotebook(kernelId: string, document: ExtHostNotebookDocument, cell: ExtHostCell | undefined) {
|
||||
async executeNotebook(kernelId: string, document: ExtHostNotebookDocument, cellRange: ICellRange[]): Promise<void> {
|
||||
const kernel = this._friendlyIdToKernel.get(document.uri)?.get(kernelId);
|
||||
|
||||
if (!kernel) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (cell) {
|
||||
return withToken(token => (kernel.executeCell as any)(document.notebookDocument, cell.cell, token));
|
||||
} else {
|
||||
return withToken(token => (kernel.executeAllCells as any)(document.notebookDocument, token));
|
||||
}
|
||||
const extCellRange = cellRange.map(c => typeConverters.NotebookCellRange.to(c));
|
||||
return kernel.executeCellsRequest(document.notebookDocument, extCellRange);
|
||||
}
|
||||
|
||||
async cancelNotebook(kernelId: string, document: ExtHostNotebookDocument, cell: ExtHostCell | undefined) {
|
||||
async interruptNotebookExecution(kernelId: string, document: ExtHostNotebookDocument): Promise<void> {
|
||||
const kernel = this._friendlyIdToKernel.get(document.uri)?.get(kernelId);
|
||||
|
||||
if (!kernel) {
|
||||
if (!kernel || !kernel.interrupt) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (cell) {
|
||||
return kernel.cancelCellExecution(document.notebookDocument, cell.cell);
|
||||
} else {
|
||||
return kernel.cancelAllCellsExecution(document.notebookDocument);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO@roblou remove 'token' passed to all execute APIs once extensions are updated
|
||||
async function withToken(cb: (token: CancellationToken) => any) {
|
||||
const source = new CancellationTokenSource();
|
||||
try {
|
||||
await cb(source.token);
|
||||
} finally {
|
||||
source.dispose();
|
||||
return kernel.interrupt(document.notebookDocument);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,6 +193,7 @@ export class NotebookEditorDecorationType {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
type NotebookContentProviderData = {
|
||||
readonly provider: vscode.NotebookContentProvider;
|
||||
readonly extension: IExtensionDescription;
|
||||
@@ -232,12 +219,12 @@ export class ExtHostNotebookController implements ExtHostNotebookShape {
|
||||
readonly onDidChangeNotebookCells = this._onDidChangeNotebookCells.event;
|
||||
private readonly _onDidChangeCellOutputs = new Emitter<vscode.NotebookCellOutputsChangeEvent>();
|
||||
readonly onDidChangeCellOutputs = this._onDidChangeCellOutputs.event;
|
||||
private readonly _onDidChangeCellLanguage = new Emitter<vscode.NotebookCellLanguageChangeEvent>();
|
||||
readonly onDidChangeCellLanguage = this._onDidChangeCellLanguage.event;
|
||||
private readonly _onDidChangeCellMetadata = new Emitter<vscode.NotebookCellMetadataChangeEvent>();
|
||||
readonly onDidChangeCellMetadata = this._onDidChangeCellMetadata.event;
|
||||
private readonly _onDidChangeActiveNotebookEditor = new Emitter<vscode.NotebookEditor | undefined>();
|
||||
readonly onDidChangeActiveNotebookEditor = this._onDidChangeActiveNotebookEditor.event;
|
||||
private readonly _onDidChangeCellExecutionState = new Emitter<vscode.NotebookCellExecutionStateChangeEvent>();
|
||||
readonly onDidChangeNotebookCellExecutionState = this._onDidChangeCellExecutionState.event;
|
||||
|
||||
private _activeNotebookEditor: ExtHostNotebookEditor | undefined;
|
||||
get activeNotebookEditor(): vscode.NotebookEditor | undefined {
|
||||
@@ -259,10 +246,13 @@ export class ExtHostNotebookController implements ExtHostNotebookShape {
|
||||
private _onDidChangeVisibleNotebookEditors = new Emitter<vscode.NotebookEditor[]>();
|
||||
onDidChangeVisibleNotebookEditors = this._onDidChangeVisibleNotebookEditors.event;
|
||||
|
||||
private _activeExecutions = new ResourceMap<NotebookCellExecutionTask>();
|
||||
|
||||
constructor(
|
||||
mainContext: IMainContext,
|
||||
commands: ExtHostCommands,
|
||||
private _documentsAndEditors: ExtHostDocumentsAndEditors,
|
||||
private _textDocumentsAndEditors: ExtHostDocumentsAndEditors,
|
||||
private _textDocuments: ExtHostDocuments,
|
||||
private readonly _webviewInitData: WebviewInitData,
|
||||
private readonly logService: ILogService,
|
||||
private readonly _extensionStoragePaths: IExtensionStoragePaths,
|
||||
@@ -337,7 +327,8 @@ export class ExtHostNotebookController implements ExtHostNotebookShape {
|
||||
let listener: IDisposable | undefined;
|
||||
if (provider.onDidChangeNotebookContentOptions) {
|
||||
listener = provider.onDidChangeNotebookContentOptions(() => {
|
||||
this._proxy.$updateNotebookProviderOptions(viewType, provider.options);
|
||||
const internalOptions = typeConverters.NotebookDocumentContentOptions.from(provider.options);
|
||||
this._proxy.$updateNotebookProviderOptions(viewType, internalOptions);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -349,9 +340,10 @@ export class ExtHostNotebookController implements ExtHostNotebookShape {
|
||||
console.warn(`Notebook content provider view options file name pattern is invalid ${options?.viewOptions?.filenamePattern}`);
|
||||
}
|
||||
|
||||
const internalOptions = typeConverters.NotebookDocumentContentOptions.from(options);
|
||||
this._proxy.$registerNotebookProvider({ id: extension.identifier, location: extension.extensionLocation, description: extension.description }, viewType, {
|
||||
transientOutputs: options?.transientOutputs || false,
|
||||
transientMetadata: options?.transientMetadata || {},
|
||||
transientOutputs: internalOptions.transientOutputs,
|
||||
transientMetadata: internalOptions.transientMetadata,
|
||||
viewOptions: options?.viewOptions && viewOptionsFilenamePattern ? { displayName: options.viewOptions.displayName, filenamePattern: viewOptionsFilenamePattern, exclusive: options.viewOptions.exclusive || false } : undefined
|
||||
});
|
||||
|
||||
@@ -382,13 +374,13 @@ export class ExtHostNotebookController implements ExtHostNotebookShape {
|
||||
return new NotebookEditorDecorationType(this._proxy, options).value;
|
||||
}
|
||||
|
||||
async openNotebookDocument(uriComponents: UriComponents, viewType?: string): Promise<vscode.NotebookDocument> {
|
||||
const cached = this._documents.get(URI.revive(uriComponents));
|
||||
async openNotebookDocument(uri: URI): Promise<vscode.NotebookDocument> {
|
||||
const cached = this._documents.get(uri);
|
||||
if (cached) {
|
||||
return cached.notebookDocument;
|
||||
}
|
||||
await this._proxy.$tryOpenDocument(uriComponents, viewType);
|
||||
const document = this._documents.get(URI.revive(uriComponents));
|
||||
const canonicalUri = await this._proxy.$tryOpenDocument(uri);
|
||||
const document = this._documents.get(URI.revive(canonicalUri));
|
||||
return assertIsDefined(document?.notebookDocument);
|
||||
}
|
||||
|
||||
@@ -408,7 +400,12 @@ export class ExtHostNotebookController implements ExtHostNotebookShape {
|
||||
return callback(provider, document);
|
||||
}
|
||||
|
||||
async showNotebookDocument(notebookDocument: vscode.NotebookDocument, options?: vscode.NotebookDocumentShowOptions): Promise<vscode.NotebookEditor> {
|
||||
async showNotebookDocument(notebookOrUri: vscode.NotebookDocument | URI, options?: vscode.NotebookDocumentShowOptions): Promise<vscode.NotebookEditor> {
|
||||
|
||||
if (URI.isUri(notebookOrUri)) {
|
||||
notebookOrUri = await this.openNotebookDocument(notebookOrUri);
|
||||
}
|
||||
|
||||
let resolvedOptions: INotebookDocumentShowOptions;
|
||||
if (typeof options === 'object') {
|
||||
resolvedOptions = {
|
||||
@@ -423,7 +420,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape {
|
||||
};
|
||||
}
|
||||
|
||||
const editorId = await this._proxy.$tryShowNotebookDocument(notebookDocument.uri, notebookDocument.viewType, resolvedOptions);
|
||||
const editorId = await this._proxy.$tryShowNotebookDocument(notebookOrUri.uri, notebookOrUri.viewType, resolvedOptions);
|
||||
const editor = editorId && this._editors.get(editorId)?.editor;
|
||||
|
||||
if (editor) {
|
||||
@@ -431,9 +428,9 @@ export class ExtHostNotebookController implements ExtHostNotebookShape {
|
||||
}
|
||||
|
||||
if (editorId) {
|
||||
throw new Error(`Could NOT open editor for "${notebookDocument.toString()}" because another editor opened in the meantime.`);
|
||||
throw new Error(`Could NOT open editor for "${notebookOrUri.toString()}" because another editor opened in the meantime.`);
|
||||
} else {
|
||||
throw new Error(`Could NOT open editor for "${notebookDocument.toString()}".`);
|
||||
throw new Error(`Could NOT open editor for "${notebookOrUri.toString()}".`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -474,32 +471,88 @@ export class ExtHostNotebookController implements ExtHostNotebookShape {
|
||||
await provider.provider.resolveNotebook(document.notebookDocument, webComm.contentProviderComm);
|
||||
}
|
||||
|
||||
async $executeNotebookKernelFromProvider(handle: number, uri: UriComponents, kernelId: string, cellHandle: number | undefined): Promise<void> {
|
||||
async $executeNotebookKernelFromProvider(handle: number, uri: UriComponents, kernelId: string, cellRange: ICellRange[]): Promise<void> {
|
||||
await this._withAdapter(handle, uri, async (adapter, document) => {
|
||||
const cell = cellHandle !== undefined ? document.getCell(cellHandle) : undefined;
|
||||
|
||||
return adapter.executeNotebook(kernelId, document, cell);
|
||||
return adapter.executeNotebook(kernelId, document, cellRange);
|
||||
});
|
||||
}
|
||||
|
||||
async $cancelNotebookKernelFromProvider(handle: number, uri: UriComponents, kernelId: string, cellHandle: number | undefined): Promise<void> {
|
||||
await this._withAdapter(handle, uri, async (adapter, document) => {
|
||||
const cell = cellHandle !== undefined ? document.getCell(cellHandle) : undefined;
|
||||
// --- serialize/deserialize
|
||||
|
||||
return adapter.cancelNotebook(kernelId, document, cell);
|
||||
private _handlePool = 0;
|
||||
private readonly _notebookSerializer = new Map<number, vscode.NotebookSerializer>();
|
||||
|
||||
registerNotebookSerializer(extension: IExtensionDescription, viewType: string, serializer: vscode.NotebookSerializer, options?: vscode.NotebookDocumentContentOptions): vscode.Disposable {
|
||||
const handle = this._handlePool++;
|
||||
this._notebookSerializer.set(handle, serializer);
|
||||
const internalOptions = typeConverters.NotebookDocumentContentOptions.from(options);
|
||||
this._proxy.$registerNotebookSerializer(
|
||||
handle,
|
||||
{ id: extension.identifier, location: extension.extensionLocation, description: extension.description },
|
||||
viewType,
|
||||
internalOptions
|
||||
);
|
||||
return toDisposable(() => {
|
||||
this._proxy.$unregisterNotebookSerializer(handle);
|
||||
});
|
||||
}
|
||||
|
||||
async $dataToNotebook(handle: number, bytes: VSBuffer): Promise<NotebookDataDto> {
|
||||
const serializer = this._notebookSerializer.get(handle);
|
||||
if (!serializer) {
|
||||
throw new Error('NO serializer found');
|
||||
}
|
||||
const data = await serializer.dataToNotebook(bytes.buffer);
|
||||
return {
|
||||
metadata: typeConverters.NotebookDocumentMetadata.from(data.metadata),
|
||||
cells: data.cells.map(typeConverters.NotebookCellData.from),
|
||||
};
|
||||
}
|
||||
|
||||
async $notebookToData(handle: number, data: NotebookDataDto): Promise<VSBuffer> {
|
||||
const serializer = this._notebookSerializer.get(handle);
|
||||
if (!serializer) {
|
||||
throw new Error('NO serializer found');
|
||||
}
|
||||
const bytes = await serializer.notebookToData({
|
||||
metadata: typeConverters.NotebookDocumentMetadata.to(data.metadata),
|
||||
cells: data.cells.map(typeConverters.NotebookCellData.to)
|
||||
});
|
||||
return VSBuffer.wrap(bytes);
|
||||
}
|
||||
|
||||
async $cancelNotebookCellExecution(handle: number, uri: UriComponents, kernelId: string, cellRange: ICellRange[]): Promise<void> {
|
||||
await this._withAdapter(handle, uri, async (adapter, document) => {
|
||||
return adapter.interruptNotebookExecution(kernelId, document);
|
||||
});
|
||||
|
||||
const document = this._documents.get(URI.revive(uri));
|
||||
if (!document) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let range of cellRange) {
|
||||
for (let i = range.start; i < range.end; i++) {
|
||||
const cell = document.getCellFromIndex(i);
|
||||
if (cell) {
|
||||
this.cancelOneNotebookCellExecution(cell);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private cancelOneNotebookCellExecution(cell: ExtHostCell): void {
|
||||
const execution = this._activeExecutions.get(cell.uri);
|
||||
execution?.cancel();
|
||||
}
|
||||
|
||||
// --- open, save, saveAs, backup
|
||||
|
||||
async $openNotebook(viewType: string, uri: UriComponents, backupId?: string): Promise<NotebookDataDto> {
|
||||
async $openNotebook(viewType: string, uri: UriComponents, backupId: string | undefined, untitledDocumentData: VSBuffer | undefined, token: CancellationToken): Promise<NotebookDataDto> {
|
||||
const { provider } = this._getProviderData(viewType);
|
||||
const data = await provider.openNotebook(URI.revive(uri), { backupId });
|
||||
const data = await provider.openNotebook(URI.revive(uri), { backupId, untitledDocumentData: untitledDocumentData?.buffer }, token);
|
||||
return {
|
||||
metadata: {
|
||||
...notebookDocumentMetadataDefaults,
|
||||
...data.metadata
|
||||
},
|
||||
metadata: typeConverters.NotebookDocumentMetadata.from(data.metadata),
|
||||
cells: data.cells.map(typeConverters.NotebookCellData.from),
|
||||
};
|
||||
}
|
||||
@@ -569,32 +622,31 @@ export class ExtHostNotebookController implements ExtHostNotebookShape {
|
||||
$acceptEditorPropertiesChanged(id: string, data: INotebookEditorPropertiesChangeData): void {
|
||||
this.logService.debug('ExtHostNotebook#$acceptEditorPropertiesChanged', id, data);
|
||||
|
||||
let editor: { editor: ExtHostNotebookEditor; } | undefined;
|
||||
this._editors.forEach(e => {
|
||||
if (e.editor.id === id) {
|
||||
editor = e;
|
||||
}
|
||||
});
|
||||
|
||||
const editor = this._editors.get(id);
|
||||
if (!editor) {
|
||||
return;
|
||||
throw new Error(`unknown text editor: ${id}`);
|
||||
}
|
||||
|
||||
// ONE: make all state updates
|
||||
if (data.visibleRanges) {
|
||||
editor.editor._acceptVisibleRanges(data.visibleRanges.ranges.map(typeConverters.NotebookCellRange.to));
|
||||
}
|
||||
if (data.selections) {
|
||||
editor.editor._acceptSelections(data.selections.selections.map(typeConverters.NotebookCellRange.to));
|
||||
}
|
||||
|
||||
// TWO: send all events after states have been updated
|
||||
if (data.visibleRanges) {
|
||||
this._onDidChangeNotebookEditorVisibleRanges.fire({
|
||||
notebookEditor: editor.editor.editor,
|
||||
visibleRanges: editor.editor.editor.visibleRanges
|
||||
});
|
||||
}
|
||||
|
||||
if (data.selections) {
|
||||
editor.editor._acceptSelections(data.selections.selections);
|
||||
|
||||
this._onDidChangeNotebookEditorSelection.fire({
|
||||
this._onDidChangeNotebookEditorSelection.fire(Object.freeze({
|
||||
notebookEditor: editor.editor.editor,
|
||||
selection: editor.editor.editor.selection
|
||||
});
|
||||
selections: editor.editor.editor.selections
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -602,9 +654,12 @@ export class ExtHostNotebookController implements ExtHostNotebookShape {
|
||||
this.logService.debug('ExtHostNotebook#$acceptDocumentPropertiesChanged', uri.path, data);
|
||||
const document = this._getNotebookDocument(URI.revive(uri));
|
||||
document.acceptDocumentPropertiesChanged(data);
|
||||
if (data.metadata) {
|
||||
this._onDidChangeNotebookDocumentMetadata.fire({ document: document.notebookDocument });
|
||||
}
|
||||
}
|
||||
|
||||
private _createExtHostEditor(document: ExtHostNotebookDocument, editorId: string, selections: ICellRange[], visibleRanges: extHostTypes.NotebookCellRange[]) {
|
||||
private _createExtHostEditor(document: ExtHostNotebookDocument, editorId: string, data: INotebookEditorAddData) {
|
||||
const revivedUri = document.uri;
|
||||
let webComm = this._webviewComm.get(editorId);
|
||||
|
||||
@@ -617,11 +672,12 @@ export class ExtHostNotebookController implements ExtHostNotebookShape {
|
||||
editorId,
|
||||
document.notebookDocument.viewType,
|
||||
this._proxy,
|
||||
document
|
||||
document,
|
||||
data.visibleRanges.map(typeConverters.NotebookCellRange.to),
|
||||
data.selections.map(typeConverters.NotebookCellRange.to),
|
||||
typeof data.viewColumn === 'number' ? typeConverters.ViewColumn.to(data.viewColumn) : undefined
|
||||
);
|
||||
|
||||
editor._acceptSelections(selections);
|
||||
editor._acceptVisibleRanges(visibleRanges);
|
||||
|
||||
this._editors.get(editorId)?.editor.dispose();
|
||||
this._editors.set(editorId, { editor });
|
||||
@@ -638,7 +694,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape {
|
||||
if (document) {
|
||||
document.dispose();
|
||||
this._documents.delete(revivedUri);
|
||||
this._documentsAndEditors.$acceptDocumentsAndEditorsDelta({ removedDocuments: document.notebookDocument.cells.map(cell => cell.uri) });
|
||||
this._textDocumentsAndEditors.$acceptDocumentsAndEditorsDelta({ removedDocuments: document.notebookDocument.cells.map(cell => cell.document.uri) });
|
||||
this._onDidCloseNotebookDocument.fire(document.notebookDocument);
|
||||
}
|
||||
|
||||
@@ -667,7 +723,8 @@ export class ExtHostNotebookController implements ExtHostNotebookShape {
|
||||
|
||||
const document = new ExtHostNotebookDocument(
|
||||
this._proxy,
|
||||
this._documentsAndEditors,
|
||||
this._textDocumentsAndEditors,
|
||||
this._textDocuments,
|
||||
{
|
||||
emitModelChange(event: vscode.NotebookCellsChangeEvent): void {
|
||||
that._onDidChangeNotebookCells.fire(event);
|
||||
@@ -675,34 +732,24 @@ export class ExtHostNotebookController implements ExtHostNotebookShape {
|
||||
emitCellOutputsChange(event: vscode.NotebookCellOutputsChangeEvent): void {
|
||||
that._onDidChangeCellOutputs.fire(event);
|
||||
},
|
||||
emitCellLanguageChange(event: vscode.NotebookCellLanguageChangeEvent): void {
|
||||
that._onDidChangeCellLanguage.fire(event);
|
||||
},
|
||||
emitCellMetadataChange(event: vscode.NotebookCellMetadataChangeEvent): void {
|
||||
that._onDidChangeCellMetadata.fire(event);
|
||||
},
|
||||
emitDocumentMetadataChange(event: vscode.NotebookDocumentMetadataChangeEvent): void {
|
||||
that._onDidChangeNotebookDocumentMetadata.fire(event);
|
||||
emitCellExecutionStateChange(event: vscode.NotebookCellExecutionStateChangeEvent): void {
|
||||
that._onDidChangeCellExecutionState.fire(event);
|
||||
}
|
||||
},
|
||||
viewType,
|
||||
modelData.contentOptions,
|
||||
modelData.metadata ? typeConverters.NotebookDocumentMetadata.to(modelData.metadata) : new extHostTypes.NotebookDocumentMetadata(),
|
||||
uri,
|
||||
);
|
||||
|
||||
document.acceptModelChanged({
|
||||
versionId: modelData.versionId,
|
||||
rawEvents: [
|
||||
{
|
||||
kind: NotebookCellsChangeType.Initialize,
|
||||
changes: [[
|
||||
0,
|
||||
0,
|
||||
modelData.cells
|
||||
]]
|
||||
}
|
||||
]
|
||||
rawEvents: [{
|
||||
kind: NotebookCellsChangeType.Initialize,
|
||||
changes: [[0, 0, modelData.cells]]
|
||||
}]
|
||||
}, false);
|
||||
|
||||
// add cell document as vscode.TextDocument
|
||||
@@ -710,7 +757,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape {
|
||||
|
||||
this._documents.get(uri)?.dispose();
|
||||
this._documents.set(uri, document);
|
||||
this._documentsAndEditors.$acceptDocumentsAndEditorsDelta({ addedDocuments: addedCellDocuments });
|
||||
this._textDocumentsAndEditors.$acceptDocumentsAndEditorsDelta({ addedDocuments: addedCellDocuments });
|
||||
|
||||
this._onDidOpenNotebookDocument.fire(document.notebookDocument);
|
||||
}
|
||||
@@ -726,7 +773,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape {
|
||||
const document = this._documents.get(revivedUri);
|
||||
|
||||
if (document) {
|
||||
this._createExtHostEditor(document, editorModelData.id, editorModelData.selections, editorModelData.visibleRanges.map(typeConverters.NotebookCellRange.to));
|
||||
this._createExtHostEditor(document, editorModelData.id, editorModelData);
|
||||
editorChanged = true;
|
||||
}
|
||||
}
|
||||
@@ -771,23 +818,13 @@ export class ExtHostNotebookController implements ExtHostNotebookShape {
|
||||
this._onDidChangeVisibleNotebookEditors.fire(this.visibleNotebookEditors);
|
||||
}
|
||||
|
||||
if (delta.newActiveEditor === null) {
|
||||
// clear active notebook as current active editor is non-notebook editor
|
||||
this._activeNotebookEditor = undefined;
|
||||
} else if (delta.newActiveEditor) {
|
||||
this._activeNotebookEditor = this._editors.get(delta.newActiveEditor)?.editor;
|
||||
}
|
||||
if (delta.newActiveEditor !== undefined) {
|
||||
if (delta.newActiveEditor) {
|
||||
this._activeNotebookEditor = this._editors.get(delta.newActiveEditor)?.editor;
|
||||
this._activeNotebookEditor?._acceptActive(true);
|
||||
for (const e of this._editors.values()) {
|
||||
if (e.editor !== this._activeNotebookEditor) {
|
||||
e.editor._acceptActive(false);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// clear active notebook as current active editor is non-notebook editor
|
||||
this._activeNotebookEditor = undefined;
|
||||
for (const e of this._editors.values()) {
|
||||
e.editor._acceptActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
this._onDidChangeActiveNotebookEditor.fire(this._activeNotebookEditor?.editor);
|
||||
}
|
||||
}
|
||||
@@ -796,7 +833,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape {
|
||||
const statusBarItem = new NotebookCellStatusBarItemInternal(this._proxy, this._commandsConverter, cell, alignment, priority);
|
||||
|
||||
// Look up the ExtHostCell for this NotebookCell URI, bind to its disposable lifecycle
|
||||
const parsedUri = CellUri.parse(cell.uri);
|
||||
const parsedUri = CellUri.parse(cell.document.uri);
|
||||
if (parsedUri) {
|
||||
const document = this._documents.get(parsedUri.notebook);
|
||||
if (document) {
|
||||
@@ -809,6 +846,35 @@ export class ExtHostNotebookController implements ExtHostNotebookShape {
|
||||
|
||||
return statusBarItem;
|
||||
}
|
||||
|
||||
createNotebookCellExecution(docUri: vscode.Uri, index: number, kernelId: string): vscode.NotebookCellExecutionTask | undefined {
|
||||
const document = this.lookupNotebookDocument(docUri);
|
||||
if (!document) {
|
||||
throw new Error(`Invalid cell uri/index: ${docUri}, ${index}`);
|
||||
}
|
||||
|
||||
const cell = document.getCellFromIndex(index);
|
||||
if (!cell) {
|
||||
throw new Error(`Invalid cell uri/index: ${docUri}, ${index}`);
|
||||
}
|
||||
|
||||
// TODO@roblou also validate kernelId, once kernel has moved from editor to document
|
||||
if (this._activeExecutions.has(cell.uri)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const execution = new NotebookCellExecutionTask(docUri, document, cell, this._proxy);
|
||||
this._activeExecutions.set(cell.uri, execution);
|
||||
const listener = execution.onDidChangeState(() => {
|
||||
if (execution.state === NotebookCellExecutionTaskState.Resolved) {
|
||||
execution.dispose();
|
||||
listener.dispose();
|
||||
this._activeExecutions.delete(cell.uri);
|
||||
}
|
||||
});
|
||||
|
||||
return execution.asApiObject();
|
||||
}
|
||||
}
|
||||
|
||||
export class NotebookCellStatusBarItemInternal extends Disposable {
|
||||
@@ -946,7 +1012,7 @@ export class NotebookCellStatusBarItemInternal extends Disposable {
|
||||
|
||||
const entry: INotebookCellStatusBarEntry = {
|
||||
alignment: this.alignment === extHostTypes.NotebookCellStatusBarAlignment.Left ? CellStatusbarAlignment.LEFT : CellStatusbarAlignment.RIGHT,
|
||||
cellResource: this.cell.uri,
|
||||
cellResource: this.cell.document.uri,
|
||||
command: this._command?.internal,
|
||||
text: this.text,
|
||||
tooltip: this.tooltip,
|
||||
@@ -985,3 +1051,157 @@ function createNotebookCellStatusBarApiItem(internalItem: NotebookCellStatusBarI
|
||||
dispose() { internalItem.dispose(); }
|
||||
});
|
||||
}
|
||||
|
||||
enum NotebookCellExecutionTaskState {
|
||||
Init,
|
||||
Started,
|
||||
Resolved
|
||||
}
|
||||
|
||||
class NotebookCellExecutionTask extends Disposable {
|
||||
private _onDidChangeState = new Emitter<void>();
|
||||
readonly onDidChangeState = this._onDidChangeState.event;
|
||||
|
||||
private _state = NotebookCellExecutionTaskState.Init;
|
||||
get state(): NotebookCellExecutionTaskState { return this._state; }
|
||||
|
||||
private readonly _tokenSource: CancellationTokenSource;
|
||||
|
||||
private _executionOrder: number | undefined;
|
||||
|
||||
constructor(
|
||||
private readonly _uri: vscode.Uri,
|
||||
private readonly _document: ExtHostNotebookDocument,
|
||||
private readonly _cell: ExtHostCell,
|
||||
private readonly _proxy: MainThreadNotebookShape) {
|
||||
super();
|
||||
this._tokenSource = this._register(new CancellationTokenSource());
|
||||
|
||||
this._executionOrder = _cell.internalMetadata.executionOrder;
|
||||
this.mixinMetadata({
|
||||
runState: NotebookCellExecutionState.Pending,
|
||||
lastRunDuration: null,
|
||||
executionOrder: null
|
||||
});
|
||||
}
|
||||
|
||||
cancel(): void {
|
||||
this._tokenSource.cancel();
|
||||
}
|
||||
|
||||
private async applyEdits(edits: IImmediateCellEditOperation[]): Promise<void> {
|
||||
return this._proxy.$applyEdits(this._uri, edits, false);
|
||||
}
|
||||
|
||||
private verifyStateForOutput() {
|
||||
if (this._state === NotebookCellExecutionTaskState.Init) {
|
||||
throw new Error('Must call start before modifying cell output');
|
||||
}
|
||||
|
||||
if (this._state === NotebookCellExecutionTaskState.Resolved) {
|
||||
throw new Error('Cannot modify cell output after calling resolve');
|
||||
}
|
||||
}
|
||||
|
||||
private mixinMetadata(mixinMetadata: NullablePartialNotebookCellMetadata) {
|
||||
const edits: IImmediateCellEditOperation[] = [
|
||||
{ editType: CellEditType.PartialMetadata, handle: this._cell.handle, metadata: mixinMetadata }
|
||||
];
|
||||
this.applyEdits(edits);
|
||||
}
|
||||
|
||||
private cellIndexToHandle(cellIndex: number | undefined): number | undefined {
|
||||
const cell = typeof cellIndex === 'number' ? this._document.getCellFromIndex(cellIndex) : this._cell;
|
||||
if (!cell) {
|
||||
return;
|
||||
}
|
||||
|
||||
return cell.handle;
|
||||
}
|
||||
|
||||
asApiObject(): vscode.NotebookCellExecutionTask {
|
||||
const that = this;
|
||||
return Object.freeze(<vscode.NotebookCellExecutionTask>{
|
||||
get document() { return that._document.notebookDocument; },
|
||||
get cell() { return that._cell.cell; },
|
||||
|
||||
get executionOrder() { return that._executionOrder; },
|
||||
set executionOrder(v: number | undefined) {
|
||||
that._executionOrder = v;
|
||||
that.mixinMetadata({
|
||||
executionOrder: v
|
||||
});
|
||||
},
|
||||
|
||||
start(context?: vscode.NotebookCellExecuteStartContext): void {
|
||||
if (that._state === NotebookCellExecutionTaskState.Resolved || that._state === NotebookCellExecutionTaskState.Started) {
|
||||
throw new Error('Cannot call start again');
|
||||
}
|
||||
|
||||
that._state = NotebookCellExecutionTaskState.Started;
|
||||
that._onDidChangeState.fire();
|
||||
|
||||
that.mixinMetadata({
|
||||
runState: NotebookCellExecutionState.Executing,
|
||||
runStartTime: context?.startTime
|
||||
});
|
||||
},
|
||||
|
||||
end(result?: vscode.NotebookCellExecuteEndContext): void {
|
||||
if (that._state === NotebookCellExecutionTaskState.Resolved) {
|
||||
throw new Error('Cannot call resolve twice');
|
||||
}
|
||||
|
||||
that._state = NotebookCellExecutionTaskState.Resolved;
|
||||
that._onDidChangeState.fire();
|
||||
|
||||
that.mixinMetadata({
|
||||
runState: NotebookCellExecutionState.Idle,
|
||||
lastRunSuccess: result?.success ?? null,
|
||||
lastRunDuration: result?.duration ?? null,
|
||||
});
|
||||
},
|
||||
|
||||
clearOutput(cellIndex?: number): Thenable<void> {
|
||||
that.verifyStateForOutput();
|
||||
return this.replaceOutput([], cellIndex);
|
||||
},
|
||||
|
||||
async appendOutput(outputs: vscode.NotebookCellOutput | vscode.NotebookCellOutput[], cellIndex?: number): Promise<void> {
|
||||
that.verifyStateForOutput();
|
||||
const handle = that.cellIndexToHandle(cellIndex);
|
||||
if (typeof handle !== 'number') {
|
||||
return;
|
||||
}
|
||||
|
||||
outputs = Array.isArray(outputs) ? outputs : [outputs];
|
||||
return that.applyEdits([{ editType: CellEditType.Output, handle, append: true, outputs: outputs.map(typeConverters.NotebookCellOutput.from) }]);
|
||||
},
|
||||
|
||||
async replaceOutput(outputs: vscode.NotebookCellOutput | vscode.NotebookCellOutput[], cellIndex?: number): Promise<void> {
|
||||
that.verifyStateForOutput();
|
||||
const handle = that.cellIndexToHandle(cellIndex);
|
||||
if (typeof handle !== 'number') {
|
||||
return;
|
||||
}
|
||||
|
||||
outputs = Array.isArray(outputs) ? outputs : [outputs];
|
||||
return that.applyEdits([{ editType: CellEditType.Output, handle, outputs: outputs.map(typeConverters.NotebookCellOutput.from) }]);
|
||||
},
|
||||
|
||||
async appendOutputItems(items: vscode.NotebookCellOutputItem | vscode.NotebookCellOutputItem[], outputId: string): Promise<void> {
|
||||
that.verifyStateForOutput();
|
||||
items = Array.isArray(items) ? items : [items];
|
||||
return that.applyEdits([{ editType: CellEditType.OutputItems, append: true, items: items.map(typeConverters.NotebookCellOutputItem.from), outputId }]);
|
||||
},
|
||||
|
||||
async replaceOutputItems(items: vscode.NotebookCellOutputItem | vscode.NotebookCellOutputItem[], outputId: string): Promise<void> {
|
||||
that.verifyStateForOutput();
|
||||
items = Array.isArray(items) ? items : [items];
|
||||
return that.applyEdits([{ editType: CellEditType.OutputItems, items: items.map(typeConverters.NotebookCellOutputItem.from), outputId }]);
|
||||
},
|
||||
|
||||
token: that._tokenSource.token
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,6 @@ export class ExtHostNotebookConcatDocument implements vscode.NotebookConcatTextD
|
||||
}
|
||||
};
|
||||
|
||||
this._disposables.add(extHostNotebooks.onDidChangeCellLanguage(e => documentChange(e.document)));
|
||||
this._disposables.add(extHostNotebooks.onDidChangeNotebookCells(e => documentChange(e.document)));
|
||||
}
|
||||
|
||||
@@ -75,8 +74,8 @@ export class ExtHostNotebookConcatDocument implements vscode.NotebookConcatTextD
|
||||
const cellLengths: number[] = [];
|
||||
const cellLineCounts: number[] = [];
|
||||
for (const cell of this._notebook.cells) {
|
||||
if (cell.cellKind === types.NotebookCellKind.Code && (!this._selector || score(this._selector, cell.uri, cell.language, true))) {
|
||||
this._cellUris.set(cell.uri, this._cells.length);
|
||||
if (cell.kind === types.NotebookCellKind.Code && (!this._selector || score(this._selector, cell.document.uri, cell.document.languageId, true))) {
|
||||
this._cellUris.set(cell.document.uri, this._cells.length);
|
||||
this._cells.push(cell);
|
||||
cellLengths.push(cell.document.getText().length + 1);
|
||||
cellLineCounts.push(cell.document.lineCount);
|
||||
@@ -162,7 +161,7 @@ export class ExtHostNotebookConcatDocument implements vscode.NotebookConcatTextD
|
||||
const range = new types.Range(startPos, endPos);
|
||||
|
||||
const startCell = this._cells[startIdx.index];
|
||||
return new types.Location(startCell.uri, <types.Range>startCell.document.validateRange(range));
|
||||
return new types.Location(startCell.document.uri, <types.Range>startCell.document.validateRange(range));
|
||||
}
|
||||
|
||||
contains(uri: vscode.Uri): boolean {
|
||||
|
||||
@@ -6,24 +6,28 @@
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { Disposable, DisposableStore, dispose } from 'vs/base/common/lifecycle';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { deepFreeze, equals } from 'vs/base/common/objects';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { CellKind, INotebookDocumentPropertiesChangeData, MainThreadNotebookShape } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments';
|
||||
import { ExtHostDocumentsAndEditors, IExtHostModelAddedData } from 'vs/workbench/api/common/extHostDocumentsAndEditors';
|
||||
import * as extHostTypeConverters from 'vs/workbench/api/common/extHostTypeConverters';
|
||||
import * as extHostTypes from 'vs/workbench/api/common/extHostTypes';
|
||||
import { IMainCellDto, IOutputDto, IOutputItemDto, NotebookCellMetadata, NotebookCellsChangedEventDto, NotebookCellsChangeType, NotebookCellsSplice2, notebookDocumentMetadataDefaults } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
import { IMainCellDto, IOutputDto, IOutputItemDto, NotebookCellMetadata, NotebookCellsChangedEventDto, NotebookCellsChangeType, NotebookCellsSplice2 } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
class RawContentChangeEvent {
|
||||
|
||||
constructor(readonly start: number, readonly deletedCount: number, readonly deletedItems: vscode.NotebookCell[], readonly items: ExtHostCell[]) { }
|
||||
|
||||
static asApiEvent(event: RawContentChangeEvent): vscode.NotebookCellsChangeData {
|
||||
return Object.freeze({
|
||||
start: event.start,
|
||||
deletedCount: event.deletedCount,
|
||||
deletedItems: event.deletedItems,
|
||||
items: event.items.map(data => data.cell)
|
||||
static asApiEvents(events: RawContentChangeEvent[]): readonly vscode.NotebookCellsChangeData[] {
|
||||
return events.map(event => {
|
||||
return {
|
||||
start: event.start,
|
||||
deletedCount: event.deletedCount,
|
||||
deletedItems: event.deletedItems,
|
||||
items: event.items.map(data => data.cell)
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -47,7 +51,9 @@ export class ExtHostCell {
|
||||
|
||||
private _outputs: extHostTypes.NotebookCellOutput[];
|
||||
private _metadata: extHostTypes.NotebookCellMetadata;
|
||||
private _previousResult: vscode.NotebookCellExecutionSummary | undefined;
|
||||
|
||||
private _internalMetadata: NotebookCellMetadata;
|
||||
readonly handle: number;
|
||||
readonly uri: URI;
|
||||
readonly cellKind: CellKind;
|
||||
@@ -63,7 +69,9 @@ export class ExtHostCell {
|
||||
this.uri = URI.revive(_cellData.uri);
|
||||
this.cellKind = _cellData.cellKind;
|
||||
this._outputs = _cellData.outputs.map(extHostTypeConverters.NotebookCellOutput.to);
|
||||
this._metadata = extHostTypeConverters.NotebookCellMetadata.to(_cellData.metadata ?? {});
|
||||
this._internalMetadata = _cellData.metadata ?? {};
|
||||
this._metadata = extHostTypeConverters.NotebookCellMetadata.to(this._internalMetadata);
|
||||
this._previousResult = extHostTypeConverters.NotebookCellPreviousExecutionResult.to(this._internalMetadata);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
@@ -71,6 +79,10 @@ export class ExtHostCell {
|
||||
this._onDidDispose.dispose();
|
||||
}
|
||||
|
||||
get internalMetadata(): NotebookCellMetadata {
|
||||
return this._internalMetadata;
|
||||
}
|
||||
|
||||
get cell(): vscode.NotebookCell {
|
||||
if (!this._cell) {
|
||||
const that = this;
|
||||
@@ -78,17 +90,14 @@ export class ExtHostCell {
|
||||
if (!data) {
|
||||
throw new Error(`MISSING extHostDocument for notebook cell: ${this.uri}`);
|
||||
}
|
||||
this._cell = Object.freeze({
|
||||
this._cell = Object.freeze<vscode.NotebookCell>({
|
||||
get index() { return that._notebook.getCellIndex(that); },
|
||||
notebook: that._notebook.notebookDocument,
|
||||
uri: that.uri,
|
||||
cellKind: extHostTypeConverters.NotebookCellKind.to(this._cellData.cellKind),
|
||||
kind: extHostTypeConverters.NotebookCellKind.to(this._cellData.cellKind),
|
||||
document: data.document,
|
||||
get language() { return data!.document.languageId; },
|
||||
get outputs() { return that._outputs.slice(0); },
|
||||
set outputs(_value) { throw new Error('Use WorkspaceEdit to update cell outputs.'); },
|
||||
get metadata() { return that._metadata; },
|
||||
set metadata(_value) { throw new Error('Use WorkspaceEdit to update cell metadata.'); },
|
||||
get latestExecutionSummary() { return that._previousResult; }
|
||||
});
|
||||
}
|
||||
return this._cell;
|
||||
@@ -110,16 +119,17 @@ export class ExtHostCell {
|
||||
}
|
||||
|
||||
setMetadata(newMetadata: NotebookCellMetadata): void {
|
||||
this._internalMetadata = newMetadata;
|
||||
this._metadata = extHostTypeConverters.NotebookCellMetadata.to(newMetadata);
|
||||
this._previousResult = extHostTypeConverters.NotebookCellPreviousExecutionResult.to(newMetadata);
|
||||
}
|
||||
}
|
||||
|
||||
export interface INotebookEventEmitter {
|
||||
emitModelChange(events: vscode.NotebookCellsChangeEvent): void;
|
||||
emitDocumentMetadataChange(event: vscode.NotebookDocumentMetadataChangeEvent): void;
|
||||
emitCellOutputsChange(event: vscode.NotebookCellOutputsChangeEvent): void;
|
||||
emitCellLanguageChange(event: vscode.NotebookCellLanguageChangeEvent): void;
|
||||
emitCellMetadataChange(event: vscode.NotebookCellMetadataChangeEvent): void;
|
||||
emitCellExecutionStateChange(event: vscode.NotebookCellExecutionStateChangeEvent): void;
|
||||
}
|
||||
|
||||
|
||||
@@ -140,10 +150,10 @@ export class ExtHostNotebookDocument extends Disposable {
|
||||
|
||||
constructor(
|
||||
private readonly _proxy: MainThreadNotebookShape,
|
||||
private readonly _documentsAndEditors: ExtHostDocumentsAndEditors,
|
||||
private readonly _textDocumentsAndEditors: ExtHostDocumentsAndEditors,
|
||||
private readonly _textDocuments: ExtHostDocuments,
|
||||
private readonly _emitter: INotebookEventEmitter,
|
||||
private readonly _viewType: string,
|
||||
private readonly _contentOptions: vscode.NotebookDocumentContentOptions,
|
||||
private _metadata: extHostTypes.NotebookDocumentMetadata,
|
||||
readonly uri: URI,
|
||||
) {
|
||||
@@ -170,7 +180,6 @@ export class ExtHostNotebookDocument extends Disposable {
|
||||
get cells(): ReadonlyArray<vscode.NotebookCell> { return that._cells.map(cell => cell.cell); },
|
||||
get metadata() { return that._metadata; },
|
||||
set metadata(_value: Required<vscode.NotebookDocumentMetadata>) { throw new Error('Use WorkspaceEdit to update metadata.'); },
|
||||
get contentOptions() { return that._contentOptions; },
|
||||
save() { return that._save(); }
|
||||
});
|
||||
}
|
||||
@@ -188,39 +197,37 @@ export class ExtHostNotebookDocument extends Disposable {
|
||||
}
|
||||
|
||||
acceptDocumentPropertiesChanged(data: INotebookDocumentPropertiesChangeData) {
|
||||
const newMetadata = {
|
||||
...notebookDocumentMetadataDefaults,
|
||||
...data.metadata
|
||||
};
|
||||
this._metadata = this._metadata.with(newMetadata);
|
||||
this._emitter.emitDocumentMetadataChange({ document: this.notebookDocument });
|
||||
if (data.metadata) {
|
||||
this._metadata = this._metadata.with(data.metadata);
|
||||
}
|
||||
}
|
||||
|
||||
acceptModelChanged(event: NotebookCellsChangedEventDto, isDirty: boolean): void {
|
||||
this._versionId = event.versionId;
|
||||
this._isDirty = isDirty;
|
||||
event.rawEvents.forEach(e => {
|
||||
if (e.kind === NotebookCellsChangeType.Initialize) {
|
||||
this._spliceNotebookCells(e.changes, true);
|
||||
} if (e.kind === NotebookCellsChangeType.ModelChange) {
|
||||
this._spliceNotebookCells(e.changes, false);
|
||||
} else if (e.kind === NotebookCellsChangeType.Move) {
|
||||
this._moveCell(e.index, e.newIdx);
|
||||
} else if (e.kind === NotebookCellsChangeType.Output) {
|
||||
this._setCellOutputs(e.index, e.outputs);
|
||||
} else if (e.kind === NotebookCellsChangeType.OutputItem) {
|
||||
this._setCellOutputItems(e.index, e.outputId, e.append, e.outputItems);
|
||||
} else if (e.kind === NotebookCellsChangeType.ChangeLanguage) {
|
||||
this._changeCellLanguage(e.index, e.language);
|
||||
} else if (e.kind === NotebookCellsChangeType.ChangeCellMetadata) {
|
||||
this._changeCellMetadata(e.index, e.metadata);
|
||||
|
||||
for (const rawEvent of event.rawEvents) {
|
||||
if (rawEvent.kind === NotebookCellsChangeType.Initialize) {
|
||||
this._spliceNotebookCells(rawEvent.changes, true);
|
||||
} if (rawEvent.kind === NotebookCellsChangeType.ModelChange) {
|
||||
this._spliceNotebookCells(rawEvent.changes, false);
|
||||
} else if (rawEvent.kind === NotebookCellsChangeType.Move) {
|
||||
this._moveCell(rawEvent.index, rawEvent.newIdx);
|
||||
} else if (rawEvent.kind === NotebookCellsChangeType.Output) {
|
||||
this._setCellOutputs(rawEvent.index, rawEvent.outputs);
|
||||
} else if (rawEvent.kind === NotebookCellsChangeType.OutputItem) {
|
||||
this._setCellOutputItems(rawEvent.index, rawEvent.outputId, rawEvent.append, rawEvent.outputItems);
|
||||
} else if (rawEvent.kind === NotebookCellsChangeType.ChangeLanguage) {
|
||||
this._changeCellLanguage(rawEvent.index, rawEvent.language);
|
||||
} else if (rawEvent.kind === NotebookCellsChangeType.ChangeCellMetadata) {
|
||||
this._changeCellMetadata(rawEvent.index, rawEvent.metadata);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async _save(): Promise<boolean> {
|
||||
if (this._disposed) {
|
||||
return Promise.reject(new Error('Document has been closed'));
|
||||
return Promise.reject(new Error('Notebook has been closed'));
|
||||
}
|
||||
return this._proxy.$trySaveDocument(this.uri);
|
||||
}
|
||||
@@ -238,7 +245,7 @@ export class ExtHostNotebookDocument extends Disposable {
|
||||
const cellDtos = splice[2];
|
||||
const newCells = cellDtos.map(cell => {
|
||||
|
||||
const extCell = new ExtHostCell(this, this._documentsAndEditors, cell);
|
||||
const extCell = new ExtHostCell(this, this._textDocumentsAndEditors, cell);
|
||||
|
||||
if (!initialization) {
|
||||
addedCellDocuments.push(ExtHostCell.asModelAddData(this.notebookDocument, cell));
|
||||
@@ -268,62 +275,67 @@ export class ExtHostNotebookDocument extends Disposable {
|
||||
contentChangeEvents.push(changeEvent);
|
||||
});
|
||||
|
||||
this._documentsAndEditors.acceptDocumentsAndEditorsDelta({
|
||||
this._textDocumentsAndEditors.acceptDocumentsAndEditorsDelta({
|
||||
addedDocuments: addedCellDocuments,
|
||||
removedDocuments: removedCellDocuments
|
||||
});
|
||||
|
||||
if (!initialization) {
|
||||
this._emitter.emitModelChange({
|
||||
this._emitter.emitModelChange(deepFreeze({
|
||||
document: this.notebookDocument,
|
||||
changes: contentChangeEvents.map(RawContentChangeEvent.asApiEvent)
|
||||
});
|
||||
changes: RawContentChangeEvent.asApiEvents(contentChangeEvents)
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
private _moveCell(index: number, newIdx: number): void {
|
||||
const cells = this._cells.splice(index, 1);
|
||||
this._cells.splice(newIdx, 0, ...cells);
|
||||
const changes: vscode.NotebookCellsChangeData[] = [{
|
||||
start: index,
|
||||
deletedCount: 1,
|
||||
deletedItems: cells.map(data => data.cell),
|
||||
items: []
|
||||
}, {
|
||||
start: newIdx,
|
||||
deletedCount: 0,
|
||||
deletedItems: [],
|
||||
items: cells.map(data => data.cell)
|
||||
}];
|
||||
this._emitter.emitModelChange({
|
||||
const changes = [
|
||||
new RawContentChangeEvent(index, 1, cells.map(c => c.cell), []),
|
||||
new RawContentChangeEvent(newIdx, 0, [], cells)
|
||||
];
|
||||
this._emitter.emitModelChange(deepFreeze({
|
||||
document: this.notebookDocument,
|
||||
changes
|
||||
});
|
||||
changes: RawContentChangeEvent.asApiEvents(changes)
|
||||
}));
|
||||
}
|
||||
|
||||
private _setCellOutputs(index: number, outputs: IOutputDto[]): void {
|
||||
const cell = this._cells[index];
|
||||
cell.setOutputs(outputs);
|
||||
this._emitter.emitCellOutputsChange({ document: this.notebookDocument, cells: [cell.cell] });
|
||||
this._emitter.emitCellOutputsChange(deepFreeze({ document: this.notebookDocument, cells: [cell.cell] }));
|
||||
}
|
||||
|
||||
private _setCellOutputItems(index: number, outputId: string, append: boolean, outputItems: IOutputItemDto[]): void {
|
||||
const cell = this._cells[index];
|
||||
cell.setOutputItems(outputId, append, outputItems);
|
||||
this._emitter.emitCellOutputsChange({ document: this.notebookDocument, cells: [cell.cell] });
|
||||
this._emitter.emitCellOutputsChange(deepFreeze({ document: this.notebookDocument, cells: [cell.cell] }));
|
||||
}
|
||||
|
||||
private _changeCellLanguage(index: number, language: string): void {
|
||||
private _changeCellLanguage(index: number, newModeId: string): void {
|
||||
const cell = this._cells[index];
|
||||
const event: vscode.NotebookCellLanguageChangeEvent = { document: this.notebookDocument, cell: cell.cell, language };
|
||||
this._emitter.emitCellLanguageChange(event);
|
||||
if (cell.cell.document.languageId !== newModeId) {
|
||||
this._textDocuments.$acceptModelModeChanged(cell.uri, newModeId);
|
||||
}
|
||||
}
|
||||
|
||||
private _changeCellMetadata(index: number, newMetadata: NotebookCellMetadata | undefined): void {
|
||||
private _changeCellMetadata(index: number, newMetadata: NotebookCellMetadata): void {
|
||||
const cell = this._cells[index];
|
||||
cell.setMetadata(newMetadata || {});
|
||||
const event: vscode.NotebookCellMetadataChangeEvent = { document: this.notebookDocument, cell: cell.cell };
|
||||
this._emitter.emitCellMetadataChange(event);
|
||||
|
||||
const originalInternalMetadata = cell.internalMetadata;
|
||||
const originalExtMetadata = cell.cell.metadata;
|
||||
cell.setMetadata(newMetadata);
|
||||
const newExtMetadata = cell.cell.metadata;
|
||||
|
||||
if (!equals(originalExtMetadata, newExtMetadata)) {
|
||||
this._emitter.emitCellMetadataChange(deepFreeze({ document: this.notebookDocument, cell: cell.cell }));
|
||||
}
|
||||
|
||||
if (originalInternalMetadata.runState !== newMetadata.runState) {
|
||||
const executionState = newMetadata.runState ?? extHostTypes.NotebookCellExecutionState.Idle;
|
||||
this._emitter.emitCellExecutionStateChange(deepFreeze({ document: this.notebookDocument, cell: cell.cell, executionState }));
|
||||
}
|
||||
}
|
||||
|
||||
getCellFromIndex(index: number): ExtHostCell | undefined {
|
||||
|
||||
@@ -3,12 +3,11 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { readonly } from 'vs/base/common/errors';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { MainThreadNotebookShape } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import * as extHostTypes from 'vs/workbench/api/common/extHostTypes';
|
||||
import * as extHostConverter from 'vs/workbench/api/common/extHostTypeConverters';
|
||||
import { CellEditType, ICellEditOperation, ICellRange, ICellReplaceEdit, notebookDocumentMetadataDefaults } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
import { CellEditType, ICellEditOperation, ICellReplaceEdit } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
import * as vscode from 'vscode';
|
||||
import { ExtHostNotebookDocument } from './extHostNotebookDocument';
|
||||
|
||||
@@ -46,7 +45,7 @@ class NotebookEditorCellEditBuilder implements vscode.NotebookEditorEdit {
|
||||
this._throwIfFinalized();
|
||||
this._collectedEdits.push({
|
||||
editType: CellEditType.DocumentMetadata,
|
||||
metadata: { ...notebookDocumentMetadataDefaults, ...value }
|
||||
metadata: value
|
||||
});
|
||||
}
|
||||
|
||||
@@ -85,28 +84,33 @@ class NotebookEditorCellEditBuilder implements vscode.NotebookEditorEdit {
|
||||
}
|
||||
|
||||
export class ExtHostNotebookEditor {
|
||||
private _selection?: vscode.NotebookCell;
|
||||
private _selections: vscode.NotebookCellRange[] = [];
|
||||
|
||||
private _visibleRanges: extHostTypes.NotebookCellRange[] = [];
|
||||
private _selections: vscode.NotebookCellRange[] = [];
|
||||
private _visibleRanges: vscode.NotebookCellRange[] = [];
|
||||
private _viewColumn?: vscode.ViewColumn;
|
||||
private _active: boolean = false;
|
||||
|
||||
private _visible: boolean = false;
|
||||
private _kernel?: vscode.NotebookKernel;
|
||||
|
||||
private _onDidDispose = new Emitter<void>();
|
||||
private readonly _hasDecorationsForKey = new Set<string>();
|
||||
private readonly _onDidDispose = new Emitter<void>();
|
||||
readonly onDidDispose: Event<void> = this._onDidDispose.event;
|
||||
|
||||
private _hasDecorationsForKey: { [key: string]: boolean; } = Object.create(null);
|
||||
|
||||
private _editor: vscode.NotebookEditor | undefined;
|
||||
private _editor?: vscode.NotebookEditor;
|
||||
|
||||
constructor(
|
||||
readonly id: string,
|
||||
private readonly _viewType: string,
|
||||
private readonly _proxy: MainThreadNotebookShape,
|
||||
readonly notebookData: ExtHostNotebookDocument,
|
||||
visibleRanges: vscode.NotebookCellRange[],
|
||||
selections: vscode.NotebookCellRange[],
|
||||
viewColumn: vscode.ViewColumn | undefined
|
||||
) {
|
||||
this._selections = selections;
|
||||
this._visibleRanges = visibleRanges;
|
||||
this._viewColumn = viewColumn;
|
||||
}
|
||||
|
||||
dispose() {
|
||||
@@ -122,7 +126,8 @@ export class ExtHostNotebookEditor {
|
||||
return that.notebookData.notebookDocument;
|
||||
},
|
||||
get selection() {
|
||||
return that._selection;
|
||||
const primarySelection = that._selections[0];
|
||||
return primarySelection && that.notebookData.getCellFromIndex(primarySelection.start)?.cell;
|
||||
},
|
||||
get selections() {
|
||||
return that._selections;
|
||||
@@ -131,7 +136,11 @@ export class ExtHostNotebookEditor {
|
||||
return that._visibleRanges;
|
||||
},
|
||||
revealRange(range, revealType) {
|
||||
that._proxy.$tryRevealRange(that.id, extHostConverter.NotebookCellRange.from(range), revealType ?? extHostTypes.NotebookEditorRevealType.Default);
|
||||
that._proxy.$tryRevealRange(
|
||||
that.id,
|
||||
extHostConverter.NotebookCellRange.from(range),
|
||||
revealType ?? extHostTypes.NotebookEditorRevealType.Default
|
||||
);
|
||||
},
|
||||
get viewColumn() {
|
||||
return that._viewColumn;
|
||||
@@ -163,34 +172,16 @@ export class ExtHostNotebookEditor {
|
||||
return this._visible;
|
||||
}
|
||||
|
||||
set visible(_state: boolean) {
|
||||
throw readonly('visible');
|
||||
}
|
||||
|
||||
_acceptVisibility(value: boolean) {
|
||||
this._visible = value;
|
||||
}
|
||||
|
||||
_acceptVisibleRanges(value: extHostTypes.NotebookCellRange[]): void {
|
||||
_acceptVisibleRanges(value: vscode.NotebookCellRange[]): void {
|
||||
this._visibleRanges = value;
|
||||
}
|
||||
|
||||
_acceptSelections(selections: ICellRange[]): void {
|
||||
const primarySelection = selections[0];
|
||||
this._selection = primarySelection ? this.notebookData.getCellFromIndex(primarySelection.start)?.cell : undefined;
|
||||
this._selections = selections.map(val => new extHostTypes.NotebookCellRange(val.start, val.end));
|
||||
}
|
||||
|
||||
get active(): boolean {
|
||||
return this._active;
|
||||
}
|
||||
|
||||
set active(_state: boolean) {
|
||||
throw readonly('active');
|
||||
}
|
||||
|
||||
_acceptActive(value: boolean) {
|
||||
this._active = value;
|
||||
_acceptSelections(selections: vscode.NotebookCellRange[]): void {
|
||||
this._selections = selections;
|
||||
}
|
||||
|
||||
private _applyEdit(editData: INotebookEditData): Promise<boolean> {
|
||||
@@ -213,9 +204,9 @@ export class ExtHostNotebookEditor {
|
||||
const prevIndex = compressedEditsIndex;
|
||||
const prev = compressedEdits[prevIndex];
|
||||
|
||||
if (prev.editType === CellEditType.Replace && editData.cellEdits[i].editType === CellEditType.Replace) {
|
||||
const edit = editData.cellEdits[i];
|
||||
if ((edit.editType !== CellEditType.DocumentMetadata) && prev.index === edit.index) {
|
||||
const edit = editData.cellEdits[i];
|
||||
if (prev.editType === CellEditType.Replace && edit.editType === CellEditType.Replace) {
|
||||
if (prev.index === edit.index) {
|
||||
prev.cells.push(...(editData.cellEdits[i] as ICellReplaceEdit).cells);
|
||||
prev.count += (editData.cellEdits[i] as ICellReplaceEdit).count;
|
||||
continue;
|
||||
@@ -230,15 +221,14 @@ export class ExtHostNotebookEditor {
|
||||
}
|
||||
|
||||
setDecorations(decorationType: vscode.NotebookEditorDecorationType, range: vscode.NotebookCellRange): void {
|
||||
const willBeEmpty = (range.start === range.end);
|
||||
if (willBeEmpty && !this._hasDecorationsForKey[decorationType.key]) {
|
||||
if (range.isEmpty && !this._hasDecorationsForKey.has(decorationType.key)) {
|
||||
// avoid no-op call to the renderer
|
||||
return;
|
||||
}
|
||||
if (willBeEmpty) {
|
||||
delete this._hasDecorationsForKey[decorationType.key];
|
||||
if (range.isEmpty) {
|
||||
this._hasDecorationsForKey.delete(decorationType.key);
|
||||
} else {
|
||||
this._hasDecorationsForKey[decorationType.key] = true;
|
||||
this._hasDecorationsForKey.add(decorationType.key);
|
||||
}
|
||||
|
||||
return this._proxy.$trySetDecorations(
|
||||
|
||||
@@ -25,7 +25,7 @@ export class ExtHostProgress implements ExtHostProgressShape {
|
||||
withProgress<R>(extension: IExtensionDescription, options: ProgressOptions, task: (progress: Progress<IProgressStep>, token: CancellationToken) => Thenable<R>): Thenable<R> {
|
||||
const handle = this._handles++;
|
||||
const { title, location, cancellable } = options;
|
||||
const source = localize('extensionSource', "{0} (Extension)", extension.displayName || extension.name);
|
||||
const source = { label: localize('extensionSource', "{0} (Extension)", extension.displayName || extension.name), id: extension.identifier.value };
|
||||
|
||||
this._proxy.$startProgress(handle, { location: ProgressLocation.from(location), title, source, cancellable }, extension);
|
||||
return this._withProgress(handle, task, !!cancellable);
|
||||
|
||||
@@ -16,6 +16,7 @@ import { ThemeIcon, QuickInputButtons } from 'vs/workbench/api/common/extHostTyp
|
||||
import { isPromiseCanceledError } from 'vs/base/common/errors';
|
||||
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
|
||||
import { coalesce } from 'vs/base/common/arrays';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
|
||||
export type Item = string | QuickPickItem;
|
||||
|
||||
@@ -67,11 +68,12 @@ export function createExtHostQuickOpen(mainContext: IMainContext, workspace: IEx
|
||||
const instance = ++this._instances;
|
||||
|
||||
const quickPickWidget = proxy.$show(instance, {
|
||||
placeHolder: options && options.placeHolder,
|
||||
matchOnDescription: options && options.matchOnDescription,
|
||||
matchOnDetail: options && options.matchOnDetail,
|
||||
ignoreFocusLost: options && options.ignoreFocusOut,
|
||||
canPickMany: options && options.canPickMany
|
||||
title: options?.title,
|
||||
placeHolder: options?.placeHolder,
|
||||
matchOnDescription: options?.matchOnDescription,
|
||||
matchOnDetail: options?.matchOnDetail,
|
||||
ignoreFocusLost: options?.ignoreFocusOut,
|
||||
canPickMany: options?.canPickMany,
|
||||
}, token);
|
||||
|
||||
const widgetClosedMarker = {};
|
||||
@@ -632,7 +634,7 @@ export function createExtHostQuickOpen(mainContext: IMainContext, workspace: IEx
|
||||
|
||||
set validationMessage(validationMessage: string | undefined) {
|
||||
this._validationMessage = validationMessage;
|
||||
this.update({ validationMessage });
|
||||
this.update({ validationMessage, severity: validationMessage ? Severity.Error : Severity.Ignore });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
31
lib/vscode/src/vs/workbench/api/common/extHostTelemetry.ts
Normal file
31
lib/vscode/src/vs/workbench/api/common/extHostTelemetry.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { ExtHostTelemetryShape } from 'vs/workbench/api/common/extHost.protocol';
|
||||
|
||||
export class ExtHostTelemetry implements ExtHostTelemetryShape {
|
||||
private readonly _onDidChangeTelemetryEnabled = new Emitter<boolean>();
|
||||
readonly onDidChangeTelemetryEnabled: Event<boolean> = this._onDidChangeTelemetryEnabled.event;
|
||||
|
||||
private _enabled: boolean = false;
|
||||
|
||||
getTelemetryEnabled(): boolean {
|
||||
return this._enabled;
|
||||
}
|
||||
|
||||
$initializeTelemetryEnabled(enabled: boolean): void {
|
||||
this._enabled = enabled;
|
||||
}
|
||||
|
||||
$onDidChangeTelemetryEnabled(enabled: boolean): void {
|
||||
this._enabled = enabled;
|
||||
this._onDidChangeTelemetryEnabled.fire(enabled);
|
||||
}
|
||||
}
|
||||
|
||||
export const IExtHostTelemetry = createDecorator<IExtHostTelemetry>('IExtHostTelemetry');
|
||||
export interface IExtHostTelemetry extends ExtHostTelemetry, ExtHostTelemetryShape { }
|
||||
@@ -5,10 +5,10 @@
|
||||
|
||||
import type * as vscode from 'vscode';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { ExtHostTerminalServiceShape, MainContext, MainThreadTerminalServiceShape, IShellLaunchConfigDto, IShellDefinitionDto, IShellAndArgsDto, ITerminalDimensionsDto, ITerminalLinkDto, TerminalIdentifier } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { ExtHostTerminalServiceShape, MainContext, MainThreadTerminalServiceShape, IShellLaunchConfigDto, 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 { URI } from 'vs/base/common/uri';
|
||||
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
|
||||
import { IDisposable, DisposableStore, Disposable } from 'vs/base/common/lifecycle';
|
||||
import { Disposable as VSCodeDisposable, EnvironmentVariableMutatorType } from './extHostTypes';
|
||||
@@ -19,8 +19,9 @@ import { serializeEnvironmentVariableCollection } from 'vs/workbench/contrib/ter
|
||||
import { CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable';
|
||||
import { ITerminalChildProcess, ITerminalDimensionsOverride, ITerminalLaunchError } from 'vs/platform/terminal/common/terminal';
|
||||
import { ITerminalChildProcess, ITerminalDimensionsOverride, ITerminalLaunchError, TerminalShellType } from 'vs/platform/terminal/common/terminal';
|
||||
import { TerminalDataBufferer } from 'vs/platform/terminal/common/terminalDataBuffering';
|
||||
import { ITerminalProfile } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
|
||||
export interface IExtHostTerminalService extends ExtHostTerminalServiceShape, IDisposable {
|
||||
|
||||
@@ -133,7 +134,7 @@ export class ExtHostTerminal {
|
||||
if (typeof this._id !== 'string') {
|
||||
throw new Error('Terminal has already been created');
|
||||
}
|
||||
await this._proxy.$createTerminal(this._id, { name: this._name, isExtensionTerminal: true });
|
||||
await this._proxy.$createTerminal(this._id, { name: this._name, isExtensionCustomPtyTerminal: true });
|
||||
// At this point, the id has been set via `$acceptTerminalOpened`
|
||||
if (typeof this._id === 'string') {
|
||||
throw new Error('Terminal creation failed');
|
||||
@@ -198,6 +199,9 @@ export class ExtHostPseudoterminal implements ITerminalChildProcess {
|
||||
public readonly onProcessTitleChanged: Event<string> = this._onProcessTitleChanged.event;
|
||||
private readonly _onProcessOverrideDimensions = new Emitter<ITerminalDimensionsOverride | undefined>();
|
||||
public get onProcessOverrideDimensions(): Event<ITerminalDimensionsOverride | undefined> { return this._onProcessOverrideDimensions.event; }
|
||||
private readonly _onProcessShellTypeChanged = new Emitter<TerminalShellType>();
|
||||
public readonly onProcessShellTypeChanged = this._onProcessShellTypeChanged.event;
|
||||
|
||||
|
||||
constructor(private readonly _pty: vscode.Pseudoterminal) { }
|
||||
|
||||
@@ -324,8 +328,7 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I
|
||||
public abstract createTerminalFromOptions(options: vscode.TerminalOptions): vscode.Terminal;
|
||||
public abstract getDefaultShell(useAutomationShell: boolean, configProvider: ExtHostConfigProvider): string;
|
||||
public abstract getDefaultShellArgs(useAutomationShell: boolean, configProvider: ExtHostConfigProvider): string[] | string;
|
||||
public abstract $spawnExtHostProcess(id: number, shellLaunchConfigDto: IShellLaunchConfigDto, activeWorkspaceRootUriComponents: UriComponents, cols: number, rows: number, isWorkspaceShellAllowed: boolean): Promise<ITerminalLaunchError | undefined>;
|
||||
public abstract $getAvailableShells(): Promise<IShellDefinitionDto[]>;
|
||||
public abstract $getAvailableProfiles(quickLaunchOnly: boolean): Promise<ITerminalProfile[]>;
|
||||
public abstract $getDefaultShellAndArgs(useAutomationShell: boolean): Promise<IShellAndArgsDto>;
|
||||
public abstract $acceptWorkspacePermissionsChanged(isAllowed: boolean): void;
|
||||
|
||||
@@ -776,11 +779,7 @@ export class WorkerExtHostTerminalService extends BaseExtHostTerminalService {
|
||||
throw new NotSupportedError();
|
||||
}
|
||||
|
||||
public $spawnExtHostProcess(id: number, shellLaunchConfigDto: IShellLaunchConfigDto, activeWorkspaceRootUriComponents: UriComponents, cols: number, rows: number, isWorkspaceShellAllowed: boolean): Promise<ITerminalLaunchError | undefined> {
|
||||
throw new NotSupportedError();
|
||||
}
|
||||
|
||||
public $getAvailableShells(): Promise<IShellDefinitionDto[]> {
|
||||
public $getAvailableProfiles(quickLaunchOnly: boolean): Promise<ITerminalProfile[]> {
|
||||
throw new NotSupportedError();
|
||||
}
|
||||
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
|
||||
import { mapFind } from 'vs/base/common/arrays';
|
||||
import { disposableTimeout } from 'vs/base/common/async';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { once } from 'vs/base/common/functional';
|
||||
import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { deepFreeze } from 'vs/base/common/objects';
|
||||
import { isDefined } from 'vs/base/common/types';
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
@@ -17,11 +17,11 @@ import { ExtHostTestingResource, ExtHostTestingShape, MainContext, MainThreadTes
|
||||
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, TestResults, TestState } from 'vs/workbench/api/common/extHostTypeConverters';
|
||||
import { Disposable } from 'vs/workbench/api/common/extHostTypes';
|
||||
import * as Convert from 'vs/workbench/api/common/extHostTypeConverters';
|
||||
import { Disposable, TestItem as TestItemImpl, TestItemHookProperty } from 'vs/workbench/api/common/extHostTypes';
|
||||
import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace';
|
||||
import { OwnedTestCollection, SingleUseTestCollection, TestPosition } from 'vs/workbench/contrib/testing/common/ownedTestCollection';
|
||||
import { AbstractIncrementalTestCollection, IncrementalChangeCollector, IncrementalTestCollectionItem, InternalTestItem, ISerializedTestResults, RunTestForProviderRequest, TestDiffOpType, TestIdWithProvider, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection';
|
||||
import { AbstractIncrementalTestCollection, IncrementalChangeCollector, IncrementalTestCollectionItem, InternalTestItem, ISerializedTestResults, RunTestForProviderRequest, TestDiffOpType, TestIdWithSrc, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection';
|
||||
import type * as vscode from 'vscode';
|
||||
|
||||
const getTestSubscriptionKey = (resource: ExtHostTestingResource, uri: URI) => `${resource}:${uri.toString()}`;
|
||||
@@ -93,7 +93,7 @@ export class ExtHostTesting implements ExtHostTestingShape {
|
||||
tests
|
||||
.map(this.getInternalTestForReference, this)
|
||||
.filter(isDefined)
|
||||
.map(t => ({ providerId: t.providerId, testId: t.item.extId }));
|
||||
.map(t => ({ src: t.src, testId: t.item.extId }));
|
||||
|
||||
await this.proxy.$runTests({
|
||||
exclude: req.exclude ? testListToProviders(req.exclude).map(t => t.testId) : undefined,
|
||||
@@ -106,7 +106,7 @@ export class ExtHostTesting implements ExtHostTestingShape {
|
||||
* Implements vscode.test.publishTestResults
|
||||
*/
|
||||
public publishExtensionProvidedResults(results: vscode.TestResults, persist: boolean): void {
|
||||
this.proxy.$publishExtensionProvidedResults(TestResults.from(generateUuid(), results), persist);
|
||||
this.proxy.$publishExtensionProvidedResults(Convert.TestResults.from(generateUuid(), results), persist);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -116,7 +116,7 @@ export class ExtHostTesting implements ExtHostTestingShape {
|
||||
public $publishTestResults(results: ISerializedTestResults[]): void {
|
||||
this.results = Object.freeze(
|
||||
results
|
||||
.map(r => deepFreeze(TestResults.to(r)))
|
||||
.map(r => deepFreeze(Convert.TestResults.to(r)))
|
||||
.concat(this.results)
|
||||
.sort((a, b) => b.completedAt - a.completedAt)
|
||||
.slice(0, 32),
|
||||
@@ -136,7 +136,8 @@ export class ExtHostTesting implements ExtHostTestingShape {
|
||||
return;
|
||||
}
|
||||
|
||||
let method: undefined | ((p: vscode.TestProvider) => vscode.TestHierarchy<vscode.TestItem> | undefined);
|
||||
const cancellation = new CancellationTokenSource();
|
||||
let method: undefined | ((p: vscode.TestProvider) => vscode.ProviderResult<vscode.TestItem>);
|
||||
if (resource === ExtHostTestingResource.TextDocument) {
|
||||
let document = this.documents.getDocument(uri);
|
||||
|
||||
@@ -155,14 +156,14 @@ export class ExtHostTesting implements ExtHostTestingShape {
|
||||
|
||||
if (document) {
|
||||
const folder = await this.workspace.getWorkspaceFolder2(uri, false);
|
||||
method = p => p.createDocumentTestHierarchy
|
||||
? p.createDocumentTestHierarchy(document!.document)
|
||||
: this.createDefaultDocumentTestHierarchy(p, document!.document, folder);
|
||||
method = p => p.provideDocumentTestRoot
|
||||
? p.provideDocumentTestRoot(document!.document, cancellation.token)
|
||||
: createDefaultDocumentTestRoot(p, document!.document, folder, cancellation.token);
|
||||
}
|
||||
} else {
|
||||
const folder = await this.workspace.getWorkspaceFolder2(uri, false);
|
||||
if (folder) {
|
||||
method = p => p.createWorkspaceTestHierarchy?.(folder);
|
||||
method = p => p.provideWorkspaceTestRoot(folder, cancellation.token);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,33 +171,21 @@ export class ExtHostTesting implements ExtHostTestingShape {
|
||||
return;
|
||||
}
|
||||
|
||||
const subscribeFn = (id: string, provider: vscode.TestProvider) => {
|
||||
const subscribeFn = async (id: string, provider: vscode.TestProvider) => {
|
||||
try {
|
||||
const hierarchy = method!(provider);
|
||||
if (!hierarchy) {
|
||||
return;
|
||||
const root = await method!(provider);
|
||||
if (root) {
|
||||
collection.addRoot(root, id);
|
||||
}
|
||||
|
||||
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));
|
||||
hierarchy.onDidInvalidateTest?.(e => {
|
||||
const internal = collection.getTestByReference(e);
|
||||
if (!internal) {
|
||||
console.warn(`Received a TestProvider.onDidInvalidateTest for a test that does not currently exist.`);
|
||||
} else {
|
||||
this.proxy.$retireTest(internal.item.extId);
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
};
|
||||
|
||||
const disposable = new DisposableStore();
|
||||
const collection = disposable.add(this.ownedTests.createForHierarchy(diff => this.proxy.$publishDiff(resource, uriComponents, diff)));
|
||||
const collection = disposable.add(this.ownedTests.createForHierarchy(
|
||||
diff => this.proxy.$publishDiff(resource, uriComponents, diff)));
|
||||
disposable.add(toDisposable(() => cancellation.dispose(true)));
|
||||
for (const [id, provider] of this.providers) {
|
||||
subscribeFn(id, provider);
|
||||
}
|
||||
@@ -208,6 +197,17 @@ export class ExtHostTesting implements ExtHostTestingShape {
|
||||
this.testSubscriptions.set(subscriptionKey, { store: disposable, collection, subscribeFn });
|
||||
}
|
||||
|
||||
/**
|
||||
* Expands the nodes in the test tree. If levels is less than zero, it will
|
||||
* be treated as infinite.
|
||||
* @override
|
||||
*/
|
||||
public async $expandTest(test: TestIdWithSrc, levels: number) {
|
||||
const sub = mapFind(this.testSubscriptions.values(), s => s.collection.treeId === test.src.tree ? s : undefined);
|
||||
await sub?.collection.expand(test.testId, levels < 0 ? Infinity : levels);
|
||||
this.flushCollectionDiffs();
|
||||
}
|
||||
|
||||
/**
|
||||
* Disposes of a previous subscription to tests.
|
||||
* @override
|
||||
@@ -238,12 +238,16 @@ export class ExtHostTesting implements ExtHostTestingShape {
|
||||
* @override
|
||||
*/
|
||||
public async $runTestsForProvider(req: RunTestForProviderRequest, cancellation: CancellationToken): Promise<void> {
|
||||
const provider = this.providers.get(req.providerId);
|
||||
if (!provider || !provider.runTests) {
|
||||
const provider = this.providers.get(req.tests[0].src.provider);
|
||||
if (!provider) {
|
||||
return;
|
||||
}
|
||||
|
||||
const includeTests = req.ids.map(id => this.ownedTests.getTestById(id)?.[1]).filter(isDefined);
|
||||
const includeTests = req.tests
|
||||
.map(({ testId, src }) => this.ownedTests.getTestById(testId, src.tree))
|
||||
.filter(isDefined)
|
||||
.map(([_tree, test]) => test);
|
||||
|
||||
const excludeTests = req.excludeExtIds
|
||||
.map(id => this.ownedTests.getTestById(id))
|
||||
.filter(isDefined)
|
||||
@@ -268,7 +272,7 @@ export class ExtHostTesting implements ExtHostTestingShape {
|
||||
}
|
||||
|
||||
this.flushCollectionDiffs();
|
||||
this.proxy.$updateTestStateInRun(req.runId, test.id, TestState.from(state));
|
||||
this.proxy.$updateTestStateInRun(req.runId, test.id, Convert.TestState.from(state));
|
||||
},
|
||||
tests: includeTests.map(t => TestItemFilteredWrapper.unwrap(t.actual)),
|
||||
exclude: excludeTests.map(([, t]) => TestItemFilteredWrapper.unwrap(t.actual)),
|
||||
@@ -286,13 +290,13 @@ export class ExtHostTesting implements ExtHostTestingShape {
|
||||
}
|
||||
}
|
||||
|
||||
public $lookupTest(req: TestIdWithProvider): Promise<InternalTestItem | undefined> {
|
||||
public $lookupTest(req: TestIdWithSrc): Promise<InternalTestItem | undefined> {
|
||||
const owned = this.ownedTests.getTestById(req.testId);
|
||||
if (!owned) {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
const { actual, previousChildren, previousEquals, ...item } = owned[1];
|
||||
const { actual, discoverCts, expandLevels, ...item } = owned[1];
|
||||
return Promise.resolve(item);
|
||||
}
|
||||
|
||||
@@ -317,95 +321,54 @@ export class ExtHostTesting implements ExtHostTestingShape {
|
||||
?? mapFind(this.testSubscriptions.values(), c => c.collection.getTestByReference(test))
|
||||
?? this.textDocumentObservers.getMirroredTestDataByReference(test);
|
||||
}
|
||||
|
||||
private createDefaultDocumentTestHierarchy(provider: vscode.TestProvider, document: vscode.TextDocument, folder: vscode.WorkspaceFolder | undefined): vscode.TestHierarchy<vscode.TestItem> | undefined {
|
||||
if (!folder) {
|
||||
return;
|
||||
}
|
||||
|
||||
const workspaceHierarchy = provider.createWorkspaceTestHierarchy?.(folder);
|
||||
if (!workspaceHierarchy) {
|
||||
return;
|
||||
}
|
||||
|
||||
const onDidInvalidateTest = new Emitter<vscode.TestItem>();
|
||||
workspaceHierarchy.onDidInvalidateTest?.(node => {
|
||||
const wrapper = TestItemFilteredWrapper.getWrapperForTestItem(node, document);
|
||||
if (wrapper.hasNodeMatchingFilter) {
|
||||
onDidInvalidateTest.fire(wrapper);
|
||||
}
|
||||
});
|
||||
|
||||
const onDidChangeTest = new Emitter<vscode.TestItem>();
|
||||
workspaceHierarchy.onDidChangeTest(node => {
|
||||
const wrapper = TestItemFilteredWrapper.getWrapperForTestItem(node, document);
|
||||
const previouslySeen = wrapper.hasNodeMatchingFilter;
|
||||
|
||||
if (previouslySeen) {
|
||||
// reset cache and get whether you can currently see the TestItem.
|
||||
wrapper.reset();
|
||||
const currentlySeen = wrapper.hasNodeMatchingFilter;
|
||||
|
||||
if (currentlySeen) {
|
||||
onDidChangeTest.fire(wrapper);
|
||||
return;
|
||||
}
|
||||
|
||||
// Fire the event to say that the current visible parent has changed.
|
||||
onDidChangeTest.fire(wrapper.visibleParent);
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// 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);
|
||||
},
|
||||
discoveredInitialTests: workspaceHierarchy.discoveredInitialTests,
|
||||
onDidInvalidateTest: onDidInvalidateTest.event,
|
||||
onDidChangeTest: onDidChangeTest.event
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const createDefaultDocumentTestRoot = async <T extends vscode.TestItem>(
|
||||
provider: vscode.TestProvider<T>,
|
||||
document: vscode.TextDocument,
|
||||
folder: vscode.WorkspaceFolder | undefined,
|
||||
token: CancellationToken,
|
||||
) => {
|
||||
if (!folder) {
|
||||
return;
|
||||
}
|
||||
|
||||
const root = await provider.provideWorkspaceTestRoot(folder, token);
|
||||
if (!root) {
|
||||
return;
|
||||
}
|
||||
|
||||
token.onCancellationRequested(() => {
|
||||
TestItemFilteredWrapper.removeFilter(document);
|
||||
});
|
||||
|
||||
return TestItemFilteredWrapper.getWrapperForTestItem(root, document);
|
||||
};
|
||||
|
||||
/*
|
||||
* 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 {
|
||||
export class TestItemFilteredWrapper<T extends vscode.TestItem = vscode.TestItem> extends TestItemImpl {
|
||||
private static wrapperMap = new WeakMap<vscode.TextDocument, WeakMap<vscode.TestItem, TestItemFilteredWrapper>>();
|
||||
public static removeFilter(document: vscode.TextDocument): void {
|
||||
this.wrapperMap.delete(document);
|
||||
}
|
||||
|
||||
// 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 {
|
||||
public static getWrapperForTestItem<T extends vscode.TestItem>(
|
||||
item: T,
|
||||
filterDocument: vscode.TextDocument,
|
||||
parent?: TestItemFilteredWrapper<T>,
|
||||
): TestItemFilteredWrapper<T> {
|
||||
let innerMap = this.wrapperMap.get(filterDocument);
|
||||
if (innerMap?.has(item)) {
|
||||
return innerMap.get(item)!;
|
||||
return innerMap.get(item) as TestItemFilteredWrapper<T>;
|
||||
}
|
||||
|
||||
if (!innerMap) {
|
||||
innerMap = new WeakMap<vscode.TestItem, TestItemFilteredWrapper>();
|
||||
this.wrapperMap.set(filterDocument, innerMap);
|
||||
|
||||
}
|
||||
|
||||
const w = new TestItemFilteredWrapper(item, filterDocument, parent);
|
||||
@@ -413,75 +376,72 @@ export class TestItemFilteredWrapper implements vscode.TestItem {
|
||||
return w;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the TestItem is wrapped, returns the unwrapped item provided
|
||||
* by the extension.
|
||||
*/
|
||||
public static unwrap(item: vscode.TestItem) {
|
||||
return item instanceof TestItemFilteredWrapper ? item.actual : item;
|
||||
}
|
||||
|
||||
public get id() {
|
||||
return this.actual.id;
|
||||
}
|
||||
private _cachedMatchesFilter: boolean | undefined;
|
||||
|
||||
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 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
|
||||
/**
|
||||
* Gets whether this node, or any of its children, match the document filter.
|
||||
*/
|
||||
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);
|
||||
if (this._cachedMatchesFilter === undefined) {
|
||||
return this.refreshMatch();
|
||||
} else {
|
||||
return this._cachedMatchesFilter;
|
||||
}
|
||||
}
|
||||
|
||||
private constructor(
|
||||
public readonly actual: T,
|
||||
private filterDocument: vscode.TextDocument,
|
||||
public readonly parent?: TestItemFilteredWrapper<T>,
|
||||
) {
|
||||
super(actual.id, actual.label, actual.uri, actual.expandable);
|
||||
if (!(actual instanceof TestItemImpl)) {
|
||||
throw new Error(`TestItems provided to the VS Code API must extend \`vscode.TestItem\`, but ${actual.id} did not`);
|
||||
}
|
||||
|
||||
return this.matchesFilter;
|
||||
(actual as TestItemImpl)[TestItemHookProperty] = {
|
||||
setProp: (key, value) => (this as Record<string, unknown>)[key] = value,
|
||||
created: child => TestItemFilteredWrapper.getWrapperForTestItem(child, this.filterDocument, this).refreshMatch(),
|
||||
invalidate: () => this.invalidate(),
|
||||
delete: child => this.children.delete(child),
|
||||
};
|
||||
}
|
||||
|
||||
// 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();
|
||||
/**
|
||||
* Refreshes the `hasNodeMatchingFilter` state for this item. It matches
|
||||
* if the test itself has a location that matches, or if any of its
|
||||
* children do.
|
||||
*/
|
||||
private refreshMatch() {
|
||||
const didMatch = this._cachedMatchesFilter;
|
||||
|
||||
// The `children` of the wrapper only include the children who match the
|
||||
// filter. Synchronize them.
|
||||
for (const rawChild of this.actual.children) {
|
||||
const wrapper = TestItemFilteredWrapper.getWrapperForTestItem(rawChild, this.filterDocument, this);
|
||||
if (wrapper.hasNodeMatchingFilter) {
|
||||
this.children.add(wrapper);
|
||||
} else {
|
||||
this.children.delete(wrapper);
|
||||
}
|
||||
}
|
||||
this.matchesFilter = undefined;
|
||||
}
|
||||
|
||||
const nowMatches = this.children.size > 0 || this.actual.uri.toString() === this.filterDocument.uri.toString();
|
||||
this._cachedMatchesFilter = nowMatches;
|
||||
|
||||
private constructor(public readonly actual: vscode.TestItem, private filterDocument: vscode.TextDocument, private readonly parent?: TestItemFilteredWrapper) {
|
||||
this.getWrappedChildren();
|
||||
}
|
||||
if (nowMatches !== didMatch) {
|
||||
this.parent?.refreshMatch();
|
||||
}
|
||||
|
||||
private getWrappedChildren() {
|
||||
return this.actual.children?.map(t => TestItemFilteredWrapper.getWrapperForTestItem(t, this.filterDocument, this)) || [];
|
||||
return this._cachedMatchesFilter;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -491,7 +451,6 @@ export class TestItemFilteredWrapper implements vscode.TestItem {
|
||||
interface MirroredCollectionTestItem extends IncrementalTestCollectionItem {
|
||||
revived: vscode.TestItem;
|
||||
depth: number;
|
||||
wrapped?: vscode.RequiredTestItem;
|
||||
}
|
||||
|
||||
class MirroredChangeCollector extends IncrementalChangeCollector<MirroredCollectionTestItem> {
|
||||
@@ -505,7 +464,7 @@ class MirroredChangeCollector extends IncrementalChangeCollector<MirroredCollect
|
||||
return this.added.size === 0 && this.removed.size === 0 && this.updated.size === 0;
|
||||
}
|
||||
|
||||
constructor(private readonly collection: MirroredTestCollection, private readonly emitter: Emitter<vscode.TestChangeEvent>) {
|
||||
constructor(private readonly emitter: Emitter<vscode.TestChangeEvent>) {
|
||||
super();
|
||||
}
|
||||
|
||||
@@ -520,7 +479,7 @@ class MirroredChangeCollector extends IncrementalChangeCollector<MirroredCollect
|
||||
* @override
|
||||
*/
|
||||
public update(node: MirroredCollectionTestItem): void {
|
||||
Object.assign(node.revived, TestItem.toShallow(node.item));
|
||||
Object.assign(node.revived, Convert.TestItem.toPlain(node.item));
|
||||
if (!this.added.has(node)) {
|
||||
this.updated.add(node);
|
||||
}
|
||||
@@ -549,79 +508,11 @@ class MirroredChangeCollector extends IncrementalChangeCollector<MirroredCollect
|
||||
* @override
|
||||
*/
|
||||
public getChangeEvent(): vscode.TestChangeEvent {
|
||||
const { collection, added, updated, removed } = this;
|
||||
const { added, updated, removed } = this;
|
||||
return {
|
||||
get added() { return [...added].map(collection.getPublicTestItem, collection); },
|
||||
get updated() { return [...updated].map(collection.getPublicTestItem, collection); },
|
||||
get removed() { return [...removed].map(collection.getPublicTestItem, collection); },
|
||||
get commonChangeAncestor() {
|
||||
let ancestorPath: MirroredCollectionTestItem[] | undefined;
|
||||
const buildAncestorPath = (node: MirroredCollectionTestItem | undefined) => {
|
||||
if (!node) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// add the node and all its parents to the list of ancestors. If
|
||||
// the node is detached, do not return a path (its parent will
|
||||
// also have been passed to remove() and be present)
|
||||
const path: MirroredCollectionTestItem[] = new Array(node.depth + 1);
|
||||
for (let i = node.depth; i >= 0; i--) {
|
||||
if (!node) {
|
||||
return undefined; // detached child
|
||||
}
|
||||
|
||||
path[node.depth] = node;
|
||||
node = node.parent ? collection.getMirroredTestDataById(node.parent) : undefined;
|
||||
}
|
||||
|
||||
return path;
|
||||
};
|
||||
|
||||
const addAncestorPath = (node: MirroredCollectionTestItem) => {
|
||||
// fast path: if the common ancestor is already the root, no more work to do
|
||||
if (ancestorPath && ancestorPath.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const thisPath = buildAncestorPath(node);
|
||||
if (!thisPath) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ancestorPath) {
|
||||
ancestorPath = thisPath;
|
||||
return;
|
||||
}
|
||||
|
||||
// removes node from the path to the ancestor that don't match
|
||||
// the corresponding node in *this* path.
|
||||
for (let i = ancestorPath.length - 1; i >= 0; i--) {
|
||||
if (ancestorPath[i] !== thisPath[i]) {
|
||||
ancestorPath.pop();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const addParentAncestor = (node: MirroredCollectionTestItem) => {
|
||||
if (ancestorPath && ancestorPath.length === 0) {
|
||||
// no-op
|
||||
} else if (node.parent === null) {
|
||||
ancestorPath = [];
|
||||
} else {
|
||||
const parent = collection.getMirroredTestDataById(node.parent);
|
||||
if (parent) {
|
||||
addAncestorPath(parent);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
for (const node of added) { addParentAncestor(node); }
|
||||
for (const node of updated) { addAncestorPath(node); }
|
||||
for (const node of removed) { addParentAncestor(node); }
|
||||
|
||||
const ancestor = ancestorPath && ancestorPath[ancestorPath.length - 1];
|
||||
return ancestor ? collection.getPublicTestItem(ancestor) : null;
|
||||
},
|
||||
get added() { return [...added].map(n => n.revived); },
|
||||
get updated() { return [...updated].map(n => n.revived); },
|
||||
get removed() { return [...removed].map(n => n.revived); },
|
||||
};
|
||||
}
|
||||
|
||||
@@ -654,12 +545,12 @@ export class MirroredTestCollection extends AbstractIncrementalTestCollection<Mi
|
||||
/**
|
||||
* Translates the item IDs to TestItems for exposure to extensions.
|
||||
*/
|
||||
public getAllAsTestItem(itemIds: Iterable<string>): vscode.RequiredTestItem[] {
|
||||
let output: vscode.RequiredTestItem[] = [];
|
||||
public getAllAsTestItem(itemIds: Iterable<string>) {
|
||||
let output: vscode.TestItem[] = [];
|
||||
for (const itemId of itemIds) {
|
||||
const item = this.items.get(itemId);
|
||||
if (item) {
|
||||
output.push(this.getPublicTestItem(item));
|
||||
output.push(item.revived);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -685,64 +576,23 @@ export class MirroredTestCollection extends AbstractIncrementalTestCollection<Mi
|
||||
* @override
|
||||
*/
|
||||
protected createItem(item: InternalTestItem, parent?: MirroredCollectionTestItem): MirroredCollectionTestItem {
|
||||
return { ...item, revived: TestItem.toShallow(item.item), depth: parent ? parent.depth + 1 : 0, children: new Set() };
|
||||
return {
|
||||
...item,
|
||||
// todo@connor4312: make this work well again with children
|
||||
revived: Convert.TestItem.toPlain(item.item) as vscode.TestItem,
|
||||
depth: parent ? parent.depth + 1 : 0,
|
||||
children: new Set(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
protected createChangeCollector() {
|
||||
return new MirroredChangeCollector(this, this.changeEmitter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the public test item instance for the given mirrored record.
|
||||
*/
|
||||
public getPublicTestItem(item: MirroredCollectionTestItem): vscode.RequiredTestItem {
|
||||
if (!item.wrapped) {
|
||||
item.wrapped = new TestItemFromMirror(item, this);
|
||||
}
|
||||
|
||||
return item.wrapped;
|
||||
return new MirroredChangeCollector(this.changeEmitter);
|
||||
}
|
||||
}
|
||||
|
||||
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 location() { return this.#internal.revived.location; }
|
||||
public get runnable() { return this.#internal.revived.runnable ?? true; }
|
||||
public get debuggable() { return this.#internal.revived.debuggable ?? false; }
|
||||
public get children() {
|
||||
return this.#collection.getAllAsTestItem(this.#internal.children);
|
||||
}
|
||||
|
||||
constructor(internal: MirroredCollectionTestItem, collection: MirroredTestCollection) {
|
||||
this.#internal = internal;
|
||||
this.#collection = collection;
|
||||
}
|
||||
|
||||
public toJSON() {
|
||||
const serialized: vscode.RequiredTestItem & TestIdWithProvider = {
|
||||
id: this.id,
|
||||
label: this.label,
|
||||
description: this.description,
|
||||
location: this.location,
|
||||
runnable: this.runnable,
|
||||
debuggable: this.debuggable,
|
||||
children: this.children.map(c => (c as TestItemFromMirror).toJSON()),
|
||||
|
||||
providerId: this.#internal.providerId,
|
||||
testId: this.id,
|
||||
};
|
||||
|
||||
return serialized;
|
||||
}
|
||||
}
|
||||
|
||||
interface IObserverData {
|
||||
observers: number;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
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';
|
||||
import { ProvidedPortAttributes, RemoteTunnel, TunnelCreationOptions, TunnelOptions } from 'vs/platform/remote/common/tunnel';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
|
||||
@@ -46,6 +46,7 @@ export interface IExtHostTunnelService extends ExtHostTunnelServiceShape {
|
||||
getTunnels(): Promise<vscode.TunnelDescription[]>;
|
||||
onDidChangeTunnels: vscode.Event<void>;
|
||||
setTunnelExtensionFunctions(provider: vscode.RemoteAuthorityResolver | undefined): Promise<IDisposable>;
|
||||
registerPortsAttributesProvider(portSelector: { pid?: number, portRange?: [number, number] }, provider: vscode.PortAttributesProvider): IDisposable;
|
||||
}
|
||||
|
||||
export const IExtHostTunnelService = createDecorator<IExtHostTunnelService>('IExtHostTunnelService');
|
||||
@@ -71,6 +72,14 @@ export class ExtHostTunnelService implements IExtHostTunnelService {
|
||||
async setTunnelExtensionFunctions(provider: vscode.RemoteAuthorityResolver | undefined): Promise<IDisposable> {
|
||||
return { dispose: () => { } };
|
||||
}
|
||||
registerPortsAttributesProvider(portSelector: { pid?: number, portRange?: [number, number] }, provider: vscode.PortAttributesProvider) {
|
||||
return { dispose: () => { } };
|
||||
}
|
||||
|
||||
async $providePortAttributes(handles: number[], ports: number[], pid: number | undefined, commandline: string | undefined, cancellationToken: vscode.CancellationToken): Promise<ProvidedPortAttributes[]> {
|
||||
return [];
|
||||
}
|
||||
|
||||
async $forwardPort(tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions): Promise<TunnelDto | undefined> { return undefined; }
|
||||
async $closeTunnel(remote: { host: string, port: number }): Promise<void> { }
|
||||
async $onDidTunnelsChange(): Promise<void> { }
|
||||
|
||||
@@ -3,36 +3,35 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as modes from 'vs/editor/common/modes';
|
||||
import * as types from './extHostTypes';
|
||||
import * as search from 'vs/workbench/contrib/search/common/search';
|
||||
import { ITextEditorOptions, EditorOverride } from 'vs/platform/editor/common/editor';
|
||||
import { IDecorationOptions, IThemeDecorationRenderOptions, IDecorationRenderOptions, IContentDecorationRenderOptions } from 'vs/editor/common/editorCommon';
|
||||
import { EndOfLineSequence, TrackedRangeStickiness } from 'vs/editor/common/model';
|
||||
import type * as vscode from 'vscode';
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import { ProgressLocation as MainProgressLocation } from 'vs/platform/progress/common/progress';
|
||||
import { EditorGroupColumn, SaveReason } from 'vs/workbench/common/editor';
|
||||
import { IPosition } from 'vs/editor/common/core/position';
|
||||
import * as editorRange from 'vs/editor/common/core/range';
|
||||
import { ISelection } from 'vs/editor/common/core/selection';
|
||||
import { coalesce, isNonEmptyArray } from 'vs/base/common/arrays';
|
||||
import * as htmlContent from 'vs/base/common/htmlContent';
|
||||
import * as languageSelector from 'vs/editor/common/modes/languageSelector';
|
||||
import * as extHostProtocol from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { MarkerSeverity, IRelatedInformation, IMarkerData, MarkerTag } from 'vs/platform/markers/common/markers';
|
||||
import { ACTIVE_GROUP, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors';
|
||||
import { isString, isNumber, isDefined } from 'vs/base/common/types';
|
||||
import * as marked from 'vs/base/common/marked/marked';
|
||||
import { parse } from 'vs/base/common/marshalling';
|
||||
import { cloneAndChange } from 'vs/base/common/objects';
|
||||
import { LogLevel as _MainLogLevel } from 'vs/platform/log/common/log';
|
||||
import { coalesce, isNonEmptyArray } from 'vs/base/common/arrays';
|
||||
import { isDefined, isNumber, isString } from 'vs/base/common/types';
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import { RenderLineNumbersType } from 'vs/editor/common/config/editorOptions';
|
||||
import { IPosition } from 'vs/editor/common/core/position';
|
||||
import * as editorRange from 'vs/editor/common/core/range';
|
||||
import { ISelection } from 'vs/editor/common/core/selection';
|
||||
import { IContentDecorationRenderOptions, IDecorationOptions, IDecorationRenderOptions, IThemeDecorationRenderOptions } from 'vs/editor/common/editorCommon';
|
||||
import { EndOfLineSequence, TrackedRangeStickiness } from 'vs/editor/common/model';
|
||||
import * as modes from 'vs/editor/common/modes';
|
||||
import * as languageSelector from 'vs/editor/common/modes/languageSelector';
|
||||
import { EditorOverride, ITextEditorOptions } from 'vs/platform/editor/common/editor';
|
||||
import { IMarkerData, IRelatedInformation, MarkerSeverity, MarkerTag } from 'vs/platform/markers/common/markers';
|
||||
import { ProgressLocation as MainProgressLocation } from 'vs/platform/progress/common/progress';
|
||||
import * as extHostProtocol from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { CommandsConverter } from 'vs/workbench/api/common/extHostCommands';
|
||||
import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors';
|
||||
import { ExtHostNotebookController } from 'vs/workbench/api/common/extHostNotebook';
|
||||
import { EditorGroupColumn, SaveReason } from 'vs/workbench/common/editor';
|
||||
import * as notebooks from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
import { ISerializedTestResults, ITestItem, ITestState, SerializedTestResultItem } from 'vs/workbench/contrib/testing/common/testCollection';
|
||||
import * as search from 'vs/workbench/contrib/search/common/search';
|
||||
import { ISerializedTestResults, ITestItem, ITestMessage, ITestState, SerializedTestResultItem, TestItemExpandState } from 'vs/workbench/contrib/testing/common/testCollection';
|
||||
import { ACTIVE_GROUP, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService';
|
||||
import type * as vscode from 'vscode';
|
||||
import * as types from './extHostTypes';
|
||||
|
||||
export interface PositionLike {
|
||||
line: number;
|
||||
@@ -568,7 +567,7 @@ export namespace WorkspaceEdit {
|
||||
metadata: entry.metadata,
|
||||
resource: entry.uri,
|
||||
edit: {
|
||||
editType: notebooks.CellEditType.Metadata,
|
||||
editType: notebooks.CellEditType.PartialMetadata,
|
||||
index: entry.index,
|
||||
metadata: entry.newMetadata
|
||||
}
|
||||
@@ -594,7 +593,6 @@ export namespace WorkspaceEdit {
|
||||
resource: entry.uri,
|
||||
edit: {
|
||||
editType: notebooks.CellEditType.OutputItems,
|
||||
index: entry.index,
|
||||
outputId: entry.outputId,
|
||||
items: entry.newOutputItems?.map(NotebookCellOutputItem.from) || [],
|
||||
append: entry.append
|
||||
@@ -790,8 +788,8 @@ export namespace location {
|
||||
};
|
||||
}
|
||||
|
||||
export function to(value: modes.Location): types.Location {
|
||||
return new types.Location(value.uri, Range.to(value.range));
|
||||
export function to(value: extHostProtocol.ILocationDto): types.Location {
|
||||
return new types.Location(URI.revive(value.uri), Range.to(value.range));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -810,9 +808,9 @@ export namespace DefinitionLink {
|
||||
: undefined,
|
||||
};
|
||||
}
|
||||
export function to(value: modes.LocationLink): vscode.LocationLink {
|
||||
export function to(value: extHostProtocol.IDefinitionLinkDto): vscode.LocationLink {
|
||||
return {
|
||||
targetUri: value.uri,
|
||||
targetUri: URI.revive(value.uri),
|
||||
targetRange: Range.to(value.range),
|
||||
targetSelectionRange: value.targetSelectionRange
|
||||
? Range.to(value.targetSelectionRange)
|
||||
@@ -901,12 +899,13 @@ export namespace InlineValue {
|
||||
export namespace InlineValueContext {
|
||||
export function from(inlineValueContext: vscode.InlineValueContext): extHostProtocol.IInlineValueContextDto {
|
||||
return <extHostProtocol.IInlineValueContextDto>{
|
||||
frameId: inlineValueContext.frameId,
|
||||
stoppedLocation: Range.from(inlineValueContext.stoppedLocation)
|
||||
};
|
||||
}
|
||||
|
||||
export function to(inlineValueContext: extHostProtocol.IInlineValueContextDto): types.InlineValueContext {
|
||||
return new types.InlineValueContext(Range.to(inlineValueContext.stoppedLocation));
|
||||
return new types.InlineValueContext(inlineValueContext.frameId, Range.to(inlineValueContext.stoppedLocation));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1419,7 +1418,7 @@ export namespace NotebookCellRange {
|
||||
export namespace NotebookCellMetadata {
|
||||
|
||||
export function to(data: notebooks.NotebookCellMetadata): types.NotebookCellMetadata {
|
||||
return new types.NotebookCellMetadata(data.editable, data.breakpointMargin, data.runnable, data.hasExecutionOrder, data.executionOrder, data.runState, data.runStartTime, data.statusMessage, data.lastRunDuration, data.inputCollapsed, data.outputCollapsed, data.custom);
|
||||
return new types.NotebookCellMetadata(data.editable, data.breakpointMargin, data.hasExecutionOrder, data.statusMessage, data.inputCollapsed, data.outputCollapsed, data.custom);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1430,9 +1429,26 @@ export namespace NotebookDocumentMetadata {
|
||||
}
|
||||
|
||||
export function to(data: notebooks.NotebookDocumentMetadata): types.NotebookDocumentMetadata {
|
||||
return new types.NotebookDocumentMetadata(data.editable, data.runnable, data.cellEditable, data.cellRunnable, data.cellHasExecutionOrder, data.displayOrder, data.custom, data.runState, data.trusted);
|
||||
return new types.NotebookDocumentMetadata(data.editable, data.cellEditable, data.cellHasExecutionOrder, data.custom, data.trusted);
|
||||
}
|
||||
}
|
||||
|
||||
export namespace NotebookCellPreviousExecutionResult {
|
||||
export function to(data: notebooks.NotebookCellMetadata): vscode.NotebookCellExecutionSummary {
|
||||
return {
|
||||
duration: data.lastRunDuration,
|
||||
executionOrder: data.executionOrder,
|
||||
success: data.lastRunSuccess
|
||||
};
|
||||
}
|
||||
|
||||
export function from(data: vscode.NotebookCellExecutionSummary): Partial<notebooks.NotebookCellMetadata> {
|
||||
return {
|
||||
lastRunSuccess: data.success,
|
||||
lastRunDuration: data.duration,
|
||||
executionOrder: data.executionOrder
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export namespace NotebookCellKind {
|
||||
@@ -1461,21 +1477,26 @@ export namespace NotebookCellData {
|
||||
|
||||
export function from(data: vscode.NotebookCellData): notebooks.ICellDto2 {
|
||||
return {
|
||||
cellKind: NotebookCellKind.from(data.cellKind),
|
||||
cellKind: NotebookCellKind.from(data.kind),
|
||||
language: data.language,
|
||||
source: data.source,
|
||||
metadata: data.metadata,
|
||||
outputs: data.outputs.map(output => ({
|
||||
outputId: output.id,
|
||||
metadata: output.metadata,
|
||||
outputs: (output.outputs || []).map(op => ({
|
||||
mime: op.mime,
|
||||
value: op.value,
|
||||
metadata: op.metadata
|
||||
}))
|
||||
}))
|
||||
metadata: {
|
||||
...data.metadata,
|
||||
...NotebookCellPreviousExecutionResult.from(data.latestExecutionSummary ?? {})
|
||||
},
|
||||
outputs: data.outputs ? data.outputs.map(NotebookCellOutput.from) : []
|
||||
};
|
||||
}
|
||||
|
||||
export function to(data: notebooks.ICellDto2): vscode.NotebookCellData {
|
||||
return new types.NotebookCellData(
|
||||
NotebookCellKind.to(data.cellKind),
|
||||
data.source,
|
||||
data.language,
|
||||
data.outputs ? data.outputs.map(NotebookCellOutput.to) : undefined,
|
||||
data.metadata ? NotebookCellMetadata.to(data.metadata) : undefined,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export namespace NotebookCellOutputItem {
|
||||
@@ -1591,64 +1612,111 @@ export namespace NotebookDecorationRenderOptions {
|
||||
}
|
||||
}
|
||||
|
||||
export namespace NotebookDocumentContentOptions {
|
||||
export function from(options: vscode.NotebookDocumentContentOptions | undefined): notebooks.TransientOptions {
|
||||
return {
|
||||
transientOutputs: options ? options.transientOutputs : false,
|
||||
transientMetadata: {
|
||||
...(options?.transientMetadata ?? {}),
|
||||
...{
|
||||
executionOrder: true,
|
||||
lastRunDuration: true,
|
||||
runState: true,
|
||||
runStartTime: true,
|
||||
lastRunSuccess: true
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export namespace TestState {
|
||||
export function from(item: vscode.TestState): ITestState {
|
||||
return {
|
||||
state: item.state,
|
||||
duration: item.duration,
|
||||
messages: item.messages?.map(message => ({
|
||||
message: MarkdownString.fromStrict(message.message) || '',
|
||||
severity: message.severity,
|
||||
expectedOutput: message.expectedOutput,
|
||||
actualOutput: message.actualOutput,
|
||||
location: message.location ? location.from(message.location) as any : undefined,
|
||||
})) ?? [],
|
||||
messages: item.messages.map(TestMessage.from),
|
||||
};
|
||||
}
|
||||
|
||||
export function to(item: ITestState): vscode.TestState {
|
||||
const ts = new types.TestState(item.state);
|
||||
ts.duration = item.duration;
|
||||
ts.messages = item.messages.map(TestMessage.to);
|
||||
return ts;
|
||||
}
|
||||
}
|
||||
|
||||
export namespace TestMessage {
|
||||
export function from(message: vscode.TestMessage): ITestMessage {
|
||||
return {
|
||||
state: item.state,
|
||||
messages: item.messages.map(message => ({
|
||||
message: typeof message.message === 'string' ? message.message : MarkdownString.to(message.message),
|
||||
severity: message.severity,
|
||||
expectedOutput: message.expectedOutput,
|
||||
actualOutput: message.actualOutput,
|
||||
location: message.location && location.to({
|
||||
range: message.location.range,
|
||||
uri: URI.revive(message.location.uri)
|
||||
}),
|
||||
})),
|
||||
duration: item.duration,
|
||||
message: MarkdownString.fromStrict(message.message) || '',
|
||||
severity: message.severity,
|
||||
expectedOutput: message.expectedOutput,
|
||||
actualOutput: message.actualOutput,
|
||||
location: message.location ? location.from(message.location) as any : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
export function to(item: ITestMessage): vscode.TestMessage {
|
||||
const message = new types.TestMessage(typeof item.message === 'string' ? item.message : MarkdownString.to(item.message));
|
||||
message.severity = item.severity;
|
||||
message.actualOutput = item.actualOutput;
|
||||
message.expectedOutput = item.expectedOutput;
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
||||
export namespace TestItem {
|
||||
export type Raw = vscode.TestItem;
|
||||
|
||||
export function from(item: vscode.TestItem): ITestItem {
|
||||
return {
|
||||
extId: item.id,
|
||||
label: item.label,
|
||||
location: item.location ? location.from(item.location) as any : undefined,
|
||||
uri: item.uri,
|
||||
range: Range.from(item.range),
|
||||
debuggable: item.debuggable ?? false,
|
||||
description: item.description,
|
||||
runnable: item.runnable ?? true,
|
||||
expandable: item.expandable,
|
||||
};
|
||||
}
|
||||
|
||||
export function toShallow(item: ITestItem): Omit<vscode.RequiredTestItem, 'children'> {
|
||||
export function fromResultSnapshot(item: vscode.TestResultSnapshot): ITestItem {
|
||||
return {
|
||||
extId: item.id,
|
||||
label: item.label,
|
||||
uri: item.uri,
|
||||
range: Range.from(item.range),
|
||||
debuggable: false,
|
||||
description: item.description,
|
||||
runnable: true,
|
||||
expandable: true,
|
||||
};
|
||||
}
|
||||
|
||||
export function toPlain(item: ITestItem): Omit<vscode.TestItem, 'children' | 'invalidate' | 'discoverChildren'> {
|
||||
return {
|
||||
id: item.extId,
|
||||
label: item.label,
|
||||
location: item.location && location.to({
|
||||
range: item.location.range,
|
||||
uri: URI.revive(item.location.uri)
|
||||
}),
|
||||
uri: URI.revive(item.uri),
|
||||
range: Range.to(item.range),
|
||||
expandable: item.expandable,
|
||||
debuggable: item.debuggable,
|
||||
description: item.description,
|
||||
runnable: item.runnable,
|
||||
};
|
||||
}
|
||||
|
||||
export function to(item: ITestItem): types.TestItem {
|
||||
const testItem = new types.TestItem(item.extId, item.label, URI.revive(item.uri), item.expandable);
|
||||
testItem.range = Range.to(item.range);
|
||||
testItem.debuggable = item.debuggable;
|
||||
testItem.description = item.description;
|
||||
testItem.runnable = item.runnable;
|
||||
return testItem;
|
||||
}
|
||||
}
|
||||
|
||||
export namespace TestResults {
|
||||
@@ -1659,7 +1727,7 @@ export namespace TestResults {
|
||||
items: [],
|
||||
};
|
||||
|
||||
const queue: [parent: SerializedTestResultItem | null, children: Iterable<vscode.TestItemWithResults>][] = [
|
||||
const queue: [parent: SerializedTestResultItem | null, children: Iterable<vscode.TestResultSnapshot>][] = [
|
||||
[null, results.results],
|
||||
];
|
||||
|
||||
@@ -1669,11 +1737,12 @@ export namespace TestResults {
|
||||
const serializedItem: SerializedTestResultItem = {
|
||||
children: item.children?.map(c => c.id) ?? [],
|
||||
computedState: item.result.state,
|
||||
item: TestItem.from(item),
|
||||
item: TestItem.fromResultSnapshot(item),
|
||||
state: TestState.from(item.result),
|
||||
retired: undefined,
|
||||
expand: TestItemExpandState.Expanded,
|
||||
parent: parent?.item.extId ?? null,
|
||||
providerId: '',
|
||||
src: { provider: '', tree: -1 },
|
||||
direct: !parent,
|
||||
};
|
||||
|
||||
@@ -1687,8 +1756,8 @@ export namespace TestResults {
|
||||
return serialized;
|
||||
}
|
||||
|
||||
const convertTestResultItem = (item: SerializedTestResultItem, byInternalId: Map<string, SerializedTestResultItem>): vscode.TestItemWithResults => ({
|
||||
...TestItem.toShallow(item.item),
|
||||
const convertTestResultItem = (item: SerializedTestResultItem, byInternalId: Map<string, SerializedTestResultItem>): vscode.TestResultSnapshot => ({
|
||||
...TestItem.toPlain(item.item),
|
||||
result: TestState.to(item.state),
|
||||
children: item.children
|
||||
.map(c => byInternalId.get(c))
|
||||
@@ -1712,3 +1781,16 @@ export namespace TestResults {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export namespace CodeActionTriggerKind {
|
||||
|
||||
export function to(value: modes.CodeActionTriggerType): types.CodeActionTriggerKind {
|
||||
switch (value) {
|
||||
case modes.CodeActionTriggerType.Invoke:
|
||||
return types.CodeActionTriggerKind.Invoke;
|
||||
|
||||
case modes.CodeActionTriggerType.Auto:
|
||||
return types.CodeActionTriggerKind.Automatic;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 { CellEditType, ICellEditOperation, notebookDocumentMetadataDefaults, NOTEBOOK_DISPLAY_ORDER } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
import { CellEditType, ICellEditOperation } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
import type * as vscode from 'vscode';
|
||||
|
||||
function es5ClassCompat(target: Function): any {
|
||||
@@ -673,7 +673,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, edit: { editType: CellEditType.DocumentMetadata, metadata: { ...notebookDocumentMetadataDefaults, ...value } }, notebookMetadata: value });
|
||||
this._edits.push({ _type: FileEditType.Cell, metadata, uri, edit: { editType: CellEditType.DocumentMetadata, metadata: value }, notebookMetadata: value });
|
||||
}
|
||||
|
||||
replaceNotebookCells(uri: URI, start: number, end: number, cells: vscode.NotebookCellData[], metadata?: vscode.WorkspaceEditEntryMetadata): void {
|
||||
@@ -707,7 +707,7 @@ export class WorkspaceEdit implements vscode.WorkspaceEdit {
|
||||
}
|
||||
|
||||
replaceNotebookCellMetadata(uri: URI, index: number, cellMetadata: vscode.NotebookCellMetadata, metadata?: vscode.WorkspaceEditEntryMetadata): void {
|
||||
this._edits.push({ _type: FileEditType.Cell, metadata, uri, edit: { editType: CellEditType.Metadata, index, metadata: cellMetadata } });
|
||||
this._edits.push({ _type: FileEditType.Cell, metadata, uri, edit: { editType: CellEditType.PartialMetadata, index, metadata: cellMetadata } });
|
||||
}
|
||||
|
||||
// --- text
|
||||
@@ -1178,9 +1178,9 @@ export class DocumentSymbol {
|
||||
}
|
||||
|
||||
|
||||
export enum CodeActionTrigger {
|
||||
Automatic = 1,
|
||||
Manual = 2,
|
||||
export enum CodeActionTriggerKind {
|
||||
Invoke = 1,
|
||||
Automatic = 2,
|
||||
}
|
||||
|
||||
@es5ClassCompat
|
||||
@@ -2476,9 +2476,11 @@ export class InlineValueEvaluatableExpression implements vscode.InlineValueEvalu
|
||||
@es5ClassCompat
|
||||
export class InlineValueContext implements vscode.InlineValueContext {
|
||||
|
||||
readonly frameId: number;
|
||||
readonly stoppedLocation: vscode.Range;
|
||||
|
||||
constructor(range: vscode.Range) {
|
||||
constructor(frameId: number, range: vscode.Range) {
|
||||
this.frameId = frameId;
|
||||
this.stoppedLocation = range;
|
||||
}
|
||||
}
|
||||
@@ -2906,14 +2908,17 @@ export class NotebookCellRange {
|
||||
return this._end;
|
||||
}
|
||||
|
||||
get isEmpty(): boolean {
|
||||
return this._start === this._end;
|
||||
}
|
||||
|
||||
constructor(start: number, end: number) {
|
||||
// todo@rebornix
|
||||
// if (start < 0) {
|
||||
// throw illegalArgument('start must be positive');
|
||||
// }
|
||||
// if (end < start) {
|
||||
// throw illegalArgument('end cannot be smaller than start');
|
||||
// }
|
||||
if (start < 0) {
|
||||
throw illegalArgument('start must be positive');
|
||||
}
|
||||
if (end < start) {
|
||||
throw illegalArgument('end cannot be smaller than start');
|
||||
}
|
||||
this._start = start;
|
||||
this._end = end;
|
||||
}
|
||||
@@ -2924,13 +2929,8 @@ export class NotebookCellMetadata {
|
||||
constructor(
|
||||
readonly editable?: boolean,
|
||||
readonly breakpointMargin?: boolean,
|
||||
readonly runnable?: boolean,
|
||||
readonly hasExecutionOrder?: boolean,
|
||||
readonly executionOrder?: number,
|
||||
readonly runState?: NotebookCellRunState,
|
||||
readonly runStartTime?: number,
|
||||
readonly statusMessage?: string,
|
||||
readonly lastRunDuration?: number,
|
||||
readonly inputCollapsed?: boolean,
|
||||
readonly outputCollapsed?: boolean,
|
||||
readonly custom?: Record<string, any>,
|
||||
@@ -2939,19 +2939,14 @@ export class NotebookCellMetadata {
|
||||
with(change: {
|
||||
editable?: boolean | null,
|
||||
breakpointMargin?: boolean | null,
|
||||
runnable?: boolean | null,
|
||||
hasExecutionOrder?: boolean | null,
|
||||
executionOrder?: number | null,
|
||||
runState?: NotebookCellRunState | null,
|
||||
runStartTime?: number | null,
|
||||
statusMessage?: string | null,
|
||||
lastRunDuration?: number | null,
|
||||
inputCollapsed?: boolean | null,
|
||||
outputCollapsed?: boolean | null,
|
||||
custom?: Record<string, any> | null,
|
||||
}): NotebookCellMetadata {
|
||||
|
||||
let { editable, breakpointMargin, runnable, hasExecutionOrder, executionOrder, runState, runStartTime, statusMessage, lastRunDuration, inputCollapsed, outputCollapsed, custom } = change;
|
||||
let { editable, breakpointMargin, hasExecutionOrder, statusMessage, inputCollapsed, outputCollapsed, custom } = change;
|
||||
|
||||
if (editable === undefined) {
|
||||
editable = this.editable;
|
||||
@@ -2963,41 +2958,16 @@ export class NotebookCellMetadata {
|
||||
} else if (breakpointMargin === null) {
|
||||
breakpointMargin = undefined;
|
||||
}
|
||||
if (runnable === undefined) {
|
||||
runnable = this.runnable;
|
||||
} else if (runnable === null) {
|
||||
runnable = undefined;
|
||||
}
|
||||
if (hasExecutionOrder === undefined) {
|
||||
hasExecutionOrder = this.hasExecutionOrder;
|
||||
} else if (hasExecutionOrder === null) {
|
||||
hasExecutionOrder = undefined;
|
||||
}
|
||||
if (executionOrder === undefined) {
|
||||
executionOrder = this.executionOrder;
|
||||
} else if (executionOrder === null) {
|
||||
executionOrder = undefined;
|
||||
}
|
||||
if (runState === undefined) {
|
||||
runState = this.runState;
|
||||
} else if (runState === null) {
|
||||
runState = undefined;
|
||||
}
|
||||
if (runStartTime === undefined) {
|
||||
runStartTime = this.runStartTime;
|
||||
} else if (runStartTime === null) {
|
||||
runStartTime = undefined;
|
||||
}
|
||||
if (statusMessage === undefined) {
|
||||
statusMessage = this.statusMessage;
|
||||
} else if (statusMessage === null) {
|
||||
statusMessage = undefined;
|
||||
}
|
||||
if (lastRunDuration === undefined) {
|
||||
lastRunDuration = this.lastRunDuration;
|
||||
} else if (lastRunDuration === null) {
|
||||
lastRunDuration = undefined;
|
||||
}
|
||||
if (inputCollapsed === undefined) {
|
||||
inputCollapsed = this.inputCollapsed;
|
||||
} else if (inputCollapsed === null) {
|
||||
@@ -3016,13 +2986,8 @@ export class NotebookCellMetadata {
|
||||
|
||||
if (editable === this.editable &&
|
||||
breakpointMargin === this.breakpointMargin &&
|
||||
runnable === this.runnable &&
|
||||
hasExecutionOrder === this.hasExecutionOrder &&
|
||||
executionOrder === this.executionOrder &&
|
||||
runState === this.runState &&
|
||||
runStartTime === this.runStartTime &&
|
||||
statusMessage === this.statusMessage &&
|
||||
lastRunDuration === this.lastRunDuration &&
|
||||
inputCollapsed === this.inputCollapsed &&
|
||||
outputCollapsed === this.outputCollapsed &&
|
||||
custom === this.custom
|
||||
@@ -3033,13 +2998,8 @@ export class NotebookCellMetadata {
|
||||
return new NotebookCellMetadata(
|
||||
editable,
|
||||
breakpointMargin,
|
||||
runnable,
|
||||
hasExecutionOrder,
|
||||
executionOrder,
|
||||
runState,
|
||||
runStartTime,
|
||||
statusMessage,
|
||||
lastRunDuration,
|
||||
inputCollapsed,
|
||||
outputCollapsed,
|
||||
custom,
|
||||
@@ -3051,70 +3011,42 @@ export class NotebookDocumentMetadata {
|
||||
|
||||
constructor(
|
||||
readonly editable: boolean = true,
|
||||
readonly runnable: boolean = true,
|
||||
readonly cellEditable: boolean = true,
|
||||
readonly cellRunnable: boolean = true,
|
||||
readonly cellHasExecutionOrder: boolean = true,
|
||||
readonly displayOrder: vscode.GlobPattern[] = NOTEBOOK_DISPLAY_ORDER,
|
||||
readonly custom: { [key: string]: any; } = {},
|
||||
readonly runState: NotebookRunState = NotebookRunState.Idle,
|
||||
readonly trusted: boolean = true,
|
||||
) { }
|
||||
|
||||
with(change: {
|
||||
editable?: boolean | null,
|
||||
runnable?: boolean | null,
|
||||
cellEditable?: boolean | null,
|
||||
cellRunnable?: boolean | null,
|
||||
cellHasExecutionOrder?: boolean | null,
|
||||
displayOrder?: vscode.GlobPattern[] | null,
|
||||
custom?: { [key: string]: any; } | null,
|
||||
runState?: NotebookRunState | null,
|
||||
trusted?: boolean | null,
|
||||
}): NotebookDocumentMetadata {
|
||||
|
||||
let { editable, runnable, cellEditable, cellRunnable, cellHasExecutionOrder, displayOrder, custom, runState, trusted } = change;
|
||||
let { editable, cellEditable, cellHasExecutionOrder, custom, trusted } = change;
|
||||
|
||||
if (editable === undefined) {
|
||||
editable = this.editable;
|
||||
} else if (editable === null) {
|
||||
editable = undefined;
|
||||
}
|
||||
if (runnable === undefined) {
|
||||
runnable = this.runnable;
|
||||
} else if (runnable === null) {
|
||||
runnable = undefined;
|
||||
}
|
||||
if (cellEditable === undefined) {
|
||||
cellEditable = this.cellEditable;
|
||||
} else if (cellEditable === null) {
|
||||
cellEditable = undefined;
|
||||
}
|
||||
if (cellRunnable === undefined) {
|
||||
cellRunnable = this.cellRunnable;
|
||||
} else if (cellRunnable === null) {
|
||||
cellRunnable = undefined;
|
||||
}
|
||||
if (cellHasExecutionOrder === undefined) {
|
||||
cellHasExecutionOrder = this.cellHasExecutionOrder;
|
||||
} else if (cellHasExecutionOrder === null) {
|
||||
cellHasExecutionOrder = undefined;
|
||||
}
|
||||
if (displayOrder === undefined) {
|
||||
displayOrder = this.displayOrder;
|
||||
} else if (displayOrder === null) {
|
||||
displayOrder = undefined;
|
||||
}
|
||||
if (custom === undefined) {
|
||||
custom = this.custom;
|
||||
} else if (custom === null) {
|
||||
custom = undefined;
|
||||
}
|
||||
if (runState === undefined) {
|
||||
runState = this.runState;
|
||||
} else if (runState === null) {
|
||||
runState = undefined;
|
||||
}
|
||||
if (trusted === undefined) {
|
||||
trusted = this.trusted;
|
||||
} else if (trusted === null) {
|
||||
@@ -3122,13 +3054,9 @@ export class NotebookDocumentMetadata {
|
||||
}
|
||||
|
||||
if (editable === this.editable &&
|
||||
runnable === this.runnable &&
|
||||
cellEditable === this.cellEditable &&
|
||||
cellRunnable === this.cellRunnable &&
|
||||
cellHasExecutionOrder === this.cellHasExecutionOrder &&
|
||||
displayOrder === this.displayOrder &&
|
||||
custom === this.custom &&
|
||||
runState === this.runState &&
|
||||
trusted === this.trusted
|
||||
) {
|
||||
return this;
|
||||
@@ -3137,20 +3065,45 @@ export class NotebookDocumentMetadata {
|
||||
|
||||
return new NotebookDocumentMetadata(
|
||||
editable,
|
||||
runnable,
|
||||
cellEditable,
|
||||
cellRunnable,
|
||||
cellHasExecutionOrder,
|
||||
displayOrder,
|
||||
custom,
|
||||
runState,
|
||||
trusted
|
||||
);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
export class NotebookCellData {
|
||||
|
||||
kind: NotebookCellKind;
|
||||
source: string;
|
||||
language: string;
|
||||
outputs?: NotebookCellOutput[];
|
||||
metadata?: NotebookCellMetadata;
|
||||
latestExecutionSummary?: vscode.NotebookCellExecutionSummary;
|
||||
|
||||
constructor(kind: NotebookCellKind, source: string, language: string, outputs?: NotebookCellOutput[], metadata?: NotebookCellMetadata, latestExecutionSummary?: vscode.NotebookCellExecutionSummary) {
|
||||
this.kind = kind;
|
||||
this.source = source;
|
||||
this.language = language;
|
||||
this.outputs = outputs ?? [];
|
||||
this.metadata = metadata;
|
||||
this.latestExecutionSummary = latestExecutionSummary;
|
||||
}
|
||||
}
|
||||
|
||||
export class NotebookData {
|
||||
|
||||
cells: NotebookCellData[];
|
||||
metadata: NotebookDocumentMetadata;
|
||||
|
||||
constructor(cells: NotebookCellData[], metadata?: NotebookDocumentMetadata) {
|
||||
this.cells = cells;
|
||||
this.metadata = metadata ?? new NotebookDocumentMetadata();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class NotebookCellOutputItem {
|
||||
|
||||
static isNotebookCellOutputItem(obj: unknown): obj is vscode.NotebookCellOutputItem {
|
||||
@@ -3191,16 +3144,10 @@ export enum NotebookCellKind {
|
||||
Code = 2
|
||||
}
|
||||
|
||||
export enum NotebookCellRunState {
|
||||
Running = 1,
|
||||
Idle = 2,
|
||||
Success = 3,
|
||||
Error = 4
|
||||
}
|
||||
|
||||
export enum NotebookRunState {
|
||||
Running = 1,
|
||||
Idle = 2
|
||||
export enum NotebookCellExecutionState {
|
||||
Idle = 1,
|
||||
Pending = 2,
|
||||
Executing = 3,
|
||||
}
|
||||
|
||||
export enum NotebookCellStatusBarAlignment {
|
||||
@@ -3276,7 +3223,7 @@ export class LinkedEditingRanges {
|
||||
}
|
||||
|
||||
//#region Testing
|
||||
export enum TestRunState {
|
||||
export enum TestResult {
|
||||
Unset = 0,
|
||||
Queued = 1,
|
||||
Running = 2,
|
||||
@@ -3293,9 +3240,155 @@ export enum TestMessageSeverity {
|
||||
Hint = 3
|
||||
}
|
||||
|
||||
export type RequiredTestItem = vscode.RequiredTestItem;
|
||||
export const TestItemHookProperty = Symbol('TestItemHookProperty');
|
||||
|
||||
export type TestItem = vscode.TestItem;
|
||||
export interface ITestItemHook {
|
||||
created(item: vscode.TestItem): void;
|
||||
setProp<K extends keyof vscode.TestItem>(key: K, value: vscode.TestItem[K]): void;
|
||||
invalidate(id: string): void;
|
||||
delete(id: string): void;
|
||||
}
|
||||
|
||||
const testItemPropAccessor = <K extends keyof vscode.TestItem>(item: TestItem, key: K, defaultValue: vscode.TestItem[K]) => {
|
||||
let value = defaultValue;
|
||||
return {
|
||||
enumerable: true,
|
||||
configurable: false,
|
||||
get() {
|
||||
return value;
|
||||
},
|
||||
set(newValue: vscode.TestItem[K]) {
|
||||
item[TestItemHookProperty]?.setProp(key, newValue);
|
||||
value = newValue;
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export class TestChildrenCollection implements vscode.TestChildrenCollection<vscode.TestItem> {
|
||||
#map = new Map<string, vscode.TestItem>();
|
||||
#hookRef: () => ITestItemHook | undefined;
|
||||
|
||||
public get size() {
|
||||
return this.#map.size;
|
||||
}
|
||||
|
||||
constructor(hookRef: () => ITestItemHook | undefined) {
|
||||
this.#hookRef = hookRef;
|
||||
}
|
||||
|
||||
public add(child: vscode.TestItem) {
|
||||
const map = this.#map;
|
||||
const hook = this.#hookRef();
|
||||
|
||||
const existing = map.get(child.id);
|
||||
if (existing === child) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (existing) {
|
||||
hook?.delete(child.id);
|
||||
}
|
||||
|
||||
map.set(child.id, child);
|
||||
hook?.created(child);
|
||||
}
|
||||
|
||||
public get(id: string) {
|
||||
return this.#map.get(id);
|
||||
}
|
||||
|
||||
public clear() {
|
||||
for (const key of this.#map.keys()) {
|
||||
this.delete(key);
|
||||
}
|
||||
}
|
||||
|
||||
public delete(childOrId: vscode.TestItem | string) {
|
||||
const id = typeof childOrId === 'string' ? childOrId : childOrId.id;
|
||||
if (this.#map.has(id)) {
|
||||
this.#map.delete(id);
|
||||
this.#hookRef()?.delete(id);
|
||||
}
|
||||
}
|
||||
|
||||
public toJSON() {
|
||||
return [...this.#map.values()];
|
||||
}
|
||||
|
||||
public [Symbol.iterator]() {
|
||||
return this.#map.values();
|
||||
}
|
||||
}
|
||||
|
||||
export class TestItem implements vscode.TestItem {
|
||||
public id!: string;
|
||||
public range!: vscode.Range | undefined;
|
||||
public description!: string | undefined;
|
||||
public runnable!: boolean;
|
||||
public debuggable!: boolean;
|
||||
public children!: TestChildrenCollection;
|
||||
public uri!: vscode.Uri;
|
||||
public [TestItemHookProperty]!: ITestItemHook | undefined;
|
||||
|
||||
constructor(id: string, public label: string, uri: vscode.Uri, public expandable: boolean) {
|
||||
Object.defineProperties(this, {
|
||||
id: {
|
||||
value: id,
|
||||
enumerable: true,
|
||||
writable: false,
|
||||
},
|
||||
uri: {
|
||||
value: uri,
|
||||
enumerable: true,
|
||||
writable: false,
|
||||
},
|
||||
children: {
|
||||
value: new TestChildrenCollection(() => this[TestItemHookProperty]),
|
||||
enumerable: true,
|
||||
writable: false,
|
||||
},
|
||||
[TestItemHookProperty]: {
|
||||
enumerable: false,
|
||||
writable: true,
|
||||
configurable: false,
|
||||
},
|
||||
range: testItemPropAccessor(this, 'range', undefined),
|
||||
description: testItemPropAccessor(this, 'description', undefined),
|
||||
runnable: testItemPropAccessor(this, 'runnable', true),
|
||||
debuggable: testItemPropAccessor(this, 'debuggable', true),
|
||||
});
|
||||
}
|
||||
|
||||
public invalidate() {
|
||||
this[TestItemHookProperty]?.invalidate(this.id);
|
||||
}
|
||||
|
||||
public discoverChildren(progress: vscode.Progress<{ busy: boolean }>, _token: vscode.CancellationToken) {
|
||||
progress.report({ busy: false });
|
||||
}
|
||||
}
|
||||
|
||||
export class TestState implements vscode.TestState {
|
||||
public messages: TestMessage[] = [];
|
||||
public duration?: number;
|
||||
|
||||
constructor(public state: TestResult) { }
|
||||
}
|
||||
|
||||
export class TestMessage implements vscode.TestMessage {
|
||||
public severity = TestMessageSeverity.Error;
|
||||
public expectedOutput?: string;
|
||||
public actualOutput?: string;
|
||||
|
||||
public static diff(message: string | vscode.MarkdownString, expected: string, actual: string) {
|
||||
const msg = new TestMessage(message);
|
||||
msg.expectedOutput = expected;
|
||||
msg.actualOutput = actual;
|
||||
return msg;
|
||||
}
|
||||
|
||||
constructor(public message: string | vscode.MarkdownString) { }
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
@@ -3311,3 +3404,11 @@ export enum WorkspaceTrustState {
|
||||
Trusted = 1,
|
||||
Unknown = 2
|
||||
}
|
||||
|
||||
export enum PortAutoForwardAction {
|
||||
Notify = 1,
|
||||
OpenBrowser = 2,
|
||||
OpenPreview = 3,
|
||||
Silent = 4,
|
||||
Ignore = 5
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import * as modes from 'vs/editor/common/modes';
|
||||
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IExtHostApiDeprecationService } from 'vs/workbench/api/common/extHostApiDeprecationService';
|
||||
@@ -97,7 +96,7 @@ export class ExtHostWebview implements vscode.Webview {
|
||||
|
||||
public set options(newOptions: vscode.WebviewOptions) {
|
||||
this.assertNotDisposed();
|
||||
this.#proxy.$setOptions(this.#handle, convertWebviewOptions(this.#extension, this.#workspace, newOptions));
|
||||
this.#proxy.$setOptions(this.#handle, serializeWebviewOptions(this.#extension, this.#workspace, newOptions));
|
||||
this.#options = newOptions;
|
||||
}
|
||||
|
||||
@@ -148,7 +147,7 @@ export class ExtHostWebviews implements extHostProtocol.ExtHostWebviewsShape {
|
||||
this._logService.warn(`${extensionId} created a webview without a content security policy: https://aka.ms/vscode-webview-missing-csp`);
|
||||
}
|
||||
|
||||
public createNewWebview(handle: string, options: modes.IWebviewOptions & modes.IWebviewPanelOptions, extension: IExtensionDescription): ExtHostWebview {
|
||||
public createNewWebview(handle: string, options: extHostProtocol.IWebviewOptions, extension: IExtensionDescription): ExtHostWebview {
|
||||
const webview = new ExtHostWebview(handle, this._webviewProxy, reviveOptions(options), this.initData, this.workspace, extension, this._deprecationService);
|
||||
this._webviews.set(handle, webview);
|
||||
|
||||
@@ -170,22 +169,24 @@ export function toExtensionData(extension: IExtensionDescription): extHostProtoc
|
||||
return { id: extension.identifier, location: extension.extensionLocation };
|
||||
}
|
||||
|
||||
export function convertWebviewOptions(
|
||||
export function serializeWebviewOptions(
|
||||
extension: IExtensionDescription,
|
||||
workspace: IExtHostWorkspace | undefined,
|
||||
options: vscode.WebviewPanelOptions & vscode.WebviewOptions,
|
||||
): modes.IWebviewOptions {
|
||||
options: vscode.WebviewOptions,
|
||||
): extHostProtocol.IWebviewOptions {
|
||||
return {
|
||||
...options,
|
||||
enableCommandUris: options.enableCommandUris,
|
||||
enableScripts: options.enableScripts,
|
||||
portMapping: options.portMapping,
|
||||
localResourceRoots: options.localResourceRoots || getDefaultLocalResourceRoots(extension, workspace)
|
||||
};
|
||||
}
|
||||
|
||||
function reviveOptions(
|
||||
options: modes.IWebviewOptions & modes.IWebviewPanelOptions
|
||||
): vscode.WebviewOptions {
|
||||
export function reviveOptions(options: extHostProtocol.IWebviewOptions): vscode.WebviewOptions {
|
||||
return {
|
||||
...options,
|
||||
enableCommandUris: options.enableCommandUris,
|
||||
enableScripts: options.enableScripts,
|
||||
portMapping: options.portMapping,
|
||||
localResourceRoots: options.localResourceRoots?.map(components => URI.from(components)),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -7,10 +7,9 @@ import { Emitter } from 'vs/base/common/event';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import * as modes from 'vs/editor/common/modes';
|
||||
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters';
|
||||
import { convertWebviewOptions, ExtHostWebview, ExtHostWebviews, toExtensionData } from 'vs/workbench/api/common/extHostWebview';
|
||||
import { serializeWebviewOptions, ExtHostWebview, ExtHostWebviews, toExtensionData } from 'vs/workbench/api/common/extHostWebview';
|
||||
import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace';
|
||||
import { EditorGroupColumn } from 'vs/workbench/common/editor';
|
||||
import type * as vscode from 'vscode';
|
||||
@@ -48,14 +47,14 @@ class ExtHostWebviewPanel extends Disposable implements vscode.WebviewPanel {
|
||||
viewType: string,
|
||||
title: string,
|
||||
viewColumn: vscode.ViewColumn | undefined,
|
||||
editorOptions: vscode.WebviewPanelOptions,
|
||||
panelOptions: vscode.WebviewPanelOptions,
|
||||
webview: ExtHostWebview
|
||||
) {
|
||||
super();
|
||||
this.#handle = handle;
|
||||
this.#proxy = proxy;
|
||||
this.#viewType = viewType;
|
||||
this.#options = editorOptions;
|
||||
this.#options = panelOptions;
|
||||
this.#viewColumn = viewColumn;
|
||||
this.#title = title;
|
||||
this.#webview = webview;
|
||||
@@ -201,7 +200,11 @@ export class ExtHostWebviewPanels implements extHostProtocol.ExtHostWebviewPanel
|
||||
};
|
||||
|
||||
const handle = ExtHostWebviewPanels.newHandle();
|
||||
this._proxy.$createWebviewPanel(toExtensionData(extension), handle, viewType, title, webviewShowOptions, convertWebviewOptions(extension, this.workspace, options));
|
||||
this._proxy.$createWebviewPanel(toExtensionData(extension), handle, viewType, {
|
||||
title,
|
||||
panelOptions: serializeWebviewPanelOptions(options),
|
||||
webviewOptions: serializeWebviewOptions(extension, this.workspace, options),
|
||||
}, webviewShowOptions);
|
||||
|
||||
const webview = this.webviews.createNewWebview(handle, options, extension);
|
||||
const panel = this.createNewWebviewPanel(handle, viewType, title, viewColumn, options, webview);
|
||||
@@ -271,10 +274,13 @@ export class ExtHostWebviewPanels implements extHostProtocol.ExtHostWebviewPanel
|
||||
async $deserializeWebviewPanel(
|
||||
webviewHandle: extHostProtocol.WebviewHandle,
|
||||
viewType: string,
|
||||
title: string,
|
||||
state: any,
|
||||
position: EditorGroupColumn,
|
||||
options: modes.IWebviewOptions & modes.IWebviewPanelOptions
|
||||
initData: {
|
||||
title: string;
|
||||
state: any;
|
||||
webviewOptions: extHostProtocol.IWebviewOptions;
|
||||
panelOptions: extHostProtocol.IWebviewPanelOptions;
|
||||
},
|
||||
position: EditorGroupColumn
|
||||
): Promise<void> {
|
||||
const entry = this._serializers.get(viewType);
|
||||
if (!entry) {
|
||||
@@ -282,12 +288,12 @@ export class ExtHostWebviewPanels implements extHostProtocol.ExtHostWebviewPanel
|
||||
}
|
||||
const { serializer, extension } = entry;
|
||||
|
||||
const webview = this.webviews.createNewWebview(webviewHandle, options, extension);
|
||||
const revivedPanel = this.createNewWebviewPanel(webviewHandle, viewType, title, position, options, webview);
|
||||
await serializer.deserializeWebviewPanel(revivedPanel, state);
|
||||
const webview = this.webviews.createNewWebview(webviewHandle, initData.webviewOptions, extension);
|
||||
const revivedPanel = this.createNewWebviewPanel(webviewHandle, viewType, initData.title, position, initData.panelOptions, webview);
|
||||
await serializer.deserializeWebviewPanel(revivedPanel, initData.state);
|
||||
}
|
||||
|
||||
public createNewWebviewPanel(webviewHandle: string, viewType: string, title: string, position: vscode.ViewColumn, options: modes.IWebviewOptions & modes.IWebviewPanelOptions, webview: ExtHostWebview) {
|
||||
public createNewWebviewPanel(webviewHandle: string, viewType: string, title: string, position: vscode.ViewColumn, options: extHostProtocol.IWebviewPanelOptions, webview: ExtHostWebview) {
|
||||
const panel = new ExtHostWebviewPanel(webviewHandle, this._proxy, viewType, title, position, options, webview);
|
||||
this._webviewPanels.set(webviewHandle, panel);
|
||||
return panel;
|
||||
@@ -297,3 +303,10 @@ export class ExtHostWebviewPanels implements extHostProtocol.ExtHostWebviewPanel
|
||||
return this._webviewPanels.get(handle);
|
||||
}
|
||||
}
|
||||
|
||||
function serializeWebviewPanelOptions(options: vscode.WebviewPanelOptions): extHostProtocol.IWebviewPanelOptions {
|
||||
return {
|
||||
enableFindWidget: options.enableFindWidget,
|
||||
retainContextWhenHidden: options.retainContextWhenHidden,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -563,8 +563,8 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac
|
||||
return this._workspaceTrustState;
|
||||
}
|
||||
|
||||
requireWorkspaceTrust(modal?: boolean): Promise<WorkspaceTrustState> {
|
||||
return this._proxy.$requireWorkspaceTrust(modal);
|
||||
requireWorkspaceTrust(options?: vscode.WorkspaceTrustRequestOptions): Promise<WorkspaceTrustState> {
|
||||
return this._proxy.$requireWorkspaceTrust(options);
|
||||
}
|
||||
|
||||
$onDidChangeWorkspaceTrustState(state: WorkspaceTrustStateChangeEvent): void {
|
||||
|
||||
@@ -53,6 +53,11 @@ const apiMenus: IAPIMenu[] = [
|
||||
id: MenuId.EditorContext,
|
||||
description: localize('menus.editorContext', "The editor context menu")
|
||||
},
|
||||
{
|
||||
key: 'editor/context/copy',
|
||||
id: MenuId.EditorContextCopy,
|
||||
description: localize('menus.editorContextCopyAs', "'Copy as' submenu in the editor context menu")
|
||||
},
|
||||
{
|
||||
key: 'explorer/context',
|
||||
id: MenuId.ExplorerContext,
|
||||
@@ -91,6 +96,11 @@ const apiMenus: IAPIMenu[] = [
|
||||
proposed: true,
|
||||
supportsSubmenus: false
|
||||
},
|
||||
{
|
||||
key: 'menuBar/edit/copy',
|
||||
id: MenuId.MenubarCopy,
|
||||
description: localize('menus.opy', "'Copy as' submenu in the top level Edit menu")
|
||||
},
|
||||
{
|
||||
key: 'scm/title',
|
||||
id: MenuId.SCMTitle,
|
||||
@@ -104,17 +114,17 @@ const apiMenus: IAPIMenu[] = [
|
||||
{
|
||||
key: 'scm/resourceState/context',
|
||||
id: MenuId.SCMResourceContext,
|
||||
description: localize('menus.resourceGroupContext', "The Source Control resource group context menu")
|
||||
description: localize('menus.resourceStateContext', "The Source Control resource state context menu")
|
||||
},
|
||||
{
|
||||
key: 'scm/resourceFolder/context',
|
||||
id: MenuId.SCMResourceFolderContext,
|
||||
description: localize('menus.resourceStateContext', "The Source Control resource state context menu")
|
||||
description: localize('menus.resourceFolderContext', "The Source Control resource folder context menu")
|
||||
},
|
||||
{
|
||||
key: 'scm/resourceGroup/context',
|
||||
id: MenuId.SCMResourceGroupContext,
|
||||
description: localize('menus.resourceFolderContext', "The Source Control resource folder context menu")
|
||||
description: localize('menus.resourceGroupContext', "The Source Control resource group context menu")
|
||||
},
|
||||
{
|
||||
key: 'scm/change/title',
|
||||
@@ -160,6 +170,12 @@ const apiMenus: IAPIMenu[] = [
|
||||
description: localize('comment.actions', "The contributed comment context menu, rendered as buttons below the comment editor"),
|
||||
supportsSubmenus: false
|
||||
},
|
||||
{
|
||||
key: 'notebook/toolbar',
|
||||
id: MenuId.NotebookToolbar,
|
||||
description: localize('notebook.toolbar', "The contributed notebook toolbar menu"),
|
||||
proposed: true
|
||||
},
|
||||
{
|
||||
key: 'notebook/cell/title',
|
||||
id: MenuId.NotebookCellTitle,
|
||||
@@ -539,7 +555,7 @@ commandsExtensionPoint.setHandler(extensions => {
|
||||
let absoluteIcon: { dark: URI; light?: URI; } | ThemeIcon | undefined;
|
||||
if (icon) {
|
||||
if (typeof icon === 'string') {
|
||||
absoluteIcon = ThemeIcon.fromString(icon) || { dark: resources.joinPath(extension.description.extensionLocation, icon) };
|
||||
absoluteIcon = ThemeIcon.fromString(icon) ?? { dark: resources.joinPath(extension.description.extensionLocation, icon), light: resources.joinPath(extension.description.extensionLocation, icon) };
|
||||
|
||||
} else {
|
||||
absoluteIcon = {
|
||||
|
||||
@@ -173,7 +173,6 @@ export class CLIServerBase {
|
||||
}
|
||||
|
||||
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 = {
|
||||
@@ -182,12 +181,16 @@ export class CLIServerBase {
|
||||
uninstall: toExtOrVSIX(data.uninstall),
|
||||
force: data.force
|
||||
};
|
||||
const output = await this._commands.executeCommand('_remoteCLI.manageExtensions', commandArgs, { allowTunneling: true });
|
||||
const output = await this._commands.executeCommand('_remoteCLI.manageExtensions', commandArgs);
|
||||
res.writeHead(200);
|
||||
res.write(output);
|
||||
} catch (e) {
|
||||
} catch (err) {
|
||||
res.writeHead(500);
|
||||
res.write(String(e));
|
||||
res.write(String(err), err => {
|
||||
if (err) {
|
||||
this.logService.error(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
res.end();
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import type * as vscode from 'vscode';
|
||||
import * as env from 'vs/base/common/platform';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import { DebugAdapterExecutable } from 'vs/workbench/api/common/extHostTypes';
|
||||
import { ExecutableDebugAdapter, SocketDebugAdapter, NamedPipeDebugAdapter } from 'vs/workbench/contrib/debug/node/debugAdapter';
|
||||
import { AbstractDebugAdapter } from 'vs/workbench/contrib/debug/common/abstractDebugAdapter';
|
||||
@@ -110,10 +110,23 @@ export class ExtHostDebugService extends ExtHostDebugServiceBase {
|
||||
if (giveShellTimeToInitialize) {
|
||||
// give a new terminal some time to initialize the shell
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
} else {
|
||||
if (configProvider.getConfiguration('debug.terminal').get<boolean>('clearBeforeReusing')) {
|
||||
// clear terminal before reusing it
|
||||
if (shell.indexOf('powershell') >= 0 || shell.indexOf('pwsh') >= 0 || shell.indexOf('cmd.exe') >= 0) {
|
||||
terminal.sendText('cls');
|
||||
} else if (shell.indexOf('bash') >= 0) {
|
||||
terminal.sendText('clear');
|
||||
} else if (platform.isWindows) {
|
||||
terminal.sendText('cls');
|
||||
} else {
|
||||
terminal.sendText('clear');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const command = prepareCommand(shell, args.args, cwdForPrepareCommand, args.env);
|
||||
terminal.sendText(command, true);
|
||||
terminal.sendText(command);
|
||||
|
||||
// Mark terminal as unused when its session ends, see #112055
|
||||
const sessionListener = this.onDidTerminateDebugSession(s => {
|
||||
@@ -133,7 +146,7 @@ export class ExtHostDebugService extends ExtHostDebugServiceBase {
|
||||
}
|
||||
|
||||
protected createVariableResolver(folders: vscode.WorkspaceFolder[], editorService: ExtHostDocumentsAndEditors, configurationService: ExtHostConfigProvider): AbstractVariableResolverService {
|
||||
return new ExtHostVariableResolverService(folders, editorService, configurationService, process.env as env.IProcessEnvironment, this._workspaceService);
|
||||
return new ExtHostVariableResolverService(folders, editorService, configurationService, this._workspaceService);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ import { Schemas } from 'vs/base/common/network';
|
||||
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { ExtensionRuntime } from 'vs/workbench/api/common/extHostTypes';
|
||||
import { CLIServer } from 'vs/workbench/api/node/extHostCLIServer';
|
||||
import { realpathSync } from 'vs/base/node/extpath';
|
||||
|
||||
class NodeModuleRequireInterceptor extends RequireInterceptor {
|
||||
|
||||
@@ -36,7 +37,7 @@ class NodeModuleRequireInterceptor extends RequireInterceptor {
|
||||
}
|
||||
return that._factories.get(request)!.load(
|
||||
request,
|
||||
URI.file(parent.filename),
|
||||
URI.file(realpathSync(parent.filename)),
|
||||
request => original.apply(this, [request, parent, isMain])
|
||||
);
|
||||
};
|
||||
|
||||
@@ -35,13 +35,15 @@ export class NativeExtHostSearch extends ExtHostSearch {
|
||||
) {
|
||||
super(extHostRpc, _uriTransformer, _logService);
|
||||
|
||||
const outputChannel = new OutputChannel('RipgrepSearchUD', this._logService);
|
||||
this.registerTextSearchProvider(Schemas.userData, new RipgrepSearchProvider(outputChannel));
|
||||
if (initData.remote.isRemote && initData.remote.authority) {
|
||||
this._registerEHSearchProviders();
|
||||
}
|
||||
}
|
||||
|
||||
private _registerEHSearchProviders(): void {
|
||||
const outputChannel = new OutputChannel(this._logService);
|
||||
const outputChannel = new OutputChannel('RipgrepSearchEH', this._logService);
|
||||
this.registerTextSearchProvider(Schemas.file, new RipgrepSearchProvider(outputChannel));
|
||||
this.registerInternalFileSearchProvider(Schemas.file, new SearchService());
|
||||
}
|
||||
@@ -101,4 +103,3 @@ export class NativeExtHostSearch extends ExtHostSearch {
|
||||
return new NativeTextSearchManager(query, provider);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,6 @@ import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitData
|
||||
import { ExtHostTaskBase, TaskHandleDTO, TaskDTO, CustomExecutionDTO, HandlerData } from 'vs/workbench/api/common/extHostTask';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IProcessEnvironment } from 'vs/base/common/platform';
|
||||
import { IExtHostApiDeprecationService } from 'vs/workbench/api/common/extHostApiDeprecationService';
|
||||
|
||||
export class ExtHostTask extends ExtHostTaskBase {
|
||||
@@ -122,7 +121,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.workspaceService);
|
||||
this._variableResolver = new ExtHostVariableResolverService(workspaceFolders, this._editorService, configProvider, this.workspaceService);
|
||||
}
|
||||
return this._variableResolver;
|
||||
}
|
||||
|
||||
@@ -3,33 +3,28 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import type * as vscode from 'vscode';
|
||||
import * as os from 'os';
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import * as terminalEnvironment from 'vs/workbench/contrib/terminal/common/terminalEnvironment';
|
||||
import { IShellLaunchConfigDto, IShellDefinitionDto, IShellAndArgsDto } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { ExtHostConfiguration, ExtHostConfigProvider, IExtHostConfiguration } from 'vs/workbench/api/common/extHostConfiguration';
|
||||
import { withNullAsUndefined } from 'vs/base/common/types';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { getSystemShell, getSystemShellSync } from 'vs/base/node/shell';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { TerminalProcess } from 'vs/platform/terminal/node/terminalProcess';
|
||||
import { ExtHostWorkspace, IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace';
|
||||
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
|
||||
import { IShellAndArgsDto } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { ExtHostConfigProvider, ExtHostConfiguration, IExtHostConfiguration } from 'vs/workbench/api/common/extHostConfiguration';
|
||||
import { ExtHostVariableResolverService } from 'vs/workbench/api/common/extHostDebugService';
|
||||
import { ExtHostDocumentsAndEditors, IExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors';
|
||||
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';
|
||||
import { IShellLaunchConfig, ITerminalEnvironment, ITerminalLaunchError } from 'vs/platform/terminal/common/terminal';
|
||||
import { BaseExtHostTerminalService, ExtHostTerminal } from 'vs/workbench/api/common/extHostTerminalService';
|
||||
import { ExtHostWorkspace, IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace';
|
||||
import { ITerminalConfiguration, ITerminalProfile } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import * as terminalEnvironment from 'vs/workbench/contrib/terminal/common/terminalEnvironment';
|
||||
import { detectAvailableProfiles } from 'vs/workbench/contrib/terminal/node/terminalProfiles';
|
||||
import type * as vscode from 'vscode';
|
||||
|
||||
export class ExtHostTerminalService extends BaseExtHostTerminalService {
|
||||
|
||||
private _variableResolver: ExtHostVariableResolverService | undefined;
|
||||
private _variableResolverPromise: Promise<ExtHostVariableResolverService>;
|
||||
private _lastActiveWorkspace: IWorkspaceFolder | undefined;
|
||||
|
||||
// TODO: Pull this from main side
|
||||
@@ -41,18 +36,17 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService {
|
||||
@IExtHostConfiguration private _extHostConfiguration: ExtHostConfiguration,
|
||||
@IExtHostWorkspace private _extHostWorkspace: ExtHostWorkspace,
|
||||
@IExtHostDocumentsAndEditors private _extHostDocumentsAndEditors: ExtHostDocumentsAndEditors,
|
||||
@ILogService private _logService: ILogService,
|
||||
@IExtHostInitDataService private _extHostInitDataService: IExtHostInitDataService
|
||||
@ILogService private _logService: ILogService
|
||||
) {
|
||||
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);
|
||||
getSystemShell(platform.platform, process.env as platform.IProcessEnvironment).then(s => this._defaultShell = s);
|
||||
|
||||
this._updateLastActiveWorkspace();
|
||||
this._updateVariableResolver();
|
||||
this._variableResolverPromise = this._updateVariableResolver();
|
||||
this._registerListeners();
|
||||
}
|
||||
|
||||
@@ -91,7 +85,7 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService {
|
||||
return terminalEnvironment.getDefaultShell(
|
||||
fetchSetting,
|
||||
this._isWorkspaceShellAllowed,
|
||||
this._defaultShell ?? getSystemShellSync(platform.platform),
|
||||
this._defaultShell ?? getSystemShellSync(platform.platform, process.env as platform.IProcessEnvironment),
|
||||
process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432'),
|
||||
process.env.windir,
|
||||
terminalEnvironment.createVariableResolver(this._lastActiveWorkspace, this._variableResolver),
|
||||
@@ -121,15 +115,11 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService {
|
||||
};
|
||||
}
|
||||
|
||||
private async _getNonInheritedEnv(): Promise<platform.IProcessEnvironment> {
|
||||
const env = await getMainProcessParentEnv();
|
||||
env.VSCODE_IPC_HOOK_CLI = process.env['VSCODE_IPC_HOOK_CLI']!;
|
||||
return env;
|
||||
}
|
||||
|
||||
private _registerListeners(): void {
|
||||
this._extHostDocumentsAndEditors.onDidChangeActiveTextEditor(() => this._updateLastActiveWorkspace());
|
||||
this._extHostWorkspace.onDidChangeWorkspace(() => this._updateVariableResolver());
|
||||
this._extHostWorkspace.onDidChangeWorkspace(() => {
|
||||
this._variableResolverPromise = this._updateVariableResolver();
|
||||
});
|
||||
}
|
||||
|
||||
private _updateLastActiveWorkspace(): void {
|
||||
@@ -139,106 +129,16 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService {
|
||||
}
|
||||
}
|
||||
|
||||
private async _updateVariableResolver(): Promise<void> {
|
||||
private async _updateVariableResolver(): Promise<ExtHostVariableResolverService> {
|
||||
const configProvider = await this._extHostConfiguration.getConfigProvider();
|
||||
const workspaceFolders = await this._extHostWorkspace.getWorkspaceFolders2();
|
||||
this._variableResolver = new ExtHostVariableResolverService(workspaceFolders || [], this._extHostDocumentsAndEditors, configProvider, process.env as platform.IProcessEnvironment);
|
||||
this._variableResolver = new ExtHostVariableResolverService(workspaceFolders || [], this._extHostDocumentsAndEditors, configProvider);
|
||||
return this._variableResolver;
|
||||
}
|
||||
|
||||
public async $spawnExtHostProcess(id: number, shellLaunchConfigDto: IShellLaunchConfigDto, activeWorkspaceRootUriComponents: UriComponents | undefined, cols: number, rows: number, isWorkspaceShellAllowed: boolean): Promise<ITerminalLaunchError | undefined> {
|
||||
const shellLaunchConfig: IShellLaunchConfig = {
|
||||
name: shellLaunchConfigDto.name,
|
||||
executable: shellLaunchConfigDto.executable,
|
||||
args: shellLaunchConfigDto.args,
|
||||
cwd: typeof shellLaunchConfigDto.cwd === 'string' ? shellLaunchConfigDto.cwd : URI.revive(shellLaunchConfigDto.cwd),
|
||||
env: shellLaunchConfigDto.env,
|
||||
flowControl: shellLaunchConfigDto.flowControl
|
||||
};
|
||||
|
||||
// Merge in shell and args from settings
|
||||
const platformKey = platform.isWindows ? 'windows' : (platform.isMacintosh ? 'osx' : 'linux');
|
||||
const configProvider = await this._extHostConfiguration.getConfigProvider();
|
||||
if (!shellLaunchConfig.executable) {
|
||||
shellLaunchConfig.executable = this.getDefaultShell(false, configProvider);
|
||||
shellLaunchConfig.args = this.getDefaultShellArgs(false, configProvider);
|
||||
} else {
|
||||
if (this._variableResolver) {
|
||||
shellLaunchConfig.executable = this._variableResolver.resolve(this._lastActiveWorkspace, shellLaunchConfig.executable);
|
||||
if (shellLaunchConfig.args) {
|
||||
if (Array.isArray(shellLaunchConfig.args)) {
|
||||
const resolvedArgs: string[] = [];
|
||||
for (const arg of shellLaunchConfig.args) {
|
||||
resolvedArgs.push(this._variableResolver.resolve(this._lastActiveWorkspace, arg));
|
||||
}
|
||||
shellLaunchConfig.args = resolvedArgs;
|
||||
} else {
|
||||
shellLaunchConfig.args = this._variableResolver.resolve(this._lastActiveWorkspace, shellLaunchConfig.args);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const activeWorkspaceRootUri = URI.revive(activeWorkspaceRootUriComponents);
|
||||
let lastActiveWorkspace: IWorkspaceFolder | undefined;
|
||||
if (activeWorkspaceRootUriComponents && activeWorkspaceRootUri) {
|
||||
// Get the environment
|
||||
const apiLastActiveWorkspace = await this._extHostWorkspace.getWorkspaceFolder(activeWorkspaceRootUri);
|
||||
if (apiLastActiveWorkspace) {
|
||||
lastActiveWorkspace = {
|
||||
uri: apiLastActiveWorkspace.uri,
|
||||
name: apiLastActiveWorkspace.name,
|
||||
index: apiLastActiveWorkspace.index,
|
||||
toResource: () => {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Get the initial cwd
|
||||
const terminalConfig = configProvider.getConfiguration('terminal.integrated');
|
||||
|
||||
const initialCwd = terminalEnvironment.getCwd(shellLaunchConfig, os.homedir(), terminalEnvironment.createVariableResolver(lastActiveWorkspace, this._variableResolver), activeWorkspaceRootUri, terminalConfig.cwd, this._logService);
|
||||
shellLaunchConfig.cwd = initialCwd;
|
||||
|
||||
const envFromConfig = this._apiInspectConfigToPlain(configProvider.getConfiguration('terminal.integrated').inspect<ITerminalEnvironment>(`env.${platformKey}`));
|
||||
const baseEnv = terminalConfig.get<boolean>('inheritEnv', true) ? process.env as platform.IProcessEnvironment : await this._getNonInheritedEnv();
|
||||
const variableResolver = terminalEnvironment.createVariableResolver(lastActiveWorkspace, this._variableResolver);
|
||||
const env = terminalEnvironment.createTerminalEnvironment(
|
||||
shellLaunchConfig,
|
||||
envFromConfig,
|
||||
variableResolver,
|
||||
isWorkspaceShellAllowed,
|
||||
this._extHostInitDataService.version,
|
||||
terminalConfig.get<'auto' | 'off' | 'on'>('detectLocale', 'auto'),
|
||||
baseEnv
|
||||
);
|
||||
|
||||
// Apply extension environment variable collections to the environment
|
||||
if (!shellLaunchConfig.strictEnv && !shellLaunchConfig.hideFromUser) {
|
||||
const mergedCollection = new MergedEnvironmentVariableCollection(this._environmentVariableCollections);
|
||||
mergedCollection.applyToProcessEnvironment(env, variableResolver);
|
||||
}
|
||||
|
||||
this._proxy.$sendResolvedLaunchConfig(id, shellLaunchConfig);
|
||||
// Fork the process and listen for messages
|
||||
this._logService.debug(`Terminal process launching on ext host`, { shellLaunchConfig, initialCwd, cols, rows, env });
|
||||
// TODO: Support conpty on remote, it doesn't seem to work for some reason?
|
||||
// TODO: When conpty is enabled, only enable it when accessibilityMode is off
|
||||
const enableConpty = false; //terminalConfig.get('windowsEnableConpty') as boolean;
|
||||
|
||||
const terminalProcess = new TerminalProcess(shellLaunchConfig, initialCwd, cols, rows, env, process.env as platform.IProcessEnvironment, enableConpty, this._logService);
|
||||
this._setupExtHostProcessListeners(id, terminalProcess);
|
||||
const error = await terminalProcess.start();
|
||||
if (error) {
|
||||
// TODO: Teardown?
|
||||
return error;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public $getAvailableShells(): Promise<IShellDefinitionDto[]> {
|
||||
return detectAvailableShells();
|
||||
public async $getAvailableProfiles(quickLaunchOnly: boolean): Promise<ITerminalProfile[]> {
|
||||
const config = await (await this._extHostConfiguration.getConfigProvider()).getConfiguration().get('terminal.integrated');
|
||||
return detectAvailableProfiles(quickLaunchOnly, this._logService, config as ITerminalConfiguration, await this._variableResolverPromise, this._lastActiveWorkspace);
|
||||
}
|
||||
|
||||
public async $getDefaultShellAndArgs(useAutomationShell: boolean): Promise<IShellAndArgsDto> {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { MainThreadTunnelServiceShape, MainContext } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { MainThreadTunnelServiceShape, MainContext, PortAttributesProviderSelector } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
|
||||
import type * as vscode from 'vscode';
|
||||
import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
@@ -13,14 +13,16 @@ 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 * as types from 'vs/workbench/api/common/extHostTypes';
|
||||
import { isLinux } from 'vs/base/common/platform';
|
||||
import { IExtHostTunnelService, TunnelDto } from 'vs/workbench/api/common/extHostTunnelService';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { TunnelOptions, TunnelCreationOptions } from 'vs/platform/remote/common/tunnel';
|
||||
import { TunnelOptions, TunnelCreationOptions, ProvidedPortAttributes, ProvidedOnAutoForward } from 'vs/platform/remote/common/tunnel';
|
||||
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { MovingAverage } from 'vs/base/common/numbers';
|
||||
import { CandidatePort } from 'vs/workbench/services/remote/common/remoteExplorerService';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { flatten } from 'vs/base/common/arrays';
|
||||
|
||||
class ExtensionTunnel implements vscode.Tunnel {
|
||||
private _onDispose: Emitter<void> = new Emitter();
|
||||
@@ -37,7 +39,7 @@ class ExtensionTunnel implements vscode.Tunnel {
|
||||
}
|
||||
}
|
||||
|
||||
export function getSockets(stdout: string): { pid: number, socket: number }[] {
|
||||
export function getSockets(stdout: string): Record<string, { pid: number; socket: number; }> {
|
||||
const lines = stdout.trim().split('\n');
|
||||
const mapped: { pid: number, socket: number }[] = [];
|
||||
lines.forEach(line => {
|
||||
@@ -49,7 +51,11 @@ export function getSockets(stdout: string): { pid: number, socket: number }[] {
|
||||
});
|
||||
}
|
||||
});
|
||||
return mapped;
|
||||
const socketMap = mapped.reduce((m, socket) => {
|
||||
m[socket.socket] = socket;
|
||||
return m;
|
||||
}, {} as Record<string, typeof mapped[0]>);
|
||||
return socketMap;
|
||||
}
|
||||
|
||||
export function loadListeningPorts(...stdouts: string[]): { socket: number, ip: string, port: number }[] {
|
||||
@@ -106,29 +112,70 @@ function knownExcludeCmdline(command: string): boolean {
|
||||
|| (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);
|
||||
export function getRootProcesses(stdout: string) {
|
||||
const lines = stdout.trim().split('\n');
|
||||
const mapped: { pid: number, cmd: string, ppid: number }[] = [];
|
||||
lines.forEach(line => {
|
||||
const match = /^\d+\s+\D+\s+root\s+(\d+)\s+(\d+).+\d+\:\d+\:\d+\s+(.+)$/.exec(line)!;
|
||||
if (match && match.length >= 4) {
|
||||
mapped.push({
|
||||
pid: parseInt(match[1], 10),
|
||||
ppid: parseInt(match[2]),
|
||||
cmd: match[3]
|
||||
});
|
||||
}
|
||||
});
|
||||
return mapped;
|
||||
}
|
||||
|
||||
const socketMap = sockets.reduce((m, socket) => {
|
||||
m[socket.socket] = socket;
|
||||
return m;
|
||||
}, {} as Record<string, typeof sockets[0]>);
|
||||
export async function findPorts(connections: { socket: number, ip: string, port: number }[], socketMap: Record<string, { pid: number, socket: number }>, processes: { pid: number, cwd: string, cmd: string }[]): Promise<CandidatePort[]> {
|
||||
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 });
|
||||
connections.forEach(({ socket, ip, port }) => {
|
||||
const pid = socketMap[socket] ? socketMap[socket].pid : undefined;
|
||||
const command: string | undefined = pid ? processMap[pid]?.cmd : undefined;
|
||||
if (pid && command && !knownExcludeCmdline(command)) {
|
||||
ports.push({ host: ip, port, detail: command, pid });
|
||||
}
|
||||
});
|
||||
return ports;
|
||||
}
|
||||
|
||||
export function tryFindRootPorts(connections: { socket: number, ip: string, port: number }[], rootProcessesStdout: string, previousPorts: Map<number, CandidatePort & { ppid: number }>): Map<number, CandidatePort & { ppid: number }> {
|
||||
const ports: Map<number, CandidatePort & { ppid: number }> = new Map();
|
||||
const rootProcesses = getRootProcesses(rootProcessesStdout);
|
||||
|
||||
for (const connection of connections) {
|
||||
const previousPort = previousPorts.get(connection.port);
|
||||
if (previousPort) {
|
||||
ports.set(connection.port, previousPort);
|
||||
continue;
|
||||
}
|
||||
const rootProcessMatch = rootProcesses.find((value) => value.cmd.includes(`${connection.port}`));
|
||||
if (rootProcessMatch) {
|
||||
let bestMatch = rootProcessMatch;
|
||||
// There are often several processes that "look" like they could match the port.
|
||||
// The one we want is usually the child of the other. Find the most child process.
|
||||
let mostChild: { pid: number, cmd: string, ppid: number } | undefined;
|
||||
do {
|
||||
mostChild = rootProcesses.find(value => value.ppid === bestMatch.pid);
|
||||
if (mostChild) {
|
||||
bestMatch = mostChild;
|
||||
}
|
||||
} while (mostChild);
|
||||
ports.set(connection.port, { host: connection.ip, port: connection.port, pid: bestMatch.pid, detail: bestMatch.cmd, ppid: bestMatch.ppid });
|
||||
} else {
|
||||
ports.set(connection.port, { host: connection.ip, port: connection.port, ppid: Number.MAX_VALUE });
|
||||
}
|
||||
}
|
||||
|
||||
return ports;
|
||||
}
|
||||
|
||||
export class ExtHostTunnelService extends Disposable implements IExtHostTunnelService {
|
||||
readonly _serviceBrand: undefined;
|
||||
private readonly _proxy: MainThreadTunnelServiceShape;
|
||||
@@ -138,6 +185,10 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
|
||||
private _onDidChangeTunnels: Emitter<void> = new Emitter<void>();
|
||||
onDidChangeTunnels: vscode.Event<void> = this._onDidChangeTunnels.event;
|
||||
private _candidateFindingEnabled: boolean = false;
|
||||
private _foundRootPorts: Map<number, CandidatePort & { ppid: number }> = new Map();
|
||||
|
||||
private _providerHandleCounter: number = 0;
|
||||
private _portAttributesProviders: Map<number, { provider: vscode.PortAttributesProvider, selector: PortAttributesProviderSelector }> = new Map();
|
||||
|
||||
constructor(
|
||||
@IExtHostRpcService extHostRpc: IExtHostRpcService,
|
||||
@@ -152,6 +203,7 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
|
||||
}
|
||||
|
||||
async openTunnel(extension: IExtensionDescription, forward: TunnelOptions): Promise<vscode.Tunnel | undefined> {
|
||||
this.logService.trace(`ForwardedPorts: (ExtHostTunnelService) ${extension.identifier} called openTunnel API for ${forward.remoteAddress.port}.`);
|
||||
const tunnel = await this._proxy.$openTunnel(forward, extension.displayName);
|
||||
if (tunnel) {
|
||||
const disposableTunnel: vscode.Tunnel = new ExtensionTunnel(tunnel.remoteAddress, tunnel.localAddress, () => {
|
||||
@@ -172,6 +224,40 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
|
||||
return Math.max(movingAverage * 20, 2000);
|
||||
}
|
||||
|
||||
private nextPortAttributesProviderHandle(): number {
|
||||
return this._providerHandleCounter++;
|
||||
}
|
||||
|
||||
registerPortsAttributesProvider(portSelector: PortAttributesProviderSelector, provider: vscode.PortAttributesProvider): vscode.Disposable {
|
||||
const providerHandle = this.nextPortAttributesProviderHandle();
|
||||
this._portAttributesProviders.set(providerHandle, { selector: portSelector, provider });
|
||||
|
||||
this._proxy.$registerPortsAttributesProvider(portSelector, providerHandle);
|
||||
return new types.Disposable(() => {
|
||||
this._portAttributesProviders.delete(providerHandle);
|
||||
this._proxy.$unregisterPortsAttributesProvider(providerHandle);
|
||||
});
|
||||
}
|
||||
|
||||
async $providePortAttributes(handles: number[], ports: number[], pid: number | undefined, commandline: string | undefined, cancellationToken: vscode.CancellationToken): Promise<ProvidedPortAttributes[]> {
|
||||
const providedAttributes = await Promise.all(handles.map(handle => {
|
||||
const provider = this._portAttributesProviders.get(handle);
|
||||
if (!provider) {
|
||||
return [];
|
||||
}
|
||||
return provider.provider.providePortAttributes(ports, pid, commandline, cancellationToken);
|
||||
}));
|
||||
|
||||
const allAttributes = <vscode.PortAttributes[][]>providedAttributes.filter(attribute => !!attribute && attribute.length > 0);
|
||||
|
||||
return (allAttributes.length > 0) ? flatten(allAttributes).map(attributes => {
|
||||
return {
|
||||
autoForwardAction: <ProvidedOnAutoForward><unknown>attributes.autoForwardAction,
|
||||
port: attributes.port
|
||||
};
|
||||
}) : [];
|
||||
}
|
||||
|
||||
async $registerCandidateFinder(enable: boolean): Promise<void> {
|
||||
if (enable && this._candidateFindingEnabled) {
|
||||
// already enabled
|
||||
@@ -180,10 +266,11 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
|
||||
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;
|
||||
let oldPorts: { host: string, port: number, detail?: string }[] | undefined = undefined;
|
||||
while (this._candidateFindingEnabled) {
|
||||
const startTime = new Date().getTime();
|
||||
const newPorts = await this.findCandidatePorts();
|
||||
this.logService.trace(`ForwardedPorts: (ExtHostTunnelService) found candidate ports ${newPorts.map(port => port.port).join(', ')}`);
|
||||
const timeTaken = new Date().getTime() - startTime;
|
||||
movingAverage.update(timeTaken);
|
||||
if (!oldPorts || (JSON.stringify(oldPorts) !== JSON.stringify(newPorts))) {
|
||||
@@ -238,31 +325,36 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
|
||||
async $forwardPort(tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions): Promise<TunnelDto | undefined> {
|
||||
if (this._forwardPortProvider) {
|
||||
try {
|
||||
this.logService.trace('$forwardPort: Getting tunnel from provider.');
|
||||
this.logService.trace('ForwardedPorts: (ExtHostTunnelService) Getting tunnel from provider.');
|
||||
const providedPort = this._forwardPortProvider(tunnelOptions, tunnelCreationOptions);
|
||||
this.logService.trace('$forwardPort: Got tunnel promise from provider.');
|
||||
this.logService.trace('ForwardedPorts: (ExtHostTunnelService) Got tunnel promise from provider.');
|
||||
if (providedPort !== undefined) {
|
||||
const tunnel = await providedPort;
|
||||
this.logService.trace('$forwardPort: Successfully awaited tunnel from provider.');
|
||||
this.logService.trace('ForwardedPorts: (ExtHostTunnelService) 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)));
|
||||
const disposeListener = this._register(tunnel.onDidDispose(() => {
|
||||
this.logService.trace('ForwardedPorts: (ExtHostTunnelService) Extension fired tunnel\'s onDidDispose.');
|
||||
return this._proxy.$closeTunnel(tunnel.remoteAddress);
|
||||
}));
|
||||
this._extensionTunnels.get(tunnelOptions.remoteAddress.host)!.set(tunnelOptions.remoteAddress.port, { tunnel, disposeListener });
|
||||
return TunnelDto.fromApiTunnel(tunnel);
|
||||
} else {
|
||||
this.logService.trace('$forwardPort: Tunnel is undefined');
|
||||
this.logService.trace('ForwardedPorts: (ExtHostTunnelService) Tunnel is undefined');
|
||||
}
|
||||
} catch (e) {
|
||||
this.logService.trace('$forwardPort: tunnel provider error');
|
||||
this.logService.trace('ForwardedPorts: (ExtHostTunnelService) 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]);
|
||||
const filter = await Promise.all(candidates.map(candidate => this._showCandidatePort(candidate.host, candidate.port, candidate.detail ?? '')));
|
||||
const result = candidates.filter((candidate, index) => filter[index]);
|
||||
this.logService.trace(`ForwardedPorts: (ExtHostTunnelService) filtered from ${candidates.map(port => port.port).join(', ')} to ${result.map(port => port.port).join(', ')}`);
|
||||
return result;
|
||||
}
|
||||
|
||||
async findCandidatePorts(): Promise<CandidatePort[]> {
|
||||
@@ -274,11 +366,14 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
|
||||
} catch (e) {
|
||||
// File reading error. No additional handling needed.
|
||||
}
|
||||
const connections: { socket: number, ip: string, port: number }[] = loadListeningPorts(tcp, tcp6);
|
||||
|
||||
const procSockets: string = await (new Promise(resolve => {
|
||||
exec('ls -l /proc/[0-9]*/fd/[0-9]* | grep socket:', (error, stdout, stderr) => {
|
||||
resolve(stdout);
|
||||
});
|
||||
}));
|
||||
const socketMap = getSockets(procSockets);
|
||||
|
||||
const procChildren = await pfs.readdir('/proc');
|
||||
const processes: {
|
||||
@@ -298,6 +393,36 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
|
||||
//
|
||||
}
|
||||
}
|
||||
return findPorts(tcp, tcp6, procSockets, processes);
|
||||
|
||||
const unFoundConnections: { socket: number, ip: string, port: number }[] = [];
|
||||
const filteredConnections = connections.filter((connection => {
|
||||
const foundConnection = socketMap[connection.socket];
|
||||
if (!foundConnection) {
|
||||
unFoundConnections.push(connection);
|
||||
}
|
||||
return foundConnection;
|
||||
}));
|
||||
|
||||
const foundPorts = findPorts(filteredConnections, socketMap, processes);
|
||||
let heuristicPorts: CandidatePort[] | undefined;
|
||||
this.logService.trace(`ForwardedPorts: (ExtHostTunnelService) number of possible root ports ${unFoundConnections.length}`);
|
||||
if (unFoundConnections.length > 0) {
|
||||
const rootProcesses: string = await (new Promise(resolve => {
|
||||
exec('ps -F -A -l | grep root', (error, stdout, stderr) => {
|
||||
resolve(stdout);
|
||||
});
|
||||
}));
|
||||
this._foundRootPorts = tryFindRootPorts(unFoundConnections, rootProcesses, this._foundRootPorts);
|
||||
heuristicPorts = Array.from(this._foundRootPorts.values());
|
||||
this.logService.trace(`ForwardedPorts: (ExtHostTunnelService) heuristic ports ${heuristicPorts.join(', ')}`);
|
||||
|
||||
}
|
||||
return foundPorts.then(foundCandidates => {
|
||||
if (heuristicPorts) {
|
||||
return foundCandidates.concat(heuristicPorts);
|
||||
} else {
|
||||
return foundCandidates;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import { RequireInterceptor } from 'vs/workbench/api/common/extHostRequireInterc
|
||||
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { ExtensionRuntime } from 'vs/workbench/api/common/extHostTypes';
|
||||
import { timeout } from 'vs/base/common/async';
|
||||
import { MainContext, MainThreadConsoleShape } from 'vs/workbench/api/common/extHost.protocol';
|
||||
|
||||
namespace TrustedFunction {
|
||||
|
||||
@@ -68,6 +69,9 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService {
|
||||
private _fakeModules?: WorkerRequireInterceptor;
|
||||
|
||||
protected async _beforeAlmostReadyToRunExtensions(): Promise<void> {
|
||||
const mainThreadConsole = this._extHostContext.getProxy(MainContext.MainThreadConsole);
|
||||
wrapConsoleMethods(mainThreadConsole);
|
||||
|
||||
// initialize API and register actors
|
||||
const apiFactory = this._instaService.invokeFunction(createApiFactoryAndRegisterActors);
|
||||
this._fakeModules = this._instaService.createInstance(WorkerRequireInterceptor, apiFactory, this._registry);
|
||||
@@ -167,3 +171,74 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService {
|
||||
function ensureSuffix(path: string, suffix: string): string {
|
||||
return path.endsWith(suffix) ? path : path + suffix;
|
||||
}
|
||||
|
||||
// copied from bootstrap-fork.js
|
||||
function wrapConsoleMethods(service: MainThreadConsoleShape) {
|
||||
wrap('info', 'log');
|
||||
wrap('log', 'log');
|
||||
wrap('warn', 'warn');
|
||||
wrap('error', 'error');
|
||||
|
||||
function wrap(method: 'error' | 'warn' | 'info' | 'log', severity: 'error' | 'warn' | 'log') {
|
||||
console[method] = function () {
|
||||
service.$logExtensionHostMessage({ type: '__$console', severity, arguments: safeToArray(arguments) });
|
||||
};
|
||||
}
|
||||
|
||||
const MAX_LENGTH = 100000;
|
||||
|
||||
function safeToArray(args: IArguments) {
|
||||
const seen: any[] = [];
|
||||
const argsArray = [];
|
||||
|
||||
// Massage some arguments with special treatment
|
||||
if (args.length) {
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
|
||||
// Any argument of type 'undefined' needs to be specially treated because
|
||||
// JSON.stringify will simply ignore those. We replace them with the string
|
||||
// 'undefined' which is not 100% right, but good enough to be logged to console
|
||||
if (typeof args[i] === 'undefined') {
|
||||
args[i] = 'undefined';
|
||||
}
|
||||
|
||||
// Any argument that is an Error will be changed to be just the error stack/message
|
||||
// itself because currently cannot serialize the error over entirely.
|
||||
else if (args[i] instanceof Error) {
|
||||
const errorObj = args[i];
|
||||
if (errorObj.stack) {
|
||||
args[i] = errorObj.stack;
|
||||
} else {
|
||||
args[i] = errorObj.toString();
|
||||
}
|
||||
}
|
||||
|
||||
argsArray.push(args[i]);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const res = JSON.stringify(argsArray, function (key, value) {
|
||||
|
||||
// Objects get special treatment to prevent circles
|
||||
if (value && typeof value === 'object') {
|
||||
if (seen.indexOf(value) !== -1) {
|
||||
return '[Circular]';
|
||||
}
|
||||
|
||||
seen.push(value);
|
||||
}
|
||||
|
||||
return value;
|
||||
});
|
||||
|
||||
if (res.length > MAX_LENGTH) {
|
||||
return 'Output omitted for a large object that exceeds the limits';
|
||||
}
|
||||
|
||||
return res;
|
||||
} catch (error) {
|
||||
return `Output omitted for an object that cannot be inspected ('${error.toString()}')`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,9 +7,9 @@ import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { List } from 'vs/base/browser/ui/list/listWidget';
|
||||
import { WorkbenchListFocusContextKey, IListService, WorkbenchListSupportsMultiSelectContextKey, ListWidget, WorkbenchListHasSelectionOrFocus, getSelectionKeyboardEvent, WorkbenchListWidget } from 'vs/platform/list/browser/listService';
|
||||
import { WorkbenchListFocusContextKey, IListService, WorkbenchListSupportsMultiSelectContextKey, ListWidget, WorkbenchListHasSelectionOrFocus, getSelectionKeyboardEvent, WorkbenchListWidget, WorkbenchListSelectionNavigation } from 'vs/platform/list/browser/listService';
|
||||
import { PagedList } from 'vs/base/browser/ui/list/listPaging';
|
||||
import { range } from 'vs/base/common/arrays';
|
||||
import { equals, range } from 'vs/base/common/arrays';
|
||||
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { ObjectTree } from 'vs/base/browser/ui/tree/objectTree';
|
||||
import { AsyncDataTree } from 'vs/base/browser/ui/tree/asyncDataTree';
|
||||
@@ -28,36 +28,40 @@ function ensureDOMFocus(widget: ListWidget | undefined): void {
|
||||
}
|
||||
}
|
||||
|
||||
function focusDown(accessor: ServicesAccessor, arg2?: number, loop: boolean = false): void {
|
||||
const focused = accessor.get(IListService).lastFocusedList;
|
||||
const count = typeof arg2 === 'number' ? arg2 : 1;
|
||||
|
||||
// List
|
||||
if (focused instanceof List || focused instanceof PagedList || focused instanceof Table) {
|
||||
const list = focused;
|
||||
|
||||
list.focusNext(count);
|
||||
const listFocus = list.getFocus();
|
||||
if (listFocus.length) {
|
||||
list.reveal(listFocus[0]);
|
||||
}
|
||||
async function updateFocus(widget: WorkbenchListWidget, updateFocusFn: (widget: WorkbenchListWidget) => void | Promise<void>): Promise<void> {
|
||||
if (!WorkbenchListSelectionNavigation.getValue(widget.contextKeyService)) {
|
||||
return updateFocusFn(widget);
|
||||
}
|
||||
|
||||
// Tree
|
||||
else if (focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) {
|
||||
const tree = focused;
|
||||
const focus = widget.getFocus();
|
||||
const selection = widget.getSelection();
|
||||
|
||||
const fakeKeyboardEvent = new KeyboardEvent('keydown');
|
||||
tree.focusNext(count, loop, fakeKeyboardEvent);
|
||||
await updateFocusFn(widget);
|
||||
|
||||
const listFocus = tree.getFocus();
|
||||
if (listFocus.length) {
|
||||
tree.reveal(listFocus[0]);
|
||||
}
|
||||
const newFocus = widget.getFocus();
|
||||
|
||||
if (selection.length > 1 || !equals(focus, selection) || equals(focus, newFocus)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure DOM Focus
|
||||
ensureDOMFocus(focused);
|
||||
const fakeKeyboardEvent = new KeyboardEvent('keydown');
|
||||
widget.setSelection(newFocus, fakeKeyboardEvent);
|
||||
}
|
||||
|
||||
async function navigate(widget: WorkbenchListWidget | undefined, updateFocusFn: (widget: WorkbenchListWidget) => void | Promise<void>): Promise<void> {
|
||||
if (!widget) {
|
||||
return;
|
||||
}
|
||||
|
||||
await updateFocus(widget, updateFocusFn);
|
||||
|
||||
const listFocus = widget.getFocus();
|
||||
|
||||
if (listFocus.length) {
|
||||
widget.reveal(listFocus[0]);
|
||||
}
|
||||
|
||||
ensureDOMFocus(widget);
|
||||
}
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
@@ -69,7 +73,81 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
primary: KeyCode.DownArrow,
|
||||
secondary: [KeyMod.WinCtrl | KeyCode.KEY_N]
|
||||
},
|
||||
handler: (accessor, arg2) => focusDown(accessor, arg2)
|
||||
handler: (accessor, arg2) => {
|
||||
navigate(accessor.get(IListService).lastFocusedList, async widget => {
|
||||
const fakeKeyboardEvent = new KeyboardEvent('keydown');
|
||||
await widget.focusNext(typeof arg2 === 'number' ? arg2 : 1, false, fakeKeyboardEvent);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'list.focusUp',
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
when: WorkbenchListFocusContextKey,
|
||||
primary: KeyCode.UpArrow,
|
||||
mac: {
|
||||
primary: KeyCode.UpArrow,
|
||||
secondary: [KeyMod.WinCtrl | KeyCode.KEY_P]
|
||||
},
|
||||
handler: (accessor, arg2) => {
|
||||
navigate(accessor.get(IListService).lastFocusedList, async widget => {
|
||||
const fakeKeyboardEvent = new KeyboardEvent('keydown');
|
||||
await widget.focusPrevious(typeof arg2 === 'number' ? arg2 : 1, false, fakeKeyboardEvent);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'list.focusPageDown',
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
when: WorkbenchListFocusContextKey,
|
||||
primary: KeyCode.PageDown,
|
||||
handler: (accessor) => {
|
||||
navigate(accessor.get(IListService).lastFocusedList, async widget => {
|
||||
const fakeKeyboardEvent = new KeyboardEvent('keydown');
|
||||
await widget.focusNextPage(fakeKeyboardEvent);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'list.focusPageUp',
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
when: WorkbenchListFocusContextKey,
|
||||
primary: KeyCode.PageUp,
|
||||
handler: (accessor) => {
|
||||
navigate(accessor.get(IListService).lastFocusedList, async widget => {
|
||||
const fakeKeyboardEvent = new KeyboardEvent('keydown');
|
||||
await widget.focusPreviousPage(fakeKeyboardEvent);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'list.focusFirst',
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
when: WorkbenchListFocusContextKey,
|
||||
primary: KeyCode.Home,
|
||||
handler: (accessor) => {
|
||||
navigate(accessor.get(IListService).lastFocusedList, async widget => {
|
||||
const fakeKeyboardEvent = new KeyboardEvent('keydown');
|
||||
await widget.focusFirst(fakeKeyboardEvent);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'list.focusLast',
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
when: WorkbenchListFocusContextKey,
|
||||
primary: KeyCode.End,
|
||||
handler: (accessor) => {
|
||||
navigate(accessor.get(IListService).lastFocusedList, async widget => {
|
||||
const fakeKeyboardEvent = new KeyboardEvent('keydown');
|
||||
await widget.focusLast(fakeKeyboardEvent);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
function expandMultiSelection(focused: WorkbenchListWidget, previousFocus: unknown): void {
|
||||
@@ -116,64 +194,28 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
when: ContextKeyExpr.and(WorkbenchListFocusContextKey, WorkbenchListSupportsMultiSelectContextKey),
|
||||
primary: KeyMod.Shift | KeyCode.DownArrow,
|
||||
handler: (accessor, arg2) => {
|
||||
const focused = accessor.get(IListService).lastFocusedList;
|
||||
const widget = accessor.get(IListService).lastFocusedList;
|
||||
|
||||
// List / Tree
|
||||
if (focused instanceof List || focused instanceof PagedList || focused instanceof Table || focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) {
|
||||
const list = focused;
|
||||
|
||||
// Focus down first
|
||||
const previousFocus = list.getFocus() ? list.getFocus()[0] : undefined;
|
||||
focusDown(accessor, arg2, false);
|
||||
|
||||
// Then adjust selection
|
||||
expandMultiSelection(focused, previousFocus);
|
||||
if (!widget) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function focusUp(accessor: ServicesAccessor, arg2?: number, loop: boolean = false): void {
|
||||
const focused = accessor.get(IListService).lastFocusedList;
|
||||
const count = typeof arg2 === 'number' ? arg2 : 1;
|
||||
|
||||
// List
|
||||
if (focused instanceof List || focused instanceof PagedList || focused instanceof Table) {
|
||||
const list = focused;
|
||||
|
||||
list.focusPrevious(count);
|
||||
const listFocus = list.getFocus();
|
||||
if (listFocus.length) {
|
||||
list.reveal(listFocus[0]);
|
||||
}
|
||||
}
|
||||
|
||||
// Tree
|
||||
else if (focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) {
|
||||
const tree = focused;
|
||||
|
||||
// Focus down first
|
||||
const previousFocus = widget.getFocus() ? widget.getFocus()[0] : undefined;
|
||||
const fakeKeyboardEvent = new KeyboardEvent('keydown');
|
||||
tree.focusPrevious(count, loop, fakeKeyboardEvent);
|
||||
widget.focusNext(typeof arg2 === 'number' ? arg2 : 1, false, fakeKeyboardEvent);
|
||||
|
||||
const listFocus = tree.getFocus();
|
||||
if (listFocus.length) {
|
||||
tree.reveal(listFocus[0]);
|
||||
// Then adjust selection
|
||||
expandMultiSelection(widget, previousFocus);
|
||||
|
||||
const focus = widget.getFocus();
|
||||
|
||||
if (focus.length) {
|
||||
widget.reveal(focus[0]);
|
||||
}
|
||||
|
||||
ensureDOMFocus(widget);
|
||||
}
|
||||
|
||||
// Ensure DOM Focus
|
||||
ensureDOMFocus(focused);
|
||||
}
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'list.focusUp',
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
when: WorkbenchListFocusContextKey,
|
||||
primary: KeyCode.UpArrow,
|
||||
mac: {
|
||||
primary: KeyCode.UpArrow,
|
||||
secondary: [KeyMod.WinCtrl | KeyCode.KEY_P]
|
||||
},
|
||||
handler: (accessor, arg2) => focusUp(accessor, arg2)
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
@@ -182,19 +224,27 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
when: ContextKeyExpr.and(WorkbenchListFocusContextKey, WorkbenchListSupportsMultiSelectContextKey),
|
||||
primary: KeyMod.Shift | KeyCode.UpArrow,
|
||||
handler: (accessor, arg2) => {
|
||||
const focused = accessor.get(IListService).lastFocusedList;
|
||||
const widget = accessor.get(IListService).lastFocusedList;
|
||||
|
||||
// List / Tree
|
||||
if (focused instanceof List || focused instanceof PagedList || focused instanceof Table || focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) {
|
||||
const list = focused;
|
||||
|
||||
// Focus up first
|
||||
const previousFocus = list.getFocus() ? list.getFocus()[0] : undefined;
|
||||
focusUp(accessor, arg2, false);
|
||||
|
||||
// Then adjust selection
|
||||
expandMultiSelection(focused, previousFocus);
|
||||
if (!widget) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Focus up first
|
||||
const previousFocus = widget.getFocus() ? widget.getFocus()[0] : undefined;
|
||||
const fakeKeyboardEvent = new KeyboardEvent('keydown');
|
||||
widget.focusPrevious(typeof arg2 === 'number' ? arg2 : 1, false, fakeKeyboardEvent);
|
||||
|
||||
// Then adjust selection
|
||||
expandMultiSelection(widget, previousFocus);
|
||||
|
||||
const focus = widget.getFocus();
|
||||
|
||||
if (focus.length) {
|
||||
widget.reveal(focus[0]);
|
||||
}
|
||||
|
||||
ensureDOMFocus(widget);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -208,29 +258,29 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
secondary: [KeyMod.CtrlCmd | KeyCode.UpArrow]
|
||||
},
|
||||
handler: (accessor) => {
|
||||
const focused = accessor.get(IListService).lastFocusedList;
|
||||
const widget = accessor.get(IListService).lastFocusedList;
|
||||
|
||||
// Tree only
|
||||
if (focused && !(focused instanceof List || focused instanceof PagedList || focused instanceof Table)) {
|
||||
if (focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) {
|
||||
const tree = focused;
|
||||
const focusedElements = tree.getFocus();
|
||||
if (!widget || !(widget instanceof ObjectTree || widget instanceof DataTree || widget instanceof AsyncDataTree)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (focusedElements.length === 0) {
|
||||
return;
|
||||
}
|
||||
const tree = widget;
|
||||
const focusedElements = tree.getFocus();
|
||||
|
||||
const focus = focusedElements[0];
|
||||
if (focusedElements.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!tree.collapse(focus)) {
|
||||
const parent = tree.getParentElement(focus);
|
||||
const focus = focusedElements[0];
|
||||
|
||||
if (parent) {
|
||||
const fakeKeyboardEvent = new KeyboardEvent('keydown');
|
||||
tree.setFocus([parent], fakeKeyboardEvent);
|
||||
tree.reveal(parent);
|
||||
}
|
||||
}
|
||||
if (!tree.collapse(focus)) {
|
||||
const parent = tree.getParentElement(focus);
|
||||
|
||||
if (parent) {
|
||||
navigate(widget, widget => {
|
||||
const fakeKeyboardEvent = new KeyboardEvent('keydown');
|
||||
widget.setFocus([parent], fakeKeyboardEvent);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -260,25 +310,24 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
when: WorkbenchListFocusContextKey,
|
||||
handler: (accessor) => {
|
||||
const focused = accessor.get(IListService).lastFocusedList;
|
||||
const widget = accessor.get(IListService).lastFocusedList;
|
||||
|
||||
if (!focused || focused instanceof List || focused instanceof PagedList || focused instanceof Table) {
|
||||
if (!widget || !(widget instanceof ObjectTree || widget instanceof DataTree || widget instanceof AsyncDataTree)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) {
|
||||
const tree = focused;
|
||||
const focusedElements = tree.getFocus();
|
||||
if (focusedElements.length === 0) {
|
||||
return;
|
||||
}
|
||||
const focus = focusedElements[0];
|
||||
const parent = tree.getParentElement(focus);
|
||||
if (parent) {
|
||||
const tree = widget;
|
||||
const focusedElements = tree.getFocus();
|
||||
if (focusedElements.length === 0) {
|
||||
return;
|
||||
}
|
||||
const focus = focusedElements[0];
|
||||
const parent = tree.getParentElement(focus);
|
||||
if (parent) {
|
||||
navigate(widget, widget => {
|
||||
const fakeKeyboardEvent = new KeyboardEvent('keydown');
|
||||
tree.setFocus([parent], fakeKeyboardEvent);
|
||||
tree.reveal(parent);
|
||||
}
|
||||
widget.setFocus([parent], fakeKeyboardEvent);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -289,202 +338,66 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
when: WorkbenchListFocusContextKey,
|
||||
primary: KeyCode.RightArrow,
|
||||
handler: (accessor) => {
|
||||
const focused = accessor.get(IListService).lastFocusedList;
|
||||
const widget = accessor.get(IListService).lastFocusedList;
|
||||
|
||||
// Tree only
|
||||
if (focused && !(focused instanceof List || focused instanceof PagedList || focused instanceof Table)) {
|
||||
if (focused instanceof ObjectTree || focused instanceof DataTree) {
|
||||
// TODO@Joao: instead of doing this here, just delegate to a tree method
|
||||
const tree = focused;
|
||||
const focusedElements = tree.getFocus();
|
||||
if (!widget) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (focusedElements.length === 0) {
|
||||
return;
|
||||
if (widget instanceof ObjectTree || widget instanceof DataTree) {
|
||||
// TODO@Joao: instead of doing this here, just delegate to a tree method
|
||||
const focusedElements = widget.getFocus();
|
||||
|
||||
if (focusedElements.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const focus = focusedElements[0];
|
||||
|
||||
if (!widget.expand(focus)) {
|
||||
const child = widget.getFirstElementChild(focus);
|
||||
|
||||
if (child) {
|
||||
const node = widget.getNode(child);
|
||||
|
||||
if (node.visible) {
|
||||
navigate(widget, widget => {
|
||||
const fakeKeyboardEvent = new KeyboardEvent('keydown');
|
||||
widget.setFocus([child], fakeKeyboardEvent);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (widget instanceof AsyncDataTree) {
|
||||
// TODO@Joao: instead of doing this here, just delegate to a tree method
|
||||
const focusedElements = widget.getFocus();
|
||||
|
||||
const focus = focusedElements[0];
|
||||
if (focusedElements.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!tree.expand(focus)) {
|
||||
const child = tree.getFirstElementChild(focus);
|
||||
const focus = focusedElements[0];
|
||||
widget.expand(focus).then(didExpand => {
|
||||
if (focus && !didExpand) {
|
||||
const child = widget.getFirstElementChild(focus);
|
||||
|
||||
if (child) {
|
||||
const node = tree.getNode(child);
|
||||
const node = widget.getNode(child);
|
||||
|
||||
if (node.visible) {
|
||||
const fakeKeyboardEvent = new KeyboardEvent('keydown');
|
||||
tree.setFocus([child], fakeKeyboardEvent);
|
||||
tree.reveal(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (focused instanceof AsyncDataTree) {
|
||||
// TODO@Joao: instead of doing this here, just delegate to a tree method
|
||||
const tree = focused;
|
||||
const focusedElements = tree.getFocus();
|
||||
|
||||
if (focusedElements.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const focus = focusedElements[0];
|
||||
tree.expand(focus).then(didExpand => {
|
||||
if (focus && !didExpand) {
|
||||
const child = tree.getFirstElementChild(focus);
|
||||
|
||||
if (child) {
|
||||
const node = tree.getNode(child);
|
||||
|
||||
if (node.visible) {
|
||||
navigate(widget, widget => {
|
||||
const fakeKeyboardEvent = new KeyboardEvent('keydown');
|
||||
tree.setFocus([child], fakeKeyboardEvent);
|
||||
tree.reveal(child);
|
||||
}
|
||||
widget.setFocus([child], fakeKeyboardEvent);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'list.focusPageUp',
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
when: WorkbenchListFocusContextKey,
|
||||
primary: KeyCode.PageUp,
|
||||
handler: (accessor) => {
|
||||
const focused = accessor.get(IListService).lastFocusedList;
|
||||
|
||||
// List
|
||||
if (focused instanceof List || focused instanceof PagedList || focused instanceof Table) {
|
||||
focused.focusPreviousPage();
|
||||
}
|
||||
|
||||
// Tree
|
||||
else if (focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) {
|
||||
const fakeKeyboardEvent = new KeyboardEvent('keydown');
|
||||
focused.focusPreviousPage(fakeKeyboardEvent);
|
||||
}
|
||||
|
||||
// Ensure DOM Focus
|
||||
ensureDOMFocus(focused);
|
||||
}
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'list.focusPageDown',
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
when: WorkbenchListFocusContextKey,
|
||||
primary: KeyCode.PageDown,
|
||||
handler: (accessor) => {
|
||||
const focused = accessor.get(IListService).lastFocusedList;
|
||||
|
||||
// List
|
||||
if (focused instanceof List || focused instanceof PagedList || focused instanceof Table) {
|
||||
focused.focusNextPage();
|
||||
}
|
||||
|
||||
// Tree
|
||||
else if (focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) {
|
||||
const fakeKeyboardEvent = new KeyboardEvent('keydown');
|
||||
focused.focusNextPage(fakeKeyboardEvent);
|
||||
}
|
||||
|
||||
// Ensure DOM Focus
|
||||
ensureDOMFocus(focused);
|
||||
}
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'list.focusFirst',
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
when: WorkbenchListFocusContextKey,
|
||||
primary: KeyCode.Home,
|
||||
handler: accessor => listFocusFirst(accessor)
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'list.focusFirstChild',
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
when: WorkbenchListFocusContextKey,
|
||||
primary: 0,
|
||||
handler: accessor => listFocusFirst(accessor, { fromFocused: true })
|
||||
});
|
||||
|
||||
function listFocusFirst(accessor: ServicesAccessor, options?: { fromFocused: boolean }): void {
|
||||
const focused = accessor.get(IListService).lastFocusedList;
|
||||
|
||||
// List
|
||||
if (focused instanceof List || focused instanceof PagedList || focused instanceof Table) {
|
||||
const list = focused;
|
||||
|
||||
list.setFocus([0]);
|
||||
list.reveal(0);
|
||||
}
|
||||
|
||||
// Tree
|
||||
else if (focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) {
|
||||
const tree = focused;
|
||||
const fakeKeyboardEvent = new KeyboardEvent('keydown');
|
||||
tree.focusFirst(fakeKeyboardEvent);
|
||||
|
||||
const focus = tree.getFocus();
|
||||
|
||||
if (focus.length > 0) {
|
||||
tree.reveal(focus[0]);
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure DOM Focus
|
||||
ensureDOMFocus(focused);
|
||||
}
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'list.focusLast',
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
when: WorkbenchListFocusContextKey,
|
||||
primary: KeyCode.End,
|
||||
handler: accessor => listFocusLast(accessor)
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'list.focusLastChild',
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
when: WorkbenchListFocusContextKey,
|
||||
primary: 0,
|
||||
handler: accessor => listFocusLast(accessor, { fromFocused: true })
|
||||
});
|
||||
|
||||
function listFocusLast(accessor: ServicesAccessor, options?: { fromFocused: boolean }): void {
|
||||
const focused = accessor.get(IListService).lastFocusedList;
|
||||
|
||||
// List
|
||||
if (focused instanceof List || focused instanceof PagedList || focused instanceof Table) {
|
||||
const list = focused;
|
||||
|
||||
list.setFocus([list.length - 1]);
|
||||
list.reveal(list.length - 1);
|
||||
}
|
||||
|
||||
// Tree
|
||||
else if (focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) {
|
||||
const tree = focused;
|
||||
const fakeKeyboardEvent = new KeyboardEvent('keydown');
|
||||
tree.focusLast(fakeKeyboardEvent);
|
||||
|
||||
const focus = tree.getFocus();
|
||||
|
||||
if (focus.length > 0) {
|
||||
tree.reveal(focus[0]);
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure DOM Focus
|
||||
ensureDOMFocus(focused);
|
||||
}
|
||||
|
||||
|
||||
function focusElement(accessor: ServicesAccessor, retainCurrentFocus: boolean): void {
|
||||
function selectElement(accessor: ServicesAccessor, retainCurrentFocus: boolean): void {
|
||||
const focused = accessor.get(IListService).lastFocusedList;
|
||||
const fakeKeyboardEvent = getSelectionKeyboardEvent('keydown', retainCurrentFocus);
|
||||
// List
|
||||
@@ -525,7 +438,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
secondary: [KeyMod.CtrlCmd | KeyCode.DownArrow]
|
||||
},
|
||||
handler: (accessor) => {
|
||||
focusElement(accessor, false);
|
||||
selectElement(accessor, false);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -534,7 +447,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
when: WorkbenchListFocusContextKey,
|
||||
handler: accessor => {
|
||||
focusElement(accessor, true);
|
||||
selectElement(accessor, true);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -654,7 +567,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
}
|
||||
}
|
||||
|
||||
focusElement(accessor, true);
|
||||
selectElement(accessor, true);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -664,43 +577,24 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
when: ContextKeyExpr.and(WorkbenchListFocusContextKey, WorkbenchListHasSelectionOrFocus),
|
||||
primary: KeyCode.Escape,
|
||||
handler: (accessor) => {
|
||||
const focused = accessor.get(IListService).lastFocusedList;
|
||||
const widget = accessor.get(IListService).lastFocusedList;
|
||||
|
||||
// List
|
||||
if (focused instanceof List || focused instanceof PagedList || focused instanceof Table) {
|
||||
const list = focused;
|
||||
|
||||
list.setSelection([]);
|
||||
list.setFocus([]);
|
||||
if (!widget) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Tree
|
||||
else if (focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) {
|
||||
const list = focused;
|
||||
const fakeKeyboardEvent = new KeyboardEvent('keydown');
|
||||
|
||||
list.setSelection([], fakeKeyboardEvent);
|
||||
list.setFocus([], fakeKeyboardEvent);
|
||||
}
|
||||
const fakeKeyboardEvent = new KeyboardEvent('keydown');
|
||||
widget.setSelection([], fakeKeyboardEvent);
|
||||
widget.setFocus([], fakeKeyboardEvent);
|
||||
widget.setAnchor(undefined);
|
||||
}
|
||||
});
|
||||
|
||||
CommandsRegistry.registerCommand({
|
||||
id: 'list.toggleKeyboardNavigation',
|
||||
handler: (accessor) => {
|
||||
const focused = accessor.get(IListService).lastFocusedList;
|
||||
|
||||
// List
|
||||
if (focused instanceof List || focused instanceof PagedList || focused instanceof Table) {
|
||||
const list = focused;
|
||||
list.toggleKeyboardNavigation();
|
||||
}
|
||||
|
||||
// Tree
|
||||
else if (focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) {
|
||||
const tree = focused;
|
||||
tree.toggleKeyboardNavigation();
|
||||
}
|
||||
const widget = accessor.get(IListService).lastFocusedList;
|
||||
widget?.toggleKeyboardNavigation();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -343,7 +343,7 @@ export class NewWindowAction extends Action {
|
||||
}
|
||||
|
||||
run(): Promise<void> {
|
||||
return this.hostService.openWindow();
|
||||
return this.hostService.openWindow({ remoteAuthority: null });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ import { IRange } from 'vs/editor/common/core/range';
|
||||
import { CursorChangeReason, ICursorPositionChangedEvent } from 'vs/editor/common/controller/cursorEvents';
|
||||
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
|
||||
import { TrackedRangeStickiness, IModelDecorationsChangeAccessor } from 'vs/editor/common/model';
|
||||
import { EditorOption } from 'vs/editor/common/config/editorOptions';
|
||||
|
||||
export interface IRangeHighlightDecoration {
|
||||
resource: URI;
|
||||
@@ -264,6 +265,11 @@ export class OpenWorkspaceButtonContribution extends Disposable implements IEdit
|
||||
}
|
||||
}
|
||||
|
||||
if (editor.getOption(EditorOption.inDiffEditor)) {
|
||||
// in diff editor
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -83,17 +83,21 @@ export abstract class Composite extends Component implements IComposite {
|
||||
|
||||
protected actionRunner: IActionRunner | undefined;
|
||||
|
||||
private _telemetryService: ITelemetryService;
|
||||
protected get telemetryService(): ITelemetryService { return this._telemetryService; }
|
||||
|
||||
private visible: boolean;
|
||||
private parent: HTMLElement | undefined;
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
private _telemetryService: ITelemetryService,
|
||||
telemetryService: ITelemetryService,
|
||||
themeService: IThemeService,
|
||||
storageService: IStorageService
|
||||
) {
|
||||
super(id, themeService, storageService);
|
||||
|
||||
this._telemetryService = telemetryService;
|
||||
this.visible = false;
|
||||
}
|
||||
|
||||
@@ -101,10 +105,6 @@ export abstract class Composite extends Component implements IComposite {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
protected get telemetryService(): ITelemetryService {
|
||||
return this._telemetryService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: Clients should not call this method, the workbench calls this
|
||||
* method. Calling it otherwise may result in unexpected behavior.
|
||||
@@ -203,7 +203,7 @@ export abstract class Composite extends Component implements IComposite {
|
||||
*/
|
||||
getActionRunner(): IActionRunner {
|
||||
if (!this.actionRunner) {
|
||||
this.actionRunner = new ActionRunner();
|
||||
this.actionRunner = this._register(new ActionRunner());
|
||||
}
|
||||
|
||||
return this.actionRunner;
|
||||
|
||||
@@ -958,10 +958,6 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
|
||||
return new Dimension(availableWidth, availableHeight);
|
||||
}
|
||||
|
||||
getWorkbenchContainer(): HTMLElement {
|
||||
return this.parent;
|
||||
}
|
||||
|
||||
toggleZenMode(skipLayout?: boolean, restoring = false): void {
|
||||
this.state.zenMode.active = !this.state.zenMode.active;
|
||||
this.state.zenMode.transitionDisposables.clear();
|
||||
|
||||
@@ -453,7 +453,7 @@ registerThemingParticipant((theme, collector) => {
|
||||
outline: 1px dashed;
|
||||
}
|
||||
|
||||
.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item:focus:before {
|
||||
.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item:focus .active-item-indicator:before {
|
||||
border-left-color: ${outline};
|
||||
}
|
||||
|
||||
@@ -472,7 +472,7 @@ registerThemingParticipant((theme, collector) => {
|
||||
const focusBorderColor = theme.getColor(focusBorder);
|
||||
if (focusBorderColor) {
|
||||
collector.addRule(`
|
||||
.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item:focus:before {
|
||||
.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item:focus .active-item-indicator:before {
|
||||
border-left-color: ${focusBorderColor};
|
||||
}
|
||||
`);
|
||||
|
||||
@@ -76,10 +76,9 @@
|
||||
}
|
||||
|
||||
.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked .active-item-indicator:before,
|
||||
.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item:focus:before {
|
||||
.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item:focus .active-item-indicator:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
@@ -92,12 +91,9 @@
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked:focus .active-item-indicator:before {
|
||||
visibility: hidden; /* don't show active border + focus at the same time, focus takes priority */
|
||||
}
|
||||
|
||||
/* Hides active elements in high contrast mode */
|
||||
.monaco-workbench.hc-black .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked .active-item-indicator {
|
||||
.monaco-workbench.hc-black .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked:not(:focus) .active-item-indicator {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -105,13 +101,11 @@
|
||||
border-left: none !important; /* no focus feedback when using mouse */
|
||||
}
|
||||
|
||||
.monaco-workbench .activitybar.left > .content :not(.monaco-menu) > .monaco-action-bar .action-item:focus:before,
|
||||
.monaco-workbench .activitybar.left > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked .active-item-indicator:before{
|
||||
.monaco-workbench .activitybar.left > .content :not(.monaco-menu) > .monaco-action-bar .action-item .active-item-indicator:before{
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.monaco-workbench .activitybar.right > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked .active-item-indicator:before,
|
||||
.monaco-workbench .activitybar.right > .content :not(.monaco-menu) > .monaco-action-bar .action-item:focus:before {
|
||||
.monaco-workbench .activitybar.right > .content :not(.monaco-menu) > .monaco-action-bar .action-item .active-item-indicator:before {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
import { localize } from 'vs/nls';
|
||||
import { IAction, toAction } from 'vs/base/common/actions';
|
||||
import { illegalArgument } from 'vs/base/common/errors';
|
||||
import { equals } from 'vs/base/common/arrays';
|
||||
import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IBadge } from 'vs/workbench/services/activity/common/activity';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
@@ -277,10 +276,8 @@ export class CompositeBar extends Widget implements ICompositeBar {
|
||||
|
||||
switch (this.options.orientation) {
|
||||
case ActionsOrientation.HORIZONTAL:
|
||||
case ActionsOrientation.HORIZONTAL_REVERSE:
|
||||
return posX < rect.left;
|
||||
case ActionsOrientation.VERTICAL:
|
||||
case ActionsOrientation.VERTICAL_REVERSE:
|
||||
return posY < rect.top;
|
||||
}
|
||||
}
|
||||
@@ -538,10 +535,8 @@ export class CompositeBar extends Widget implements ICompositeBar {
|
||||
compositesToShow.length ? compositesToShow.splice(compositesToShow.length - 2, 1) : compositesToShow.pop();
|
||||
}
|
||||
|
||||
const visibleCompositesChange = !equals(compositesToShow, this.visibleComposites);
|
||||
|
||||
// Pull out overflow action if there is a composite change so that we can add it to the end later
|
||||
if (this.compositeOverflowAction && visibleCompositesChange) {
|
||||
// Remove the overflow action if there are no overflows
|
||||
if (!overflows && this.compositeOverflowAction) {
|
||||
compositeSwitcherBar.pull(compositeSwitcherBar.length() - 1);
|
||||
|
||||
this.compositeOverflowAction.dispose();
|
||||
@@ -584,7 +579,7 @@ export class CompositeBar extends Widget implements ICompositeBar {
|
||||
});
|
||||
|
||||
// Add overflow action as needed
|
||||
if ((visibleCompositesChange && overflows)) {
|
||||
if ((overflows && !this.compositeOverflowAction)) {
|
||||
this.compositeOverflowAction = this.instantiationService.createInstance(CompositeOverflowActivityAction, () => {
|
||||
if (this.compositeOverflowActionViewItem) {
|
||||
this.compositeOverflowActionViewItem.showMenu();
|
||||
|
||||
@@ -305,8 +305,9 @@ export class ActivityActionViewItem extends BaseActionViewItem {
|
||||
}
|
||||
|
||||
if (clazz) {
|
||||
this.badge.classList.add(...clazz.split(' '));
|
||||
this.badgeDisposable.value = toDisposable(() => this.badge.classList.remove(...clazz.split(' ')));
|
||||
const classNames = clazz.split(' ');
|
||||
this.badge.classList.add(...classNames);
|
||||
this.badgeDisposable.value = toDisposable(() => this.badge.classList.remove(...classNames));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -162,9 +162,9 @@ export class BreadcrumbsControl {
|
||||
static readonly Payload_RevealAside = {};
|
||||
static readonly Payload_Pick = {};
|
||||
|
||||
static readonly CK_BreadcrumbsPossible = new RawContextKey('breadcrumbsPossible', false);
|
||||
static readonly CK_BreadcrumbsVisible = new RawContextKey('breadcrumbsVisible', false);
|
||||
static readonly CK_BreadcrumbsActive = new RawContextKey('breadcrumbsActive', false);
|
||||
static readonly CK_BreadcrumbsPossible = new RawContextKey('breadcrumbsPossible', false, localize('breadcrumbsPossible', "Whether the editor can show breadcrumbs"));
|
||||
static readonly CK_BreadcrumbsVisible = new RawContextKey('breadcrumbsVisible', false, localize('breadcrumbsVisible', "Whether breadcrumbs are currently visible"));
|
||||
static readonly CK_BreadcrumbsActive = new RawContextKey('breadcrumbsActive', false, localize('breadcrumbsActive', "Whether breadcrumbs have focus"));
|
||||
|
||||
private readonly _ckBreadcrumbsPossible: IContextKey<boolean>;
|
||||
private readonly _ckBreadcrumbsVisible: IContextKey<boolean>;
|
||||
|
||||
@@ -287,7 +287,7 @@ registerEditorContribution(UntitledHintContribution.ID, UntitledHintContribution
|
||||
|
||||
// Register Status Actions
|
||||
const registry = Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions);
|
||||
registry.registerWorkbenchAction(SyncActionDescriptor.from(ChangeModeAction, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_M) }), 'Change Language Mode');
|
||||
registry.registerWorkbenchAction(SyncActionDescriptor.from(ChangeModeAction, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_M) }), 'Change Language Mode', undefined, ContextKeyExpr.not('notebookEditorFocused'));
|
||||
registry.registerWorkbenchAction(SyncActionDescriptor.from(ChangeEOLAction), 'Change End of Line Sequence');
|
||||
registry.registerWorkbenchAction(SyncActionDescriptor.from(ChangeEncodingAction), 'Change File Encoding');
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/la
|
||||
import { IHistoryService } from 'vs/workbench/services/history/common/history';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { CLOSE_EDITOR_COMMAND_ID, MOVE_ACTIVE_EDITOR_COMMAND_ID, ActiveEditorMoveArguments, SPLIT_EDITOR_LEFT, SPLIT_EDITOR_RIGHT, SPLIT_EDITOR_UP, SPLIT_EDITOR_DOWN, splitEditor, LAYOUT_EDITOR_GROUPS_COMMAND_ID, mergeAllGroups, UNPIN_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands';
|
||||
import { CLOSE_EDITOR_COMMAND_ID, MOVE_ACTIVE_EDITOR_COMMAND_ID, ActiveEditorMoveArguments, SPLIT_EDITOR_LEFT, SPLIT_EDITOR_RIGHT, SPLIT_EDITOR_UP, SPLIT_EDITOR_DOWN, splitEditor, LAYOUT_EDITOR_GROUPS_COMMAND_ID, UNPIN_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands';
|
||||
import { IEditorGroupsService, IEditorGroup, GroupsArrangement, GroupLocation, GroupDirection, preferredSideBySideGroupDirection, IFindGroupScope, GroupOrientation, EditorGroupLayout, GroupsOrder, OpenEditorContext } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
@@ -216,7 +216,7 @@ export class JoinAllGroupsAction extends Action {
|
||||
}
|
||||
|
||||
async run(): Promise<void> {
|
||||
mergeAllGroups(this.editorGroupService);
|
||||
this.editorGroupService.mergeAllGroups();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -279,17 +279,6 @@ function registerEditorGroupsLayoutCommand(): void {
|
||||
});
|
||||
}
|
||||
|
||||
export function mergeAllGroups(editorGroupService: IEditorGroupsService): void {
|
||||
const target = editorGroupService.activeGroup;
|
||||
for (const group of editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE)) {
|
||||
if (group === target) {
|
||||
continue; // keep target
|
||||
}
|
||||
|
||||
editorGroupService.mergeGroup(group, target);
|
||||
}
|
||||
}
|
||||
|
||||
function registerDiffEditorCommands(): void {
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: GOTO_NEXT_CHANGE,
|
||||
|
||||
@@ -1287,7 +1287,18 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
// Open next active if there are more to show
|
||||
const nextActiveEditor = this._group.activeEditor;
|
||||
if (nextActiveEditor) {
|
||||
const options = EditorOptions.create({ preserveFocus: !focusNext });
|
||||
const preserveFocus = !focusNext;
|
||||
|
||||
let activation: EditorActivation | undefined = undefined;
|
||||
if (preserveFocus && this.accessor.activeGroup !== this) {
|
||||
// If we are opening the next editor in an inactive group
|
||||
// without focussing it, ensure we preserve the editor
|
||||
// group sizes in case that group is minimized.
|
||||
// https://github.com/microsoft/vscode/issues/117686
|
||||
activation = EditorActivation.PRESERVE;
|
||||
}
|
||||
|
||||
const options = EditorOptions.create({ preserveFocus, activation });
|
||||
|
||||
// When closing an editor due to an error we can end up in a loop where we continue closing
|
||||
// editors that fail to open (e.g. when the file no longer exists). We do not want to show
|
||||
@@ -1610,7 +1621,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
// Extract active vs. inactive replacements
|
||||
let activeReplacement: EditorReplacement | undefined;
|
||||
const inactiveReplacements: EditorReplacement[] = [];
|
||||
for (let { editor, replacement, options } of editors) {
|
||||
for (let { editor, replacement, forceReplaceDirty, options } of editors) {
|
||||
const index = this.getIndexOfEditor(editor);
|
||||
if (index >= 0) {
|
||||
const isActiveEditor = this.isActive(editor);
|
||||
@@ -1625,7 +1636,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
options.inactive = !isActiveEditor;
|
||||
options.pinned = options.pinned ?? true; // unless specified, prefer to pin upon replace
|
||||
|
||||
const editorToReplace = { editor, replacement, options };
|
||||
const editorToReplace = { editor, replacement, forceReplaceDirty, options };
|
||||
if (isActiveEditor) {
|
||||
activeReplacement = editorToReplace;
|
||||
} else {
|
||||
@@ -1635,14 +1646,20 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
}
|
||||
|
||||
// Handle inactive first
|
||||
for (const { editor, replacement, options } of inactiveReplacements) {
|
||||
for (const { editor, replacement, forceReplaceDirty, options } of inactiveReplacements) {
|
||||
|
||||
// Open inactive editor
|
||||
await this.doOpenEditor(replacement, options);
|
||||
|
||||
// Close replaced inactive editor unless they match
|
||||
if (!editor.matches(replacement)) {
|
||||
const closed = await this.doCloseEditorWithDirtyHandling(editor, { preserveFocus: true });
|
||||
let closed = false;
|
||||
if (forceReplaceDirty) {
|
||||
this.doCloseEditor(editor, false);
|
||||
closed = true;
|
||||
} else {
|
||||
closed = await this.doCloseEditorWithDirtyHandling(editor, { preserveFocus: true });
|
||||
}
|
||||
if (!closed) {
|
||||
return; // canceled
|
||||
}
|
||||
@@ -1657,7 +1674,11 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
|
||||
// Close replaced active editor unless they match
|
||||
if (!activeReplacement.editor.matches(activeReplacement.replacement)) {
|
||||
await this.doCloseEditorWithDirtyHandling(activeReplacement.editor, { preserveFocus: true });
|
||||
if (activeReplacement.forceReplaceDirty) {
|
||||
this.doCloseEditor(activeReplacement.editor, false);
|
||||
} else {
|
||||
await this.doCloseEditorWithDirtyHandling(activeReplacement.editor, { preserveFocus: true });
|
||||
}
|
||||
}
|
||||
|
||||
await openEditorResult;
|
||||
@@ -1776,6 +1797,8 @@ class EditorOpeningEvent implements IEditorOpeningEvent {
|
||||
export interface EditorReplacement {
|
||||
readonly editor: EditorInput;
|
||||
readonly replacement: EditorInput;
|
||||
/** Skips asking the user for confirmation and doesn't save the document. Only use this if you really need to! */
|
||||
readonly forceReplaceDirty?: boolean;
|
||||
readonly options?: EditorOptions;
|
||||
}
|
||||
|
||||
|
||||
@@ -764,6 +764,18 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro
|
||||
return targetView;
|
||||
}
|
||||
|
||||
mergeAllGroups(target = this.activeGroup): IEditorGroupView {
|
||||
for (const group of this.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE)) {
|
||||
if (group === target) {
|
||||
continue; // keep target
|
||||
}
|
||||
|
||||
this.mergeGroup(group, target);
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
private assertGroupView(group: IEditorGroupView | GroupIdentifier): IEditorGroupView {
|
||||
let groupView: IEditorGroupView | undefined;
|
||||
if (typeof group === 'number') {
|
||||
|
||||
@@ -1064,19 +1064,12 @@ export class ChangeModeAction extends Action {
|
||||
@IPreferencesService private readonly preferencesService: IPreferencesService,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@ITextFileService private readonly textFileService: ITextFileService,
|
||||
@ICommandService private readonly commandService: ICommandService,
|
||||
@ITelemetryService private readonly telemetryService: ITelemetryService
|
||||
) {
|
||||
super(actionId, actionLabel);
|
||||
}
|
||||
|
||||
async run(event: any, data: ITelemetryData): Promise<void> {
|
||||
const activeEditorPane = this.editorService.activeEditorPane as unknown as { isNotebookEditor?: boolean } | undefined;
|
||||
if (activeEditorPane?.isNotebookEditor) { // TODO@rebornix TODO@jrieken debt: https://github.com/microsoft/vscode/issues/114554
|
||||
// it's inside notebook editor
|
||||
return this.commandService.executeCommand('notebook.cell.changeLanguage');
|
||||
}
|
||||
|
||||
const activeTextEditorControl = getCodeEditor(this.editorService.activeTextEditorControl);
|
||||
if (!activeTextEditorControl) {
|
||||
await this.quickInputService.pick([{ label: localize('noEditor', "No text editor active at this time") }]);
|
||||
|
||||
@@ -101,6 +101,10 @@
|
||||
margin-right: var(--last-tab-margin-right); /* when tabs wrap, we need a margin away from the absolute positioned editor actions */
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title > .tabs-and-actions-container.wrapping .tabs-container > .tab.last-in-row:not(:last-child) {
|
||||
border-right: 0 !important; /* ensure no border for every last tab in a row except last row (#115046) */
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.has-icon.tab-actions-right,
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.has-icon.tab-actions-off:not(.sticky-compact) {
|
||||
padding-left: 5px; /* reduce padding when we show icons and are in shrinking mode and tab actions is not left (unless sticky-compact) */
|
||||
@@ -113,8 +117,8 @@
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title > .tabs-and-actions-container.wrapping .tabs-container > .tab.sizing-fit {
|
||||
flex-grow: 1; /* grow the tabs to fill each row for a more homogeneous look when tabs wrap */
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title > .tabs-and-actions-container.wrapping .tabs-container > .tab.sizing-fit.last-in-row:not(:last-child) {
|
||||
flex-grow: 1; /* grow the last tab in a row for a more homogeneous look except for last row (#113801) */
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink {
|
||||
|
||||
@@ -1482,6 +1482,48 @@ export class TabsTitleControl extends TitleControl {
|
||||
});
|
||||
}
|
||||
|
||||
// Update the `last-in-row` class on tabs when wrapping
|
||||
// is enabled (it doesn't do any harm otherwise). This
|
||||
// class controls additional properties of tab when it is
|
||||
// the last tab in a row
|
||||
if (tabsWrapMultiLine) {
|
||||
|
||||
// Using a map here to change classes after the for loop is
|
||||
// crucial for performance because changing the class on a
|
||||
// tab can result in layouts of the rendering engine.
|
||||
const tabs = new Map<HTMLElement, boolean /* last in row */>();
|
||||
|
||||
let currentTabsPosY: number | undefined = undefined;
|
||||
let lastTab: HTMLElement | undefined = undefined;
|
||||
for (const child of tabsContainer.children) {
|
||||
const tab = child as HTMLElement;
|
||||
const tabPosY = tab.offsetTop;
|
||||
|
||||
// Marks a new or the first row of tabs
|
||||
if (tabPosY !== currentTabsPosY) {
|
||||
currentTabsPosY = tabPosY;
|
||||
if (lastTab) {
|
||||
tabs.set(lastTab, true); // previous tab must be last in row then
|
||||
}
|
||||
}
|
||||
|
||||
// Always remember last tab and ensure the
|
||||
// last-in-row class is not present until
|
||||
// we know the tab is last
|
||||
lastTab = tab;
|
||||
tabs.set(tab, false);
|
||||
}
|
||||
|
||||
// Last tab overally is always last-in-row
|
||||
if (lastTab) {
|
||||
tabs.set(lastTab, true);
|
||||
}
|
||||
|
||||
for (const [tab, lastInRow] of tabs) {
|
||||
tab.classList.toggle('last-in-row', lastInRow);
|
||||
}
|
||||
}
|
||||
|
||||
return tabsWrapMultiLine;
|
||||
}
|
||||
|
||||
|
||||
@@ -224,12 +224,11 @@ export abstract class TitleControl extends Themable {
|
||||
this.updateEditorActionsToolbar(); // Update editor toolbar whenever contributed actions change
|
||||
}));
|
||||
|
||||
const isPrimaryGroup = (group: string) => group === 'navigation' || group === '1_run';
|
||||
const shouldInlineGroup = (action: SubmenuAction, group: string) => isPrimaryGroup(group) && action.actions.length <= 1;
|
||||
const shouldInlineGroup = (action: SubmenuAction, group: string) => group === 'navigation' && action.actions.length <= 1;
|
||||
|
||||
this.editorToolBarMenuDisposables.add(createAndFillInActionBarActions(
|
||||
titleBarMenu, { arg: this.resourceContext.get(), shouldForwardArgs: true }, { primary, secondary },
|
||||
isPrimaryGroup, 9, shouldInlineGroup
|
||||
'navigation', 9, shouldInlineGroup
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import { Schemas } from 'vs/base/common/network';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { FloatingClickWidget } from 'vs/workbench/browser/codeeditor';
|
||||
import { ITASExperimentService } from 'vs/workbench/services/experiment/common/experimentService';
|
||||
const $ = dom.$;
|
||||
|
||||
const untitledHintSetting = 'workbench.editor.untitled.hint';
|
||||
@@ -28,13 +29,15 @@ export class UntitledHintContribution implements IEditorContribution {
|
||||
private toDispose: IDisposable[];
|
||||
private untitledHintContentWidget: UntitledHintContentWidget | undefined;
|
||||
private button: FloatingClickWidget | undefined;
|
||||
private experimentTreatment: 'text' | 'button' | 'hidden' | undefined;
|
||||
|
||||
constructor(
|
||||
private editor: ICodeEditor,
|
||||
@ICommandService private readonly commandService: ICommandService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@IKeybindingService private readonly keybindingService: IKeybindingService,
|
||||
@IThemeService private readonly themeService: IThemeService
|
||||
@IThemeService private readonly themeService: IThemeService,
|
||||
@ITASExperimentService private readonly experimentService: ITASExperimentService
|
||||
) {
|
||||
this.toDispose = [];
|
||||
this.toDispose.push(this.editor.onDidChangeModel(() => this.update()));
|
||||
@@ -44,13 +47,17 @@ export class UntitledHintContribution implements IEditorContribution {
|
||||
this.update();
|
||||
}
|
||||
}));
|
||||
this.update();
|
||||
this.experimentService.getTreatment<'text' | 'button'>('untitledhint').then(treatment => {
|
||||
this.experimentTreatment = treatment;
|
||||
this.update();
|
||||
});
|
||||
}
|
||||
|
||||
private update(): void {
|
||||
this.untitledHintContentWidget?.dispose();
|
||||
this.button?.dispose();
|
||||
const untitledHintMode = this.configurationService.getValue(untitledHintSetting);
|
||||
const configValue = this.configurationService.getValue<'text' | 'button' | 'hidden' | 'default'>(untitledHintSetting);
|
||||
const untitledHintMode = configValue === 'default' ? (this.experimentTreatment || 'hidden') : configValue;
|
||||
const model = this.editor.getModel();
|
||||
|
||||
if (model && model.uri.scheme === Schemas.untitled && model.getModeId() === PLAINTEXT_MODE_ID) {
|
||||
|
||||
@@ -15,6 +15,7 @@ import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
import { registerIcon } from 'vs/platform/theme/common/iconRegistry';
|
||||
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
|
||||
import { hash } from 'vs/base/common/hash';
|
||||
|
||||
const clearIcon = registerIcon('notifications-clear', Codicon.close, localize('clearIcon', 'Icon for the clear action in notifications.'));
|
||||
const clearAllIcon = registerIcon('notifications-clear-all', Codicon.clearAll, localize('clearAllIcon', 'Icon for the clear all action in notifications.'));
|
||||
@@ -145,6 +146,17 @@ export class CopyNotificationMessageAction extends Action {
|
||||
}
|
||||
}
|
||||
|
||||
interface NotificationActionMetrics {
|
||||
id: string;
|
||||
actionLabel: string;
|
||||
source: string | undefined;
|
||||
}
|
||||
type NotificationActionMetricsClassification = {
|
||||
id: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
|
||||
actionLabel: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
|
||||
source: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
|
||||
};
|
||||
|
||||
export class NotificationActionRunner extends ActionRunner {
|
||||
|
||||
constructor(
|
||||
@@ -154,8 +166,12 @@ export class NotificationActionRunner extends ActionRunner {
|
||||
super();
|
||||
}
|
||||
|
||||
protected async runAction(action: IAction, context: INotificationViewItem): Promise<void> {
|
||||
protected async runAction(action: IAction, context: INotificationViewItem | undefined): Promise<void> {
|
||||
this.telemetryService.publicLog2<WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification>('workbenchActionExecuted', { id: action.id, from: 'message' });
|
||||
if (context) {
|
||||
// If the context is not present it is a "global" notification action. Will be captured by other events
|
||||
this.telemetryService.publicLog2<NotificationActionMetrics, NotificationActionMetricsClassification>('notification:actionExecuted', { id: hash(context.message.original.toString()).toString(), actionLabel: action.label, source: context.sourceId });
|
||||
}
|
||||
|
||||
// Run and make sure to notify on any error again
|
||||
try {
|
||||
|
||||
@@ -7,10 +7,12 @@ import { CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||
import { RawContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
import { INotificationViewItem, isNotificationViewItem } from 'vs/workbench/common/notifications';
|
||||
import { INotificationViewItem, isNotificationViewItem, NotificationsModel } from 'vs/workbench/common/notifications';
|
||||
import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IListService, WorkbenchList } from 'vs/platform/list/browser/listService';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { NotificationMetrics, NotificationMetricsClassification, notificationToMetrics } from 'vs/workbench/browser/parts/notifications/notificationsTelemetry';
|
||||
|
||||
// Center
|
||||
export const SHOW_NOTIFICATIONS_CENTER = 'notifications.showList';
|
||||
@@ -55,7 +57,7 @@ export interface INotificationsToastController {
|
||||
hide(): void;
|
||||
}
|
||||
|
||||
export function registerNotificationCommands(center: INotificationsCenterController, toasts: INotificationsToastController): void {
|
||||
export function registerNotificationCommands(center: INotificationsCenterController, toasts: INotificationsToastController, model: NotificationsModel): void {
|
||||
|
||||
function getNotificationFromContext(listService: IListService, context?: unknown): INotificationViewItem | undefined {
|
||||
if (isNotificationViewItem(context)) {
|
||||
@@ -85,7 +87,16 @@ export function registerNotificationCommands(center: INotificationsCenterControl
|
||||
weight: KeybindingWeight.WorkbenchContrib + 50,
|
||||
when: NotificationsCenterVisibleContext,
|
||||
primary: KeyCode.Escape,
|
||||
handler: accessor => center.hide()
|
||||
handler: accessor => {
|
||||
const telemetryService = accessor.get(ITelemetryService);
|
||||
model.notifications.forEach(n => {
|
||||
if (n.visible) {
|
||||
telemetryService.publicLog2<NotificationMetrics, NotificationMetricsClassification>('notification:hide', notificationToMetrics(n.message.original, n.sourceId, n.silent));
|
||||
}
|
||||
});
|
||||
|
||||
center.hide();
|
||||
}
|
||||
});
|
||||
|
||||
// Toggle Notifications Center
|
||||
@@ -159,7 +170,15 @@ export function registerNotificationCommands(center: INotificationsCenterControl
|
||||
});
|
||||
|
||||
// Hide Toasts
|
||||
CommandsRegistry.registerCommand(HIDE_NOTIFICATION_TOAST, accessor => toasts.hide());
|
||||
CommandsRegistry.registerCommand(HIDE_NOTIFICATION_TOAST, accessor => {
|
||||
const telemetryService = accessor.get(ITelemetryService);
|
||||
model.notifications.forEach(n => {
|
||||
if (n.visible) {
|
||||
telemetryService.publicLog2<NotificationMetrics, NotificationMetricsClassification>('notification:hide', notificationToMetrics(n.message.original, n.sourceId, n.silent));
|
||||
}
|
||||
});
|
||||
toasts.hide();
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerKeybindingRule({
|
||||
id: HIDE_NOTIFICATION_TOAST,
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { INotificationService, NotificationMessage } from 'vs/platform/notification/common/notification';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
|
||||
import { hash } from 'vs/base/common/hash';
|
||||
|
||||
export interface NotificationMetrics {
|
||||
id: string;
|
||||
silent: boolean;
|
||||
source?: string;
|
||||
}
|
||||
|
||||
export type NotificationMetricsClassification = {
|
||||
id: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
|
||||
silent: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
|
||||
source?: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
|
||||
};
|
||||
|
||||
export function notificationToMetrics(message: NotificationMessage, source: string | undefined, silent: boolean): NotificationMetrics {
|
||||
return {
|
||||
id: hash(message.toString()).toString(),
|
||||
silent,
|
||||
source: source || 'core'
|
||||
};
|
||||
}
|
||||
|
||||
export class NotificationsTelemetry extends Disposable implements IWorkbenchContribution {
|
||||
|
||||
constructor(
|
||||
@ITelemetryService private readonly telemetryService: ITelemetryService,
|
||||
@INotificationService private readonly notificationService: INotificationService
|
||||
) {
|
||||
super();
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
this._register(this.notificationService.onDidAddNotification(notification => {
|
||||
const source = notification.source && typeof notification.source !== 'string' ? notification.source.id : notification.source;
|
||||
this.telemetryService.publicLog2<NotificationMetrics, NotificationMetricsClassification>('notification:show', notificationToMetrics(notification.message, source, !!notification.silent));
|
||||
}));
|
||||
|
||||
this._register(this.notificationService.onDidRemoveNotification(notification => {
|
||||
const source = notification.source && typeof notification.source !== 'string' ? notification.source.id : notification.source;
|
||||
this.telemetryService.publicLog2<NotificationMetrics, NotificationMetricsClassification>('notification:close', notificationToMetrics(notification.message, source, !!notification.silent));
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -178,15 +178,17 @@
|
||||
}
|
||||
|
||||
/* Rotate icons when panel is on right */
|
||||
.monaco-workbench .part.panel.right .title-actions .codicon-split-horizontal,
|
||||
.monaco-workbench .part.panel.right .title-actions .codicon-panel-maximize,
|
||||
.monaco-workbench .part.panel.right .title-actions .codicon-panel-restore {
|
||||
.monaco-workbench .part.panel.right .title-actions .codicon-split-horizontal::before,
|
||||
.monaco-workbench .part.panel.right .title-actions .codicon-panel-maximize::before,
|
||||
.monaco-workbench .part.panel.right .title-actions .codicon-panel-restore::before {
|
||||
display: inline-block;
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
/* Rotate icons when panel is on left */
|
||||
.monaco-workbench .part.panel.left .title-actions .codicon-split-horizontal,
|
||||
.monaco-workbench .part.panel.left .title-actions .codicon-panel-maximize,
|
||||
.monaco-workbench .part.panel.left .title-actions .codicon-panel-restore {
|
||||
.monaco-workbench .part.panel.left .title-actions .codicon-split-horizontal::before,
|
||||
.monaco-workbench .part.panel.left .title-actions .codicon-panel-maximize::before,
|
||||
.monaco-workbench .part.panel.left .title-actions .codicon-panel-restore::before {
|
||||
display: inline-block;
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
@@ -41,6 +41,8 @@ import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { Cookie } from 'vs/server/common/cookie';
|
||||
|
||||
export type IOpenRecentAction = IAction & { uri: URI, remoteAuthority?: string };
|
||||
|
||||
export abstract class MenubarControl extends Disposable {
|
||||
|
||||
protected keys = [
|
||||
@@ -160,7 +162,7 @@ export abstract class MenubarControl extends Disposable {
|
||||
this.updateMenubar();
|
||||
}
|
||||
|
||||
protected getOpenRecentActions(): (Separator | IAction & { uri: URI })[] {
|
||||
protected getOpenRecentActions(): (Separator | IOpenRecentAction)[] {
|
||||
if (!this.recentlyOpened) {
|
||||
return [];
|
||||
}
|
||||
@@ -226,12 +228,13 @@ export abstract class MenubarControl extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
private createOpenRecentMenuAction(recent: IRecent): IAction & { uri: URI } {
|
||||
private createOpenRecentMenuAction(recent: IRecent): IOpenRecentAction {
|
||||
|
||||
let label: string;
|
||||
let uri: URI;
|
||||
let commandId: string;
|
||||
let openable: IWindowOpenable;
|
||||
const remoteAuthority = recent.remoteAuthority;
|
||||
|
||||
if (isRecentFolder(recent)) {
|
||||
uri = recent.folderUri;
|
||||
@@ -254,11 +257,12 @@ export abstract class MenubarControl extends Disposable {
|
||||
const openInNewWindow = event && ((!isMacintosh && (event.ctrlKey || event.shiftKey)) || (isMacintosh && (event.metaKey || event.altKey)));
|
||||
|
||||
return this.hostService.openWindow([openable], {
|
||||
forceNewWindow: openInNewWindow
|
||||
forceNewWindow: openInNewWindow,
|
||||
remoteAuthority
|
||||
});
|
||||
});
|
||||
|
||||
return Object.assign(ret, { uri });
|
||||
return Object.assign(ret, { uri, remoteAuthority });
|
||||
}
|
||||
|
||||
private notifyUserOfCustomMenubarAccessibility(): void {
|
||||
@@ -415,7 +419,6 @@ export class CustomMenubarControl extends MenubarControl {
|
||||
.monaco-workbench .menubar > .menubar-menu-button.open,
|
||||
.monaco-workbench .menubar > .menubar-menu-button:focus,
|
||||
.monaco-workbench .menubar > .menubar-menu-button:hover {
|
||||
outline-offset: -1px;
|
||||
outline-color: ${menubarSelectedBorderColor};
|
||||
}
|
||||
`);
|
||||
@@ -594,6 +597,7 @@ export class CustomMenubarControl extends MenubarControl {
|
||||
const updateActions = (menu: IMenu, target: IAction[], topLevelTitle: string) => {
|
||||
target.splice(0);
|
||||
let groups = menu.getActions();
|
||||
|
||||
for (let group of groups) {
|
||||
const [, actions] = group;
|
||||
|
||||
@@ -622,7 +626,10 @@ export class CustomMenubarControl extends MenubarControl {
|
||||
|
||||
const submenuActions: SubmenuAction[] = [];
|
||||
updateActions(submenu, submenuActions, topLevelTitle);
|
||||
target.push(new SubmenuAction(action.id, mnemonicMenuLabel(title), submenuActions));
|
||||
|
||||
if (submenuActions.length > 0) {
|
||||
target.push(new SubmenuAction(action.id, mnemonicMenuLabel(title), submenuActions));
|
||||
}
|
||||
} else {
|
||||
const newAction = new Action(action.id, mnemonicMenuLabel(title), action.class, action.enabled, () => this.commandService.executeCommand(action.id));
|
||||
newAction.tooltip = action.tooltip;
|
||||
|
||||
@@ -133,7 +133,7 @@ export class TitlebarPart extends Part implements ITitleService {
|
||||
this.titleUpdater.schedule();
|
||||
}
|
||||
|
||||
if (this.titleBarStyle !== 'native') {
|
||||
if (this.titleBarStyle !== 'native' && (!isMacintosh || isWeb)) {
|
||||
if (event.affectsConfiguration('window.menuBarVisibility')) {
|
||||
if (this.currentMenubarVisibility === 'compact') {
|
||||
this.uninstallMenubar();
|
||||
|
||||
@@ -205,11 +205,11 @@ export class TreeView extends Disposable implements ITreeView {
|
||||
) {
|
||||
super();
|
||||
this.root = new Root();
|
||||
this.collapseAllContextKey = new RawContextKey<boolean>(`treeView.${this.id}.enableCollapseAll`, false);
|
||||
this.collapseAllContextKey = new RawContextKey<boolean>(`treeView.${this.id}.enableCollapseAll`, false, localize('treeView.enableCollapseAll', "Whether the the tree view with id {0} enables collapse all.", this.id));
|
||||
this.collapseAllContext = this.collapseAllContextKey.bindTo(contextKeyService);
|
||||
this.collapseAllToggleContextKey = new RawContextKey<boolean>(`treeView.${this.id}.toggleCollapseAll`, false);
|
||||
this.collapseAllToggleContextKey = new RawContextKey<boolean>(`treeView.${this.id}.toggleCollapseAll`, false, localize('treeView.toggleCollapseAll', "Whether collapse all is toggled for the tree view with id {0}.", this.id));
|
||||
this.collapseAllToggleContext = this.collapseAllToggleContextKey.bindTo(contextKeyService);
|
||||
this.refreshContextKey = new RawContextKey<boolean>(`treeView.${this.id}.enableRefresh`, false);
|
||||
this.refreshContextKey = new RawContextKey<boolean>(`treeView.${this.id}.enableRefresh`, false, localize('treeView.enableRefresh', "Whether the tree view with id {0} enables refresh.", this.id));
|
||||
this.refreshContext = this.refreshContextKey.bindTo(contextKeyService);
|
||||
|
||||
this._register(this.themeService.onDidFileIconThemeChange(() => this.doRefresh([this.root]) /** soft refresh **/));
|
||||
@@ -853,7 +853,8 @@ class TreeRenderer extends Disposable implements ITreeRenderer<ITreeItem, FuzzyS
|
||||
this._hoverDelegate = {
|
||||
showHover: (options: IHoverDelegateOptions): IDisposable | undefined => {
|
||||
return this.hoverService.showHover(options);
|
||||
}
|
||||
},
|
||||
delay: <number>this.configurationService.getValue('workbench.hover.delay')
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1133,7 +1134,7 @@ class TreeMenus extends Disposable implements IDisposable {
|
||||
const primary: IAction[] = [];
|
||||
const secondary: IAction[] = [];
|
||||
const result = { primary, secondary };
|
||||
createAndFillInContextMenuActions(menu, { shouldForwardArgs: true }, result, g => /^inline/.test(g));
|
||||
createAndFillInContextMenuActions(menu, { shouldForwardArgs: true }, result, 'inline');
|
||||
menu.dispose();
|
||||
|
||||
return result;
|
||||
|
||||
@@ -52,13 +52,13 @@ export class BrowserWindow extends Disposable {
|
||||
this._register(addDisposableListener(viewport, EventType.RESIZE, () => this.onWindowResize()));
|
||||
|
||||
// Prevent the back/forward gestures in macOS
|
||||
this._register(addDisposableListener(this.layoutService.getWorkbenchContainer(), EventType.WHEEL, e => e.preventDefault(), { passive: false }));
|
||||
this._register(addDisposableListener(this.layoutService.container, EventType.WHEEL, e => e.preventDefault(), { passive: false }));
|
||||
|
||||
// Prevent native context menus in web
|
||||
this._register(addDisposableListener(this.layoutService.getWorkbenchContainer(), EventType.CONTEXT_MENU, e => EventHelper.stop(e, true)));
|
||||
this._register(addDisposableListener(this.layoutService.container, EventType.CONTEXT_MENU, e => EventHelper.stop(e, true)));
|
||||
|
||||
// Prevent default navigation on drop
|
||||
this._register(addDisposableListener(this.layoutService.getWorkbenchContainer(), EventType.DROP, e => EventHelper.stop(e, true)));
|
||||
this._register(addDisposableListener(this.layoutService.container, EventType.DROP, e => EventHelper.stop(e, true)));
|
||||
|
||||
// Fullscreen (Browser)
|
||||
[EventType.FULLSCREEN_CHANGE, EventType.WK_FULLSCREEN_CHANGE].forEach(event => {
|
||||
@@ -153,7 +153,7 @@ export class BrowserWindow extends Disposable {
|
||||
scheme: Schemas.userData,
|
||||
priority: true,
|
||||
formatting: {
|
||||
label: '${scheme}:${path}',
|
||||
label: '(Settings) ${path}',
|
||||
separator: '/',
|
||||
}
|
||||
});
|
||||
|
||||
@@ -23,7 +23,7 @@ import { isStandalone } from 'vs/base/browser/browser';
|
||||
enum: ['default', 'large'],
|
||||
enumDescriptions: [
|
||||
localize('workbench.editor.titleScrollbarSizing.default', "The default size."),
|
||||
localize('workbench.editor.titleScrollbarSizing.large', "Increases the size, so it can be grabbed more easily with the mouse")
|
||||
localize('workbench.editor.titleScrollbarSizing.large', "Increases the size, so it can be grabbed more easily with the mouse.")
|
||||
],
|
||||
description: localize('tabScrollbarHeight', "Controls the height of the scrollbars used for tabs and breadcrumbs in the editor title area."),
|
||||
default: 'default',
|
||||
@@ -40,7 +40,7 @@ import { isStandalone } from 'vs/base/browser/browser';
|
||||
},
|
||||
'workbench.editor.scrollToSwitchTabs': {
|
||||
'type': 'boolean',
|
||||
'markdownDescription': localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'scrollToSwitchTabs' }, "Controls whether scrolling over tabs will open them or not. By default tabs will only reveal upon scrolling, but not open. You can press and hold the Shift-key while scrolling to change this behaviour for that duration. This value is ignored when `#workbench.editor.showTabs#` is disabled."),
|
||||
'markdownDescription': localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'scrollToSwitchTabs' }, "Controls whether scrolling over tabs will open them or not. By default tabs will only reveal upon scrolling, but not open. You can press and hold the Shift-key while scrolling to change this behavior for that duration. This value is ignored when `#workbench.editor.showTabs#` is disabled."),
|
||||
'default': false
|
||||
},
|
||||
'workbench.editor.highlightModifiedTabs': {
|
||||
@@ -51,12 +51,12 @@ import { isStandalone } from 'vs/base/browser/browser';
|
||||
'workbench.editor.decorations.badges': {
|
||||
'type': 'boolean',
|
||||
'markdownDescription': localize('decorations.badges', "Controls whether editor file decorations should use badges."),
|
||||
'default': false
|
||||
'default': true
|
||||
},
|
||||
'workbench.editor.decorations.colors': {
|
||||
'type': 'boolean',
|
||||
'markdownDescription': localize('decorations.colors', "Controls whether editor file decorations should use colors."),
|
||||
'default': false
|
||||
'default': true
|
||||
},
|
||||
'workbench.editor.labelFormat': {
|
||||
'type': 'string',
|
||||
@@ -88,8 +88,8 @@ import { isStandalone } from 'vs/base/browser/browser';
|
||||
},
|
||||
'workbench.editor.untitled.hint': {
|
||||
'type': 'string',
|
||||
'enum': ['text', 'button', 'hidden'],
|
||||
'default': 'hidden',
|
||||
'enum': ['text', 'button', 'hidden', 'default'],
|
||||
'default': 'default',
|
||||
'markdownDescription': localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'untitledHint' }, "Controls if the untitled hint should be inline text in the editor or a floating button or hidden.")
|
||||
},
|
||||
'workbench.editor.tabCloseButton': {
|
||||
@@ -174,7 +174,7 @@ import { isStandalone } from 'vs/base/browser/browser';
|
||||
'type': 'string',
|
||||
'enum': ['right', 'down'],
|
||||
'default': 'right',
|
||||
'markdownDescription': localize('sideBySideDirection', "Controls the default direction of editors that are opened side by side (e.g. from the explorer). By default, editors will open on the right hand side of the currently active one. If changed to `down`, the editors will open below the currently active one.")
|
||||
'markdownDescription': localize('sideBySideDirection', "Controls the default direction of editors that are opened side by side (for example, from the Explorer). By default, editors will open on the right hand side of the currently active one. If changed to `down`, the editors will open below the currently active one.")
|
||||
},
|
||||
'workbench.editor.closeEmptyGroups': {
|
||||
'type': 'boolean',
|
||||
@@ -325,6 +325,13 @@ import { isStandalone } from 'vs/base/browser/browser';
|
||||
'description': localize('settings.editor.desc', "Determines which settings editor to use by default."),
|
||||
'default': 'ui',
|
||||
'scope': ConfigurationScope.WINDOW
|
||||
},
|
||||
'workbench.hover.delay': {
|
||||
'type': 'number',
|
||||
'description': localize('workbench.hover.delay', "Controls the delay in milliseconds after which the hover is shown for workbench items (ex. some extension provided tree view items). Already visible items may require a refresh before reflecting this setting change."),
|
||||
// Testing has indicated that on Windows and Linux 500 ms matches the native hovers most closely.
|
||||
// On Mac, the delay is 1500.
|
||||
'default': isMacintosh ? 1500 : 500
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -26,6 +26,7 @@ import { NotificationService } from 'vs/workbench/services/notification/common/n
|
||||
import { NotificationsCenter } from 'vs/workbench/browser/parts/notifications/notificationsCenter';
|
||||
import { NotificationsAlerts } from 'vs/workbench/browser/parts/notifications/notificationsAlerts';
|
||||
import { NotificationsStatus } from 'vs/workbench/browser/parts/notifications/notificationsStatus';
|
||||
import { NotificationsTelemetry } from 'vs/workbench/browser/parts/notifications/notificationsTelemetry';
|
||||
import { registerNotificationCommands } from 'vs/workbench/browser/parts/notifications/notificationsCommands';
|
||||
import { NotificationsToasts } from 'vs/workbench/browser/parts/notifications/notificationsToasts';
|
||||
import { setARIAContainer } from 'vs/base/browser/ui/aria/aria';
|
||||
@@ -378,6 +379,7 @@ export class Workbench extends Layout {
|
||||
const notificationsToasts = this._register(instantiationService.createInstance(NotificationsToasts, this.container, notificationService.model));
|
||||
this._register(instantiationService.createInstance(NotificationsAlerts, notificationService.model));
|
||||
const notificationsStatus = instantiationService.createInstance(NotificationsStatus, notificationService.model);
|
||||
this._register(instantiationService.createInstance(NotificationsTelemetry));
|
||||
|
||||
// Visibility
|
||||
this._register(notificationsCenter.onDidChangeVisibility(() => {
|
||||
@@ -390,7 +392,7 @@ export class Workbench extends Layout {
|
||||
}));
|
||||
|
||||
// Register Commands
|
||||
registerNotificationCommands(notificationsCenter, notificationsToasts);
|
||||
registerNotificationCommands(notificationsCenter, notificationsToasts, notificationService.model);
|
||||
|
||||
// Register with Layout
|
||||
this.registerNotifications({
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { EditorInput, Verbosity, GroupIdentifier, IEditorInput, ISaveOptions, IRevertOptions, IEditorInputWithPreferredResource } from 'vs/workbench/common/editor';
|
||||
import { EditorInput, Verbosity, GroupIdentifier, IEditorInput, IRevertOptions, IEditorInputWithPreferredResource } from 'vs/workbench/common/editor';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { ITextFileService, ITextFileSaveOptions } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
@@ -164,9 +164,9 @@ export abstract class AbstractTextResourceEditorInput extends EditorInput implem
|
||||
}
|
||||
|
||||
isUntitled(): boolean {
|
||||
// anyFile: is never untitled as it can be saved
|
||||
// untitled: is untitled by definition
|
||||
// anyOther: is untitled because it cannot be saved, as such we expect a "Save As" dialog
|
||||
// any file: is never untitled as it can be saved
|
||||
// untitled: is untitled by definition
|
||||
// any other: is untitled because it cannot be saved, as such we expect a "Save As" dialog
|
||||
return !this.fileService.canHandleResource(this.resource);
|
||||
}
|
||||
|
||||
@@ -206,7 +206,7 @@ export abstract class AbstractTextResourceEditorInput extends EditorInput implem
|
||||
return this.doSave(options, true);
|
||||
}
|
||||
|
||||
private async doSave(options: ISaveOptions | undefined, saveAs: boolean): Promise<IEditorInput | undefined> {
|
||||
private async doSave(options: ITextFileSaveOptions | undefined, saveAs: boolean): Promise<IEditorInput | undefined> {
|
||||
|
||||
// Save / Save As
|
||||
let target: URI | undefined;
|
||||
|
||||
@@ -280,10 +280,12 @@ export interface INotificationViewItem {
|
||||
readonly silent: boolean;
|
||||
readonly message: INotificationMessage;
|
||||
readonly source: string | undefined;
|
||||
readonly sourceId: string | undefined;
|
||||
readonly actions: INotificationActions | undefined;
|
||||
readonly progress: INotificationViewItemProgress;
|
||||
|
||||
readonly expanded: boolean;
|
||||
readonly visible: boolean;
|
||||
readonly canCollapse: boolean;
|
||||
readonly hasProgress: boolean;
|
||||
|
||||
@@ -502,7 +504,7 @@ export class NotificationViewItem extends Disposable implements INotificationVie
|
||||
private _sticky: boolean | undefined,
|
||||
private _silent: boolean | undefined,
|
||||
private _message: INotificationMessage,
|
||||
private _source: string | undefined,
|
||||
private _source: string | { label: string, id: string } | undefined,
|
||||
progress: INotificationProgressProperties | undefined,
|
||||
actions?: INotificationActions
|
||||
) {
|
||||
@@ -599,13 +601,21 @@ export class NotificationViewItem extends Disposable implements INotificationVie
|
||||
}
|
||||
|
||||
get source(): string | undefined {
|
||||
return this._source;
|
||||
return typeof this._source === 'string' ? this._source : (this._source ? this._source.label : undefined);
|
||||
}
|
||||
|
||||
get sourceId(): string | undefined {
|
||||
return (this._source && typeof this._source !== 'string' && 'id' in this._source) ? this._source.id : undefined;
|
||||
}
|
||||
|
||||
get actions(): INotificationActions | undefined {
|
||||
return this._actions;
|
||||
}
|
||||
|
||||
get visible(): boolean {
|
||||
return this._visible;
|
||||
}
|
||||
|
||||
updateSeverity(severity: Severity): void {
|
||||
this._severity = severity;
|
||||
this._onDidChangeContent.fire({ kind: NotificationViewItemContentChangeKind.SEVERITY });
|
||||
|
||||
@@ -15,7 +15,7 @@ import { getOrSet } from 'vs/base/common/map';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IKeybindings } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
|
||||
import { flatten, mergeSort } from 'vs/base/common/arrays';
|
||||
import { flatten } from 'vs/base/common/arrays';
|
||||
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
import { SetMap } from 'vs/base/common/collections';
|
||||
import { IProgressIndicator } from 'vs/platform/progress/common/progress';
|
||||
@@ -381,6 +381,7 @@ export interface IViewsRegistry {
|
||||
|
||||
readonly onDidChangeViewWelcomeContent: Event<string>;
|
||||
registerViewWelcomeContent(id: string, viewContent: IViewContentDescriptor): IDisposable;
|
||||
registerViewWelcomeContent2<TKey>(id: string, viewContentMap: Map<TKey, IViewContentDescriptor>): Map<TKey, IDisposable>;
|
||||
getViewWelcomeContent(id: string): IViewContentDescriptor[];
|
||||
}
|
||||
|
||||
@@ -473,11 +474,26 @@ class ViewsRegistry extends Disposable implements IViewsRegistry {
|
||||
});
|
||||
}
|
||||
|
||||
registerViewWelcomeContent2<TKey>(id: string, viewContentMap: Map<TKey, IViewContentDescriptor>): Map<TKey, IDisposable> {
|
||||
const disposables = new Map<TKey, IDisposable>();
|
||||
|
||||
for (const [key, content] of viewContentMap) {
|
||||
this._viewWelcomeContents.add(id, content);
|
||||
|
||||
disposables.set(key, toDisposable(() => {
|
||||
this._viewWelcomeContents.delete(id, content);
|
||||
this._onDidChangeViewWelcomeContent.fire(id);
|
||||
}));
|
||||
}
|
||||
this._onDidChangeViewWelcomeContent.fire(id);
|
||||
|
||||
return disposables;
|
||||
}
|
||||
|
||||
getViewWelcomeContent(id: string): IViewContentDescriptor[] {
|
||||
const result: IViewContentDescriptor[] = [];
|
||||
this._viewWelcomeContents.forEach(id, descriptor => result.push(descriptor));
|
||||
mergeSort(result, compareViewContentDescriptors);
|
||||
return result;
|
||||
return result.sort(compareViewContentDescriptors);
|
||||
}
|
||||
|
||||
private addViews(viewDescriptors: IViewDescriptor[], viewContainer: ViewContainer): void {
|
||||
|
||||
@@ -5,53 +5,22 @@
|
||||
|
||||
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
|
||||
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
|
||||
import { AutoSaveMode, IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService';
|
||||
import { IWorkingCopy, IWorkingCopyService, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopyService';
|
||||
import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService';
|
||||
import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService';
|
||||
import { ILifecycleService, ShutdownReason } from 'vs/workbench/services/lifecycle/common/lifecycle';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { BackupTracker } from 'vs/workbench/contrib/backup/common/backupTracker';
|
||||
|
||||
export class BrowserBackupTracker extends BackupTracker implements IWorkbenchContribution {
|
||||
|
||||
// Delay creation of backups when content changes to avoid too much
|
||||
// load on the backup service when the user is typing into the editor
|
||||
// Since we always schedule a backup, even when auto save is on (web
|
||||
// only), we have different scheduling delays based on auto save. This
|
||||
// helps to avoid a race between saving (after 1s per default) and making
|
||||
// a backup of the working copy.
|
||||
private static readonly BACKUP_SCHEDULE_DELAYS = {
|
||||
[AutoSaveMode.OFF]: 1000,
|
||||
[AutoSaveMode.ON_FOCUS_CHANGE]: 1000,
|
||||
[AutoSaveMode.ON_WINDOW_CHANGE]: 1000,
|
||||
[AutoSaveMode.AFTER_SHORT_DELAY]: 2000, // explicitly higher to prevent races
|
||||
[AutoSaveMode.AFTER_LONG_DELAY]: 1000
|
||||
};
|
||||
|
||||
constructor(
|
||||
@IBackupFileService backupFileService: IBackupFileService,
|
||||
@IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService,
|
||||
@IFilesConfigurationService filesConfigurationService: IFilesConfigurationService,
|
||||
@IWorkingCopyService workingCopyService: IWorkingCopyService,
|
||||
@ILifecycleService lifecycleService: ILifecycleService,
|
||||
@ILogService logService: ILogService
|
||||
) {
|
||||
super(backupFileService, workingCopyService, logService, lifecycleService);
|
||||
}
|
||||
|
||||
protected shouldScheduleBackup(workingCopy: IWorkingCopy): boolean {
|
||||
// Web: we always want to schedule a backup, even if auto save
|
||||
// is enabled because in web there is no handler on shutdown
|
||||
// to trigger saving so there is a higher chance of dataloss.
|
||||
// See https://github.com/microsoft/vscode/issues/108789
|
||||
return true;
|
||||
}
|
||||
|
||||
protected getBackupScheduleDelay(workingCopy: IWorkingCopy): number {
|
||||
let autoSaveMode = this.filesConfigurationService.getAutoSaveMode();
|
||||
if (workingCopy.capabilities & WorkingCopyCapabilities.Untitled) {
|
||||
autoSaveMode = AutoSaveMode.OFF; // auto-save is never on for untitled working copies
|
||||
}
|
||||
|
||||
return BrowserBackupTracker.BACKUP_SCHEDULE_DELAYS[autoSaveMode];
|
||||
super(backupFileService, workingCopyService, logService, lifecycleService, filesConfigurationService);
|
||||
}
|
||||
|
||||
protected onBeforeShutdown(reason: ShutdownReason): boolean | Promise<boolean> {
|
||||
|
||||
@@ -5,10 +5,11 @@
|
||||
|
||||
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
|
||||
import { Disposable, IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IWorkingCopyService, IWorkingCopy } from 'vs/workbench/services/workingCopy/common/workingCopyService';
|
||||
import { IWorkingCopyService, IWorkingCopy, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopyService';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { ShutdownReason, ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle';
|
||||
import { CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { AutoSaveMode, IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService';
|
||||
|
||||
export abstract class BackupTracker extends Disposable {
|
||||
|
||||
@@ -20,11 +21,26 @@ export abstract class BackupTracker extends Disposable {
|
||||
// A map of scheduled pending backups for working copies
|
||||
protected readonly pendingBackups = new Map<IWorkingCopy, IDisposable>();
|
||||
|
||||
// Delay creation of backups when content changes to avoid too much
|
||||
// load on the backup service when the user is typing into the editor
|
||||
// Since we always schedule a backup, even when auto save is on, we
|
||||
// have different scheduling delays based on auto save. This helps to
|
||||
// avoid a (not critical but also not really wanted) race between saving
|
||||
// (after 1s per default) and making a backup of the working copy.
|
||||
private static readonly BACKUP_SCHEDULE_DELAYS = {
|
||||
[AutoSaveMode.OFF]: 1000,
|
||||
[AutoSaveMode.ON_FOCUS_CHANGE]: 1000,
|
||||
[AutoSaveMode.ON_WINDOW_CHANGE]: 1000,
|
||||
[AutoSaveMode.AFTER_SHORT_DELAY]: 2000, // explicitly higher to prevent races
|
||||
[AutoSaveMode.AFTER_LONG_DELAY]: 1000
|
||||
};
|
||||
|
||||
constructor(
|
||||
protected readonly backupFileService: IBackupFileService,
|
||||
protected readonly workingCopyService: IWorkingCopyService,
|
||||
protected readonly logService: ILogService,
|
||||
protected readonly lifecycleService: ILifecycleService
|
||||
protected readonly lifecycleService: ILifecycleService,
|
||||
protected readonly filesConfigurationService: IFilesConfigurationService
|
||||
) {
|
||||
super();
|
||||
|
||||
@@ -84,28 +100,11 @@ export abstract class BackupTracker extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows subclasses to conditionally opt-out of doing a backup, e.g. if
|
||||
* auto save is enabled.
|
||||
*/
|
||||
protected abstract shouldScheduleBackup(workingCopy: IWorkingCopy): boolean;
|
||||
|
||||
/**
|
||||
* Allows subclasses to control the delay before performing a backup from
|
||||
* working copy content changes.
|
||||
*/
|
||||
protected abstract getBackupScheduleDelay(workingCopy: IWorkingCopy): number;
|
||||
|
||||
private scheduleBackup(workingCopy: IWorkingCopy): void {
|
||||
|
||||
// Clear any running backup operation
|
||||
this.cancelBackup(workingCopy);
|
||||
|
||||
// subclass prevented backup for working copy
|
||||
if (!this.shouldScheduleBackup(workingCopy)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.logService.trace(`[backup tracker] scheduling backup`, workingCopy.resource.toString());
|
||||
|
||||
// Schedule new backup
|
||||
@@ -153,6 +152,15 @@ export abstract class BackupTracker extends Disposable {
|
||||
}));
|
||||
}
|
||||
|
||||
protected getBackupScheduleDelay(workingCopy: IWorkingCopy): number {
|
||||
let autoSaveMode = this.filesConfigurationService.getAutoSaveMode();
|
||||
if (workingCopy.capabilities & WorkingCopyCapabilities.Untitled) {
|
||||
autoSaveMode = AutoSaveMode.OFF; // auto-save is never on for untitled working copies
|
||||
}
|
||||
|
||||
return BackupTracker.BACKUP_SCHEDULE_DELAYS[autoSaveMode];
|
||||
}
|
||||
|
||||
protected getContentVersion(workingCopy: IWorkingCopy): number {
|
||||
return this.mapWorkingCopyToContentVersion.get(workingCopy) || 0;
|
||||
}
|
||||
|
||||
@@ -26,21 +26,9 @@ import { Promises, raceCancellation } from 'vs/base/common/async';
|
||||
|
||||
export class NativeBackupTracker extends BackupTracker implements IWorkbenchContribution {
|
||||
|
||||
// Delay creation of backups when working copy changes to avoid too much
|
||||
// load on the backup service when the user is typing into the editor
|
||||
private static readonly BACKUP_SCHEDULE_DELAY = 1000;
|
||||
|
||||
// Disable backup for when a short auto-save delay is configured with
|
||||
// the rationale that the auto save will trigger a save periodically
|
||||
// anway and thus creating frequent backups is not useful
|
||||
//
|
||||
// This will only apply to working copies that are not untitled where
|
||||
// auto save is actually saving.
|
||||
private static readonly DISABLE_BACKUP_AUTO_SAVE_THRESHOLD = 1500;
|
||||
|
||||
constructor(
|
||||
@IBackupFileService backupFileService: IBackupFileService,
|
||||
@IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService,
|
||||
@IFilesConfigurationService filesConfigurationService: IFilesConfigurationService,
|
||||
@IWorkingCopyService workingCopyService: IWorkingCopyService,
|
||||
@ILifecycleService lifecycleService: ILifecycleService,
|
||||
@IFileDialogService private readonly fileDialogService: IFileDialogService,
|
||||
@@ -52,24 +40,7 @@ export class NativeBackupTracker extends BackupTracker implements IWorkbenchCont
|
||||
@IEnvironmentService private readonly environmentService: IEnvironmentService,
|
||||
@IProgressService private readonly progressService: IProgressService
|
||||
) {
|
||||
super(backupFileService, workingCopyService, logService, lifecycleService);
|
||||
}
|
||||
|
||||
protected shouldScheduleBackup(workingCopy: IWorkingCopy): boolean {
|
||||
if (workingCopy.capabilities & WorkingCopyCapabilities.Untitled) {
|
||||
return true; // always backup untitled
|
||||
}
|
||||
|
||||
const autoSaveConfiguration = this.filesConfigurationService.getAutoSaveConfiguration();
|
||||
if (typeof autoSaveConfiguration.autoSaveDelay === 'number' && autoSaveConfiguration.autoSaveDelay < NativeBackupTracker.DISABLE_BACKUP_AUTO_SAVE_THRESHOLD) {
|
||||
return false; // skip backup when auto save is already enabled with a low delay
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected getBackupScheduleDelay(): number {
|
||||
return NativeBackupTracker.BACKUP_SCHEDULE_DELAY;
|
||||
super(backupFileService, workingCopyService, logService, lifecycleService, filesConfigurationService);
|
||||
}
|
||||
|
||||
protected onBeforeShutdown(reason: ShutdownReason): boolean | Promise<boolean> {
|
||||
|
||||
@@ -9,13 +9,12 @@ import { URI } from 'vs/base/common/uri';
|
||||
import { createTextBufferFactory } from 'vs/editor/common/model/textModel';
|
||||
import { DefaultEndOfLine } from 'vs/editor/common/model';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart';
|
||||
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { EditorService } from 'vs/workbench/services/editor/browser/editorService';
|
||||
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { isEqual } from 'vs/base/common/resources';
|
||||
import { InMemoryTestBackupFileService, registerTestResourceEditor, TestServiceAccessor, workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices';
|
||||
import { createEditorPart, InMemoryTestBackupFileService, registerTestResourceEditor, TestServiceAccessor, workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices';
|
||||
import { BackupRestorer } from 'vs/workbench/contrib/backup/common/backupRestorer';
|
||||
import { BrowserBackupTracker } from 'vs/workbench/contrib/backup/browser/backupTracker';
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
@@ -49,9 +48,7 @@ suite('BackupRestorer', () => {
|
||||
const instantiationService = workbenchInstantiationService();
|
||||
instantiationService.stub(IBackupFileService, backupFileService);
|
||||
|
||||
const part = disposables.add(instantiationService.createInstance(EditorPart));
|
||||
part.create(document.createElement('div'));
|
||||
part.layout(400, 300);
|
||||
const part = createEditorPart(instantiationService, disposables);
|
||||
|
||||
instantiationService.stub(IEditorGroupsService, part);
|
||||
|
||||
|
||||
@@ -19,11 +19,12 @@ import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecy
|
||||
import { BackupTracker } from 'vs/workbench/contrib/backup/common/backupTracker';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput';
|
||||
import { InMemoryTestBackupFileService, registerTestResourceEditor, TestServiceAccessor, workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices';
|
||||
import { createEditorPart, InMemoryTestBackupFileService, registerTestResourceEditor, TestServiceAccessor, workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices';
|
||||
import { TestWorkingCopy } from 'vs/workbench/test/common/workbenchTestServices';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { timeout } from 'vs/base/common/async';
|
||||
import { BrowserBackupTracker } from 'vs/workbench/contrib/backup/browser/backupTracker';
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
|
||||
suite('BackupTracker (browser)', function () {
|
||||
let accessor: TestServiceAccessor;
|
||||
@@ -46,15 +47,15 @@ suite('BackupTracker (browser)', function () {
|
||||
}
|
||||
|
||||
async function createTracker(): Promise<{ accessor: TestServiceAccessor, part: EditorPart, tracker: BackupTracker, backupFileService: InMemoryTestBackupFileService, instantiationService: IInstantiationService, cleanup: () => void }> {
|
||||
const disposables = new DisposableStore();
|
||||
|
||||
const backupFileService = new InMemoryTestBackupFileService();
|
||||
const instantiationService = workbenchInstantiationService();
|
||||
instantiationService.stub(IBackupFileService, backupFileService);
|
||||
|
||||
const part = instantiationService.createInstance(EditorPart);
|
||||
part.create(document.createElement('div'));
|
||||
part.layout(400, 300);
|
||||
const part = createEditorPart(instantiationService, disposables);
|
||||
|
||||
const editorRegistration = registerTestResourceEditor();
|
||||
disposables.add(registerTestResourceEditor());
|
||||
|
||||
instantiationService.stub(IEditorGroupsService, part);
|
||||
|
||||
@@ -65,15 +66,9 @@ suite('BackupTracker (browser)', function () {
|
||||
|
||||
await part.whenRestored;
|
||||
|
||||
const tracker = instantiationService.createInstance(TestBackupTracker);
|
||||
const tracker = disposables.add(instantiationService.createInstance(TestBackupTracker));
|
||||
|
||||
const cleanup = () => {
|
||||
part.dispose();
|
||||
tracker.dispose();
|
||||
editorRegistration.dispose();
|
||||
};
|
||||
|
||||
return { accessor, part, tracker, backupFileService, instantiationService, cleanup };
|
||||
return { accessor, part, tracker, backupFileService, instantiationService, cleanup: () => disposables.dispose() };
|
||||
}
|
||||
|
||||
async function untitledBackupTest(untitled: IUntitledTextResourceEditorInput = {}): Promise<void> {
|
||||
|
||||
@@ -11,7 +11,7 @@ import { join } from 'vs/base/common/path';
|
||||
import { rimraf, writeFile } from 'vs/base/node/pfs';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { flakySuite, getRandomTestPath } from 'vs/base/test/node/testUtils';
|
||||
import { hashPath } from 'vs/workbench/services/backup/electron-browser/backupFileService';
|
||||
import { hashPath } from 'vs/workbench/services/backup/common/backupFileService';
|
||||
import { NativeBackupTracker } from 'vs/workbench/contrib/backup/electron-sandbox/backupTracker';
|
||||
import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
@@ -35,7 +35,7 @@ import { workbenchInstantiationService, TestServiceAccessor } from 'vs/workbench
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { registerTestFileEditor, TestFilesConfigurationService } from 'vs/workbench/test/browser/workbenchTestServices';
|
||||
import { createEditorPart, registerTestFileEditor, TestFilesConfigurationService } from 'vs/workbench/test/browser/workbenchTestServices';
|
||||
import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
@@ -135,9 +135,7 @@ flakySuite('BackupTracker (native)', function () {
|
||||
configurationService
|
||||
));
|
||||
|
||||
const part = instantiationService.createInstance(EditorPart);
|
||||
part.create(document.createElement('div'));
|
||||
part.layout(400, 300);
|
||||
const part = createEditorPart(instantiationService, disposables);
|
||||
|
||||
instantiationService.stub(IEditorGroupsService, part);
|
||||
|
||||
@@ -161,10 +159,17 @@ flakySuite('BackupTracker (native)', function () {
|
||||
return { accessor, part, tracker, instantiationService, cleanup };
|
||||
}
|
||||
|
||||
test('Track backups (file)', async function () {
|
||||
const { accessor, cleanup } = await createTracker();
|
||||
test('Track backups (file, auto save off)', function () {
|
||||
return trackBackupsTest(toResource.call(this, '/path/index.txt'), false);
|
||||
});
|
||||
|
||||
test('Track backups (file, auto save on)', function () {
|
||||
return trackBackupsTest(toResource.call(this, '/path/index.txt'), true);
|
||||
});
|
||||
|
||||
async function trackBackupsTest(resource: URI, autoSave: boolean) {
|
||||
const { accessor, cleanup } = await createTracker(autoSave);
|
||||
|
||||
const resource = toResource.call(this, '/path/index.txt');
|
||||
await accessor.editorService.openEditor({ resource, options: { pinned: true } });
|
||||
|
||||
const fileModel = accessor.textFileService.files.get(resource);
|
||||
@@ -181,7 +186,7 @@ flakySuite('BackupTracker (native)', function () {
|
||||
assert.strictEqual(accessor.backupFileService.hasBackupSync(resource), false);
|
||||
|
||||
await cleanup();
|
||||
});
|
||||
}
|
||||
|
||||
test('onWillShutdown - no veto if no dirty files', async function () {
|
||||
const { accessor, cleanup } = await createTracker();
|
||||
|
||||
@@ -49,14 +49,14 @@ export class BulkCellEdits {
|
||||
const ref = await this._notebookModelService.resolve(first.resource);
|
||||
|
||||
// check state
|
||||
// if (typeof first.versionId === 'number' && ref.object.notebook.versionId !== first.versionId) {
|
||||
// ref.dispose();
|
||||
// throw new Error(`Notebook '${first.resource}' has changed in the meantime`);
|
||||
// }
|
||||
if (typeof first.versionId === 'number' && ref.object.notebook.versionId !== first.versionId) {
|
||||
ref.dispose();
|
||||
throw new Error(`Notebook '${first.resource}' has changed in the meantime`);
|
||||
}
|
||||
|
||||
// apply edits
|
||||
const edits = group.map(entry => entry.cellEdit);
|
||||
ref.object.notebook.applyEdits(ref.object.notebook.versionId, edits, true, undefined, () => undefined, this._undoRedoGroup);
|
||||
ref.object.notebook.applyEdits(edits, true, undefined, () => undefined, this._undoRedoGroup);
|
||||
ref.dispose();
|
||||
|
||||
this._progress.report(undefined);
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { mergeSort } from 'vs/base/common/arrays';
|
||||
import { dispose, IDisposable, IReference } from 'vs/base/common/lifecycle';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
@@ -92,7 +91,7 @@ class ModelEditTask implements IDisposable {
|
||||
|
||||
apply(): void {
|
||||
if (this._edits.length > 0) {
|
||||
this._edits = mergeSort(this._edits, (a, b) => Range.compareRangesUsingStarts(a.range, b.range));
|
||||
this._edits = this._edits.sort((a, b) => Range.compareRangesUsingStarts(a.range, b.range));
|
||||
this.model.pushEditOperations(null, this._edits, () => null);
|
||||
}
|
||||
if (this._newEol !== undefined) {
|
||||
@@ -116,7 +115,7 @@ class EditorEditTask extends ModelEditTask {
|
||||
|
||||
apply(): void {
|
||||
if (this._edits.length > 0) {
|
||||
this._edits = mergeSort(this._edits, (a, b) => Range.compareRangesUsingStarts(a.range, b.range));
|
||||
this._edits = this._edits.sort((a, b) => Range.compareRangesUsingStarts(a.range, b.range));
|
||||
this._editor.executeEdits('', this._edits);
|
||||
}
|
||||
if (this._newEol !== undefined) {
|
||||
|
||||
@@ -137,7 +137,7 @@ export class BulkEditPane extends ViewPane {
|
||||
multipleSelectionSupport: false,
|
||||
keyboardNavigationLabelProvider: new BulkEditNaviLabelProvider(),
|
||||
sorter: new BulkEditSorter(),
|
||||
openOnFocus: true
|
||||
selectionNavigation: true
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { createTextBufferFactoryFromSnapshot } from 'vs/editor/common/model/textModel';
|
||||
import { WorkspaceEditMetadata } from 'vs/editor/common/modes';
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { mergeSort, coalesceInPlace } from 'vs/base/common/arrays';
|
||||
import { coalesceInPlace } from 'vs/base/common/arrays';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { EditOperation } from 'vs/editor/common/core/editOperation';
|
||||
import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
@@ -329,10 +329,7 @@ export class BulkFileOperations {
|
||||
return [];
|
||||
}
|
||||
|
||||
return mergeSort(
|
||||
result,
|
||||
(a, b) => Range.compareRangesUsingStarts(a.range, b.range)
|
||||
);
|
||||
return result.sort((a, b) => Range.compareRangesUsingStarts(a.range, b.range));
|
||||
}
|
||||
}
|
||||
return [];
|
||||
|
||||
@@ -111,12 +111,17 @@ class InstallAction extends Action2 {
|
||||
});
|
||||
}
|
||||
|
||||
private isInstalled(target: string): Promise<boolean> {
|
||||
return fs.promises.lstat(target)
|
||||
.then(stat => stat.isSymbolicLink())
|
||||
.then(() => extpath.realpath(target))
|
||||
.then(link => link === getSource())
|
||||
.then(undefined, ignore('ENOENT', false));
|
||||
private async isInstalled(target: string): Promise<boolean> {
|
||||
try {
|
||||
const stat = await fs.promises.lstat(target);
|
||||
return stat.isSymbolicLink() && getSource() === await extpath.realpath(target);
|
||||
} catch (err) {
|
||||
if (err.code === 'ENOENT') {
|
||||
return false;
|
||||
}
|
||||
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -162,7 +162,7 @@ export class GotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccess
|
||||
}
|
||||
}));
|
||||
|
||||
const entries = Array.from(outline.config.quickPickDataSource.getQuickPickElements());
|
||||
const entries = outline.config.quickPickDataSource.getQuickPickElements();
|
||||
|
||||
const items: IGotoSymbolQuickPickItem[] = entries.map((entry, idx) => {
|
||||
return {
|
||||
|
||||
@@ -5,24 +5,29 @@
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { IActiveCodeEditor, ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { EditorAction, ServicesAccessor, registerEditorAction, registerEditorContribution } from 'vs/editor/browser/editorExtensions';
|
||||
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
|
||||
import { EditorOption } from 'vs/editor/common/config/editorOptions';
|
||||
import { IEditorContribution } from 'vs/editor/common/editorCommon';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions';
|
||||
import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { DefaultSettingsEditorContribution } from 'vs/workbench/contrib/preferences/browser/preferencesEditor';
|
||||
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions } from 'vs/workbench/common/contributions';
|
||||
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
|
||||
const transientWordWrapState = 'transientWordWrapState';
|
||||
const isWordWrapMinifiedKey = 'isWordWrapMinified';
|
||||
const isDominatedByLongLinesKey = 'isDominatedByLongLines';
|
||||
const CAN_TOGGLE_WORD_WRAP = new RawContextKey<boolean>('canToggleWordWrap', false, true);
|
||||
const EDITOR_WORD_WRAP = new RawContextKey<boolean>('editorWordWrap', false, nls.localize('editorWordWrap', 'Whether the editor is currently using word wrapping.'));
|
||||
|
||||
/**
|
||||
* State written/read by the toggle word wrap action and associated with a particular model.
|
||||
@@ -63,21 +68,13 @@ class ToggleWordWrapAction extends EditorAction {
|
||||
}
|
||||
|
||||
public run(accessor: ServicesAccessor, editor: ICodeEditor): void {
|
||||
if (editor.getContribution(DefaultSettingsEditorContribution.ID)) {
|
||||
// in the settings editor...
|
||||
return;
|
||||
}
|
||||
if (!editor.hasModel()) {
|
||||
if (!canToggleWordWrap(editor)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const codeEditorService = accessor.get(ICodeEditorService);
|
||||
const model = editor.getModel();
|
||||
|
||||
if (!canToggleWordWrap(model.uri)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Read the current state
|
||||
const transientState = readTransientState(model, codeEditorService);
|
||||
|
||||
@@ -137,25 +134,11 @@ class ToggleWordWrapController extends Disposable implements IEditorContribution
|
||||
}));
|
||||
|
||||
const ensureWordWrapSettings = () => {
|
||||
if (this._editor.getContribution(DefaultSettingsEditorContribution.ID)) {
|
||||
// in the settings editor...
|
||||
return;
|
||||
}
|
||||
if (this._editor.isSimpleWidget) {
|
||||
// in a simple widget...
|
||||
return;
|
||||
}
|
||||
// Ensure correct word wrap settings
|
||||
const newModel = this._editor.getModel();
|
||||
if (!newModel) {
|
||||
if (!canToggleWordWrap(this._editor)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!canToggleWordWrap(newModel.uri)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const transientState = readTransientState(newModel, this._codeEditorService);
|
||||
const transientState = readTransientState(this._editor.getModel(), this._codeEditorService);
|
||||
|
||||
// Apply the state
|
||||
try {
|
||||
@@ -175,13 +158,89 @@ class ToggleWordWrapController extends Disposable implements IEditorContribution
|
||||
}
|
||||
}
|
||||
|
||||
function canToggleWordWrap(uri: URI): boolean {
|
||||
if (!uri) {
|
||||
function canToggleWordWrap(editor: ICodeEditor | null): editor is IActiveCodeEditor {
|
||||
if (!editor) {
|
||||
return false;
|
||||
}
|
||||
return (uri.scheme !== 'output');
|
||||
if (editor.getContribution(DefaultSettingsEditorContribution.ID)) {
|
||||
// in the settings editor...
|
||||
return false;
|
||||
}
|
||||
if (editor.isSimpleWidget) {
|
||||
// in a simple widget...
|
||||
return false;
|
||||
}
|
||||
// Ensure correct word wrap settings
|
||||
const model = editor.getModel();
|
||||
if (!model) {
|
||||
return false;
|
||||
}
|
||||
if (model.uri.scheme === 'output') {
|
||||
// in output editor
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
class EditorWordWrapContextKeyTracker implements IWorkbenchContribution {
|
||||
|
||||
private readonly _canToggleWordWrap: IContextKey<boolean>;
|
||||
private readonly _editorWordWrap: IContextKey<boolean>;
|
||||
private _activeEditor: ICodeEditor | null;
|
||||
private readonly _activeEditorListener: DisposableStore;
|
||||
|
||||
constructor(
|
||||
@IEditorService private readonly _editorService: IEditorService,
|
||||
@ICodeEditorService private readonly _codeEditorService: ICodeEditorService,
|
||||
@IContextKeyService private readonly _contextService: IContextKeyService,
|
||||
) {
|
||||
window.addEventListener('focus', () => this._update(), true);
|
||||
window.addEventListener('blur', () => this._update(), true);
|
||||
this._editorService.onDidActiveEditorChange(() => this._update());
|
||||
this._canToggleWordWrap = CAN_TOGGLE_WORD_WRAP.bindTo(this._contextService);
|
||||
this._editorWordWrap = EDITOR_WORD_WRAP.bindTo(this._contextService);
|
||||
this._activeEditor = null;
|
||||
this._activeEditorListener = new DisposableStore();
|
||||
this._update();
|
||||
}
|
||||
|
||||
private _update(): void {
|
||||
const activeEditor = this._codeEditorService.getFocusedCodeEditor() || this._codeEditorService.getActiveCodeEditor();
|
||||
if (this._activeEditor === activeEditor) {
|
||||
// no change
|
||||
return;
|
||||
}
|
||||
this._activeEditorListener.clear();
|
||||
this._activeEditor = activeEditor;
|
||||
|
||||
if (activeEditor) {
|
||||
this._activeEditorListener.add(activeEditor.onDidChangeModel(() => this._updateFromCodeEditor()));
|
||||
this._activeEditorListener.add(activeEditor.onDidChangeConfiguration((e) => {
|
||||
if (e.hasChanged(EditorOption.wrappingInfo)) {
|
||||
this._updateFromCodeEditor();
|
||||
}
|
||||
}));
|
||||
this._updateFromCodeEditor();
|
||||
}
|
||||
}
|
||||
|
||||
private _updateFromCodeEditor(): void {
|
||||
if (!canToggleWordWrap(this._activeEditor)) {
|
||||
return this._setValues(false, false);
|
||||
} else {
|
||||
const wrappingInfo = this._activeEditor.getOption(EditorOption.wrappingInfo);
|
||||
this._setValues(true, wrappingInfo.wrappingColumn !== -1);
|
||||
}
|
||||
}
|
||||
|
||||
private _setValues(canToggleWordWrap: boolean, isWordWrap: boolean): void {
|
||||
this._canToggleWordWrap.set(canToggleWordWrap);
|
||||
this._editorWordWrap.set(isWordWrap);
|
||||
}
|
||||
}
|
||||
|
||||
const workbenchRegistry = Registry.as<IWorkbenchContributionsRegistry>(Extensions.Workbench);
|
||||
workbenchRegistry.registerWorkbenchContribution(EditorWordWrapContextKeyTracker, LifecyclePhase.Ready);
|
||||
|
||||
registerEditorContribution(ToggleWordWrapController.ID, ToggleWordWrapController);
|
||||
|
||||
@@ -221,7 +280,9 @@ MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, {
|
||||
group: '5_editor',
|
||||
command: {
|
||||
id: TOGGLE_WORD_WRAP_ID,
|
||||
title: nls.localize({ key: 'miToggleWordWrap', comment: ['&& denotes a mnemonic'] }, "Toggle &&Word Wrap")
|
||||
title: nls.localize({ key: 'miToggleWordWrap', comment: ['&& denotes a mnemonic'] }, "Toggle &&Word Wrap"),
|
||||
toggled: EDITOR_WORD_WRAP,
|
||||
precondition: CAN_TOGGLE_WORD_WRAP
|
||||
},
|
||||
order: 1
|
||||
});
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user