Merge from vscode ad407028575a77ea387eb7cc219b323dc017b686

This commit is contained in:
ADS Merger
2020-08-22 06:06:52 +00:00
committed by Anthony Dresser
parent 404260b8a0
commit 4ad73d381c
480 changed files with 14360 additions and 14122 deletions

View File

@@ -0,0 +1,597 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async';
import { CancellationToken } from 'vs/base/common/cancellation';
import { isPromiseCanceledError, onUnexpectedError } from 'vs/base/common/errors';
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable, DisposableStore, IDisposable, IReference } from 'vs/base/common/lifecycle';
import { 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 { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ILabelService } from 'vs/platform/label/common/label';
import { IUndoRedoService, UndoRedoElementType } from 'vs/platform/undoRedo/common/undoRedo';
import type { MainThreadWebviews } from 'vs/workbench/api/browser/mainThreadWebview';
import * as extHostProtocol from 'vs/workbench/api/common/extHost.protocol';
import { editorGroupToViewColumn } from 'vs/workbench/api/common/shared/editor';
import { IRevertOptions, ISaveOptions } from 'vs/workbench/common/editor';
import { CustomEditorInput } from 'vs/workbench/contrib/customEditor/browser/customEditorInput';
import { CustomDocumentBackupData } from 'vs/workbench/contrib/customEditor/browser/customEditorInputFactory';
import { ICustomEditorModel, ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor';
import { CustomTextEditorModel } from 'vs/workbench/contrib/customEditor/common/customTextEditorModel';
import { WebviewExtensionDescription } from 'vs/workbench/contrib/webview/browser/webview';
import { IWebviewWorkbenchService } from 'vs/workbench/contrib/webview/browser/webviewWorkbenchService';
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService';
import { IWorkingCopy, IWorkingCopyBackup, IWorkingCopyService, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopyService';
export const enum CustomEditorModelType {
Custom,
Text,
}
export class MainThreadCustomEditors extends Disposable {
private readonly _proxyCustomEditors: extHostProtocol.ExtHostCustomEditorsShape;
private readonly _editorProviders = new Map<string, IDisposable>();
constructor(
private readonly mainThreadWebviews: MainThreadWebviews,
context: extHostProtocol.IExtHostContext,
@IWorkingCopyService workingCopyService: IWorkingCopyService,
@IWorkingCopyFileService workingCopyFileService: IWorkingCopyFileService,
@ICustomEditorService private readonly _customEditorService: ICustomEditorService,
@IEditorGroupsService private readonly _editorGroupService: IEditorGroupsService,
@IWebviewWorkbenchService private readonly _webviewWorkbenchService: IWebviewWorkbenchService,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@IBackupFileService private readonly _backupService: IBackupFileService,
) {
super();
this._proxyCustomEditors = context.getProxy(extHostProtocol.ExtHostContext.ExtHostCustomEditors);
workingCopyFileService.registerWorkingCopyProvider((editorResource) => {
const matchedWorkingCopies: IWorkingCopy[] = [];
for (const workingCopy of workingCopyService.workingCopies) {
if (workingCopy instanceof MainThreadCustomEditorModel) {
if (isEqualOrParent(editorResource, workingCopy.editorResource)) {
matchedWorkingCopies.push(workingCopy);
}
}
}
return matchedWorkingCopies;
});
}
dispose() {
super.dispose();
for (const disposable of this._editorProviders.values()) {
disposable.dispose();
}
this._editorProviders.clear();
}
public registerEditorProvider(
modelType: CustomEditorModelType,
extension: WebviewExtensionDescription,
viewType: string,
options: modes.IWebviewPanelOptions,
capabilities: extHostProtocol.CustomTextEditorCapabilities,
supportsMultipleEditorsPerDocument: boolean,
): void {
if (this._editorProviders.has(viewType)) {
throw new Error(`Provider for ${viewType} already registered`);
}
const disposables = new DisposableStore();
disposables.add(this._customEditorService.registerCustomEditorCapabilities(viewType, {
supportsMultipleEditorsPerDocument
}));
disposables.add(this._webviewWorkbenchService.registerResolver({
canResolve: (webviewInput) => {
return webviewInput instanceof CustomEditorInput && webviewInput.viewType === viewType;
},
resolveWebview: async (webviewInput: CustomEditorInput, cancellation: CancellationToken) => {
const handle = webviewInput.id;
const resource = webviewInput.resource;
this.mainThreadWebviews.addWebviewInput(handle, webviewInput);
webviewInput.webview.options = options;
webviewInput.webview.extension = extension;
let modelRef: IReference<ICustomEditorModel>;
try {
modelRef = await this.getOrCreateCustomEditorModel(modelType, resource, viewType, { backupId: webviewInput.backupId }, cancellation);
} catch (error) {
onUnexpectedError(error);
webviewInput.webview.html = this.mainThreadWebviews.getWebviewResolvedFailedContent(viewType);
return;
}
if (cancellation.isCancellationRequested) {
modelRef.dispose();
return;
}
webviewInput.webview.onDispose(() => {
// If the model is still dirty, make sure we have time to save it
if (modelRef.object.isDirty()) {
const sub = modelRef.object.onDidChangeDirty(() => {
if (!modelRef.object.isDirty()) {
sub.dispose();
modelRef.dispose();
}
});
return;
}
modelRef.dispose();
});
if (capabilities.supportsMove) {
webviewInput.onMove(async (newResource: URI) => {
const oldModel = modelRef;
modelRef = await this.getOrCreateCustomEditorModel(modelType, newResource, viewType, {}, CancellationToken.None);
this._proxyCustomEditors.$onMoveCustomEditor(handle, newResource, viewType);
oldModel.dispose();
});
}
try {
await this._proxyCustomEditors.$resolveWebviewEditor(resource, handle, viewType, webviewInput.getTitle(), editorGroupToViewColumn(this._editorGroupService, webviewInput.group || 0), webviewInput.webview.options, cancellation);
} catch (error) {
onUnexpectedError(error);
webviewInput.webview.html = this.mainThreadWebviews.getWebviewResolvedFailedContent(viewType);
modelRef.dispose();
return;
}
}
}));
this._editorProviders.set(viewType, disposables);
}
public $unregisterEditorProvider(viewType: string): void {
const provider = this._editorProviders.get(viewType);
if (!provider) {
throw new Error(`No provider for ${viewType} registered`);
}
provider.dispose();
this._editorProviders.delete(viewType);
this._customEditorService.models.disposeAllModelsForView(viewType);
}
private async getOrCreateCustomEditorModel(
modelType: CustomEditorModelType,
resource: URI,
viewType: string,
options: { backupId?: string },
cancellation: CancellationToken,
): Promise<IReference<ICustomEditorModel>> {
const existingModel = this._customEditorService.models.tryRetain(resource, viewType);
if (existingModel) {
return existingModel;
}
switch (modelType) {
case CustomEditorModelType.Text:
{
const model = CustomTextEditorModel.create(this._instantiationService, viewType, resource);
return this._customEditorService.models.add(resource, viewType, model);
}
case CustomEditorModelType.Custom:
{
const model = MainThreadCustomEditorModel.create(this._instantiationService, this._proxyCustomEditors, viewType, resource, options, () => {
return Array.from(this.mainThreadWebviews.webviewInputs)
.filter(editor => editor instanceof CustomEditorInput && isEqual(editor.resource, resource)) as CustomEditorInput[];
}, cancellation, this._backupService);
return this._customEditorService.models.add(resource, viewType, model);
}
}
}
public async $onDidEdit(resourceComponents: UriComponents, viewType: string, editId: number, label: string | undefined): Promise<void> {
const model = await this.getCustomEditorModel(resourceComponents, viewType);
model.pushEdit(editId, label);
}
public async $onContentChange(resourceComponents: UriComponents, viewType: string): Promise<void> {
const model = await this.getCustomEditorModel(resourceComponents, viewType);
model.changeContent();
}
private async getCustomEditorModel(resourceComponents: UriComponents, viewType: string) {
const resource = URI.revive(resourceComponents);
const model = await this._customEditorService.models.get(resource, viewType);
if (!model || !(model instanceof MainThreadCustomEditorModel)) {
throw new Error('Could not find model for webview editor');
}
return model;
}
}
namespace HotExitState {
export const enum Type {
Allowed,
NotAllowed,
Pending,
}
export const Allowed = Object.freeze({ type: Type.Allowed } as const);
export const NotAllowed = Object.freeze({ type: Type.NotAllowed } as const);
export class Pending {
readonly type = Type.Pending;
constructor(
public readonly operation: CancelablePromise<string>,
) { }
}
export type State = typeof Allowed | typeof NotAllowed | Pending;
}
class MainThreadCustomEditorModel extends Disposable implements ICustomEditorModel, IWorkingCopy {
private _fromBackup: boolean = false;
private _hotExitState: HotExitState.State = HotExitState.Allowed;
private _backupId: string | undefined;
private _currentEditIndex: number = -1;
private _savePoint: number = -1;
private readonly _edits: Array<number> = [];
private _isDirtyFromContentChange = false;
private _ongoingSave?: CancelablePromise<void>;
public static async create(
instantiationService: IInstantiationService,
proxy: extHostProtocol.ExtHostCustomEditorsShape,
viewType: string,
resource: URI,
options: { backupId?: string },
getEditors: () => CustomEditorInput[],
cancellation: CancellationToken,
_backupFileService: IBackupFileService,
) {
const { editable } = await proxy.$createCustomDocument(resource, viewType, options.backupId, cancellation);
return instantiationService.createInstance(MainThreadCustomEditorModel, proxy, viewType, resource, !!options.backupId, editable, getEditors);
}
constructor(
private readonly _proxy: extHostProtocol.ExtHostCustomEditorsShape,
private readonly _viewType: string,
private readonly _editorResource: URI,
fromBackup: boolean,
private readonly _editable: boolean,
private readonly _getEditors: () => CustomEditorInput[],
@IFileDialogService private readonly _fileDialogService: IFileDialogService,
@IFileService private readonly _fileService: IFileService,
@ILabelService private readonly _labelService: ILabelService,
@IUndoRedoService private readonly _undoService: IUndoRedoService,
@IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService,
@IWorkingCopyService workingCopyService: IWorkingCopyService,
) {
super();
this._fromBackup = fromBackup;
if (_editable) {
this._register(workingCopyService.registerWorkingCopy(this));
}
}
get editorResource() {
return this._editorResource;
}
dispose() {
if (this._editable) {
this._undoService.removeElements(this._editorResource);
}
this._proxy.$disposeCustomDocument(this._editorResource, this._viewType);
super.dispose();
}
//#region IWorkingCopy
public get resource() {
// Make sure each custom editor has a unique resource for backup and edits
return MainThreadCustomEditorModel.toWorkingCopyResource(this._viewType, this._editorResource);
}
private static toWorkingCopyResource(viewType: string, resource: URI) {
return URI.from({
scheme: Schemas.vscodeCustomEditor,
authority: viewType,
path: resource.path,
query: JSON.stringify(resource.toJSON()),
});
}
public get name() {
return basename(this._labelService.getUriLabel(this._editorResource));
}
public get capabilities(): WorkingCopyCapabilities {
return 0;
}
public isDirty(): boolean {
if (this._isDirtyFromContentChange) {
return true;
}
if (this._edits.length > 0) {
return this._savePoint !== this._currentEditIndex;
}
return this._fromBackup;
}
private readonly _onDidChangeDirty: Emitter<void> = this._register(new Emitter<void>());
readonly onDidChangeDirty: Event<void> = this._onDidChangeDirty.event;
private readonly _onDidChangeContent: Emitter<void> = this._register(new Emitter<void>());
readonly onDidChangeContent: Event<void> = this._onDidChangeContent.event;
//#endregion
public isReadonly() {
return !this._editable;
}
public get viewType() {
return this._viewType;
}
public get backupId() {
return this._backupId;
}
public pushEdit(editId: number, label: string | undefined) {
if (!this._editable) {
throw new Error('Document is not editable');
}
this.change(() => {
this.spliceEdits(editId);
this._currentEditIndex = this._edits.length - 1;
});
this._undoService.pushElement({
type: UndoRedoElementType.Resource,
resource: this._editorResource,
label: label ?? localize('defaultEditLabel', "Edit"),
undo: () => this.undo(),
redo: () => this.redo(),
});
}
public changeContent() {
this.change(() => {
this._isDirtyFromContentChange = true;
});
}
private async undo(): Promise<void> {
if (!this._editable) {
return;
}
if (this._currentEditIndex < 0) {
// nothing to undo
return;
}
const undoneEdit = this._edits[this._currentEditIndex];
this.change(() => {
--this._currentEditIndex;
});
await this._proxy.$undo(this._editorResource, this.viewType, undoneEdit, this.isDirty());
}
private async redo(): Promise<void> {
if (!this._editable) {
return;
}
if (this._currentEditIndex >= this._edits.length - 1) {
// nothing to redo
return;
}
const redoneEdit = this._edits[this._currentEditIndex + 1];
this.change(() => {
++this._currentEditIndex;
});
await this._proxy.$redo(this._editorResource, this.viewType, redoneEdit, this.isDirty());
}
private spliceEdits(editToInsert?: number) {
const start = this._currentEditIndex + 1;
const toRemove = this._edits.length - this._currentEditIndex;
const removedEdits = typeof editToInsert === 'number'
? this._edits.splice(start, toRemove, editToInsert)
: this._edits.splice(start, toRemove);
if (removedEdits.length) {
this._proxy.$disposeEdits(this._editorResource, this._viewType, removedEdits);
}
}
private change(makeEdit: () => void): void {
const wasDirty = this.isDirty();
makeEdit();
this._onDidChangeContent.fire();
if (this.isDirty() !== wasDirty) {
this._onDidChangeDirty.fire();
}
}
public async revert(_options?: IRevertOptions) {
if (!this._editable) {
return;
}
if (this._currentEditIndex === this._savePoint && !this._isDirtyFromContentChange && !this._fromBackup) {
return;
}
this._proxy.$revert(this._editorResource, this.viewType, CancellationToken.None);
this.change(() => {
this._isDirtyFromContentChange = false;
this._fromBackup = false;
this._currentEditIndex = this._savePoint;
this.spliceEdits();
});
}
public async save(options?: ISaveOptions): Promise<boolean> {
return !!await this.saveCustomEditor(options);
}
public async saveCustomEditor(options?: ISaveOptions): Promise<URI | undefined> {
if (!this._editable) {
return undefined;
}
if (this._editorResource.scheme === Schemas.untitled) {
const targetUri = await this.suggestUntitledSavePath(options);
if (!targetUri) {
return undefined;
}
await this.saveCustomEditorAs(this._editorResource, targetUri, options);
return targetUri;
}
const savePromise = createCancelablePromise(token => this._proxy.$onSave(this._editorResource, this.viewType, token));
this._ongoingSave?.cancel();
this._ongoingSave = savePromise;
this.change(() => {
this._isDirtyFromContentChange = false;
this._savePoint = this._currentEditIndex;
this._fromBackup = false;
});
try {
await savePromise;
} finally {
if (this._ongoingSave === savePromise) {
this._ongoingSave = undefined;
}
}
return this._editorResource;
}
private suggestUntitledSavePath(options: ISaveOptions | undefined): Promise<URI | undefined> {
if (this._editorResource.scheme !== Schemas.untitled) {
throw new Error('Resource is not untitled');
}
const remoteAuthority = this._environmentService.configuration.remoteAuthority;
const localResource = toLocalResource(this._editorResource, remoteAuthority);
return this._fileDialogService.pickFileToSave(localResource, options?.availableFileSystems);
}
public async saveCustomEditorAs(resource: URI, targetResource: URI, _options?: ISaveOptions): Promise<boolean> {
if (this._editable) {
// TODO: handle cancellation
await createCancelablePromise(token => this._proxy.$onSaveAs(this._editorResource, this.viewType, targetResource, token));
this.change(() => {
this._savePoint = this._currentEditIndex;
});
return true;
} else {
// Since the editor is readonly, just copy the file over
await this._fileService.copy(resource, targetResource, false /* overwrite */);
return true;
}
}
public async backup(): Promise<IWorkingCopyBackup> {
const editors = this._getEditors();
if (!editors.length) {
throw new Error('No editors found for resource, cannot back up');
}
const primaryEditor = editors[0];
const backupData: IWorkingCopyBackup<CustomDocumentBackupData> = {
meta: {
viewType: this.viewType,
editorResource: this._editorResource,
backupId: '',
extension: primaryEditor.extension ? {
id: primaryEditor.extension.id.value,
location: primaryEditor.extension.location,
} : undefined,
webview: {
id: primaryEditor.id,
options: primaryEditor.webview.options,
state: primaryEditor.webview.state,
}
}
};
if (!this._editable) {
return backupData;
}
if (this._hotExitState.type === HotExitState.Type.Pending) {
this._hotExitState.operation.cancel();
}
const pendingState = new HotExitState.Pending(
createCancelablePromise(token =>
this._proxy.$backup(this._editorResource.toJSON(), this.viewType, token)));
this._hotExitState = pendingState;
try {
const backupId = await pendingState.operation;
// Make sure state has not changed in the meantime
if (this._hotExitState === pendingState) {
this._hotExitState = HotExitState.Allowed;
backupData.meta!.backupId = backupId;
this._backupId = backupId;
}
} catch (e) {
if (isPromiseCanceledError(e)) {
// This is expected
throw e;
}
// Otherwise it could be a real error. Make sure state has not changed in the meantime.
if (this._hotExitState === pendingState) {
this._hotExitState = HotExitState.NotAllowed;
}
}
if (this._hotExitState === HotExitState.Allowed) {
return backupData;
}
throw new Error('Cannot back up in this state');
}
}

View File

@@ -264,6 +264,14 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb
return Promise.reject(new Error('debug session not found'));
}
public $getDebugProtocolBreakpoint(sessionId: DebugSessionUUID, breakpoinId: string): Promise<DebugProtocol.Breakpoint | undefined> {
const session = this.debugService.getModel().getSession(sessionId, true);
if (session) {
return Promise.resolve(session.getDebugProtocolBreakpoint(breakpoinId));
}
return Promise.reject(new Error('debug session not found'));
}
public $stopDebugging(sessionId: DebugSessionUUID | undefined): Promise<void> {
if (sessionId) {
const session = this.debugService.getModel().getSession(sessionId, true);

View File

@@ -263,12 +263,19 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
// --- on type rename
$registerOnTypeRenameProvider(handle: number, selector: IDocumentFilterDto[], stopPattern?: IRegExpDto): void {
const revivedStopPattern = stopPattern ? MainThreadLanguageFeatures._reviveRegExp(stopPattern) : undefined;
$registerOnTypeRenameProvider(handle: number, selector: IDocumentFilterDto[], wordPattern?: IRegExpDto): void {
const revivedWordPattern = wordPattern ? MainThreadLanguageFeatures._reviveRegExp(wordPattern) : undefined;
this._registrations.set(handle, modes.OnTypeRenameProviderRegistry.register(selector, <modes.OnTypeRenameProvider>{
stopPattern: revivedStopPattern,
provideOnTypeRenameRanges: (model: ITextModel, position: EditorPosition, token: CancellationToken): Promise<IRange[] | undefined> => {
return this._proxy.$provideOnTypeRenameRanges(handle, model.uri, position, token);
wordPattern: revivedWordPattern,
provideOnTypeRenameRanges: async (model: ITextModel, position: EditorPosition, token: CancellationToken): Promise<{ ranges: IRange[]; wordPattern?: RegExp; } | undefined> => {
const res = await this._proxy.$provideOnTypeRenameRanges(handle, model.uri, position, token);
if (res) {
return {
ranges: res.ranges,
wordPattern: res.wordPattern ? MainThreadLanguageFeatures._reviveRegExp(res.wordPattern) : undefined
};
}
return undefined;
}
}));
}

View File

@@ -9,7 +9,7 @@ import { MainContext, MainThreadNotebookShape, NotebookExtensionDescription, IEx
import { Disposable, IDisposable, combinedDisposable, DisposableStore } from 'vs/base/common/lifecycle';
import { URI, UriComponents } from 'vs/base/common/uri';
import { INotebookService, IMainNotebookController } from 'vs/workbench/contrib/notebook/common/notebookService';
import { INotebookMimeTypeSelector, NOTEBOOK_DISPLAY_ORDER, NotebookCellOutputsSplice, NotebookDocumentMetadata, NotebookCellMetadata, ICellEditOperation, ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, CellEditType, CellKind, INotebookKernelInfo, INotebookKernelInfoDto, IEditor, INotebookRendererInfo, IOutputRenderRequest, IOutputRenderResponse, INotebookDocumentFilter } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { NOTEBOOK_DISPLAY_ORDER, NotebookCellOutputsSplice, NotebookDocumentMetadata, NotebookCellMetadata, ICellEditOperation, ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, CellEditType, CellKind, INotebookKernelInfo, INotebookKernelInfoDto, IEditor, INotebookDocumentFilter, DisplayOrderKey } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
@@ -21,6 +21,7 @@ import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo';
import { ITextModelService } from 'vs/editor/common/services/resolverService';
import { Emitter } from 'vs/base/common/event';
import { ILogService } from 'vs/platform/log/common/log';
export class MainThreadNotebookDocument extends Disposable {
private _textModel: NotebookTextModel;
@@ -170,7 +171,6 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo
private readonly _notebookProviders = new Map<string, IMainNotebookController>();
private readonly _notebookKernels = new Map<string, MainThreadNotebookKernel>();
private readonly _notebookKernelProviders = new Map<number, { extension: NotebookExtensionDescription, emitter: Emitter<void>, provider: IDisposable }>();
private readonly _notebookRenderers = new Map<string, MainThreadNotebookRenderer>();
private readonly _proxy: ExtHostNotebookShape;
private _toDisposeOnEditorRemove = new Map<string, IDisposable>();
private _currentState?: DocumentAndEditorState;
@@ -181,7 +181,8 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo
@INotebookService private _notebookService: INotebookService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IEditorService private readonly editorService: IEditorService,
@IAccessibilityService private readonly accessibilityService: IAccessibilityService
@IAccessibilityService private readonly accessibilityService: IAccessibilityService,
@ILogService private readonly logService: ILogService
) {
super();
@@ -189,10 +190,10 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo
this.registerListeners();
}
async $tryApplyEdits(viewType: string, resource: UriComponents, modelVersionId: number, edits: ICellEditOperation[], renderers: number[]): Promise<boolean> {
async $tryApplyEdits(viewType: string, resource: UriComponents, modelVersionId: number, edits: ICellEditOperation[]): Promise<boolean> {
const textModel = this._notebookService.getNotebookTextModel(URI.from(resource));
if (textModel) {
await this._notebookService.transformEditsOutputs(textModel, edits);
this._notebookService.transformEditsOutputs(textModel, edits);
return textModel.$applyEdit(modelVersionId, edits, true);
}
@@ -201,7 +202,7 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo
async removeNotebookTextModel(uri: URI): Promise<void> {
// TODO@rebornix, remove cell should use emitDelta as well to ensure document/editor events are sent together
await this._proxy.$acceptDocumentAndEditorsDelta({ removedDocuments: [uri] });
this._proxy.$acceptDocumentAndEditorsDelta({ removedDocuments: [uri] });
let textModelDisposableStore = this._editorEventListenersMapping.get(uri.toString());
textModelDisposableStore?.dispose();
this._editorEventListenersMapping.delete(URI.from(uri).toString());
@@ -310,7 +311,7 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo
}));
const updateOrder = () => {
let userOrder = this.configurationService.getValue<string[]>('notebook.displayOrder');
let userOrder = this.configurationService.getValue<string[]>(DisplayOrderKey);
this._proxy.$acceptDisplayOrder({
defaultOrder: this.accessibilityService.isScreenReaderOptimized() ? ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER : NOTEBOOK_DISPLAY_ORDER,
userOrder: userOrder
@@ -320,7 +321,7 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo
updateOrder();
this._register(this.configurationService.onDidChangeConfiguration(e => {
if (e.affectedKeys.indexOf('notebook.displayOrder') >= 0) {
if (e.affectedKeys.indexOf(DisplayOrderKey) >= 0) {
updateOrder();
}
}));
@@ -412,16 +413,6 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo
// }
}
async $registerNotebookRenderer(extension: NotebookExtensionDescription, type: string, selectors: INotebookMimeTypeSelector, preloads: UriComponents[]): Promise<void> {
const renderer = new MainThreadNotebookRenderer(this._proxy, type, extension.id, URI.revive(extension.location), selectors, preloads.map(uri => URI.revive(uri)));
this._notebookRenderers.set(type, renderer);
this._notebookService.registerNotebookRenderer(type, renderer);
}
async $unregisterNotebookRenderer(id: string): Promise<void> {
this._notebookService.unregisterNotebookRenderer(id);
}
async $registerNotebookProvider(_extension: NotebookExtensionDescription, _viewType: string, _supportBackup: boolean, _kernel: INotebookKernelInfoDto | undefined): Promise<void> {
const controller: IMainNotebookController = {
kernel: _kernel,
@@ -440,7 +431,7 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo
{ editType: CellEditType.Insert, index: 0, cells: data.cells }
];
await this._notebookService.transformEditsOutputs(mainthreadTextModel, edits);
this._notebookService.transformEditsOutputs(mainthreadTextModel, edits);
await new Promise(resolve => {
DOM.scheduleAtNextAnimationFrame(() => {
const ret = mainthreadTextModel!.$applyEdit(mainthreadTextModel!.versionId, edits, true);
@@ -472,10 +463,10 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo
await this._proxy.$resolveNotebookEditor(viewType, uri, editorId);
},
executeNotebookByAttachedKernel: async (viewType: string, uri: URI) => {
return this.executeNotebookByAttachedKernel(viewType, uri);
return this.executeNotebookByAttachedKernel(viewType, uri, undefined);
},
cancelNotebookByAttachedKernel: async (viewType: string, uri: URI) => {
return this.cancelNotebookByAttachedKernel(viewType, uri);
return this.cancelNotebookByAttachedKernel(viewType, uri, undefined);
},
onDidReceiveMessage: (editorId: string, rendererType: string | undefined, message: unknown) => {
this._proxy.$onDidReceiveMessage(editorId, rendererType, message);
@@ -484,10 +475,10 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo
return this.removeNotebookTextModel(uri);
},
executeNotebookCell: async (uri: URI, handle: number) => {
return this._proxy.$executeNotebookByAttachedKernel(_viewType, uri, handle);
return this.executeNotebookByAttachedKernel(_viewType, uri, handle);
},
cancelNotebookCell: async (uri: URI, handle: number) => {
return this._proxy.$cancelNotebookByAttachedKernel(_viewType, uri, handle);
return this.cancelNotebookByAttachedKernel(_viewType, uri, handle);
},
save: async (uri: URI, token: CancellationToken) => {
return this._proxy.$saveNotebook(_viewType, uri, token);
@@ -517,7 +508,7 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo
}
async $registerNotebookKernel(extension: NotebookExtensionDescription, id: string, label: string, selectors: (string | IRelativePattern)[], preloads: UriComponents[]): Promise<void> {
const kernel = new MainThreadNotebookKernel(this._proxy, id, label, selectors, extension.id, URI.revive(extension.location), preloads.map(preload => URI.revive(preload)));
const kernel = new MainThreadNotebookKernel(this._proxy, id, label, selectors, extension.id, URI.revive(extension.location), preloads.map(preload => URI.revive(preload)), this.logService);
this._notebookKernels.set(id, kernel);
this._notebookService.registerNotebookKernel(kernel);
return;
@@ -550,9 +541,11 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo
return that._proxy.$resolveNotebookKernel(handle, editorId, uri, kernelId, token);
},
executeNotebook: (uri: URI, kernelId: string, cellHandle: number | undefined) => {
this.logService.debug('MainthreadNotebooks.registerNotebookKernelProvider#executeNotebook', uri.path, kernelId, cellHandle);
return that._proxy.$executeNotebookKernelFromProvider(handle, uri, kernelId, cellHandle);
},
cancelNotebook: (uri: URI, kernelId: string, cellHandle: number | undefined) => {
this.logService.debug('MainthreadNotebooks.registerNotebookKernelProvider#cancelNotebook', uri.path, kernelId, cellHandle);
return that._proxy.$cancelNotebookKernelFromProvider(handle, uri, kernelId, cellHandle);
},
});
@@ -582,35 +575,41 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo
}
async $updateNotebookLanguages(viewType: string, resource: UriComponents, languages: string[]): Promise<void> {
this.logService.debug('MainThreadNotebooks#updateNotebookLanguages', resource.path, languages);
const textModel = this._notebookService.getNotebookTextModel(URI.from(resource));
textModel?.updateLanguages(languages);
}
async $updateNotebookMetadata(viewType: string, resource: UriComponents, metadata: NotebookDocumentMetadata): Promise<void> {
this.logService.debug('MainThreadNotebooks#updateNotebookMetadata', resource.path, metadata);
const textModel = this._notebookService.getNotebookTextModel(URI.from(resource));
textModel?.updateNotebookMetadata(metadata);
}
async $updateNotebookCellMetadata(viewType: string, resource: UriComponents, handle: number, metadata: NotebookCellMetadata): Promise<void> {
this.logService.debug('MainThreadNotebooks#updateNotebookCellMetadata', resource.path, handle, metadata);
const textModel = this._notebookService.getNotebookTextModel(URI.from(resource));
textModel?.updateNotebookCellMetadata(handle, metadata);
}
async $spliceNotebookCellOutputs(viewType: string, resource: UriComponents, cellHandle: number, splices: NotebookCellOutputsSplice[], renderers: number[]): Promise<void> {
async $spliceNotebookCellOutputs(viewType: string, resource: UriComponents, cellHandle: number, splices: NotebookCellOutputsSplice[]): Promise<void> {
this.logService.debug('MainThreadNotebooks#spliceNotebookCellOutputs', resource.path, cellHandle);
const textModel = this._notebookService.getNotebookTextModel(URI.from(resource));
if (textModel) {
await this._notebookService.transformSpliceOutputs(textModel, splices);
this._notebookService.transformSpliceOutputs(textModel, splices);
textModel.$spliceNotebookCellOutputs(cellHandle, splices);
}
}
async executeNotebookByAttachedKernel(viewType: string, uri: URI): Promise<void> {
return this._proxy.$executeNotebookByAttachedKernel(viewType, uri, undefined);
async executeNotebookByAttachedKernel(viewType: string, uri: URI, handle: number | undefined): Promise<void> {
this.logService.debug('MainthreadNotebooks#executeNotebookByAttachedKernel', uri.path, handle);
return this._proxy.$executeNotebookByAttachedKernel(viewType, uri, handle);
}
async cancelNotebookByAttachedKernel(viewType: string, uri: URI): Promise<void> {
return this._proxy.$cancelNotebookByAttachedKernel(viewType, uri, undefined);
async cancelNotebookByAttachedKernel(viewType: string, uri: URI, handle: number | undefined): Promise<void> {
this.logService.debug('MainthreadNotebooks#cancelNotebookByAttachedKernel', uri.path, handle);
return this._proxy.$cancelNotebookByAttachedKernel(viewType, uri, handle);
}
async $postMessage(editorId: string, forRendererId: string | undefined, value: any): Promise<boolean> {
@@ -649,32 +648,13 @@ export class MainThreadNotebookKernel implements INotebookKernelInfo {
readonly selectors: (string | IRelativePattern)[],
readonly extension: ExtensionIdentifier,
readonly extensionLocation: URI,
readonly preloads: URI[]
readonly preloads: URI[],
readonly logService: ILogService
) {
}
async executeNotebook(viewType: string, uri: URI, handle: number | undefined): Promise<void> {
this.logService.debug('MainThreadNotebookKernel#executeNotebook', uri.path, handle);
return this._proxy.$executeNotebook2(this.id, viewType, uri, handle);
}
}
export class MainThreadNotebookRenderer implements INotebookRendererInfo {
constructor(
private readonly _proxy: ExtHostNotebookShape,
readonly id: string,
readonly extensionId: ExtensionIdentifier,
readonly extensionLocation: URI,
readonly selectors: INotebookMimeTypeSelector,
readonly preloads: URI[]
) {
}
render(uri: URI, request: IOutputRenderRequest<UriComponents>): Promise<IOutputRenderResponse<UriComponents> | undefined> {
return this._proxy.$renderOutputs(uri, this.id, request);
}
render2<T>(uri: URI, request: IOutputRenderRequest<T>): Promise<IOutputRenderResponse<T> | undefined> {
return this._proxy.$renderOutputs2(uri, this.id, request);
}
}

View File

@@ -66,9 +66,10 @@ class MainThreadSCMResource implements ISCMResource {
private readonly sourceControlHandle: number,
private readonly groupHandle: number,
private readonly handle: number,
public sourceUri: URI,
public resourceGroup: ISCMResourceGroup,
public decorations: ISCMResourceDecorations
readonly sourceUri: URI,
readonly resourceGroup: ISCMResourceGroup,
readonly decorations: ISCMResourceDecorations,
readonly contextValue: string | undefined
) { }
open(preserveFocus: boolean): Promise<void> {
@@ -150,18 +151,22 @@ class MainThreadSCMProvider implements ISCMProvider {
}
}
$registerGroup(handle: number, id: string, label: string): void {
const group = new MainThreadSCMResourceGroup(
this.handle,
handle,
this,
{},
label,
id
);
$registerGroups(_groups: [number /*handle*/, string /*id*/, string /*label*/, SCMGroupFeatures][]): void {
const groups = _groups.map(([handle, id, label, features]) => {
const group = new MainThreadSCMResourceGroup(
this.handle,
handle,
this,
features,
label,
id
);
this._groupsByHandle[handle] = group;
this.groups.splice(this.groups.elements.length, 0, [group]);
this._groupsByHandle[handle] = group;
return group;
});
this.groups.splice(this.groups.elements.length, 0, groups);
}
$updateGroup(handle: number, features: SCMGroupFeatures): void {
@@ -198,7 +203,7 @@ class MainThreadSCMProvider implements ISCMProvider {
for (const [start, deleteCount, rawResources] of groupSlices) {
const resources = rawResources.map(rawResource => {
const [handle, sourceUri, icons, tooltip, strikeThrough, faded] = rawResource;
const [handle, sourceUri, icons, tooltip, strikeThrough, faded, contextValue] = rawResource;
const icon = icons[0];
const iconDark = icons[1] || icon;
const decorations = {
@@ -216,7 +221,8 @@ class MainThreadSCMProvider implements ISCMProvider {
handle,
URI.revive(sourceUri),
group,
decorations
decorations,
contextValue || undefined
);
});
@@ -326,7 +332,7 @@ export class MainThreadSCM implements MainThreadSCMShape {
this._repositories.delete(handle);
}
$registerGroup(sourceControlHandle: number, groupHandle: number, id: string, label: string): void {
$registerGroups(sourceControlHandle: number, groups: [number /*handle*/, string /*id*/, string /*label*/, SCMGroupFeatures][], splices: SCMRawResourceSplices[]): void {
const repository = this._repositories.get(sourceControlHandle);
if (!repository) {
@@ -334,7 +340,8 @@ export class MainThreadSCM implements MainThreadSCMShape {
}
const provider = repository.provider as MainThreadSCMProvider;
provider.$registerGroup(groupHandle, id, label);
provider.$registerGroups(groups);
provider.$spliceGroupResourceStates(splices);
}
$updateGroup(sourceControlHandle: number, groupHandle: number, features: SCMGroupFeatures): void {

View File

@@ -5,14 +5,14 @@
import * as nls from 'vs/nls';
import { URI } from 'vs/base/common/uri';
import { URI, UriComponents } from 'vs/base/common/uri';
import { generateUuid } from 'vs/base/common/uuid';
import * as Types from 'vs/base/common/types';
import * as Platform from 'vs/base/common/platform';
import { IStringDictionary, forEach } from 'vs/base/common/collections';
import { IDisposable } from 'vs/base/common/lifecycle';
import { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { IWorkspace, IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import {
ContributedTask, ConfiguringTask, KeyedTaskIdentifier, TaskExecution, Task, TaskEvent, TaskEventKind,
@@ -414,10 +414,18 @@ export class MainThreadTask implements MainThreadTaskShape {
) {
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostTask);
this._providers = new Map();
this._taskService.onDidStateChange((event: TaskEvent) => {
this._taskService.onDidStateChange(async (event: TaskEvent) => {
const task = event.__task!;
if (event.kind === TaskEventKind.Start) {
this._proxy.$onDidStartTask(TaskExecutionDTO.from(task.getTaskExecution()), event.terminalId!);
const execution = TaskExecutionDTO.from(task.getTaskExecution());
let resolvedDefinition: TaskDefinitionDTO = execution.task!.definition;
if (execution.task?.execution && CustomExecutionDTO.is(execution.task.execution) && event.resolvedVariables) {
const dictionary: IStringDictionary<string> = {};
Array.from(event.resolvedVariables.entries()).forEach(entry => dictionary[entry[0]] = entry[1]);
resolvedDefinition = await this._configurationResolverService.resolveAny(task.getWorkspaceFolder(),
execution.task.definition, dictionary);
}
this._proxy.$onDidStartTask(execution, event.terminalId!, resolvedDefinition);
} else if (event.kind === TaskEventKind.ProcessStarted) {
this._proxy.$onDidStartTaskProcess(TaskProcessStartedDTO.from(task.getTaskExecution(), event.processId!));
} else if (event.kind === TaskEventKind.ProcessEnded) {
@@ -509,11 +517,27 @@ export class MainThreadTask implements MainThreadTaskShape {
});
}
private getWorkspace(value: UriComponents | string): string | IWorkspace | IWorkspaceFolder | null {
let workspace;
if (typeof value === 'string') {
workspace = value;
} else {
const workspaceObject = this._workspaceContextServer.getWorkspace();
const uri = URI.revive(value);
if (workspaceObject.configuration?.toString() === uri.toString()) {
workspace = workspaceObject;
} else {
workspace = this._workspaceContextServer.getWorkspaceFolder(uri);
}
}
return workspace;
}
public async $getTaskExecution(value: TaskHandleDTO | TaskDTO): Promise<TaskExecutionDTO> {
if (TaskHandleDTO.is(value)) {
const workspaceFolder = typeof value.workspaceFolder === 'string' ? value.workspaceFolder : this._workspaceContextServer.getWorkspaceFolder(URI.revive(value.workspaceFolder));
if (workspaceFolder) {
const task = await this._taskService.getTask(workspaceFolder, value.id, true);
const workspace = this.getWorkspace(value.workspaceFolder);
if (workspace) {
const task = await this._taskService.getTask(workspace, value.id, true);
if (task) {
return {
id: task._id,
@@ -538,9 +562,9 @@ export class MainThreadTask implements MainThreadTaskShape {
public $executeTask(value: TaskHandleDTO | TaskDTO): Promise<TaskExecutionDTO> {
return new Promise<TaskExecutionDTO>((resolve, reject) => {
if (TaskHandleDTO.is(value)) {
const workspaceFolder = typeof value.workspaceFolder === 'string' ? value.workspaceFolder : this._workspaceContextServer.getWorkspaceFolder(URI.revive(value.workspaceFolder));
if (workspaceFolder) {
this._taskService.getTask(workspaceFolder, value.id, true).then((task: Task | undefined) => {
const workspace = this.getWorkspace(value.workspaceFolder);
if (workspace) {
this._taskService.getTask(workspace, value.id, true).then((task: Task | undefined) => {
if (!task) {
reject(new Error('Task not found'));
} else {

View File

@@ -9,7 +9,7 @@ import { ExtHostContext, ExtHostTerminalServiceShape, MainThreadTerminalServiceS
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { URI } from 'vs/base/common/uri';
import { StopWatch } from 'vs/base/common/stopwatch';
import { ITerminalInstanceService, ITerminalService, ITerminalInstance, ITerminalBeforeHandleLinkEvent, ITerminalExternalLinkProvider, ITerminalLink } from 'vs/workbench/contrib/terminal/browser/terminal';
import { ITerminalInstanceService, ITerminalService, ITerminalInstance, ITerminalExternalLinkProvider, ITerminalLink } from 'vs/workbench/contrib/terminal/browser/terminal';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { TerminalDataBufferer } from 'vs/workbench/contrib/terminal/common/terminalDataBuffering';
@@ -25,7 +25,6 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
private readonly _toDispose = new DisposableStore();
private readonly _terminalProcessProxies = new Map<number, ITerminalProcessExtHostProxy>();
private _dataEventTracker: TerminalDataEventTracker | undefined;
private _linkHandler: IDisposable | undefined;
/**
* 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
@@ -95,7 +94,6 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
public dispose(): void {
this._toDispose.dispose();
this._linkHandler?.dispose();
this._linkProvider?.dispose();
// TODO@Daniel: Should all the previously created terminals be disposed
@@ -156,6 +154,10 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
this._dataEventTracker = this._instantiationService.createInstance(TerminalDataEventTracker, (id, data) => {
this._onTerminalData(id, data);
});
// Send initial events if they exist
this._terminalService.terminalInstances.forEach(t => {
t.initialDataEvents?.forEach(d => this._onTerminalData(t.id, d));
});
}
}
@@ -166,16 +168,6 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
}
}
public $startHandlingLinks(): void {
this._linkHandler?.dispose();
this._linkHandler = this._terminalService.addLinkHandler(this._remoteAuthority || '', e => this._handleLink(e));
}
public $stopHandlingLinks(): void {
this._linkHandler?.dispose();
this._linkHandler = undefined;
}
public $startLinkProvider(): void {
this._linkProvider?.dispose();
this._linkProvider = this._terminalService.registerLinkProvider(new ExtensionTerminalLinkProvider(this._proxy));
@@ -186,13 +178,6 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
this._linkProvider = undefined;
}
private async _handleLink(e: ITerminalBeforeHandleLinkEvent): Promise<boolean> {
if (!e.terminal) {
return false;
}
return this._proxy.$handleLink(e.terminal.id, e.link);
}
private _onActiveTerminalChanged(terminalId: number | null): void {
this._proxy.$acceptActiveTerminalChanged(terminalId);
}

View File

@@ -3,46 +3,30 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async';
import { CancellationToken } from 'vs/base/common/cancellation';
import { isPromiseCanceledError, onUnexpectedError } from 'vs/base/common/errors';
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable, DisposableStore, dispose, IDisposable, IReference } from 'vs/base/common/lifecycle';
import { Disposable, DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle';
import { Schemas } from 'vs/base/common/network';
import { basename } from 'vs/base/common/path';
import { isWeb } from 'vs/base/common/platform';
import { isEqual, isEqualOrParent, toLocalResource } from 'vs/base/common/resources';
import { escape } from 'vs/base/common/strings';
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 { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { IFileService } from 'vs/platform/files/common/files';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ILabelService } from 'vs/platform/label/common/label';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { IProductService } from 'vs/platform/product/common/productService';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IUndoRedoService, UndoRedoElementType } from 'vs/platform/undoRedo/common/undoRedo';
import { CustomEditorModelType, MainThreadCustomEditors } from 'vs/workbench/api/browser/mainThreadCustomEditors';
import { MainThreadWebviewSerializers } from 'vs/workbench/api/browser/mainThreadWebviewSerializer';
import { MainThreadWebviewsViews } from 'vs/workbench/api/browser/mainThreadWebviewViews';
import * as extHostProtocol from 'vs/workbench/api/common/extHost.protocol';
import { editorGroupToViewColumn, EditorViewColumn, viewColumnToEditorGroup } from 'vs/workbench/api/common/shared/editor';
import { IEditorInput, IRevertOptions, ISaveOptions } from 'vs/workbench/common/editor';
import { IEditorInput } from 'vs/workbench/common/editor';
import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput';
import { CustomEditorInput } from 'vs/workbench/contrib/customEditor/browser/customEditorInput';
import { CustomDocumentBackupData } from 'vs/workbench/contrib/customEditor/browser/customEditorInputFactory';
import { ICustomEditorModel, ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor';
import { CustomTextEditorModel } from 'vs/workbench/contrib/customEditor/common/customTextEditorModel';
import { WebviewExtensionDescription, WebviewIcons } from 'vs/workbench/contrib/webview/browser/webview';
import { Webview, WebviewExtensionDescription, WebviewIcons, WebviewOverlay } from 'vs/workbench/contrib/webview/browser/webview';
import { WebviewInput } from 'vs/workbench/contrib/webview/browser/webviewEditorInput';
import { ICreateWebViewShowOptions, IWebviewWorkbenchService, WebviewInputOptions } from 'vs/workbench/contrib/webview/browser/webviewWorkbenchService';
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService';
import { IWorkingCopy, IWorkingCopyBackup, IWorkingCopyService, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopyService';
import { extHostNamedCustomer } from '../common/extHostCustomers';
/**
@@ -98,13 +82,6 @@ class WebviewViewTypeTransformer {
}
}
const enum ModelType {
Custom,
Text,
}
const webviewPanelViewType = new WebviewViewTypeTransformer('mainThreadWebview-');
@extHostNamedCustomer(extHostProtocol.MainContext.MainThreadWebviews)
export class MainThreadWebviews extends Disposable implements extHostProtocol.MainThreadWebviewsShape {
@@ -116,18 +93,22 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
'vscode-insider',
]);
public readonly webviewPanelViewType = new WebviewViewTypeTransformer('mainThreadWebview-');
private readonly _proxy: extHostProtocol.ExtHostWebviewsShape;
private readonly _webviews = new Map<string, Webview>();
private readonly _webviewInputs = new WebviewInputStore();
private readonly _revivers = new Map<string, IDisposable>();
private readonly _editorProviders = new Map<string, IDisposable>();
private readonly _webviewFromDiffEditorHandles = new Set<string>();
private readonly serializers: MainThreadWebviewSerializers;
private readonly customEditors: MainThreadCustomEditors;
private readonly webviewViews: MainThreadWebviewsViews;
constructor(
context: extHostProtocol.IExtHostContext,
@IExtensionService extensionService: IExtensionService,
@IWorkingCopyService workingCopyService: IWorkingCopyService,
@IWorkingCopyFileService workingCopyFileService: IWorkingCopyFileService,
@ICustomEditorService private readonly _customEditorService: ICustomEditorService,
@IEditorGroupsService private readonly _editorGroupService: IEditorGroupsService,
@IEditorService private readonly _editorService: IEditorService,
@IOpenerService private readonly _openerService: IOpenerService,
@@ -135,12 +116,15 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
@ITelemetryService private readonly _telemetryService: ITelemetryService,
@IWebviewWorkbenchService private readonly _webviewWorkbenchService: IWebviewWorkbenchService,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@IBackupFileService private readonly _backupService: IBackupFileService,
) {
super();
this._proxy = context.getProxy(extHostProtocol.ExtHostContext.ExtHostWebviews);
this.serializers = this._instantiationService.createInstance(MainThreadWebviewSerializers, this, context);
this.customEditors = this._instantiationService.createInstance(MainThreadCustomEditors, this, context);
this.webviewViews = this._instantiationService.createInstance(MainThreadWebviewsViews, this, context);
this._register(_editorService.onDidActiveEditorChange(() => {
const activeInput = this._editorService.activeEditor;
if (activeInput instanceof DiffEditorInput && activeInput.primary instanceof WebviewInput && activeInput.secondary instanceof WebviewInput) {
@@ -153,38 +137,6 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
this._register(_editorService.onDidVisibleEditorsChange(() => {
this.updateWebviewViewStates(this._editorService.activeEditor);
}));
// This reviver's only job is to activate extensions.
// This should trigger the real reviver to be registered from the extension host side.
this._register(_webviewWorkbenchService.registerResolver({
canResolve: (webview: WebviewInput) => {
if (webview instanceof CustomEditorInput) {
extensionService.activateByEvent(`onCustomEditor:${webview.viewType}`);
return false;
}
const viewType = webviewPanelViewType.toExternal(webview.viewType);
if (typeof viewType === 'string') {
extensionService.activateByEvent(`onWebviewPanel:${viewType}`);
}
return false;
},
resolveWebview: () => { throw new Error('not implemented'); }
}));
workingCopyFileService.registerWorkingCopyProvider((editorResource) => {
const matchedWorkingCopies: IWorkingCopy[] = [];
for (const workingCopy of workingCopyService.workingCopies) {
if (workingCopy instanceof MainThreadCustomEditorModel) {
if (isEqualOrParent(editorResource, workingCopy.editorResource)) {
matchedWorkingCopies.push(workingCopy);
}
}
}
return matchedWorkingCopies;
});
}
dispose() {
@@ -196,6 +148,18 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
this._editorProviders.clear();
}
public get webviewInputs(): Iterable<WebviewInput> { return this._webviewInputs; }
public addWebviewInput(handle: extHostProtocol.WebviewPanelHandle, input: WebviewInput): void {
this._webviewInputs.add(handle, input);
this.addWebview(handle, input.webview);
}
public addWebview(handle: extHostProtocol.WebviewPanelHandle, webview: WebviewOverlay): void {
this._webviews.set(handle, webview);
this.hookupWebviewEventDelegate(handle, webview);
}
public $createWebviewPanel(
extensionData: extHostProtocol.WebviewExtensionDescription,
handle: extHostProtocol.WebviewPanelHandle,
@@ -211,8 +175,8 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
}
const extension = reviveWebviewExtension(extensionData);
const webview = this._webviewWorkbenchService.createWebview(handle, webviewPanelViewType.fromExternal(viewType), title, mainThreadShowOptions, reviveWebviewOptions(options), extension);
this.hookupWebviewEventDelegate(handle, webview);
const webview = this._webviewWorkbenchService.createWebview(handle, this.webviewPanelViewType.fromExternal(viewType), title, mainThreadShowOptions, reviveWebviewOptions(options), extension);
this.hookupWebviewEventDelegate(handle, webview.webview);
this._webviewInputs.add(handle, webview);
@@ -234,19 +198,23 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
webview.setName(value);
}
public $setWebviewViewTitle(handle: extHostProtocol.WebviewPanelHandle, value: string | undefined): void {
this.webviewViews.$setWebviewViewTitle(handle, value);
}
public $setIconPath(handle: extHostProtocol.WebviewPanelHandle, value: { light: UriComponents, dark: UriComponents; } | undefined): void {
const webview = this.getWebviewInput(handle);
webview.iconPath = reviveWebviewIcon(value);
}
public $setHtml(handle: extHostProtocol.WebviewPanelHandle, value: string): void {
const webview = this.getWebviewInput(handle);
webview.webview.html = value;
const webview = this.getWebview(handle);
webview.html = value;
}
public $setOptions(handle: extHostProtocol.WebviewPanelHandle, options: modes.IWebviewOptions): void {
const webview = this.getWebviewInput(handle);
webview.webview.contentOptions = reviveWebviewOptions(options);
const webview = this.getWebview(handle);
webview.contentOptions = reviveWebviewOptions(options);
}
public $reveal(handle: extHostProtocol.WebviewPanelHandle, showOptions: extHostProtocol.WebviewPanelShowOptions): void {
@@ -262,215 +230,59 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
}
public async $postMessage(handle: extHostProtocol.WebviewPanelHandle, message: any): Promise<boolean> {
const webview = this.getWebviewInput(handle);
webview.webview.postMessage(message);
const webview = this.getWebview(handle);
webview.postMessage(message);
return true;
}
public $registerSerializer(viewType: string): void {
if (this._revivers.has(viewType)) {
throw new Error(`Reviver for ${viewType} already registered`);
}
this._revivers.set(viewType, this._webviewWorkbenchService.registerResolver({
canResolve: (webviewInput) => {
return webviewInput.viewType === webviewPanelViewType.fromExternal(viewType);
},
resolveWebview: async (webviewInput): Promise<void> => {
const viewType = webviewPanelViewType.toExternal(webviewInput.viewType);
if (!viewType) {
webviewInput.webview.html = MainThreadWebviews.getWebviewResolvedFailedContent(webviewInput.viewType);
return;
}
const handle = webviewInput.id;
this._webviewInputs.add(handle, webviewInput);
this.hookupWebviewEventDelegate(handle, webviewInput);
let state = undefined;
if (webviewInput.webview.state) {
try {
state = JSON.parse(webviewInput.webview.state);
} catch (e) {
console.error('Could not load webview state', e, webviewInput.webview.state);
}
}
try {
await this._proxy.$deserializeWebviewPanel(handle, viewType, webviewInput.getTitle(), state, editorGroupToViewColumn(this._editorGroupService, webviewInput.group || 0), webviewInput.webview.options);
} catch (error) {
onUnexpectedError(error);
webviewInput.webview.html = MainThreadWebviews.getWebviewResolvedFailedContent(viewType);
}
}
}));
this.serializers.$registerSerializer(viewType);
}
public $unregisterSerializer(viewType: string): void {
const reviver = this._revivers.get(viewType);
if (!reviver) {
throw new Error(`No reviver for ${viewType} registered`);
}
this.serializers.$unregisterSerializer(viewType);
}
reviver.dispose();
this._revivers.delete(viewType);
public $registerWebviewViewProvider(viewType: string, options?: { retainContextWhenHidden?: boolean }): void {
this.webviewViews.$registerWebviewViewProvider(viewType, options);
}
public $unregisterWebviewViewProvider(viewType: string): void {
this.webviewViews.$unregisterWebviewViewProvider(viewType);
}
public $registerTextEditorProvider(extensionData: extHostProtocol.WebviewExtensionDescription, viewType: string, options: modes.IWebviewPanelOptions, capabilities: extHostProtocol.CustomTextEditorCapabilities): void {
this.registerEditorProvider(ModelType.Text, extensionData, viewType, options, capabilities, true);
this.customEditors.registerEditorProvider(CustomEditorModelType.Text, reviveWebviewExtension(extensionData), viewType, options, capabilities, true);
}
public $registerCustomEditorProvider(extensionData: extHostProtocol.WebviewExtensionDescription, viewType: string, options: modes.IWebviewPanelOptions, supportsMultipleEditorsPerDocument: boolean): void {
this.registerEditorProvider(ModelType.Custom, extensionData, viewType, options, {}, supportsMultipleEditorsPerDocument);
}
private registerEditorProvider(
modelType: ModelType,
extensionData: extHostProtocol.WebviewExtensionDescription,
viewType: string,
options: modes.IWebviewPanelOptions,
capabilities: extHostProtocol.CustomTextEditorCapabilities,
supportsMultipleEditorsPerDocument: boolean,
): void {
if (this._editorProviders.has(viewType)) {
throw new Error(`Provider for ${viewType} already registered`);
}
const extension = reviveWebviewExtension(extensionData);
const disposables = new DisposableStore();
disposables.add(this._customEditorService.registerCustomEditorCapabilities(viewType, {
supportsMultipleEditorsPerDocument
}));
disposables.add(this._webviewWorkbenchService.registerResolver({
canResolve: (webviewInput) => {
return webviewInput instanceof CustomEditorInput && webviewInput.viewType === viewType;
},
resolveWebview: async (webviewInput: CustomEditorInput, cancellation: CancellationToken) => {
const handle = webviewInput.id;
const resource = webviewInput.resource;
this._webviewInputs.add(handle, webviewInput);
this.hookupWebviewEventDelegate(handle, webviewInput);
webviewInput.webview.options = options;
webviewInput.webview.extension = extension;
let modelRef: IReference<ICustomEditorModel>;
try {
modelRef = await this.getOrCreateCustomEditorModel(modelType, resource, viewType, { backupId: webviewInput.backupId }, cancellation);
} catch (error) {
onUnexpectedError(error);
webviewInput.webview.html = MainThreadWebviews.getWebviewResolvedFailedContent(viewType);
return;
}
if (cancellation.isCancellationRequested) {
modelRef.dispose();
return;
}
webviewInput.webview.onDispose(() => {
// If the model is still dirty, make sure we have time to save it
if (modelRef.object.isDirty()) {
const sub = modelRef.object.onDidChangeDirty(() => {
if (!modelRef.object.isDirty()) {
sub.dispose();
modelRef.dispose();
}
});
return;
}
modelRef.dispose();
});
if (capabilities.supportsMove) {
webviewInput.onMove(async (newResource: URI) => {
const oldModel = modelRef;
modelRef = await this.getOrCreateCustomEditorModel(modelType, newResource, viewType, {}, CancellationToken.None);
this._proxy.$onMoveCustomEditor(handle, newResource, viewType);
oldModel.dispose();
});
}
try {
await this._proxy.$resolveWebviewEditor(resource, handle, viewType, webviewInput.getTitle(), editorGroupToViewColumn(this._editorGroupService, webviewInput.group || 0), webviewInput.webview.options, cancellation);
} catch (error) {
onUnexpectedError(error);
webviewInput.webview.html = MainThreadWebviews.getWebviewResolvedFailedContent(viewType);
modelRef.dispose();
return;
}
}
}));
this._editorProviders.set(viewType, disposables);
this.customEditors.registerEditorProvider(CustomEditorModelType.Custom, reviveWebviewExtension(extensionData), viewType, options, {}, supportsMultipleEditorsPerDocument);
}
public $unregisterEditorProvider(viewType: string): void {
const provider = this._editorProviders.get(viewType);
if (!provider) {
throw new Error(`No provider for ${viewType} registered`);
}
provider.dispose();
this._editorProviders.delete(viewType);
this._customEditorService.models.disposeAllModelsForView(viewType);
}
private async getOrCreateCustomEditorModel(
modelType: ModelType,
resource: URI,
viewType: string,
options: { backupId?: string },
cancellation: CancellationToken,
): Promise<IReference<ICustomEditorModel>> {
const existingModel = this._customEditorService.models.tryRetain(resource, viewType);
if (existingModel) {
return existingModel;
}
switch (modelType) {
case ModelType.Text:
{
const model = CustomTextEditorModel.create(this._instantiationService, viewType, resource);
return this._customEditorService.models.add(resource, viewType, model);
}
case ModelType.Custom:
{
const model = MainThreadCustomEditorModel.create(this._instantiationService, this._proxy, viewType, resource, options, () => {
return Array.from(this._webviewInputs)
.filter(editor => editor instanceof CustomEditorInput && isEqual(editor.resource, resource)) as CustomEditorInput[];
}, cancellation, this._backupService);
return this._customEditorService.models.add(resource, viewType, model);
}
}
this.customEditors.$unregisterEditorProvider(viewType);
}
public async $onDidEdit(resourceComponents: UriComponents, viewType: string, editId: number, label: string | undefined): Promise<void> {
const model = await this.getCustomEditorModel(resourceComponents, viewType);
model.pushEdit(editId, label);
this.customEditors.$onDidEdit(resourceComponents, viewType, editId, label);
}
public async $onContentChange(resourceComponents: UriComponents, viewType: string): Promise<void> {
const model = await this.getCustomEditorModel(resourceComponents, viewType);
model.changeContent();
this.customEditors.$onContentChange(resourceComponents, viewType);
}
private hookupWebviewEventDelegate(handle: extHostProtocol.WebviewPanelHandle, input: WebviewInput) {
public hookupWebviewEventDelegate(handle: extHostProtocol.WebviewPanelHandle, webview: WebviewOverlay) {
const disposables = new DisposableStore();
disposables.add(input.webview.onDidClickLink((uri) => this.onDidClickLink(handle, uri)));
disposables.add(input.webview.onMessage((message: any) => { this._proxy.$onMessage(handle, message); }));
disposables.add(input.webview.onMissingCsp((extension: ExtensionIdentifier) => this._proxy.$onMissingCsp(handle, extension.value)));
disposables.add(webview.onDidClickLink((uri) => this.onDidClickLink(handle, uri)));
disposables.add(webview.onMessage((message: any) => { this._proxy.$onMessage(handle, message); }));
disposables.add(webview.onMissingCsp((extension: ExtensionIdentifier) => this._proxy.$onMissingCsp(handle, extension.value)));
disposables.add(input.webview.onDispose(() => {
disposables.add(webview.onDispose(() => {
disposables.dispose();
this._proxy.$onDidDisposeWebviewPanel(handle).finally(() => {
this._webviews.delete(handle);
this._webviewInputs.delete(handle);
});
}));
@@ -554,6 +366,14 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
return !!webview.webview.contentOptions.enableCommandUris && link.scheme === Schemas.command;
}
private getWebview(handle: extHostProtocol.WebviewPanelHandle): Webview {
const webview = this._webviews.get(handle);
if (!webview) {
throw new Error(`Unknown webview handle:${handle}`);
}
return webview;
}
private getWebviewInput(handle: extHostProtocol.WebviewPanelHandle): WebviewInput {
const webview = this.tryGetWebviewInput(handle);
if (!webview) {
@@ -566,16 +386,7 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
return this._webviewInputs.getInputForHandle(handle);
}
private async getCustomEditorModel(resourceComponents: UriComponents, viewType: string) {
const resource = URI.revive(resourceComponents);
const model = await this._customEditorService.models.get(resource, viewType);
if (!model || !(model instanceof MainThreadCustomEditorModel)) {
throw new Error('Could not find model for webview editor');
}
return model;
}
private static getWebviewResolvedFailedContent(viewType: string) {
public getWebviewResolvedFailedContent(viewType: string) {
return `<!DOCTYPE html>
<html>
<head>
@@ -607,371 +418,3 @@ function reviveWebviewIcon(
: undefined;
}
namespace HotExitState {
export const enum Type {
Allowed,
NotAllowed,
Pending,
}
export const Allowed = Object.freeze({ type: Type.Allowed } as const);
export const NotAllowed = Object.freeze({ type: Type.NotAllowed } as const);
export class Pending {
readonly type = Type.Pending;
constructor(
public readonly operation: CancelablePromise<string>,
) { }
}
export type State = typeof Allowed | typeof NotAllowed | Pending;
}
class MainThreadCustomEditorModel extends Disposable implements ICustomEditorModel, IWorkingCopy {
private _fromBackup: boolean = false;
private _hotExitState: HotExitState.State = HotExitState.Allowed;
private _backupId: string | undefined;
private _currentEditIndex: number = -1;
private _savePoint: number = -1;
private readonly _edits: Array<number> = [];
private _isDirtyFromContentChange = false;
private _ongoingSave?: CancelablePromise<void>;
public static async create(
instantiationService: IInstantiationService,
proxy: extHostProtocol.ExtHostWebviewsShape,
viewType: string,
resource: URI,
options: { backupId?: string },
getEditors: () => CustomEditorInput[],
cancellation: CancellationToken,
_backupFileService: IBackupFileService,
) {
const { editable } = await proxy.$createCustomDocument(resource, viewType, options.backupId, cancellation);
return instantiationService.createInstance(MainThreadCustomEditorModel, proxy, viewType, resource, !!options.backupId, editable, getEditors);
}
constructor(
private readonly _proxy: extHostProtocol.ExtHostWebviewsShape,
private readonly _viewType: string,
private readonly _editorResource: URI,
fromBackup: boolean,
private readonly _editable: boolean,
private readonly _getEditors: () => CustomEditorInput[],
@IFileDialogService private readonly _fileDialogService: IFileDialogService,
@IFileService private readonly _fileService: IFileService,
@ILabelService private readonly _labelService: ILabelService,
@IUndoRedoService private readonly _undoService: IUndoRedoService,
@IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService,
@IWorkingCopyService workingCopyService: IWorkingCopyService,
) {
super();
this._fromBackup = fromBackup;
if (_editable) {
this._register(workingCopyService.registerWorkingCopy(this));
}
}
get editorResource() {
return this._editorResource;
}
dispose() {
if (this._editable) {
this._undoService.removeElements(this._editorResource);
}
this._proxy.$disposeCustomDocument(this._editorResource, this._viewType);
super.dispose();
}
//#region IWorkingCopy
public get resource() {
// Make sure each custom editor has a unique resource for backup and edits
return MainThreadCustomEditorModel.toWorkingCopyResource(this._viewType, this._editorResource);
}
private static toWorkingCopyResource(viewType: string, resource: URI) {
return URI.from({
scheme: Schemas.vscodeCustomEditor,
authority: viewType,
path: resource.path,
query: JSON.stringify(resource.toJSON()),
});
}
public get name() {
return basename(this._labelService.getUriLabel(this._editorResource));
}
public get capabilities(): WorkingCopyCapabilities {
return 0;
}
public isDirty(): boolean {
if (this._isDirtyFromContentChange) {
return true;
}
if (this._edits.length > 0) {
return this._savePoint !== this._currentEditIndex;
}
return this._fromBackup;
}
private readonly _onDidChangeDirty: Emitter<void> = this._register(new Emitter<void>());
readonly onDidChangeDirty: Event<void> = this._onDidChangeDirty.event;
private readonly _onDidChangeContent: Emitter<void> = this._register(new Emitter<void>());
readonly onDidChangeContent: Event<void> = this._onDidChangeContent.event;
//#endregion
public isReadonly() {
return !this._editable;
}
public get viewType() {
return this._viewType;
}
public get backupId() {
return this._backupId;
}
public pushEdit(editId: number, label: string | undefined) {
if (!this._editable) {
throw new Error('Document is not editable');
}
this.change(() => {
this.spliceEdits(editId);
this._currentEditIndex = this._edits.length - 1;
});
this._undoService.pushElement({
type: UndoRedoElementType.Resource,
resource: this._editorResource,
label: label ?? localize('defaultEditLabel', "Edit"),
undo: () => this.undo(),
redo: () => this.redo(),
});
}
public changeContent() {
this.change(() => {
this._isDirtyFromContentChange = true;
});
}
private async undo(): Promise<void> {
if (!this._editable) {
return;
}
if (this._currentEditIndex < 0) {
// nothing to undo
return;
}
const undoneEdit = this._edits[this._currentEditIndex];
this.change(() => {
--this._currentEditIndex;
});
await this._proxy.$undo(this._editorResource, this.viewType, undoneEdit, this.isDirty());
}
private async redo(): Promise<void> {
if (!this._editable) {
return;
}
if (this._currentEditIndex >= this._edits.length - 1) {
// nothing to redo
return;
}
const redoneEdit = this._edits[this._currentEditIndex + 1];
this.change(() => {
++this._currentEditIndex;
});
await this._proxy.$redo(this._editorResource, this.viewType, redoneEdit, this.isDirty());
}
private spliceEdits(editToInsert?: number) {
const start = this._currentEditIndex + 1;
const toRemove = this._edits.length - this._currentEditIndex;
const removedEdits = typeof editToInsert === 'number'
? this._edits.splice(start, toRemove, editToInsert)
: this._edits.splice(start, toRemove);
if (removedEdits.length) {
this._proxy.$disposeEdits(this._editorResource, this._viewType, removedEdits);
}
}
private change(makeEdit: () => void): void {
const wasDirty = this.isDirty();
makeEdit();
this._onDidChangeContent.fire();
if (this.isDirty() !== wasDirty) {
this._onDidChangeDirty.fire();
}
}
public async revert(_options?: IRevertOptions) {
if (!this._editable) {
return;
}
if (this._currentEditIndex === this._savePoint && !this._isDirtyFromContentChange && !this._fromBackup) {
return;
}
this._proxy.$revert(this._editorResource, this.viewType, CancellationToken.None);
this.change(() => {
this._isDirtyFromContentChange = false;
this._fromBackup = false;
this._currentEditIndex = this._savePoint;
this.spliceEdits();
});
}
public async save(options?: ISaveOptions): Promise<boolean> {
return !!await this.saveCustomEditor(options);
}
public async saveCustomEditor(options?: ISaveOptions): Promise<URI | undefined> {
if (!this._editable) {
return undefined;
}
if (this._editorResource.scheme === Schemas.untitled) {
const targetUri = await this.suggestUntitledSavePath(options);
if (!targetUri) {
return undefined;
}
await this.saveCustomEditorAs(this._editorResource, targetUri, options);
return targetUri;
}
const savePromise = createCancelablePromise(token => this._proxy.$onSave(this._editorResource, this.viewType, token));
this._ongoingSave?.cancel();
this._ongoingSave = savePromise;
this.change(() => {
this._isDirtyFromContentChange = false;
this._savePoint = this._currentEditIndex;
this._fromBackup = false;
});
try {
await savePromise;
} finally {
if (this._ongoingSave === savePromise) {
this._ongoingSave = undefined;
}
}
return this._editorResource;
}
private suggestUntitledSavePath(options: ISaveOptions | undefined): Promise<URI | undefined> {
if (this._editorResource.scheme !== Schemas.untitled) {
throw new Error('Resource is not untitled');
}
const remoteAuthority = this._environmentService.configuration.remoteAuthority;
const localResrouce = toLocalResource(this._editorResource, remoteAuthority);
return this._fileDialogService.pickFileToSave(localResrouce, options?.availableFileSystems);
}
public async saveCustomEditorAs(resource: URI, targetResource: URI, _options?: ISaveOptions): Promise<boolean> {
if (this._editable) {
// TODO: handle cancellation
await createCancelablePromise(token => this._proxy.$onSaveAs(this._editorResource, this.viewType, targetResource, token));
this.change(() => {
this._savePoint = this._currentEditIndex;
});
return true;
} else {
// Since the editor is readonly, just copy the file over
await this._fileService.copy(resource, targetResource, false /* overwrite */);
return true;
}
}
public async backup(): Promise<IWorkingCopyBackup> {
const editors = this._getEditors();
if (!editors.length) {
throw new Error('No editors found for resource, cannot back up');
}
const primaryEditor = editors[0];
const backupData: IWorkingCopyBackup<CustomDocumentBackupData> = {
meta: {
viewType: this.viewType,
editorResource: this._editorResource,
backupId: '',
extension: primaryEditor.extension ? {
id: primaryEditor.extension.id.value,
location: primaryEditor.extension.location,
} : undefined,
webview: {
id: primaryEditor.id,
options: primaryEditor.webview.options,
state: primaryEditor.webview.state,
}
}
};
if (!this._editable) {
return backupData;
}
if (this._hotExitState.type === HotExitState.Type.Pending) {
this._hotExitState.operation.cancel();
}
const pendingState = new HotExitState.Pending(
createCancelablePromise(token =>
this._proxy.$backup(this._editorResource.toJSON(), this.viewType, token)));
this._hotExitState = pendingState;
try {
const backupId = await pendingState.operation;
// Make sure state has not changed in the meantime
if (this._hotExitState === pendingState) {
this._hotExitState = HotExitState.Allowed;
backupData.meta!.backupId = backupId;
this._backupId = backupId;
}
} catch (e) {
if (isPromiseCanceledError(e)) {
// This is expected
throw e;
}
// Otherwise it could be a real error. Make sure state has not changed in the meantime.
if (this._hotExitState === pendingState) {
this._hotExitState = HotExitState.NotAllowed;
}
}
if (this._hotExitState === HotExitState.Allowed) {
return backupData;
}
throw new Error('Cannot back up in this state');
}
}

View File

@@ -0,0 +1,102 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { onUnexpectedError } from 'vs/base/common/errors';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import type { MainThreadWebviews } from 'vs/workbench/api/browser/mainThreadWebview';
import * as extHostProtocol from 'vs/workbench/api/common/extHost.protocol';
import { editorGroupToViewColumn } from 'vs/workbench/api/common/shared/editor';
import { CustomEditorInput } from 'vs/workbench/contrib/customEditor/browser/customEditorInput';
import { WebviewInput } from 'vs/workbench/contrib/webview/browser/webviewEditorInput';
import { IWebviewWorkbenchService } from 'vs/workbench/contrib/webview/browser/webviewWorkbenchService';
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
export class MainThreadWebviewSerializers extends Disposable {
private readonly _proxy: extHostProtocol.ExtHostWebviewSerializerShape;
private readonly _revivers = new Map<string, IDisposable>();
constructor(
private readonly mainThreadWebviews: MainThreadWebviews,
context: extHostProtocol.IExtHostContext,
@IExtensionService extensionService: IExtensionService,
@IEditorGroupsService private readonly _editorGroupService: IEditorGroupsService,
@IWebviewWorkbenchService private readonly _webviewWorkbenchService: IWebviewWorkbenchService,
) {
super();
this._proxy = context.getProxy(extHostProtocol.ExtHostContext.ExtHostWebviewSerializer);
// This reviver's only job is to activate extensions.
// This should trigger the real reviver to be registered from the extension host side.
this._register(_webviewWorkbenchService.registerResolver({
canResolve: (webview: WebviewInput) => {
if (webview instanceof CustomEditorInput) {
extensionService.activateByEvent(`onCustomEditor:${webview.viewType}`);
return false;
}
const viewType = this.mainThreadWebviews.webviewPanelViewType.toExternal(webview.viewType);
if (typeof viewType === 'string') {
extensionService.activateByEvent(`onWebviewPanel:${viewType}`);
}
return false;
},
resolveWebview: () => { throw new Error('not implemented'); }
}));
}
public $registerSerializer(viewType: string): void {
if (this._revivers.has(viewType)) {
throw new Error(`Reviver for ${viewType} already registered`);
}
this._revivers.set(viewType, this._webviewWorkbenchService.registerResolver({
canResolve: (webviewInput) => {
return webviewInput.viewType === this.mainThreadWebviews.webviewPanelViewType.fromExternal(viewType);
},
resolveWebview: async (webviewInput): Promise<void> => {
const viewType = this.mainThreadWebviews.webviewPanelViewType.toExternal(webviewInput.viewType);
if (!viewType) {
webviewInput.webview.html = this.mainThreadWebviews.getWebviewResolvedFailedContent(webviewInput.viewType);
return;
}
const handle = webviewInput.id;
this.mainThreadWebviews.addWebviewInput(handle, webviewInput);
let state = undefined;
if (webviewInput.webview.state) {
try {
state = JSON.parse(webviewInput.webview.state);
} catch (e) {
console.error('Could not load webview state', e, webviewInput.webview.state);
}
}
try {
await this._proxy.$deserializeWebviewPanel(handle, viewType, webviewInput.getTitle(), state, editorGroupToViewColumn(this._editorGroupService, webviewInput.group || 0), webviewInput.webview.options);
} catch (error) {
onUnexpectedError(error);
webviewInput.webview.html = this.mainThreadWebviews.getWebviewResolvedFailedContent(viewType);
}
}
}));
}
public $unregisterSerializer(viewType: string): void {
const reviver = this._revivers.get(viewType);
if (!reviver) {
throw new Error(`No reviver for ${viewType} registered`);
}
reviver.dispose();
this._revivers.delete(viewType);
}
}

View File

@@ -0,0 +1,92 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { CancellationToken } from 'vs/base/common/cancellation';
import { onUnexpectedError } from 'vs/base/common/errors';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import type { MainThreadWebviews } from 'vs/workbench/api/browser/mainThreadWebview';
import * as extHostProtocol from 'vs/workbench/api/common/extHost.protocol';
import { IWebviewViewService, WebviewView } from 'vs/workbench/contrib/webviewView/browser/webviewViewService';
export class MainThreadWebviewsViews extends Disposable {
private readonly _proxyViews: extHostProtocol.ExtHostWebviewViewsShape;
private readonly _webviewViews = new Map<string, WebviewView>();
private readonly _webviewViewProviders = new Map<string, IDisposable>();
constructor(
private readonly mainThreadWebviews: MainThreadWebviews,
context: extHostProtocol.IExtHostContext,
@IWebviewViewService private readonly _webviewViewService: IWebviewViewService,
) {
super();
this._proxyViews = context.getProxy(extHostProtocol.ExtHostContext.ExtHostWebviewViews);
}
public $setWebviewViewTitle(handle: extHostProtocol.WebviewPanelHandle, value: string | undefined): void {
const webviewView = this._webviewViews.get(handle);
if (!webviewView) {
throw new Error('unknown webview view');
}
webviewView.title = value;
}
public $registerWebviewViewProvider(viewType: string, options?: { retainContextWhenHidden?: boolean }): void {
if (this._webviewViewProviders.has(viewType)) {
throw new Error(`View provider for ${viewType} already registered`);
}
this._webviewViewService.register(viewType, {
resolve: async (webviewView: WebviewView, cancellation: CancellationToken) => {
this._webviewViews.set(viewType, webviewView);
const handle = webviewView.webview.id;
this.mainThreadWebviews.addWebview(handle, webviewView.webview);
let state = undefined;
if (webviewView.webview.state) {
try {
state = JSON.parse(webviewView.webview.state);
} catch (e) {
console.error('Could not load webview state', e, webviewView.webview.state);
}
}
if (options) {
webviewView.webview.options = options;
}
webviewView.onDidChangeVisibility(visible => {
this._proxyViews.$onDidChangeWebviewViewVisibility(handle, visible);
});
webviewView.onDispose(() => {
this._proxyViews.$disposeWebviewView(handle);
});
try {
await this._proxyViews.$resolveWebviewView(handle, viewType, state, cancellation);
} catch (error) {
onUnexpectedError(error);
webviewView.webview.html = this.mainThreadWebviews.getWebviewResolvedFailedContent(viewType);
}
}
});
}
public $unregisterWebviewViewProvider(viewType: string): void {
const provider = this._webviewViewProviders.get(viewType);
if (!provider) {
throw new Error(`No view provider for ${viewType} registered`);
}
provider.dispose();
this._webviewViewProviders.delete(viewType);
}
}

View File

@@ -33,6 +33,7 @@ import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneCont
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { Codicon } from 'vs/base/common/codicons';
import { CustomTreeView } from 'vs/workbench/contrib/views/browser/treeView';
import { WebviewViewPane } from 'vs/workbench/contrib/webviewView/browser/webviewViewPane';
export interface IUserFriendlyViewsContainerDescriptor {
id: string;
@@ -77,7 +78,15 @@ export const viewsContainersContribution: IJSONSchema = {
}
};
enum ViewType {
Tree = 'tree',
Webview = 'webview'
}
interface IUserFriendlyViewDescriptor {
type?: ViewType;
id: string;
name: string;
when?: string;
@@ -216,11 +225,18 @@ const viewsContribution: IJSONSchema = {
}
};
export interface ICustomViewDescriptor extends ITreeViewDescriptor {
export interface ICustomTreeViewDescriptor extends ITreeViewDescriptor {
readonly extensionId: ExtensionIdentifier;
readonly originalContainerId: string;
}
export interface ICustomWebviewViewDescriptor extends IViewDescriptor {
readonly extensionId: ExtensionIdentifier;
readonly originalContainerId: string;
}
export type ICustomViewDescriptor = ICustomTreeViewDescriptor | ICustomWebviewViewDescriptor;
type ViewContainerExtensionPointType = { [loc: string]: IUserFriendlyViewsContainerDescriptor[] };
const viewsContainersExtensionPoint: IExtensionPoint<ViewContainerExtensionPointType> = ExtensionsRegistry.registerExtensionPoint<ViewContainerExtensionPointType>({
extensionPoint: 'viewsContainers',
@@ -450,16 +466,24 @@ class ViewsExtensionHandler implements IWorkbenchContribution {
const icon = item.icon ? resources.joinPath(extension.description.extensionLocation, item.icon) : undefined;
const initialVisibility = this.convertInitialVisibility(item.visibility);
const viewDescriptor = <ICustomViewDescriptor>{
const type = this.getViewType(item.type);
if (!type) {
collector.error(localize('unknownViewType', "Unknown view type `{0}`.", item.type));
return null;
}
const viewDescriptor = <ICustomTreeViewDescriptor>{
type: type,
ctorDescriptor: type === ViewType.Tree ? new SyncDescriptor(TreeViewPane) : new SyncDescriptor(WebviewViewPane),
id: item.id,
name: item.name,
ctorDescriptor: new SyncDescriptor(TreeViewPane),
when: ContextKeyExpr.deserialize(item.when),
containerIcon: icon || viewContainer?.icon,
containerTitle: item.contextualTitle || viewContainer?.name,
canToggleVisibility: true,
canMoveView: true,
treeView: this.instantiationService.createInstance(CustomTreeView, item.id, item.name),
treeView: type === ViewType.Tree ? this.instantiationService.createInstance(CustomTreeView, item.id, item.name) : undefined,
collapsed: this.showCollapsed(container) || initialVisibility === InitialVisibility.Collapsed,
order: order,
extensionId: extension.description.identifier,
@@ -469,6 +493,7 @@ class ViewsExtensionHandler implements IWorkbenchContribution {
hideByDefault: initialVisibility === InitialVisibility.Hidden
};
viewIds.add(viewDescriptor.id);
return viewDescriptor;
}));
@@ -481,6 +506,16 @@ class ViewsExtensionHandler implements IWorkbenchContribution {
this.viewsRegistry.registerViews2(allViewDescriptors);
}
private getViewType(type: string | undefined): ViewType | undefined {
if (type === ViewType.Webview) {
return ViewType.Webview;
}
if (!type || type === ViewType.Tree) {
return ViewType.Tree;
}
return undefined;
}
private getDefaultViewContainer(): ViewContainer {
return this.viewContainersRegistry.get(EXPLORER)!;
}

View File

@@ -77,6 +77,9 @@ import { ExtHostTimeline } from 'vs/workbench/api/common/extHostTimeline';
import { ExtHostNotebookConcatDocument } from 'vs/workbench/api/common/extHostNotebookConcatDocument';
import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths';
import { IExtHostConsumerFileSystem } from 'vs/workbench/api/common/extHostFileSystemConsumer';
import { ExtHostWebviewViews } from 'vs/workbench/api/common/extHostWebviewView';
import { ExtHostCustomEditors } from 'vs/workbench/api/common/extHostCustomEditors';
import { ExtHostWebviewSerializer } from 'vs/workbench/api/common/extHostWebviewSerializer';
export interface IExtensionApiFactory {
(extension: IExtensionDescription, registry: ExtensionDescriptionRegistry, configProvider: ExtHostConfigProvider): typeof vscode;
@@ -138,11 +141,14 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
const extHostComment = rpcProtocol.set(ExtHostContext.ExtHostComments, new ExtHostComments(rpcProtocol, extHostCommands, extHostDocuments));
const extHostProgress = rpcProtocol.set(ExtHostContext.ExtHostProgress, new ExtHostProgress(rpcProtocol.getProxy(MainContext.MainThreadProgress)));
const extHostLabelService = rpcProtocol.set(ExtHostContext.ExtHosLabelService, new ExtHostLabelService(rpcProtocol));
const extHostNotebook = rpcProtocol.set(ExtHostContext.ExtHostNotebook, initData.uiKind === UIKind.Web ? new ExtHostNotebookController(rpcProtocol, extHostCommands, extHostDocumentsAndEditors, initData.environment) : new ExtHostNotebookController(rpcProtocol, extHostCommands, extHostDocumentsAndEditors, initData.environment, extensionStoragePaths));
const extHostNotebook = rpcProtocol.set(ExtHostContext.ExtHostNotebook, new ExtHostNotebookController(rpcProtocol, extHostCommands, extHostDocumentsAndEditors, initData.environment, extHostLogService, extensionStoragePaths));
const extHostTheming = rpcProtocol.set(ExtHostContext.ExtHostTheming, new ExtHostTheming(rpcProtocol));
const extHostAuthentication = rpcProtocol.set(ExtHostContext.ExtHostAuthentication, new ExtHostAuthentication(rpcProtocol));
const extHostTimeline = rpcProtocol.set(ExtHostContext.ExtHostTimeline, new ExtHostTimeline(rpcProtocol, extHostCommands));
const extHostWebviews = rpcProtocol.set(ExtHostContext.ExtHostWebviews, new ExtHostWebviews(rpcProtocol, initData.environment, extHostWorkspace, extHostLogService, extHostApiDeprecation, extHostDocuments, extensionStoragePaths));
const extHostWebviews = rpcProtocol.set(ExtHostContext.ExtHostWebviews, new ExtHostWebviews(rpcProtocol, initData.environment, extHostWorkspace, extHostLogService, extHostApiDeprecation));
const extHostWebviewSerializers = rpcProtocol.set(ExtHostContext.ExtHostWebviewSerializer, new ExtHostWebviewSerializer(rpcProtocol, extHostWebviews));
const extHostCustomEditors = rpcProtocol.set(ExtHostContext.ExtHostCustomEditors, new ExtHostCustomEditors(rpcProtocol, extHostDocuments, extensionStoragePaths, extHostWebviews));
const extHostWebviewViews = rpcProtocol.set(ExtHostContext.ExtHostWebviewViews, new ExtHostWebviewViews(rpcProtocol, extHostWebviews));
// Check that no named customers are missing
// {{SQL CARBON EDIT}} filter out the services we don't expose
@@ -582,12 +588,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
}
return extHostTerminalService.createTerminal(nameOrOptions, shellPath, shellArgs);
},
registerTerminalLinkHandler(handler: vscode.TerminalLinkHandler): vscode.Disposable {
checkProposedApiEnabled(extension);
return extHostTerminalService.registerLinkHandler(handler);
},
registerTerminalLinkProvider(handler: vscode.TerminalLinkProvider): vscode.Disposable {
checkProposedApiEnabled(extension);
return extHostTerminalService.registerLinkProvider(handler);
},
registerTreeDataProvider(viewId: string, treeDataProvider: vscode.TreeDataProvider<any>): vscode.Disposable {
@@ -597,10 +598,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
return extHostTreeViews.createTreeView(viewId, options, extension);
},
registerWebviewPanelSerializer: (viewType: string, serializer: vscode.WebviewPanelSerializer) => {
return extHostWebviews.registerWebviewPanelSerializer(extension, viewType, serializer);
return extHostWebviewSerializers.registerWebviewPanelSerializer(extension, viewType, serializer);
},
registerCustomEditorProvider: (viewType: string, provider: vscode.CustomTextEditorProvider | vscode.CustomReadonlyEditorProvider, options: { webviewOptions?: vscode.WebviewPanelOptions, supportsMultipleEditorsPerDocument?: boolean } = {}) => {
return extHostWebviews.registerCustomEditorProvider(extension, viewType, provider, options);
return extHostCustomEditors.registerCustomEditorProvider(extension, viewType, provider, options);
},
registerDecorationProvider(provider: vscode.DecorationProvider) {
checkProposedApiEnabled(extension);
@@ -620,6 +621,14 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
},
onDidChangeActiveColorTheme(listener, thisArg?, disposables?) {
return extHostTheming.onDidChangeActiveColorTheme(listener, thisArg, disposables);
},
registerWebviewViewProvider(viewId: string, provider: vscode.WebviewViewProvider, options?: {
webviewOptions?: {
retainContextWhenHidden?: boolean
}
}) {
checkProposedApiEnabled(extension);
return extHostWebviewViews.registerWebviewViewProvider(extension, viewId, provider, options?.webviewOptions);
}
};
@@ -880,7 +889,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
extHostLogService.warn('Debug API is disabled in Azure Data Studio');
return undefined!;
},
stopDebugging(session: vscode.DebugSession | undefined) {
stopDebugging(session?: vscode.DebugSession) {
extHostLogService.warn('Debug API is disabled in Azure Data Studio');
return undefined!;
},
@@ -975,10 +984,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
checkProposedApiEnabled(extension);
return extHostNotebook.registerNotebookKernelProvider(extension, selector, provider);
},
registerNotebookOutputRenderer: (type: string, outputFilter: vscode.NotebookOutputSelector, renderer: vscode.NotebookOutputRenderer) => {
checkProposedApiEnabled(extension);
return extHostNotebook.registerNotebookOutputRenderer(type, extension, outputFilter, renderer);
},
get activeNotebookEditor(): vscode.NotebookEditor | undefined {
checkProposedApiEnabled(extension);
return extHostNotebook.activeNotebookEditor;
@@ -1047,6 +1052,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
ConfigurationTarget: extHostTypes.ConfigurationTarget,
DebugAdapterExecutable: extHostTypes.DebugAdapterExecutable,
DebugAdapterServer: extHostTypes.DebugAdapterServer,
DebugAdapterNamedPipeServer: extHostTypes.DebugAdapterNamedPipeServer,
DebugAdapterInlineImplementation: extHostTypes.DebugAdapterInlineImplementation,
DecorationRangeBehavior: extHostTypes.DecorationRangeBehavior,
Diagnostic: extHostTypes.Diagnostic,
@@ -1064,8 +1070,8 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
EventEmitter: Emitter,
ExtensionKind: extHostTypes.ExtensionKind,
ExtensionMode: extHostTypes.ExtensionMode,
ExtensionRuntime: extHostTypes.ExtensionRuntime,
CustomExecution: extHostTypes.CustomExecution,
CustomExecution2: extHostTypes.CustomExecution,
FileChangeType: extHostTypes.FileChangeType,
FileSystemError: extHostTypes.FileSystemError,
FileType: files.FileType,

View File

@@ -51,7 +51,7 @@ import { TunnelDto } from 'vs/workbench/api/common/extHostTunnelService';
import { TunnelOptions } from 'vs/platform/remote/common/tunnel';
import { Timeline, TimelineChangeEvent, TimelineOptions, TimelineProviderDescriptor, InternalTimelineOptions } from 'vs/workbench/contrib/timeline/common/timeline';
import { revive } from 'vs/base/common/marshalling';
import { INotebookMimeTypeSelector, IProcessedOutput, INotebookDisplayOrder, NotebookCellMetadata, NotebookDocumentMetadata, ICellEditOperation, NotebookCellsChangedEvent, NotebookDataDto, INotebookKernelInfoDto, IMainCellDto, IOutputRenderRequest, IOutputRenderResponse, INotebookDocumentFilter, INotebookKernelInfoDto2 } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { IProcessedOutput, INotebookDisplayOrder, NotebookCellMetadata, NotebookDocumentMetadata, ICellEditOperation, NotebookCellsChangedEvent, NotebookDataDto, INotebookKernelInfoDto, IMainCellDto, INotebookDocumentFilter, INotebookKernelInfoDto2 } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { CallHierarchyItem } from 'vs/workbench/contrib/callHierarchy/common/callHierarchy';
import { Dto } from 'vs/base/common/types';
import { ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable';
@@ -452,8 +452,6 @@ export interface MainThreadTerminalServiceShape extends IDisposable {
$show(terminalId: number, preserveFocus: boolean): void;
$startSendingDataEvents(): void;
$stopSendingDataEvents(): void;
$startHandlingLinks(): void;
$stopHandlingLinks(): void;
$startLinkProvider(): void;
$stopLinkProvider(): void;
$setEnvironmentVariableCollection(extensionIdentifier: string, persistent: boolean, collection: ISerializableEnvironmentVariableCollection | undefined): void;
@@ -635,6 +633,11 @@ export interface MainThreadWebviewsShape extends IDisposable {
$onDidEdit(resource: UriComponents, viewType: string, editId: number, label: string | undefined): void;
$onContentChange(resource: UriComponents, viewType: string): void;
$registerWebviewViewProvider(viewType: string, options?: { retainContextWhenHidden?: boolean }): void;
$unregisterWebviewViewProvider(viewType: string): void;
$setWebviewViewTitle(handle: WebviewPanelHandle, value: string | undefined): void;
}
export interface WebviewPanelViewStateData {
@@ -650,9 +653,13 @@ export interface ExtHostWebviewsShape {
$onMissingCsp(handle: WebviewPanelHandle, extensionId: string): void;
$onDidChangeWebviewPanelViewStates(newState: WebviewPanelViewStateData): void;
$onDidDisposeWebviewPanel(handle: WebviewPanelHandle): Promise<void>;
}
export interface ExtHostWebviewSerializerShape {
$deserializeWebviewPanel(newWebviewHandle: WebviewPanelHandle, viewType: string, title: string, state: any, position: EditorViewColumn, options: modes.IWebviewOptions & modes.IWebviewPanelOptions): Promise<void>;
}
export interface ExtHostCustomEditorsShape {
$resolveWebviewEditor(resource: UriComponents, newWebviewHandle: WebviewPanelHandle, viewType: string, title: string, position: EditorViewColumn, options: modes.IWebviewOptions & modes.IWebviewPanelOptions, cancellation: CancellationToken): Promise<void>;
$createCustomDocument(resource: UriComponents, viewType: string, backupId: string | undefined, cancellation: CancellationToken): Promise<{ editable: boolean }>;
$disposeCustomDocument(resource: UriComponents, viewType: string): Promise<void>;
@@ -670,6 +677,14 @@ export interface ExtHostWebviewsShape {
$onMoveCustomEditor(handle: WebviewPanelHandle, newResource: UriComponents, viewType: string): Promise<void>;
}
export interface ExtHostWebviewViewsShape {
$resolveWebviewView(webviewHandle: WebviewPanelHandle, viewType: string, state: any, cancellation: CancellationToken): Promise<void>;
$onDidChangeWebviewViewVisibility(webviewHandle: WebviewPanelHandle, visible: boolean): void;
$disposeWebviewView(webviewHandle: WebviewPanelHandle): void;
}
export enum CellKind {
Markdown = 1,
Code = 2
@@ -707,8 +722,6 @@ export interface MainThreadNotebookShape extends IDisposable {
$registerNotebookProvider(extension: NotebookExtensionDescription, viewType: string, supportBackup: boolean, kernelInfoDto: INotebookKernelInfoDto | undefined): Promise<void>;
$onNotebookChange(viewType: string, resource: UriComponents): Promise<void>;
$unregisterNotebookProvider(viewType: string): Promise<void>;
$registerNotebookRenderer(extension: NotebookExtensionDescription, type: string, selectors: INotebookMimeTypeSelector, preloads: UriComponents[]): Promise<void>;
$unregisterNotebookRenderer(id: string): Promise<void>;
$registerNotebookKernel(extension: NotebookExtensionDescription, id: string, label: string, selectors: (string | IRelativePattern)[], preloads: UriComponents[]): Promise<void>;
$registerNotebookKernelProvider(extension: NotebookExtensionDescription, handle: number, documentFilter: INotebookDocumentFilter): Promise<void>;
$unregisterNotebookKernelProvider(handle: number): Promise<void>;
@@ -718,7 +731,7 @@ export interface MainThreadNotebookShape extends IDisposable {
$updateNotebookLanguages(viewType: string, resource: UriComponents, languages: string[]): Promise<void>;
$updateNotebookMetadata(viewType: string, resource: UriComponents, metadata: NotebookDocumentMetadata): Promise<void>;
$updateNotebookCellMetadata(viewType: string, resource: UriComponents, handle: number, metadata: NotebookCellMetadata | undefined): Promise<void>;
$spliceNotebookCellOutputs(viewType: string, resource: UriComponents, cellHandle: number, splices: NotebookCellOutputsSplice[], renderers: number[]): Promise<void>;
$spliceNotebookCellOutputs(viewType: string, resource: UriComponents, cellHandle: number, splices: NotebookCellOutputsSplice[]): Promise<void>;
$postMessage(editorId: string, forRendererId: string | undefined, value: any): Promise<boolean>;
$onDidEdit(resource: UriComponents, viewType: string, editId: number, label: string | undefined): void;
@@ -822,7 +835,8 @@ export type SCMRawResource = [
UriComponents[] /*icons: light, dark*/,
string /*tooltip*/,
boolean /*strike through*/,
boolean /*faded*/
boolean /*faded*/,
string /*context value*/
];
export type SCMRawResourceSplice = [
@@ -841,7 +855,7 @@ export interface MainThreadSCMShape extends IDisposable {
$updateSourceControl(handle: number, features: SCMProviderFeatures): void;
$unregisterSourceControl(handle: number): void;
$registerGroup(sourceControlHandle: number, handle: number, id: string, label: string): void;
$registerGroups(sourceControlHandle: number, groups: [number /*handle*/, string /*id*/, string /*label*/, SCMGroupFeatures][], splices: SCMRawResourceSplices[]): void;
$updateGroup(sourceControlHandle: number, handle: number, features: SCMGroupFeatures): void;
$updateGroupLabel(sourceControlHandle: number, handle: number, label: string): void;
$unregisterGroup(sourceControlHandle: number, handle: number): void;
@@ -884,6 +898,7 @@ export interface MainThreadDebugServiceShape extends IDisposable {
$stopDebugging(sessionId: DebugSessionUUID | undefined): Promise<void>;
$setDebugSessionName(id: DebugSessionUUID, name: string): void;
$customDebugAdapterRequest(id: DebugSessionUUID, command: string, args: any): Promise<any>;
$getDebugProtocolBreakpoint(id: DebugSessionUUID, breakpoinId: string): Promise<DebugProtocol.Breakpoint | undefined>;
$appendDebugConsole(value: string): void;
$startBreakpointEvents(): void;
$registerBreakpoints(breakpoints: Array<ISourceMultiBreakpointDto | IFunctionBreakpointDto | IDataBreakpointDto>): Promise<void>;
@@ -1338,7 +1353,7 @@ export interface ExtHostLanguageFeaturesShape {
$provideHover(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<modes.Hover | undefined>;
$provideEvaluatableExpression(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<modes.EvaluatableExpression | undefined>;
$provideDocumentHighlights(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<modes.DocumentHighlight[] | undefined>;
$provideOnTypeRenameRanges(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<IRange[] | undefined>;
$provideOnTypeRenameRanges(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<{ ranges: IRange[]; wordPattern?: IRegExpDto; } | undefined>;
$provideReferences(handle: number, resource: UriComponents, position: IPosition, context: modes.ReferenceContext, token: CancellationToken): Promise<ILocationDto[] | undefined>;
$provideCodeActions(handle: number, resource: UriComponents, rangeOrSelection: IRange | ISelection, context: modes.CodeActionContext, token: CancellationToken): Promise<ICodeActionListDto | undefined>;
$releaseCodeActions(handle: number, cacheId: number): void;
@@ -1438,7 +1453,6 @@ export interface ExtHostTerminalServiceShape {
$acceptWorkspacePermissionsChanged(isAllowed: boolean): void;
$getAvailableShells(): Promise<IShellDefinitionDto[]>;
$getDefaultShellAndArgs(useAutomationShell: boolean): Promise<IShellAndArgsDto>;
$handleLink(id: number, link: string): Promise<boolean>;
$provideLinks(id: number, line: string): Promise<ITerminalLinkDto[]>;
$activateLink(id: number, linkId: number): void;
$initEnvironmentVariableCollections(collections: [string, ISerializableEnvironmentVariableCollection][]): void;
@@ -1455,7 +1469,7 @@ export interface ExtHostSCMShape {
export interface ExtHostTaskShape {
$provideTasks(handle: number, validTypes: { [key: string]: boolean; }): Thenable<tasks.TaskSetDTO>;
$resolveTask(handle: number, taskDTO: tasks.TaskDTO): Thenable<tasks.TaskDTO | undefined>;
$onDidStartTask(execution: tasks.TaskExecutionDTO, terminalId: number): void;
$onDidStartTask(execution: tasks.TaskExecutionDTO, terminalId: number, resolvedDefinition: tasks.TaskDefinitionDTO): void;
$onDidStartTaskProcess(value: tasks.TaskProcessStartedDTO): void;
$onDidEndTaskProcess(value: tasks.TaskProcessEndedDTO): void;
$OnDidEndTask(execution: tasks.TaskExecutionDTO): void;
@@ -1633,13 +1647,11 @@ export interface ExtHostNotebookShape {
$backup(viewType: string, uri: UriComponents, cancellation: CancellationToken): Promise<string | undefined>;
$acceptDisplayOrder(displayOrder: INotebookDisplayOrder): void;
$acceptNotebookActiveKernelChange(event: { uri: UriComponents, providerHandle: number | undefined, kernelId: string | undefined }): void;
$renderOutputs(uriComponents: UriComponents, id: string, request: IOutputRenderRequest<UriComponents>): Promise<IOutputRenderResponse<UriComponents> | undefined>;
$renderOutputs2<T>(uriComponents: UriComponents, id: string, request: IOutputRenderRequest<T>): Promise<IOutputRenderResponse<T> | undefined>;
$onDidReceiveMessage(editorId: string, rendererId: string | undefined, message: unknown): void;
$acceptModelChanged(uriComponents: UriComponents, event: NotebookCellsChangedEvent): void;
$acceptModelSaved(uriComponents: UriComponents): void;
$acceptEditorPropertiesChanged(uriComponents: UriComponents, data: INotebookEditorPropertiesChangeData): void;
$acceptDocumentAndEditorsDelta(delta: INotebookDocumentsAndEditorsDelta): Promise<void>;
$acceptDocumentAndEditorsDelta(delta: INotebookDocumentsAndEditorsDelta): void;
$undoNotebook(viewType: string, uri: UriComponents, editId: number, isDirty: boolean): Promise<void>;
$redoNotebook(viewType: string, uri: UriComponents, editId: number, isDirty: boolean): Promise<void>;
@@ -1741,6 +1753,9 @@ export const ExtHostContext = {
ExtHostWorkspace: createExtId<ExtHostWorkspaceShape>('ExtHostWorkspace'),
ExtHostWindow: createExtId<ExtHostWindowShape>('ExtHostWindow'),
ExtHostWebviews: createExtId<ExtHostWebviewsShape>('ExtHostWebviews'),
ExtHostWebviewSerializer: createExtId<ExtHostWebviewSerializerShape>('ExtHostWebviewSerializer'),
ExtHostCustomEditors: createExtId<ExtHostCustomEditorsShape>('ExtHostCustomEditors'),
ExtHostWebviewViews: createExtId<ExtHostWebviewViewsShape>('ExtHostWebviewViews'),
ExtHostEditorInsets: createExtId<ExtHostEditorInsetsShape>('ExtHostEditorInsets'),
ExtHostProgress: createMainId<ExtHostProgressShape>('ExtHostProgress'),
ExtHostComments: createMainId<ExtHostCommentsShape>('ExtHostComments'),

View File

@@ -0,0 +1,385 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
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';
import { ExtHostWebviews, toExtensionData } from 'vs/workbench/api/common/extHostWebview';
import { EditorViewColumn } from 'vs/workbench/api/common/shared/editor';
import type * as vscode from 'vscode';
import { Cache } from './cache';
import * as extHostProtocol from './extHost.protocol';
import * as extHostTypes from './extHostTypes';
class CustomDocumentStoreEntry {
private _backupCounter = 1;
constructor(
public readonly document: vscode.CustomDocument,
private readonly _storagePath: URI | undefined,
) { }
private readonly _edits = new Cache<vscode.CustomDocumentEditEvent>('custom documents');
private _backup?: vscode.CustomDocumentBackup;
addEdit(item: vscode.CustomDocumentEditEvent): number {
return this._edits.add([item]);
}
async undo(editId: number, isDirty: boolean): Promise<void> {
await this.getEdit(editId).undo();
if (!isDirty) {
this.disposeBackup();
}
}
async redo(editId: number, isDirty: boolean): Promise<void> {
await this.getEdit(editId).redo();
if (!isDirty) {
this.disposeBackup();
}
}
disposeEdits(editIds: number[]): void {
for (const id of editIds) {
this._edits.delete(id);
}
}
getNewBackupUri(): URI {
if (!this._storagePath) {
throw new Error('Backup requires a valid storage path');
}
const fileName = hashPath(this.document.uri) + (this._backupCounter++);
return joinPath(this._storagePath, fileName);
}
updateBackup(backup: vscode.CustomDocumentBackup): void {
this._backup?.delete();
this._backup = backup;
}
disposeBackup(): void {
this._backup?.delete();
this._backup = undefined;
}
private getEdit(editId: number): vscode.CustomDocumentEditEvent {
const edit = this._edits.get(editId, 0);
if (!edit) {
throw new Error('No edit found');
}
return edit;
}
}
class CustomDocumentStore {
private readonly _documents = new Map<string, CustomDocumentStoreEntry>();
public get(viewType: string, resource: vscode.Uri): CustomDocumentStoreEntry | undefined {
return this._documents.get(this.key(viewType, resource));
}
public add(viewType: string, document: vscode.CustomDocument, storagePath: URI | undefined): CustomDocumentStoreEntry {
const key = this.key(viewType, document.uri);
if (this._documents.has(key)) {
throw new Error(`Document already exists for viewType:${viewType} resource:${document.uri}`);
}
const entry = new CustomDocumentStoreEntry(document, storagePath);
this._documents.set(key, entry);
return entry;
}
public delete(viewType: string, document: vscode.CustomDocument) {
const key = this.key(viewType, document.uri);
this._documents.delete(key);
}
private key(viewType: string, resource: vscode.Uri): string {
return `${viewType}@@@${resource}`;
}
}
const enum WebviewEditorType {
Text,
Custom
}
type ProviderEntry = {
readonly extension: IExtensionDescription;
readonly type: WebviewEditorType.Text;
readonly provider: vscode.CustomTextEditorProvider;
} | {
readonly extension: IExtensionDescription;
readonly type: WebviewEditorType.Custom;
readonly provider: vscode.CustomReadonlyEditorProvider;
};
class EditorProviderStore {
private readonly _providers = new Map<string, ProviderEntry>();
public addTextProvider(viewType: string, extension: IExtensionDescription, provider: vscode.CustomTextEditorProvider): vscode.Disposable {
return this.add(WebviewEditorType.Text, viewType, extension, provider);
}
public addCustomProvider(viewType: string, extension: IExtensionDescription, provider: vscode.CustomReadonlyEditorProvider): vscode.Disposable {
return this.add(WebviewEditorType.Custom, viewType, extension, provider);
}
public get(viewType: string): ProviderEntry | undefined {
return this._providers.get(viewType);
}
private add(type: WebviewEditorType, viewType: string, extension: IExtensionDescription, provider: vscode.CustomTextEditorProvider | vscode.CustomReadonlyEditorProvider): vscode.Disposable {
if (this._providers.has(viewType)) {
throw new Error(`Provider for viewType:${viewType} already registered`);
}
this._providers.set(viewType, { type, extension, provider } as ProviderEntry);
return new extHostTypes.Disposable(() => this._providers.delete(viewType));
}
}
export class ExtHostCustomEditors implements extHostProtocol.ExtHostCustomEditorsShape {
private readonly _proxy: extHostProtocol.MainThreadWebviewsShape;
private readonly _editorProviders = new EditorProviderStore();
private readonly _documents = new CustomDocumentStore();
constructor(
mainContext: extHostProtocol.IMainContext,
private readonly _extHostDocuments: ExtHostDocuments,
private readonly _extensionStoragePaths: IExtensionStoragePaths | undefined,
private readonly _extHostWebview: ExtHostWebviews,
) {
this._proxy = mainContext.getProxy(extHostProtocol.MainContext.MainThreadWebviews);
}
public registerCustomEditorProvider(
extension: IExtensionDescription,
viewType: string,
provider: vscode.CustomReadonlyEditorProvider | vscode.CustomTextEditorProvider,
options: { webviewOptions?: vscode.WebviewPanelOptions, supportsMultipleEditorsPerDocument?: boolean },
): vscode.Disposable {
const disposables = new DisposableStore();
if ('resolveCustomTextEditor' in provider) {
disposables.add(this._editorProviders.addTextProvider(viewType, extension, provider));
this._proxy.$registerTextEditorProvider(toExtensionData(extension), viewType, options.webviewOptions || {}, {
supportsMove: !!provider.moveCustomTextEditor,
});
} else {
disposables.add(this._editorProviders.addCustomProvider(viewType, extension, provider));
if (this.supportEditing(provider)) {
disposables.add(provider.onDidChangeCustomDocument(e => {
const entry = this.getCustomDocumentEntry(viewType, e.document.uri);
if (isEditEvent(e)) {
const editId = entry.addEdit(e);
this._proxy.$onDidEdit(e.document.uri, viewType, editId, e.label);
} else {
this._proxy.$onContentChange((e as vscode.CustomDocumentContentChangeEvent).document.uri, viewType); // {{SQL CARBON EDIT}} strict-null-checks
}
}));
}
this._proxy.$registerCustomEditorProvider(toExtensionData(extension), viewType, options.webviewOptions || {}, !!options.supportsMultipleEditorsPerDocument);
}
return extHostTypes.Disposable.from(
disposables,
new extHostTypes.Disposable(() => {
this._proxy.$unregisterEditorProvider(viewType);
}));
}
async $createCustomDocument(resource: UriComponents, viewType: string, backupId: string | undefined, cancellation: CancellationToken) {
const entry = this._editorProviders.get(viewType);
if (!entry) {
throw new Error(`No provider found for '${viewType}'`);
}
if (entry.type !== WebviewEditorType.Custom) {
throw new Error(`Invalid provide type for '${viewType}'`);
}
const revivedResource = URI.revive(resource);
const document = await entry.provider.openCustomDocument(revivedResource, { backupId }, cancellation);
let storageRoot: URI | undefined;
if (this.supportEditing(entry.provider) && this._extensionStoragePaths) {
storageRoot = this._extensionStoragePaths.workspaceValue(entry.extension) ?? this._extensionStoragePaths.globalValue(entry.extension);
}
this._documents.add(viewType, document, storageRoot);
return { editable: this.supportEditing(entry.provider) };
}
async $disposeCustomDocument(resource: UriComponents, viewType: string): Promise<void> {
const entry = this._editorProviders.get(viewType);
if (!entry) {
throw new Error(`No provider found for '${viewType}'`);
}
if (entry.type !== WebviewEditorType.Custom) {
throw new Error(`Invalid provider type for '${viewType}'`);
}
const revivedResource = URI.revive(resource);
const { document } = this.getCustomDocumentEntry(viewType, revivedResource);
this._documents.delete(viewType, document);
document.dispose();
}
async $resolveWebviewEditor(
resource: UriComponents,
handle: extHostProtocol.WebviewPanelHandle,
viewType: string,
title: string,
position: EditorViewColumn,
options: modes.IWebviewOptions & modes.IWebviewPanelOptions,
cancellation: CancellationToken,
): Promise<void> {
const entry = this._editorProviders.get(viewType);
if (!entry) {
throw new Error(`No provider found for '${viewType}'`);
}
const webview = this._extHostWebview.createNewWebview(handle, options, entry.extension);
const panel = this._extHostWebview.createNewWebviewPanel(handle, viewType, title, position, options, webview);
const revivedResource = URI.revive(resource);
switch (entry.type) {
case WebviewEditorType.Custom:
{
const { document } = this.getCustomDocumentEntry(viewType, revivedResource);
return entry.provider.resolveCustomEditor(document, panel, cancellation);
}
case WebviewEditorType.Text:
{
const document = this._extHostDocuments.getDocument(revivedResource);
return entry.provider.resolveCustomTextEditor(document, panel, cancellation);
}
default:
{
throw new Error('Unknown webview provider type');
}
}
}
$disposeEdits(resourceComponents: UriComponents, viewType: string, editIds: number[]): void {
const document = this.getCustomDocumentEntry(viewType, resourceComponents);
document.disposeEdits(editIds);
}
async $onMoveCustomEditor(handle: string, newResourceComponents: UriComponents, viewType: string): Promise<void> {
const entry = this._editorProviders.get(viewType);
if (!entry) {
throw new Error(`No provider found for '${viewType}'`);
}
if (!(entry.provider as vscode.CustomTextEditorProvider).moveCustomTextEditor) {
throw new Error(`Provider does not implement move '${viewType}'`);
}
const webview = this._extHostWebview.getWebviewPanel(handle);
if (!webview) {
throw new Error(`No webview found`);
}
const resource = URI.revive(newResourceComponents);
const document = this._extHostDocuments.getDocument(resource);
await (entry.provider as vscode.CustomTextEditorProvider).moveCustomTextEditor!(document, webview, CancellationToken.None);
}
async $undo(resourceComponents: UriComponents, viewType: string, editId: number, isDirty: boolean): Promise<void> {
const entry = this.getCustomDocumentEntry(viewType, resourceComponents);
return entry.undo(editId, isDirty);
}
async $redo(resourceComponents: UriComponents, viewType: string, editId: number, isDirty: boolean): Promise<void> {
const entry = this.getCustomDocumentEntry(viewType, resourceComponents);
return entry.redo(editId, isDirty);
}
async $revert(resourceComponents: UriComponents, viewType: string, cancellation: CancellationToken): Promise<void> {
const entry = this.getCustomDocumentEntry(viewType, resourceComponents);
const provider = this.getCustomEditorProvider(viewType);
await provider.revertCustomDocument(entry.document, cancellation);
entry.disposeBackup();
}
async $onSave(resourceComponents: UriComponents, viewType: string, cancellation: CancellationToken): Promise<void> {
const entry = this.getCustomDocumentEntry(viewType, resourceComponents);
const provider = this.getCustomEditorProvider(viewType);
await provider.saveCustomDocument(entry.document, cancellation);
entry.disposeBackup();
}
async $onSaveAs(resourceComponents: UriComponents, viewType: string, targetResource: UriComponents, cancellation: CancellationToken): Promise<void> {
const entry = this.getCustomDocumentEntry(viewType, resourceComponents);
const provider = this.getCustomEditorProvider(viewType);
return provider.saveCustomDocumentAs(entry.document, URI.revive(targetResource), cancellation);
}
async $backup(resourceComponents: UriComponents, viewType: string, cancellation: CancellationToken): Promise<string> {
const entry = this.getCustomDocumentEntry(viewType, resourceComponents);
const provider = this.getCustomEditorProvider(viewType);
const backup = await provider.backupCustomDocument(entry.document, {
destination: entry.getNewBackupUri(),
}, cancellation);
entry.updateBackup(backup);
return backup.id;
}
private getCustomDocumentEntry(viewType: string, resource: UriComponents): CustomDocumentStoreEntry {
const entry = this._documents.get(viewType, URI.revive(resource));
if (!entry) {
throw new Error('No custom document found');
}
return entry;
}
private getCustomEditorProvider(viewType: string): vscode.CustomEditorProvider {
const entry = this._editorProviders.get(viewType);
const provider = entry?.provider;
if (!provider || !this.supportEditing(provider)) {
throw new Error('Custom document is not editable');
}
return provider;
}
private supportEditing(
provider: vscode.CustomTextEditorProvider | vscode.CustomEditorProvider | vscode.CustomReadonlyEditorProvider
): provider is vscode.CustomEditorProvider {
return !!(provider as vscode.CustomEditorProvider).onDidChangeCustomDocument;
}
}
function isEditEvent(e: vscode.CustomDocumentContentChangeEvent | vscode.CustomDocumentEditEvent): e is vscode.CustomDocumentEditEvent {
return typeof (e as vscode.CustomDocumentEditEvent).undo === 'function'
&& typeof (e as vscode.CustomDocumentEditEvent).redo === 'function';
}
function hashPath(resource: URI): string {
const str = resource.scheme === Schemas.file || resource.scheme === Schemas.untitled ? resource.fsPath : resource.toString();
return hash(str) + '';
}

View File

@@ -11,12 +11,12 @@ import {
MainContext, MainThreadDebugServiceShape, ExtHostDebugServiceShape, DebugSessionUUID,
IBreakpointsDeltaDto, ISourceMultiBreakpointDto, IFunctionBreakpointDto, IDebugSessionDto
} from 'vs/workbench/api/common/extHost.protocol';
import { Disposable, Position, Location, SourceBreakpoint, FunctionBreakpoint, DebugAdapterServer, DebugAdapterExecutable, DataBreakpoint, DebugConsoleMode, DebugAdapterInlineImplementation } from 'vs/workbench/api/common/extHostTypes';
import { Disposable, Position, Location, SourceBreakpoint, FunctionBreakpoint, DebugAdapterServer, DebugAdapterExecutable, DataBreakpoint, DebugConsoleMode, DebugAdapterInlineImplementation, DebugAdapterNamedPipeServer } from 'vs/workbench/api/common/extHostTypes';
import { AbstractDebugAdapter } from 'vs/workbench/contrib/debug/common/abstractDebugAdapter';
import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace';
import { IExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensionService';
import { ExtHostDocumentsAndEditors, IExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors';
import { IDebuggerContribution, IConfig, IDebugAdapter, IDebugAdapterServer, IDebugAdapterExecutable, IAdapterDescriptor, IDebugAdapterImpl } from 'vs/workbench/contrib/debug/common/debug';
import { IDebuggerContribution, IConfig, IDebugAdapter, IDebugAdapterServer, IDebugAdapterExecutable, IAdapterDescriptor, IDebugAdapterImpl, IDebugAdapterNamedPipeServer } from 'vs/workbench/contrib/debug/common/debug';
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { AbstractVariableResolverService } from 'vs/workbench/services/configurationResolver/common/variableResolver';
import { ExtHostConfigProvider, IExtHostConfiguration } from '../common/extHostConfiguration';
@@ -51,7 +51,7 @@ export interface IExtHostDebugService extends ExtHostDebugServiceShape {
addBreakpoints(breakpoints0: vscode.Breakpoint[]): Promise<void>;
removeBreakpoints(breakpoints0: vscode.Breakpoint[]): Promise<void>;
startDebugging(folder: vscode.WorkspaceFolder | undefined, nameOrConfig: string | vscode.DebugConfiguration, options: vscode.DebugSessionOptions): Promise<boolean>;
stopDebugging(session: vscode.DebugSession | undefined): Promise<void>;
stopDebugging(session?: vscode.DebugSession): Promise<void>;
registerDebugConfigurationProvider(type: string, provider: vscode.DebugConfigurationProvider, trigger: vscode.DebugConfigurationProviderTriggerKind): vscode.Disposable;
registerDebugAdapterDescriptorFactory(extension: IExtensionDescription, type: string, factory: vscode.DebugAdapterDescriptorFactory): vscode.Disposable;
registerDebugAdapterTrackerFactory(type: string, factory: vscode.DebugAdapterTrackerFactory): vscode.Disposable;
@@ -302,7 +302,7 @@ export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, E
});
}
public stopDebugging(session: vscode.DebugSession | undefined): Promise<void> {
public stopDebugging(session?: vscode.DebugSession): Promise<void> {
return this._debugServiceProxy.$stopDebugging(session ? session.id : undefined);
}
@@ -737,6 +737,11 @@ export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, E
port: x.port,
host: x.host
};
} else if (x instanceof DebugAdapterNamedPipeServer) {
return <IDebugAdapterNamedPipeServer>{
type: 'pipeServer',
path: x.path
};
} else if (x instanceof DebugAdapterInlineImplementation) {
return <IDebugAdapterImpl>{
type: 'implementation',
@@ -957,6 +962,10 @@ export class ExtHostDebugSession implements vscode.DebugSession {
public customRequest(command: string, args: any): Promise<any> {
return this._debugServiceProxy.$customDebugAdapterRequest(this._id, command, args);
}
public getDebugProtocolBreakpoint(breakpoint: vscode.Breakpoint): Promise<vscode.DebugProtocolBreakpoint | undefined> {
return this._debugServiceProxy.$getDebugProtocolBreakpoint(this._id, breakpoint.id);
}
}
export class ExtHostDebugConsole implements vscode.DebugConsole {

View File

@@ -29,19 +29,17 @@ export function getWordDefinitionFor(modeId: string): RegExp | undefined {
export class ExtHostDocumentData extends MirrorTextModel {
private _proxy: MainThreadDocumentsShape;
private _languageId: string;
private _isDirty: boolean;
private _document?: vscode.TextDocument;
private _isDisposed: boolean = false;
constructor(proxy: MainThreadDocumentsShape, uri: URI, lines: string[], eol: string,
languageId: string, versionId: number, isDirty: boolean
constructor(
private readonly _proxy: MainThreadDocumentsShape,
uri: URI, lines: string[], eol: string, versionId: number,
private _languageId: string,
private _isDirty: boolean,
private readonly _notebook?: vscode.NotebookDocument | undefined
) {
super(uri, lines, eol, versionId);
this._proxy = proxy;
this._languageId = languageId;
this._isDirty = isDirty;
}
dispose(): void {
@@ -59,25 +57,26 @@ export class ExtHostDocumentData extends MirrorTextModel {
get document(): vscode.TextDocument {
if (!this._document) {
const data = this;
const that = this;
this._document = {
get uri() { return data._uri; },
get fileName() { return data._uri.fsPath; },
get isUntitled() { return data._uri.scheme === Schemas.untitled; },
get languageId() { return data._languageId; },
get version() { return data._versionId; },
get isClosed() { return data._isDisposed; },
get isDirty() { return data._isDirty; },
save() { return data._save(); },
getText(range?) { return range ? data._getTextInRange(range) : data.getText(); },
get eol() { return data._eol === '\n' ? EndOfLine.LF : EndOfLine.CRLF; },
get lineCount() { return data._lines.length; },
lineAt(lineOrPos: number | vscode.Position) { return data._lineAt(lineOrPos); },
offsetAt(pos) { return data._offsetAt(pos); },
positionAt(offset) { return data._positionAt(offset); },
validateRange(ran) { return data._validateRange(ran); },
validatePosition(pos) { return data._validatePosition(pos); },
getWordRangeAtPosition(pos, regexp?) { return data._getWordRangeAtPosition(pos, regexp); }
get uri() { return that._uri; },
get fileName() { return that._uri.fsPath; },
get isUntitled() { return that._uri.scheme === Schemas.untitled; },
get languageId() { return that._languageId; },
get version() { return that._versionId; },
get isClosed() { return that._isDisposed; },
get isDirty() { return that._isDirty; },
get notebook() { return that._notebook; },
save() { return that._save(); },
getText(range?) { return range ? that._getTextInRange(range) : that.getText(); },
get eol() { return that._eol === '\n' ? EndOfLine.LF : EndOfLine.CRLF; },
get lineCount() { return that._lines.length; },
lineAt(lineOrPos: number | vscode.Position) { return that._lineAt(lineOrPos); },
offsetAt(pos) { return that._offsetAt(pos); },
positionAt(offset) { return that._positionAt(offset); },
validateRange(ran) { return that._validateRange(ran); },
validatePosition(pos) { return that._validatePosition(pos); },
getWordRangeAtPosition(pos, regexp?) { return that._getWordRangeAtPosition(pos, regexp); },
};
}
return Object.freeze(this._document);

View File

@@ -53,7 +53,7 @@ export class ExtHostDocuments implements ExtHostDocumentsShape {
}
public getAllDocumentData(): ExtHostDocumentData[] {
return this._documentsAndEditors.allDocuments();
return [...this._documentsAndEditors.allDocuments()];
}
public getDocumentData(resource: vscode.Uri): ExtHostDocumentData | undefined {
@@ -69,8 +69,8 @@ export class ExtHostDocuments implements ExtHostDocumentsShape {
public getDocument(resource: vscode.Uri): vscode.TextDocument {
const data = this.getDocumentData(resource);
if (!data || !data.document) {
throw new Error('Unable to retrieve document from URI');
if (!data?.document) {
throw new Error(`Unable to retrieve document from URI '${resource}'`);
}
return data.document;
}

View File

@@ -4,17 +4,39 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'vs/base/common/assert';
import * as vscode from 'vscode';
import { Emitter, Event } from 'vs/base/common/event';
import { dispose } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { ExtHostDocumentsAndEditorsShape, IDocumentsAndEditorsDelta, MainContext } from 'vs/workbench/api/common/extHost.protocol';
import { ExtHostDocumentsAndEditorsShape, IDocumentsAndEditorsDelta, IModelAddedData, MainContext } from 'vs/workbench/api/common/extHost.protocol';
import { ExtHostDocumentData } from 'vs/workbench/api/common/extHostDocumentData';
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
import { ExtHostTextEditor } from 'vs/workbench/api/common/extHostTextEditor';
import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters';
import { ILogService } from 'vs/platform/log/common/log';
import { ResourceMap } from 'vs/base/common/map';
import { Schemas } from 'vs/base/common/network';
import { Iterable } from 'vs/base/common/iterator';
class Reference<T> {
private _count = 0;
constructor(readonly value: T) { }
ref() {
this._count++;
}
unref() {
return --this._count === 0;
}
}
export interface IExtHostModelAddedData extends IModelAddedData {
notebook?: vscode.NotebookDocument;
}
export interface IExtHostDocumentsAndEditorsDelta extends IDocumentsAndEditorsDelta {
addedDocuments?: IExtHostModelAddedData[];
}
export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsShape {
@@ -23,7 +45,7 @@ export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsSha
private _activeEditorId: string | null = null;
private readonly _editors = new Map<string, ExtHostTextEditor>();
private readonly _documents = new ResourceMap<ExtHostDocumentData>();
private readonly _documents = new ResourceMap<Reference<ExtHostDocumentData>>();
private readonly _onDidAddDocuments = new Emitter<ExtHostDocumentData[]>();
private readonly _onDidRemoveDocuments = new Emitter<ExtHostDocumentData[]>();
@@ -41,6 +63,10 @@ export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsSha
) { }
$acceptDocumentsAndEditorsDelta(delta: IDocumentsAndEditorsDelta): void {
this.acceptDocumentsAndEditorsDelta(delta);
}
acceptDocumentsAndEditorsDelta(delta: IExtHostDocumentsAndEditorsDelta): void {
const removedDocuments: ExtHostDocumentData[] = [];
const addedDocuments: ExtHostDocumentData[] = [];
@@ -50,9 +76,9 @@ export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsSha
for (const uriComponent of delta.removedDocuments) {
const uri = URI.revive(uriComponent);
const data = this._documents.get(uri);
this._documents.delete(uri);
if (data) {
removedDocuments.push(data);
if (data?.unref()) {
this._documents.delete(uri);
removedDocuments.push(data.value);
}
}
}
@@ -60,19 +86,31 @@ export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsSha
if (delta.addedDocuments) {
for (const data of delta.addedDocuments) {
const resource = URI.revive(data.uri);
assert.ok(!this._documents.has(resource), `document '${resource} already exists!'`);
let ref = this._documents.get(resource);
const documentData = new ExtHostDocumentData(
this._extHostRpc.getProxy(MainContext.MainThreadDocuments),
resource,
data.lines,
data.EOL,
data.modeId,
data.versionId,
data.isDirty
);
this._documents.set(resource, documentData);
addedDocuments.push(documentData);
// double check -> only notebook cell documents should be
// referenced/opened more than once...
if (ref) {
if (resource.scheme !== Schemas.vscodeNotebookCell) {
throw new Error(`document '${resource} already exists!'`);
}
}
if (!ref) {
ref = new Reference(new ExtHostDocumentData(
this._extHostRpc.getProxy(MainContext.MainThreadDocuments),
resource,
data.lines,
data.EOL,
data.versionId,
data.modeId,
data.isDirty,
data.notebook
));
this._documents.set(resource, ref);
addedDocuments.push(ref.value);
}
ref.ref();
}
}
@@ -92,7 +130,7 @@ export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsSha
assert.ok(this._documents.has(resource), `document '${resource}' does not exist`);
assert.ok(!this._editors.has(data.id), `editor '${data.id}' already exists!`);
const documentData = this._documents.get(resource)!;
const documentData = this._documents.get(resource)!.value;
const editor = new ExtHostTextEditor(
data.id,
this._extHostRpc.getProxy(MainContext.MainThreadTextEditors),
@@ -132,11 +170,11 @@ export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsSha
}
getDocument(uri: URI): ExtHostDocumentData | undefined {
return this._documents.get(uri);
return this._documents.get(uri)?.value;
}
allDocuments(): ExtHostDocumentData[] {
return [...this._documents.values()];
allDocuments(): Iterable<ExtHostDocumentData> {
return Iterable.map(this._documents.values(), ref => ref.value);
}
getEditor(id: string): ExtHostTextEditor | undefined {

View File

@@ -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 { ExtensionMemento } from 'vs/workbench/api/common/extHostMemento';
import { RemoteAuthorityResolverError, ExtensionMode } from 'vs/workbench/api/common/extHostTypes';
import { RemoteAuthorityResolverError, 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';
@@ -71,6 +71,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
readonly _serviceBrand: undefined;
abstract readonly extensionRuntime: ExtensionRuntime;
private readonly _onDidChangeRemoteConnectionData = this._register(new Emitter<void>());
public readonly onDidChangeRemoteConnectionData = this._onDidChangeRemoteConnectionData.event;
@@ -395,19 +396,14 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
get storagePath() { return that._storagePath.workspaceValue(extensionDescription)?.fsPath; },
get globalStoragePath() { return that._storagePath.globalValue(extensionDescription).fsPath; },
get logPath() { return path.join(that._initData.logsLocation.fsPath, extensionDescription.identifier.value); },
get logUri() {
checkProposedApiEnabled(extensionDescription);
return URI.joinPath(that._initData.logsLocation, extensionDescription.identifier.value);
},
get storageUri() {
checkProposedApiEnabled(extensionDescription);
return that._storagePath.workspaceValue(extensionDescription);
},
get globalStorageUri() {
checkProposedApiEnabled(extensionDescription);
return that._storagePath.globalValue(extensionDescription);
},
get logUri() { return URI.joinPath(that._initData.logsLocation, extensionDescription.identifier.value); },
get storageUri() { return that._storagePath.workspaceValue(extensionDescription); },
get globalStorageUri() { return that._storagePath.globalValue(extensionDescription); },
get extensionMode() { return extensionMode; },
get extensionRuntime() {
checkProposedApiEnabled(extensionDescription);
return that.extensionRuntime;
},
get environmentVariableCollection() { return that._extHostTerminalService.getEnvironmentVariableCollection(extensionDescription); }
});
});

View File

@@ -324,14 +324,17 @@ class OnTypeRenameAdapter {
private readonly _provider: vscode.OnTypeRenameProvider
) { }
provideOnTypeRenameRanges(resource: URI, position: IPosition, token: CancellationToken): Promise<IRange[] | undefined> {
provideOnTypeRenameRanges(resource: URI, position: IPosition, token: CancellationToken): Promise<{ ranges: IRange[]; wordPattern?: RegExp; } | undefined> {
const doc = this._documents.getDocument(resource);
const pos = typeConvert.Position.to(position);
return asPromise(() => this._provider.provideOnTypeRenameRanges(doc, pos, token)).then(value => {
if (Array.isArray(value)) {
return coalesce(value.map(typeConvert.Range.from));
if (value && Array.isArray(value.ranges)) {
return {
ranges: coalesce(value.ranges.map(typeConvert.Range.from)),
wordPattern: value.wordPattern
};
}
return undefined;
});
@@ -1549,15 +1552,24 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF
// --- on type rename
registerOnTypeRenameProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.OnTypeRenameProvider, stopPattern?: RegExp): vscode.Disposable {
registerOnTypeRenameProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.OnTypeRenameProvider, wordPattern?: RegExp): vscode.Disposable {
const handle = this._addNewAdapter(new OnTypeRenameAdapter(this._documents, provider), extension);
const serializedStopPattern = stopPattern ? ExtHostLanguageFeatures._serializeRegExp(stopPattern) : undefined;
this._proxy.$registerOnTypeRenameProvider(handle, this._transformDocumentSelector(selector), serializedStopPattern);
const serializedWordPattern = wordPattern ? ExtHostLanguageFeatures._serializeRegExp(wordPattern) : undefined;
this._proxy.$registerOnTypeRenameProvider(handle, this._transformDocumentSelector(selector), serializedWordPattern);
return this._createDisposable(handle);
}
$provideOnTypeRenameRanges(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<IRange[] | undefined> {
return this._withAdapter(handle, OnTypeRenameAdapter, adapter => adapter.provideOnTypeRenameRanges(URI.revive(resource), position, token), undefined);
$provideOnTypeRenameRanges(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<{ ranges: IRange[]; wordPattern?: extHostProtocol.IRegExpDto; } | undefined> {
return this._withAdapter(handle, OnTypeRenameAdapter, async adapter => {
const res = await adapter.provideOnTypeRenameRanges(URI.revive(resource), position, token);
if (res) {
return {
ranges: res.ranges,
wordPattern: res.wordPattern ? ExtHostLanguageFeatures._serializeRegExp(res.wordPattern) : undefined
};
}
return undefined;
}, undefined);
}
// --- references

File diff suppressed because it is too large Load Diff

View File

@@ -6,7 +6,7 @@
import * as types from 'vs/workbench/api/common/extHostTypes';
import * as vscode from 'vscode';
import { Event, Emitter } from 'vs/base/common/event';
import { ExtHostNotebookController, ExtHostCell } from 'vs/workbench/api/common/extHostNotebook';
import { ExtHostNotebookController } from 'vs/workbench/api/common/extHostNotebook';
import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments';
import { PrefixSumComputer } from 'vs/editor/common/viewModel/prefixSumComputer';
import { DisposableStore } from 'vs/base/common/lifecycle';
@@ -21,7 +21,7 @@ export class ExtHostNotebookConcatDocument implements vscode.NotebookConcatTextD
private _disposables = new DisposableStore();
private _isClosed = false;
private _cells!: ExtHostCell[];
private _cells!: vscode.NotebookCell[];
private _cellUris!: ResourceMap<number>;
private _cellLengths!: PrefixSumComputer;
private _cellLines!: PrefixSumComputer;
@@ -78,7 +78,7 @@ export class ExtHostNotebookConcatDocument implements vscode.NotebookConcatTextD
for (const cell of this._notebook.cells) {
if (cell.cellKind === CellKind.Code && (!this._selector || score(this._selector, cell.uri, cell.language, true))) {
this._cellUris.set(cell.uri, this._cells.length);
this._cells.push(<ExtHostCell>cell);
this._cells.push(cell);
cellLengths.push(cell.document.getText().length + 1);
cellLineCounts.push(cell.document.lineCount);
}

View File

@@ -6,10 +6,10 @@
import { URI, UriComponents } from 'vs/base/common/uri';
import { Event, Emitter } from 'vs/base/common/event';
import { debounce } from 'vs/base/common/decorators';
import { DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle';
import { DisposableStore, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle';
import { asPromise } from 'vs/base/common/async';
import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands';
import { MainContext, MainThreadSCMShape, SCMRawResource, SCMRawResourceSplice, SCMRawResourceSplices, IMainContext, ExtHostSCMShape, ICommandDto, MainThreadTelemetryShape } from './extHost.protocol';
import { MainContext, MainThreadSCMShape, SCMRawResource, SCMRawResourceSplice, SCMRawResourceSplices, IMainContext, ExtHostSCMShape, ICommandDto, MainThreadTelemetryShape, SCMGroupFeatures } from './extHost.protocol';
import { sortedDiff, equals } from 'vs/base/common/arrays';
import { comparePaths } from 'vs/base/common/comparers';
import type * as vscode from 'vscode';
@@ -228,6 +228,9 @@ class ExtHostSourceControlResourceGroup implements vscode.SourceControlResourceG
private readonly _onDidUpdateResourceStates = new Emitter<void>();
readonly onDidUpdateResourceStates = this._onDidUpdateResourceStates.event;
private _disposed = false;
get disposed(): boolean { return this._disposed; }
private readonly _onDidDispose = new Emitter<void>();
readonly onDidDispose = this._onDidDispose.event;
@@ -246,7 +249,13 @@ class ExtHostSourceControlResourceGroup implements vscode.SourceControlResourceG
get hideWhenEmpty(): boolean | undefined { return this._hideWhenEmpty; }
set hideWhenEmpty(hideWhenEmpty: boolean | undefined) {
this._hideWhenEmpty = hideWhenEmpty;
this._proxy.$updateGroup(this._sourceControlHandle, this.handle, { hideWhenEmpty });
this._proxy.$updateGroup(this._sourceControlHandle, this.handle, this.features);
}
get features(): SCMGroupFeatures {
return {
hideWhenEmpty: this.hideWhenEmpty
};
}
get resourceStates(): vscode.SourceControlResourceState[] { return [...this._resourceStates]; }
@@ -263,9 +272,7 @@ class ExtHostSourceControlResourceGroup implements vscode.SourceControlResourceG
private _sourceControlHandle: number,
private _id: string,
private _label: string,
) {
this._proxy.$registerGroup(_sourceControlHandle, this.handle, _id, _label);
}
) { }
getResourceState(handle: number): vscode.SourceControlResourceState | undefined {
return this._resourceStatesMap.get(handle);
@@ -311,8 +318,9 @@ class ExtHostSourceControlResourceGroup implements vscode.SourceControlResourceG
const tooltip = (r.decorations && r.decorations.tooltip) || '';
const strikeThrough = r.decorations && !!r.decorations.strikeThrough;
const faded = r.decorations && !!r.decorations.faded;
const contextValue = r.contextValue || '';
const rawResource = [handle, sourceUri, icons, tooltip, strikeThrough, faded] as SCMRawResource;
const rawResource = [handle, sourceUri, icons, tooltip, strikeThrough, faded, contextValue] as SCMRawResource;
return { rawResource, handle };
});
@@ -340,7 +348,7 @@ class ExtHostSourceControlResourceGroup implements vscode.SourceControlResourceG
}
dispose(): void {
this._proxy.$unregisterGroup(this._sourceControlHandle, this.handle);
this._disposed = true;
this._onDidDispose.fire();
}
}
@@ -465,26 +473,51 @@ class ExtHostSourceControl implements vscode.SourceControl {
this._proxy.$registerSourceControl(this.handle, _id, _label, _rootUri);
}
private createdResourceGroups = new Map<ExtHostSourceControlResourceGroup, IDisposable>();
private updatedResourceGroups = new Set<ExtHostSourceControlResourceGroup>();
createResourceGroup(id: string, label: string): ExtHostSourceControlResourceGroup {
const group = new ExtHostSourceControlResourceGroup(this._proxy, this._commands, this.handle, id, label);
const updateListener = group.onDidUpdateResourceStates(() => {
this.updatedResourceGroups.add(group);
this.eventuallyUpdateResourceStates();
});
Event.once(group.onDidDispose)(() => {
this.updatedResourceGroups.delete(group);
updateListener.dispose();
this._groups.delete(group.handle);
});
this._groups.set(group.handle, group);
const disposable = Event.once(group.onDidDispose)(() => this.createdResourceGroups.delete(group));
this.createdResourceGroups.set(group, disposable);
this.eventuallyAddResourceGroups();
return group;
}
@debounce(100)
eventuallyAddResourceGroups(): void {
const groups: [number /*handle*/, string /*id*/, string /*label*/, SCMGroupFeatures][] = [];
const splices: SCMRawResourceSplices[] = [];
for (const [group, disposable] of this.createdResourceGroups) {
disposable.dispose();
const updateListener = group.onDidUpdateResourceStates(() => {
this.updatedResourceGroups.add(group);
this.eventuallyUpdateResourceStates();
});
Event.once(group.onDidDispose)(() => {
this.updatedResourceGroups.delete(group);
updateListener.dispose();
this._groups.delete(group.handle);
this._proxy.$unregisterGroup(this.handle, group.handle);
});
groups.push([group.handle, group.id, group.label, group.features]);
const snapshot = group._takeResourceStateSnapshot();
if (snapshot.length > 0) {
splices.push([group.handle, snapshot]);
}
this._groups.set(group.handle, group);
}
this._proxy.$registerGroups(this.handle, groups, splices);
}
@debounce(100)
eventuallyUpdateResourceStates(): void {
const splices: SCMRawResourceSplices[] = [];

View File

@@ -192,12 +192,16 @@ export namespace CustomExecutionDTO {
export namespace TaskHandleDTO {
export function from(value: types.Task): tasks.TaskHandleDTO {
export function from(value: types.Task, workspaceService?: IExtHostWorkspace): tasks.TaskHandleDTO {
let folder: UriComponents | string;
if (value.scope !== undefined && typeof value.scope !== 'number') {
folder = value.scope.uri;
} else if (value.scope !== undefined && typeof value.scope === 'number') {
folder = USER_TASKS_GROUP_KEY;
if ((value.scope === types.TaskScope.Workspace) && workspaceService && workspaceService.workspaceFile) {
folder = workspaceService.workspaceFile;
} else {
folder = USER_TASKS_GROUP_KEY;
}
}
return {
id: value._id!,
@@ -471,11 +475,7 @@ export abstract class ExtHostTaskBase implements ExtHostTaskShape, IExtHostTask
return this._onDidExecuteTask.event;
}
protected async resolveDefinition(uri: number | UriComponents | undefined, definition: vscode.TaskDefinition | undefined): Promise<vscode.TaskDefinition | undefined> {
return definition;
}
public async $onDidStartTask(execution: tasks.TaskExecutionDTO, terminalId: number): Promise<void> {
public async $onDidStartTask(execution: tasks.TaskExecutionDTO, terminalId: number, resolvedDefinition: tasks.TaskDefinitionDTO): Promise<void> {
const customExecution: types.CustomExecution | undefined = this._providedCustomExecutions2.get(execution.id);
if (customExecution) {
if (this._activeCustomExecutions2.get(execution.id) !== undefined) {
@@ -484,7 +484,7 @@ export abstract class ExtHostTaskBase implements ExtHostTaskShape, IExtHostTask
// Clone the custom execution to keep the original untouched. This is important for multiple runs of the same task.
this._activeCustomExecutions2.set(execution.id, customExecution);
this._terminalService.attachPtyToTerminal(terminalId, await customExecution.callback(await this.resolveDefinition(execution.task?.source.scope, execution.task?.definition)));
this._terminalService.attachPtyToTerminal(terminalId, await customExecution.callback(resolvedDefinition));
}
this._lastStartedTask = execution.id;

View File

@@ -41,7 +41,6 @@ export interface IExtHostTerminalService extends ExtHostTerminalServiceShape {
attachPtyToTerminal(id: number, pty: vscode.Pseudoterminal): void;
getDefaultShell(useAutomationShell: boolean, configProvider: ExtHostConfigProvider): string;
getDefaultShellArgs(useAutomationShell: boolean, configProvider: ExtHostConfigProvider): string[] | string;
registerLinkHandler(handler: vscode.TerminalLinkHandler): vscode.Disposable;
registerLinkProvider(provider: vscode.TerminalLinkProvider): vscode.Disposable;
getEnvironmentVariableCollection(extension: IExtensionDescription, persistent?: boolean): vscode.EnvironmentVariableCollection;
}
@@ -176,6 +175,9 @@ export class ExtHostTerminal extends BaseExtHostTerminal implements vscode.Termi
// Nothing changed
return false;
}
if (cols === 0 || rows === 0) {
return false;
}
this._cols = cols;
this._rows = rows;
return true;
@@ -318,7 +320,6 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ
protected _environmentVariableCollections: Map<string, EnvironmentVariableCollection> = new Map();
private readonly _bufferer: TerminalDataBufferer;
private readonly _linkHandlers: Set<vscode.TerminalLinkHandler> = new Set();
private readonly _linkProviders: Set<vscode.TerminalLinkProvider> = new Set();
private readonly _terminalLinkCache: Map<number, Map<number, ICachedLinkEntry>> = new Map();
private readonly _terminalLinkCancellationSource: Map<number, CancellationTokenSource> = new Map();
@@ -559,19 +560,6 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ
return id;
}
public registerLinkHandler(handler: vscode.TerminalLinkHandler): vscode.Disposable {
this._linkHandlers.add(handler);
if (this._linkHandlers.size === 1 && this._linkProviders.size === 0) {
this._proxy.$startHandlingLinks();
}
return new VSCodeDisposable(() => {
this._linkHandlers.delete(handler);
if (this._linkHandlers.size === 0 && this._linkProviders.size === 0) {
this._proxy.$stopHandlingLinks();
}
});
}
public registerLinkProvider(provider: vscode.TerminalLinkProvider): vscode.Disposable {
this._linkProviders.add(provider);
if (this._linkProviders.size === 1) {
@@ -585,25 +573,6 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ
});
}
public async $handleLink(id: number, link: string): Promise<boolean> {
const terminal = this._getTerminalById(id);
if (!terminal) {
return false;
}
// Call each handler synchronously so multiple handlers aren't triggered at once
const it = this._linkHandlers.values();
let next = it.next();
while (!next.done) {
const handled = await next.value.handleLink(terminal, link);
if (handled) {
return true;
}
next = it.next();
}
return false;
}
public async $provideLinks(terminalId: number, line: string): Promise<ITerminalLinkDto[]> {
const terminal = this._getTerminalById(terminalId);
if (!terminal) {

View File

@@ -131,8 +131,9 @@ export namespace DiagnosticTag {
return types.DiagnosticTag.Unnecessary;
case MarkerTag.Deprecated:
return types.DiagnosticTag.Deprecated;
default:
return undefined;
}
return undefined;
}
}
@@ -210,8 +211,9 @@ export namespace DiagnosticSeverity {
return types.DiagnosticSeverity.Error;
case MarkerSeverity.Hint:
return types.DiagnosticSeverity.Hint;
default:
return types.DiagnosticSeverity.Error;
}
return types.DiagnosticSeverity.Error;
}
}
@@ -1253,9 +1255,9 @@ export namespace LogLevel {
return _MainLogLevel.Critical;
case types.LogLevel.Off:
return _MainLogLevel.Off;
default:
return _MainLogLevel.Info;
}
return _MainLogLevel.Info;
}
export function to(mainLevel: _MainLogLevel): types.LogLevel {
@@ -1274,8 +1276,8 @@ export namespace LogLevel {
return types.LogLevel.Critical;
case _MainLogLevel.Off:
return types.LogLevel.Off;
default:
return types.LogLevel.Info;
}
return types.LogLevel.Info;
}
}

View File

@@ -885,6 +885,12 @@ export class Diagnostic {
tags?: DiagnosticTag[];
constructor(range: Range, message: string, severity: DiagnosticSeverity = DiagnosticSeverity.Error) {
if (!Range.isRange(range)) {
throw new TypeError('range must be set');
}
if (!message) {
throw new TypeError('message must be set');
}
this.range = range;
this.message = message;
this.severity = severity;
@@ -1825,20 +1831,20 @@ export enum TaskScope {
Workspace = 2
}
export class CustomExecution implements vscode.CustomExecution2 {
private _callback: (resolvedDefintion?: vscode.TaskDefinition) => Thenable<vscode.Pseudoterminal>;
constructor(callback: (resolvedDefintion?: vscode.TaskDefinition) => Thenable<vscode.Pseudoterminal>) {
export class CustomExecution implements vscode.CustomExecution {
private _callback: (resolvedDefintion: vscode.TaskDefinition) => Thenable<vscode.Pseudoterminal>;
constructor(callback: (resolvedDefintion: vscode.TaskDefinition) => Thenable<vscode.Pseudoterminal>) {
this._callback = callback;
}
public computeId(): string {
return 'customExecution' + generateUuid();
}
public set callback(value: (resolvedDefintion?: vscode.TaskDefinition) => Thenable<vscode.Pseudoterminal>) {
public set callback(value: (resolvedDefintion: vscode.TaskDefinition) => Thenable<vscode.Pseudoterminal>) {
this._callback = value;
}
public get callback(): ((resolvedDefintion?: vscode.TaskDefinition) => Thenable<vscode.Pseudoterminal>) {
public get callback(): ((resolvedDefintion: vscode.TaskDefinition) => Thenable<vscode.Pseudoterminal>) {
return this._callback;
}
}
@@ -2288,6 +2294,12 @@ export class DebugAdapterServer implements vscode.DebugAdapterServer {
}
}
@es5ClassCompat
export class DebugAdapterNamedPipeServer implements vscode.DebugAdapterNamedPipeServer {
constructor(public readonly path: string) {
}
}
@es5ClassCompat
export class DebugAdapterInlineImplementation implements vscode.DebugAdapterInlineImplementation {
readonly implementation: vscode.DebugAdapter;
@@ -2777,6 +2789,17 @@ export enum ExtensionMode {
Test = 3,
}
export enum ExtensionRuntime {
/**
* The extension is running in a NodeJS extension host. Runtime access to NodeJS APIs is available.
*/
Node = 1,
/**
* The extension is running in a Webworker extension host. Runtime access is limited to Webworker APIs.
*/
Webworker = 2
}
//#endregion ExtensionContext
export enum StandardTokenType {

View File

@@ -3,30 +3,19 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { CancellationToken } from 'vs/base/common/cancellation';
import { Emitter, Event } from 'vs/base/common/event';
import { hash } from 'vs/base/common/hash';
import { Disposable, 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 { 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 { ILogService } from 'vs/platform/log/common/log';
import { IExtHostApiDeprecationService } from 'vs/workbench/api/common/extHostApiDeprecationService';
import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments';
import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths';
import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters';
import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace';
import { EditorViewColumn } from 'vs/workbench/api/common/shared/editor';
import { asWebviewUri, WebviewInitData } from 'vs/workbench/api/common/shared/webview';
import type * as vscode from 'vscode';
import { Cache } from './cache';
import * as extHostProtocol from './extHost.protocol';
import * as extHostTypes from './extHostTypes';
type IconPath = URI | { light: URI, dark: URI };
export class ExtHostWebview implements vscode.Webview {
@@ -64,7 +53,13 @@ export class ExtHostWebview implements vscode.Webview {
/* internal */ readonly _onMessageEmitter = new Emitter<any>();
public readonly onDidReceiveMessage: Event<any> = this._onMessageEmitter.event;
readonly #onDidDisposeEmitter = new Emitter<void>();
/* internal */ readonly _onDidDispose: Event<void> = this.#onDidDisposeEmitter.event;
public dispose() {
this.#onDidDisposeEmitter.fire();
this.#onDidDisposeEmitter.dispose();
this._onMessageEmitter.dispose();
}
@@ -119,7 +114,10 @@ export class ExtHostWebview implements vscode.Webview {
}
}
export class ExtHostWebviewEditor extends Disposable implements vscode.WebviewPanel {
type IconPath = URI | { light: URI, dark: URI };
class ExtHostWebviewPanel extends Disposable implements vscode.WebviewPanel {
readonly #handle: extHostProtocol.WebviewPanelHandle;
readonly #proxy: extHostProtocol.MainThreadWebviewsShape;
@@ -167,6 +165,7 @@ export class ExtHostWebviewEditor extends Disposable implements vscode.WebviewPa
this.#isDisposed = true;
this.#onDidDispose.fire();
this.#proxy.$disposeWebview(this.#handle);
this.#webview.dispose();
@@ -267,137 +266,6 @@ export class ExtHostWebviewEditor extends Disposable implements vscode.WebviewPa
}
}
class CustomDocumentStoreEntry {
private _backupCounter = 1;
constructor(
public readonly document: vscode.CustomDocument,
private readonly _storagePath: URI | undefined,
) { }
private readonly _edits = new Cache<vscode.CustomDocumentEditEvent>('custom documents');
private _backup?: vscode.CustomDocumentBackup;
addEdit(item: vscode.CustomDocumentEditEvent): number {
return this._edits.add([item]);
}
async undo(editId: number, isDirty: boolean): Promise<void> {
await this.getEdit(editId).undo();
if (!isDirty) {
this.disposeBackup();
}
}
async redo(editId: number, isDirty: boolean): Promise<void> {
await this.getEdit(editId).redo();
if (!isDirty) {
this.disposeBackup();
}
}
disposeEdits(editIds: number[]): void {
for (const id of editIds) {
this._edits.delete(id);
}
}
getNewBackupUri(): URI {
if (!this._storagePath) {
throw new Error('Backup requires a valid storage path');
}
const fileName = hashPath(this.document.uri) + (this._backupCounter++);
return joinPath(this._storagePath, fileName);
}
updateBackup(backup: vscode.CustomDocumentBackup): void {
this._backup?.delete();
this._backup = backup;
}
disposeBackup(): void {
this._backup?.delete();
this._backup = undefined;
}
private getEdit(editId: number): vscode.CustomDocumentEditEvent {
const edit = this._edits.get(editId, 0);
if (!edit) {
throw new Error('No edit found');
}
return edit;
}
}
class CustomDocumentStore {
private readonly _documents = new Map<string, CustomDocumentStoreEntry>();
public get(viewType: string, resource: vscode.Uri): CustomDocumentStoreEntry | undefined {
return this._documents.get(this.key(viewType, resource));
}
public add(viewType: string, document: vscode.CustomDocument, storagePath: URI | undefined): CustomDocumentStoreEntry {
const key = this.key(viewType, document.uri);
if (this._documents.has(key)) {
throw new Error(`Document already exists for viewType:${viewType} resource:${document.uri}`);
}
const entry = new CustomDocumentStoreEntry(document, storagePath);
this._documents.set(key, entry);
return entry;
}
public delete(viewType: string, document: vscode.CustomDocument) {
const key = this.key(viewType, document.uri);
this._documents.delete(key);
}
private key(viewType: string, resource: vscode.Uri): string {
return `${viewType}@@@${resource}`;
}
}
const enum WebviewEditorType {
Text,
Custom
}
type ProviderEntry = {
readonly extension: IExtensionDescription;
readonly type: WebviewEditorType.Text;
readonly provider: vscode.CustomTextEditorProvider;
} | {
readonly extension: IExtensionDescription;
readonly type: WebviewEditorType.Custom;
readonly provider: vscode.CustomReadonlyEditorProvider;
};
class EditorProviderStore {
private readonly _providers = new Map<string, ProviderEntry>();
public addTextProvider(viewType: string, extension: IExtensionDescription, provider: vscode.CustomTextEditorProvider): vscode.Disposable {
return this.add(WebviewEditorType.Text, viewType, extension, provider);
}
public addCustomProvider(viewType: string, extension: IExtensionDescription, provider: vscode.CustomReadonlyEditorProvider): vscode.Disposable {
return this.add(WebviewEditorType.Custom, viewType, extension, provider);
}
public get(viewType: string): ProviderEntry | undefined {
return this._providers.get(viewType);
}
private add(type: WebviewEditorType, viewType: string, extension: IExtensionDescription, provider: vscode.CustomTextEditorProvider | vscode.CustomReadonlyEditorProvider): vscode.Disposable {
if (this._providers.has(viewType)) {
throw new Error(`Provider for viewType:${viewType} already registered`);
}
this._providers.set(viewType, { type, extension, provider } as ProviderEntry);
return new extHostTypes.Disposable(() => this._providers.delete(viewType));
}
}
export class ExtHostWebviews implements extHostProtocol.ExtHostWebviewsShape {
private static newHandle(): extHostProtocol.WebviewPanelHandle {
@@ -405,16 +273,10 @@ export class ExtHostWebviews implements extHostProtocol.ExtHostWebviewsShape {
}
private readonly _proxy: extHostProtocol.MainThreadWebviewsShape;
private readonly _webviewPanels = new Map<extHostProtocol.WebviewPanelHandle, ExtHostWebviewEditor>();
private readonly _serializers = new Map<string, {
readonly serializer: vscode.WebviewPanelSerializer;
readonly extension: IExtensionDescription;
}>();
private readonly _webviews = new Map<extHostProtocol.WebviewPanelHandle, ExtHostWebview>();
private readonly _webviewPanels = new Map<extHostProtocol.WebviewPanelHandle, ExtHostWebviewPanel>();
private readonly _editorProviders = new EditorProviderStore();
private readonly _documents = new CustomDocumentStore();
constructor(
mainContext: extHostProtocol.IMainContext,
@@ -422,8 +284,6 @@ export class ExtHostWebviews implements extHostProtocol.ExtHostWebviewsShape {
private readonly workspace: IExtHostWorkspace | undefined,
private readonly _logService: ILogService,
private readonly _deprecationService: IExtHostApiDeprecationService,
private readonly _extHostDocuments: ExtHostDocuments,
private readonly _extensionStoragePaths?: IExtensionStoragePaths,
) {
this._proxy = mainContext.getProxy(extHostProtocol.MainContext.MainThreadWebviews);
}
@@ -444,74 +304,19 @@ export class ExtHostWebviews implements extHostProtocol.ExtHostWebviewsShape {
const handle = ExtHostWebviews.newHandle();
this._proxy.$createWebviewPanel(toExtensionData(extension), handle, viewType, title, webviewShowOptions, convertWebviewOptions(extension, this.workspace, options));
const webview = new ExtHostWebview(handle, this._proxy, options, this.initData, this.workspace, extension, this._deprecationService);
const panel = new ExtHostWebviewEditor(handle, this._proxy, viewType, title, viewColumn, options, webview);
this._webviewPanels.set(handle, panel);
const webview = this.createNewWebview(handle, options, extension);
const panel = this.createNewWebviewPanel(handle, viewType, title, viewColumn, options, webview);
return panel;
}
public registerWebviewPanelSerializer(
extension: IExtensionDescription,
viewType: string,
serializer: vscode.WebviewPanelSerializer
): vscode.Disposable {
if (this._serializers.has(viewType)) {
throw new Error(`Serializer for '${viewType}' already registered`);
}
this._serializers.set(viewType, { serializer, extension });
this._proxy.$registerSerializer(viewType);
return new extHostTypes.Disposable(() => {
this._serializers.delete(viewType);
this._proxy.$unregisterSerializer(viewType);
});
}
public registerCustomEditorProvider(
extension: IExtensionDescription,
viewType: string,
provider: vscode.CustomReadonlyEditorProvider | vscode.CustomTextEditorProvider,
options: { webviewOptions?: vscode.WebviewPanelOptions, supportsMultipleEditorsPerDocument?: boolean },
): vscode.Disposable {
const disposables = new DisposableStore();
if ('resolveCustomTextEditor' in provider) {
disposables.add(this._editorProviders.addTextProvider(viewType, extension, provider));
this._proxy.$registerTextEditorProvider(toExtensionData(extension), viewType, options.webviewOptions || {}, {
supportsMove: !!provider.moveCustomTextEditor,
});
} else {
disposables.add(this._editorProviders.addCustomProvider(viewType, extension, provider));
if (this.supportEditing(provider)) {
disposables.add(provider.onDidChangeCustomDocument(e => {
const entry = this.getCustomDocumentEntry(viewType, e.document.uri);
if (isEditEvent(e)) {
const editId = entry.addEdit(e);
this._proxy.$onDidEdit(e.document.uri, viewType, editId, e.label);
} else {
this._proxy.$onContentChange((<any>e).document.uri, viewType); // {{SQL CARBON EDIT}} strict-null-checks
}
}));
}
this._proxy.$registerCustomEditorProvider(toExtensionData(extension), viewType, options.webviewOptions || {}, !!options.supportsMultipleEditorsPerDocument);
}
return extHostTypes.Disposable.from(
disposables,
new extHostTypes.Disposable(() => {
this._proxy.$unregisterEditorProvider(viewType);
}));
}
public $onMessage(
handle: extHostProtocol.WebviewPanelHandle,
message: any
): void {
const panel = this.getWebviewPanel(handle);
if (panel) {
panel.webview._onMessageEmitter.fire(message);
const webview = this.getWebview(handle);
if (webview) {
webview._onMessageEmitter.fire(message);
}
}
@@ -557,203 +362,37 @@ export class ExtHostWebviews implements extHostProtocol.ExtHostWebviewsShape {
async $onDidDisposeWebviewPanel(handle: extHostProtocol.WebviewPanelHandle): Promise<void> {
const panel = this.getWebviewPanel(handle);
if (panel) {
panel.dispose();
this._webviewPanels.delete(handle);
}
panel?.dispose();
this._webviewPanels.delete(handle);
this._webviews.delete(handle);
}
async $deserializeWebviewPanel(
webviewHandle: extHostProtocol.WebviewPanelHandle,
viewType: string,
title: string,
state: any,
position: EditorViewColumn,
options: modes.IWebviewOptions & modes.IWebviewPanelOptions
): Promise<void> {
const entry = this._serializers.get(viewType);
if (!entry) {
throw new Error(`No serializer found for '${viewType}'`);
}
const { serializer, extension } = entry;
const webview = new ExtHostWebview(webviewHandle, this._proxy, reviveOptions(options), this.initData, this.workspace, extension, this._deprecationService);
const revivedPanel = new ExtHostWebviewEditor(webviewHandle, this._proxy, viewType, title, typeof position === 'number' && position >= 0 ? typeConverters.ViewColumn.to(position) : undefined, options, webview);
this._webviewPanels.set(webviewHandle, revivedPanel);
await serializer.deserializeWebviewPanel(revivedPanel, state);
public createNewWebviewPanel(webviewHandle: string, viewType: string, title: string, position: number, options: modes.IWebviewOptions & modes.IWebviewPanelOptions, webview: ExtHostWebview) {
const panel = new ExtHostWebviewPanel(webviewHandle, this._proxy, viewType, title, typeof position === 'number' && position >= 0 ? typeConverters.ViewColumn.to(position) : undefined, options, webview);
this._webviewPanels.set(webviewHandle, panel);
return panel;
}
async $createCustomDocument(resource: UriComponents, viewType: string, backupId: string | undefined, cancellation: CancellationToken) {
const entry = this._editorProviders.get(viewType);
if (!entry) {
throw new Error(`No provider found for '${viewType}'`);
}
public createNewWebview(handle: string, options: modes.IWebviewOptions & modes.IWebviewPanelOptions, extension: IExtensionDescription): ExtHostWebview {
const webview = new ExtHostWebview(handle, this._proxy, reviveOptions(options), this.initData, this.workspace, extension, this._deprecationService);
this._webviews.set(handle, webview);
if (entry.type !== WebviewEditorType.Custom) {
throw new Error(`Invalid provide type for '${viewType}'`);
}
webview._onDidDispose(() => { this._webviews.delete(handle); });
const revivedResource = URI.revive(resource);
const document = await entry.provider.openCustomDocument(revivedResource, { backupId }, cancellation);
let storageRoot: URI | undefined;
if (this.supportEditing(entry.provider) && this._extensionStoragePaths) {
storageRoot = this._extensionStoragePaths.workspaceValue(entry.extension) ?? this._extensionStoragePaths.globalValue(entry.extension);
}
this._documents.add(viewType, document, storageRoot);
return { editable: this.supportEditing(entry.provider) };
return webview;
}
async $disposeCustomDocument(resource: UriComponents, viewType: string): Promise<void> {
const entry = this._editorProviders.get(viewType);
if (!entry) {
throw new Error(`No provider found for '${viewType}'`);
}
if (entry.type !== WebviewEditorType.Custom) {
throw new Error(`Invalid provider type for '${viewType}'`);
}
const revivedResource = URI.revive(resource);
const { document } = this.getCustomDocumentEntry(viewType, revivedResource);
this._documents.delete(viewType, document);
document.dispose();
private getWebview(handle: extHostProtocol.WebviewPanelHandle): ExtHostWebview | undefined {
return this._webviews.get(handle);
}
async $resolveWebviewEditor(
resource: UriComponents,
handle: extHostProtocol.WebviewPanelHandle,
viewType: string,
title: string,
position: EditorViewColumn,
options: modes.IWebviewOptions & modes.IWebviewPanelOptions,
cancellation: CancellationToken,
): Promise<void> {
const entry = this._editorProviders.get(viewType);
if (!entry) {
throw new Error(`No provider found for '${viewType}'`);
}
const webview = new ExtHostWebview(handle, this._proxy, reviveOptions(options), this.initData, this.workspace, entry.extension, this._deprecationService);
const revivedPanel = new ExtHostWebviewEditor(handle, this._proxy, viewType, title, typeof position === 'number' && position >= 0 ? typeConverters.ViewColumn.to(position) : undefined, options, webview);
this._webviewPanels.set(handle, revivedPanel);
const revivedResource = URI.revive(resource);
switch (entry.type) {
case WebviewEditorType.Custom:
{
const { document } = this.getCustomDocumentEntry(viewType, revivedResource);
return entry.provider.resolveCustomEditor(document, revivedPanel, cancellation);
}
case WebviewEditorType.Text:
{
const document = this._extHostDocuments.getDocument(revivedResource);
return entry.provider.resolveCustomTextEditor(document, revivedPanel, cancellation);
}
default:
{
throw new Error('Unknown webview provider type');
}
}
}
$disposeEdits(resourceComponents: UriComponents, viewType: string, editIds: number[]): void {
const document = this.getCustomDocumentEntry(viewType, resourceComponents);
document.disposeEdits(editIds);
}
async $onMoveCustomEditor(handle: string, newResourceComponents: UriComponents, viewType: string): Promise<void> {
const entry = this._editorProviders.get(viewType);
if (!entry) {
throw new Error(`No provider found for '${viewType}'`);
}
if (!(entry.provider as vscode.CustomTextEditorProvider).moveCustomTextEditor) {
throw new Error(`Provider does not implement move '${viewType}'`);
}
const webview = this.getWebviewPanel(handle);
if (!webview) {
throw new Error(`No webview found`);
}
const resource = URI.revive(newResourceComponents);
const document = this._extHostDocuments.getDocument(resource);
await (entry.provider as vscode.CustomTextEditorProvider).moveCustomTextEditor!(document, webview, CancellationToken.None);
}
async $undo(resourceComponents: UriComponents, viewType: string, editId: number, isDirty: boolean): Promise<void> {
const entry = this.getCustomDocumentEntry(viewType, resourceComponents);
return entry.undo(editId, isDirty);
}
async $redo(resourceComponents: UriComponents, viewType: string, editId: number, isDirty: boolean): Promise<void> {
const entry = this.getCustomDocumentEntry(viewType, resourceComponents);
return entry.redo(editId, isDirty);
}
async $revert(resourceComponents: UriComponents, viewType: string, cancellation: CancellationToken): Promise<void> {
const entry = this.getCustomDocumentEntry(viewType, resourceComponents);
const provider = this.getCustomEditorProvider(viewType);
await provider.revertCustomDocument(entry.document, cancellation);
entry.disposeBackup();
}
async $onSave(resourceComponents: UriComponents, viewType: string, cancellation: CancellationToken): Promise<void> {
const entry = this.getCustomDocumentEntry(viewType, resourceComponents);
const provider = this.getCustomEditorProvider(viewType);
await provider.saveCustomDocument(entry.document, cancellation);
entry.disposeBackup();
}
async $onSaveAs(resourceComponents: UriComponents, viewType: string, targetResource: UriComponents, cancellation: CancellationToken): Promise<void> {
const entry = this.getCustomDocumentEntry(viewType, resourceComponents);
const provider = this.getCustomEditorProvider(viewType);
return provider.saveCustomDocumentAs(entry.document, URI.revive(targetResource), cancellation);
}
async $backup(resourceComponents: UriComponents, viewType: string, cancellation: CancellationToken): Promise<string> {
const entry = this.getCustomDocumentEntry(viewType, resourceComponents);
const provider = this.getCustomEditorProvider(viewType);
const backup = await provider.backupCustomDocument(entry.document, {
destination: entry.getNewBackupUri(),
}, cancellation);
entry.updateBackup(backup);
return backup.id;
}
private getWebviewPanel(handle: extHostProtocol.WebviewPanelHandle): ExtHostWebviewEditor | undefined {
public getWebviewPanel(handle: extHostProtocol.WebviewPanelHandle): ExtHostWebviewPanel | undefined {
return this._webviewPanels.get(handle);
}
private getCustomDocumentEntry(viewType: string, resource: UriComponents): CustomDocumentStoreEntry {
const entry = this._documents.get(viewType, URI.revive(resource));
if (!entry) {
throw new Error('No custom document found');
}
return entry;
}
private getCustomEditorProvider(viewType: string): vscode.CustomEditorProvider {
const entry = this._editorProviders.get(viewType);
const provider = entry?.provider;
if (!provider || !this.supportEditing(provider)) {
throw new Error('Custom document is not editable');
}
return provider;
}
private supportEditing(
provider: vscode.CustomTextEditorProvider | vscode.CustomEditorProvider | vscode.CustomReadonlyEditorProvider
): provider is vscode.CustomEditorProvider {
return !!(provider as vscode.CustomEditorProvider).onDidChangeCustomDocument;
}
}
function toExtensionData(extension: IExtensionDescription): extHostProtocol.WebviewExtensionDescription {
export function toExtensionData(extension: IExtensionDescription): extHostProtocol.WebviewExtensionDescription {
return { id: extension.identifier, location: extension.extensionLocation };
}
@@ -786,13 +425,3 @@ function getDefaultLocalResourceRoots(
extension.extensionLocation,
];
}
function isEditEvent(e: vscode.CustomDocumentContentChangeEvent | vscode.CustomDocumentEditEvent): e is vscode.CustomDocumentEditEvent {
return typeof (e as vscode.CustomDocumentEditEvent).undo === 'function'
&& typeof (e as vscode.CustomDocumentEditEvent).redo === 'function';
}
function hashPath(resource: URI): string {
const str = resource.scheme === Schemas.file || resource.scheme === Schemas.untitled ? resource.fsPath : resource.toString();
return hash(str) + '';
}

View File

@@ -0,0 +1,66 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as modes from 'vs/editor/common/modes';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { ExtHostWebviews } from 'vs/workbench/api/common/extHostWebview';
import { EditorViewColumn } from 'vs/workbench/api/common/shared/editor';
import type * as vscode from 'vscode';
import * as extHostProtocol from './extHost.protocol';
import * as extHostTypes from './extHostTypes';
export class ExtHostWebviewSerializer implements extHostProtocol.ExtHostWebviewSerializerShape {
private readonly _proxy: extHostProtocol.MainThreadWebviewsShape;
private readonly _serializers = new Map<string, {
readonly serializer: vscode.WebviewPanelSerializer;
readonly extension: IExtensionDescription;
}>();
constructor(
mainContext: extHostProtocol.IMainContext,
private readonly _webviewService: ExtHostWebviews,
) {
this._proxy = mainContext.getProxy(extHostProtocol.MainContext.MainThreadWebviews);
}
public registerWebviewPanelSerializer(
extension: IExtensionDescription,
viewType: string,
serializer: vscode.WebviewPanelSerializer
): vscode.Disposable {
if (this._serializers.has(viewType)) {
throw new Error(`Serializer for '${viewType}' already registered`);
}
this._serializers.set(viewType, { serializer, extension });
this._proxy.$registerSerializer(viewType);
return new extHostTypes.Disposable(() => {
this._serializers.delete(viewType);
this._proxy.$unregisterSerializer(viewType);
});
}
async $deserializeWebviewPanel(
webviewHandle: extHostProtocol.WebviewPanelHandle,
viewType: string,
title: string,
state: any,
position: EditorViewColumn,
options: modes.IWebviewOptions & modes.IWebviewPanelOptions
): Promise<void> {
const entry = this._serializers.get(viewType);
if (!entry) {
throw new Error(`No serializer found for '${viewType}'`);
}
const { serializer, extension } = entry;
const webview = this._webviewService.createNewWebview(webviewHandle, options, extension);
const revivedPanel = this._webviewService.createNewWebviewPanel(webviewHandle, viewType, title, position, options, webview);
await serializer.deserializeWebviewPanel(revivedPanel, state);
}
}

View File

@@ -0,0 +1,176 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { CancellationToken } from 'vs/base/common/cancellation';
import { Emitter } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { ExtHostWebview, ExtHostWebviews } from 'vs/workbench/api/common/extHostWebview';
import type * as vscode from 'vscode';
import * as extHostProtocol from './extHost.protocol';
import * as extHostTypes from './extHostTypes';
class ExtHostWebviewView extends Disposable implements vscode.WebviewView {
readonly #handle: extHostProtocol.WebviewPanelHandle;
readonly #proxy: extHostProtocol.MainThreadWebviewsShape;
readonly #viewType: string;
readonly #webview: ExtHostWebview;
#isDisposed = false;
#isVisible: boolean;
#title: string | undefined;
constructor(
handle: extHostProtocol.WebviewPanelHandle,
proxy: extHostProtocol.MainThreadWebviewsShape,
viewType: string,
webview: ExtHostWebview,
isVisible: boolean,
) {
super();
this.#viewType = viewType;
this.#handle = handle;
this.#proxy = proxy;
this.#webview = webview;
this.#isVisible = isVisible;
}
public dispose() {
if (this.#isDisposed) {
return;
}
this.#isDisposed = true;
this.#onDidDispose.fire();
super.dispose();
}
readonly #onDidChangeVisibility = this._register(new Emitter<void>());
public readonly onDidChangeVisibility = this.#onDidChangeVisibility.event;
readonly #onDidDispose = this._register(new Emitter<void>());
public readonly onDidDispose = this.#onDidDispose.event;
public get title(): string | undefined {
this.assertNotDisposed();
return this.#title;
}
public set title(value: string | undefined) {
this.assertNotDisposed();
if (this.#title !== value) {
this.#title = value;
this.#proxy.$setWebviewViewTitle(this.#handle, value);
}
}
public get visible(): boolean { return this.#isVisible; }
public get webview(): vscode.Webview { return this.#webview; }
public get viewType(): string { return this.#viewType; }
/* internal */ _setVisible(visible: boolean) {
if (visible === this.#isVisible) {
return;
}
this.#isVisible = visible;
this.#onDidChangeVisibility.fire();
}
private assertNotDisposed() {
if (this.#isDisposed) {
throw new Error('Webview is disposed');
}
}
}
export class ExtHostWebviewViews implements extHostProtocol.ExtHostWebviewViewsShape {
private readonly _proxy: extHostProtocol.MainThreadWebviewsShape;
private readonly _viewProviders = new Map<string, {
readonly provider: vscode.WebviewViewProvider;
readonly extension: IExtensionDescription;
}>();
private readonly _webviewViews = new Map<extHostProtocol.WebviewPanelHandle, ExtHostWebviewView>();
constructor(
mainContext: extHostProtocol.IMainContext,
private readonly _extHostWebview: ExtHostWebviews,
) {
this._proxy = mainContext.getProxy(extHostProtocol.MainContext.MainThreadWebviews);
}
public registerWebviewViewProvider(
extension: IExtensionDescription,
viewType: string,
provider: vscode.WebviewViewProvider,
webviewOptions?: {
retainContextWhenHidden?: boolean
},
): vscode.Disposable {
if (this._viewProviders.has(viewType)) {
throw new Error(`View provider for '${viewType}' already registered`);
}
this._viewProviders.set(viewType, { provider, extension });
this._proxy.$registerWebviewViewProvider(viewType, webviewOptions);
return new extHostTypes.Disposable(() => {
this._viewProviders.delete(viewType);
this._proxy.$unregisterWebviewViewProvider(viewType);
});
}
async $resolveWebviewView(
webviewHandle: string,
viewType: string,
state: any,
cancellation: CancellationToken,
): Promise<void> {
const entry = this._viewProviders.get(viewType);
if (!entry) {
throw new Error(`No view provider found for '${viewType}'`);
}
const { provider, extension } = entry;
const webview = this._extHostWebview.createNewWebview(webviewHandle, { /* todo */ }, extension);
const revivedView = new ExtHostWebviewView(webviewHandle, this._proxy, viewType, webview, true);
this._webviewViews.set(webviewHandle, revivedView);
await provider.resolveWebviewView(revivedView, { state }, cancellation);
}
async $onDidChangeWebviewViewVisibility(
webviewHandle: string,
visible: boolean
) {
const webviewView = this.getWebviewView(webviewHandle);
webviewView._setVisible(visible);
}
async $disposeWebviewView(webviewHandle: string) {
const webviewView = this.getWebviewView(webviewHandle);
this._webviewViews.delete(webviewHandle);
webviewView.dispose();
}
private getWebviewView(handle: string): ExtHostWebviewView {
const entry = this._webviewViews.get(handle);
if (!entry) {
throw new Error('No webview found');
}
return entry;
}
}

View File

@@ -7,7 +7,7 @@ import * as nls from 'vs/nls';
import type * as vscode from 'vscode';
import * as env from 'vs/base/common/platform';
import { DebugAdapterExecutable } from 'vs/workbench/api/common/extHostTypes';
import { ExecutableDebugAdapter, SocketDebugAdapter } from 'vs/workbench/contrib/debug/node/debugAdapter';
import { ExecutableDebugAdapter, SocketDebugAdapter, NamedPipeDebugAdapter } from 'vs/workbench/contrib/debug/node/debugAdapter';
import { AbstractDebugAdapter } from 'vs/workbench/contrib/debug/common/abstractDebugAdapter';
import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace';
import { IExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensionService';
@@ -49,6 +49,8 @@ export class ExtHostDebugService extends ExtHostDebugServiceBase {
switch (adapter.type) {
case 'server':
return new SocketDebugAdapter(adapter);
case 'pipeServer':
return new NamedPipeDebugAdapter(adapter);
case 'executable':
return new ExecutableDebugAdapter(adapter, session.type);
}

View File

@@ -14,6 +14,7 @@ import { CLIServer } from 'vs/workbench/api/node/extHostCLIServer';
import { URI } from 'vs/base/common/uri';
import { Schemas } from 'vs/base/common/network';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { ExtensionRuntime } from 'vs/workbench/api/common/extHostTypes';
class NodeModuleRequireInterceptor extends RequireInterceptor {
@@ -43,6 +44,8 @@ class NodeModuleRequireInterceptor extends RequireInterceptor {
export class ExtHostExtensionService extends AbstractExtHostExtensionService {
readonly extensionRuntime = ExtensionRuntime.Node;
protected async _beforeAlmostReadyToRunExtensions(): Promise<void> {
// initialize API and register actors
const extensionApiFactory = this._instaService.invokeFunction(createApiFactoryAndRegisterActors);

View File

@@ -5,17 +5,15 @@
// import * as path from 'vs/base/common/path';
import { URI, UriComponents } from 'vs/base/common/uri';
import { UriComponents } from 'vs/base/common/uri';
import { win32 } from 'vs/base/node/processes';
import * as types from 'vs/workbench/api/common/extHostTypes';
import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace';
import type * as vscode from 'vscode';
import * as tasks from '../common/shared/tasks';
import * as Objects from 'vs/base/common/objects';
import { ExtHostVariableResolverService } from 'vs/workbench/api/common/extHostDebugService';
// import { ExtHostVariableResolverService } from 'vs/workbench/api/common/extHostDebugService';
import { IExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors';
import { IExtHostConfiguration } from 'vs/workbench/api/common/extHostConfiguration';
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { IExtHostTerminalService } from 'vs/workbench/api/common/extHostTerminalService';
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
@@ -23,16 +21,15 @@ 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 {
private _variableResolver: ExtHostVariableResolverService | undefined;
// private _variableResolver: ExtHostVariableResolverService | undefined; {{ SQL CARBON EDIT }}
constructor(
@IExtHostRpcService extHostRpc: IExtHostRpcService,
@IExtHostInitDataService initData: IExtHostInitDataService,
@IExtHostWorkspace workspaceService: IExtHostWorkspace,
@IExtHostWorkspace private readonly workspaceService: IExtHostWorkspace,
@IExtHostDocumentsAndEditors editorService: IExtHostDocumentsAndEditors,
@IExtHostConfiguration configurationService: IExtHostConfiguration,
@IExtHostTerminalService extHostTerminalService: IExtHostTerminalService,
@@ -55,7 +52,7 @@ export class ExtHostTask extends ExtHostTaskBase {
// We have a preserved ID. So the task didn't change.
if (tTask._id !== undefined) {
// Always get the task execution first to prevent timing issues when retrieving it later
const handleDto = TaskHandleDTO.from(tTask);
const handleDto = TaskHandleDTO.from(tTask, this.workspaceService);
const executionDTO = await this._proxy.$getTaskExecution(handleDto);
if (executionDTO.task === undefined) {
throw new Error('Task from execution DTO is undefined');
@@ -115,39 +112,13 @@ export class ExtHostTask extends ExtHostTaskBase {
return resolvedTaskDTO;
}
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);
}
return this._variableResolver;
}
protected async resolveDefinition(uri: number | UriComponents | undefined, definition: vscode.TaskDefinition | undefined): Promise<vscode.TaskDefinition | undefined> {
if (!uri || (typeof uri === 'number') || !definition) {
return definition;
}
const workspaceFolder = await this._workspaceProvider.resolveWorkspaceFolder(URI.revive(uri));
const workspaceFolders = await this._workspaceProvider.getWorkspaceFolders2();
if (!workspaceFolders || !workspaceFolder) {
return definition;
}
const resolver = await this.getVariableResolver(workspaceFolders);
const ws: IWorkspaceFolder = {
uri: workspaceFolder.uri,
name: workspaceFolder.name,
index: workspaceFolder.index,
toResource: () => {
throw new Error('Not implemented');
}
};
const resolvedDefinition = Objects.deepClone(definition);
for (const key in resolvedDefinition) {
resolvedDefinition[key] = resolver.resolve(ws, resolvedDefinition[key]);
}
return resolvedDefinition;
}
// 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);
// }
// return this._variableResolver;
// } {{ SQL CARBON EDIT }}
public async $resolveVariables(uriComponents: UriComponents, toResolve: { process?: { name: string; cwd?: string; path?: string }, variables: string[] }): Promise<{ process?: string, variables: { [key: string]: string; } }> {
/*const uri: URI = URI.revive(uriComponents);
@@ -186,7 +157,7 @@ export class ExtHostTask extends ExtHostTaskBase {
paths
);
}
return result;*/
return result;*/ // {{ SQL CARBON EDIT }}
return undefined;
}

View File

@@ -9,6 +9,7 @@ import { AbstractExtHostExtensionService } from 'vs/workbench/api/common/extHost
import { URI } from 'vs/base/common/uri';
import { RequireInterceptor } from 'vs/workbench/api/common/extHostRequireInterceptor';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { ExtensionRuntime } from 'vs/workbench/api/common/extHostTypes';
class WorkerRequireInterceptor extends RequireInterceptor {
@@ -31,6 +32,7 @@ class WorkerRequireInterceptor extends RequireInterceptor {
}
export class ExtHostExtensionService extends AbstractExtHostExtensionService {
readonly extensionRuntime = ExtensionRuntime.Node;
private _fakeModules?: WorkerRequireInterceptor;