UNPKG

@codingame/monaco-vscode-extensions-service-override

Version:

VSCode public API plugged on the monaco editor - extensions service-override

580 lines (577 loc) 27.6 kB
import { __decorate, __param } from 'vscode/external/tslib/tslib.es6.js'; import { multibyteAwareBtoa } from 'vscode/vscode/vs/base/browser/dom'; import { createCancelablePromise } from 'vscode/vscode/vs/base/common/async'; import { CancellationToken } from 'vscode/vscode/vs/base/common/cancellation'; import { onUnexpectedError, isCancellationError } from 'vscode/vscode/vs/base/common/errors'; import { Emitter, Event } from 'vscode/vscode/vs/base/common/event'; import { Disposable, DisposableMap, DisposableStore } from 'vscode/vscode/vs/base/common/lifecycle'; import { Schemas } from 'vscode/vscode/vs/base/common/network'; import { basename } from 'vscode/vscode/vs/base/common/path'; import { isEqualOrParent, isEqual, toLocalResource } from 'vscode/vscode/vs/base/common/resources'; import { URI } from 'vscode/vscode/vs/base/common/uri'; import { generateUuid } from 'vscode/vscode/vs/base/common/uuid'; import { localize } from 'vscode/vscode/vs/nls'; import { IFileDialogService } from 'vscode/vscode/vs/platform/dialogs/common/dialogs.service'; import { FileOperation } from 'vscode/vscode/vs/platform/files/common/files'; import { IFileService } from 'vscode/vscode/vs/platform/files/common/files.service'; import { IInstantiationService } from 'vscode/vscode/vs/platform/instantiation/common/instantiation'; import { ILabelService } from 'vscode/vscode/vs/platform/label/common/label.service'; import { IStorageService } from 'vscode/vscode/vs/platform/storage/common/storage.service'; import { UndoRedoElementType } from 'vscode/vscode/vs/platform/undoRedo/common/undoRedo'; import { IUndoRedoService } from 'vscode/vscode/vs/platform/undoRedo/common/undoRedo.service'; import { reviveWebviewExtension } from './mainThreadWebviews.js'; import { ExtHostContext } from 'vscode/vscode/vs/workbench/api/common/extHost.protocol'; import { CustomEditorInput } from 'vscode/vscode/vs/workbench/contrib/customEditor/browser/customEditorInput'; import { ICustomEditorService } from 'vscode/vscode/vs/workbench/contrib/customEditor/common/customEditor.service'; import { CustomTextEditorModel } from '../../contrib/customEditor/common/customTextEditorModel.js'; import { ExtensionKeyedWebviewOriginStore } from 'vscode/vscode/vs/workbench/contrib/webview/browser/webview'; import { IWebviewWorkbenchService } from 'vscode/vscode/vs/workbench/contrib/webviewPanel/browser/webviewWorkbenchService.service'; import { editorGroupToColumn } from 'vscode/vscode/vs/workbench/services/editor/common/editorGroupColumn'; import { IEditorGroupsService } from 'vscode/vscode/vs/workbench/services/editor/common/editorGroupsService.service'; import { IEditorService } from 'vscode/vscode/vs/workbench/services/editor/common/editorService.service'; import { IWorkbenchEnvironmentService } from 'vscode/vscode/vs/workbench/services/environment/common/environmentService.service'; import { IExtensionService } from 'vscode/vscode/vs/workbench/services/extensions/common/extensions.service'; import { IPathService } from 'vscode/vscode/vs/workbench/services/path/common/pathService.service'; import { ResourceWorkingCopy } from 'vscode/vscode/vs/workbench/services/workingCopy/common/resourceWorkingCopy'; import { NO_TYPE_ID, WorkingCopyCapabilities } from 'vscode/vscode/vs/workbench/services/workingCopy/common/workingCopy'; import { IWorkingCopyFileService } from 'vscode/vscode/vs/workbench/services/workingCopy/common/workingCopyFileService.service'; import { IWorkingCopyService } from 'vscode/vscode/vs/workbench/services/workingCopy/common/workingCopyService.service'; var MainThreadCustomEditorModel_1; var CustomEditorModelType; ( ((function(CustomEditorModelType) { CustomEditorModelType[CustomEditorModelType["Custom"] = 0] = "Custom"; CustomEditorModelType[CustomEditorModelType["Text"] = 1] = "Text"; })(CustomEditorModelType || (CustomEditorModelType = {})))); let MainThreadCustomEditors = class MainThreadCustomEditors extends Disposable { constructor(context, mainThreadWebview, mainThreadWebviewPanels, extensionService, storageService, workingCopyService, workingCopyFileService, _customEditorService, _editorGroupService, _editorService, _instantiationService, _webviewWorkbenchService) { super(); this.mainThreadWebview = mainThreadWebview; this.mainThreadWebviewPanels = mainThreadWebviewPanels; this._customEditorService = _customEditorService; this._editorGroupService = _editorGroupService; this._editorService = _editorService; this._instantiationService = _instantiationService; this._webviewWorkbenchService = _webviewWorkbenchService; this._editorProviders = this._register(( (new DisposableMap()))); this._editorRenameBackups = ( (new Map())); this._webviewOriginStore = ( (new ExtensionKeyedWebviewOriginStore('mainThreadCustomEditors.origins', storageService))); this._proxyCustomEditors = ( (context.getProxy(ExtHostContext.ExtHostCustomEditors))); this._register(workingCopyFileService.registerWorkingCopyProvider((editorResource) => { const matchedWorkingCopies = []; for (const workingCopy of workingCopyService.workingCopies) { if (workingCopy instanceof MainThreadCustomEditorModel) { if (isEqualOrParent(editorResource, workingCopy.editorResource)) { matchedWorkingCopies.push(workingCopy); } } } return matchedWorkingCopies; })); this._register(_webviewWorkbenchService.registerResolver({ canResolve: (webview) => { if (webview instanceof CustomEditorInput) { extensionService.activateByEvent(`onCustomEditor:${webview.viewType}`); } return false; }, resolveWebview: () => { throw ( (new Error('not implemented'))); } })); this._register(workingCopyFileService.onWillRunWorkingCopyFileOperation(async (e) => this.onWillRunWorkingCopyFileOperation(e))); } $registerTextEditorProvider(extensionData, viewType, options, capabilities, serializeBuffersForPostMessage) { this.registerEditorProvider(CustomEditorModelType.Text, reviveWebviewExtension(extensionData), viewType, options, capabilities, true, serializeBuffersForPostMessage); } $registerCustomEditorProvider(extensionData, viewType, options, supportsMultipleEditorsPerDocument, serializeBuffersForPostMessage) { this.registerEditorProvider(CustomEditorModelType.Custom, reviveWebviewExtension(extensionData), viewType, options, {}, supportsMultipleEditorsPerDocument, serializeBuffersForPostMessage); } registerEditorProvider(modelType, extension, viewType, options, capabilities, supportsMultipleEditorsPerDocument, serializeBuffersForPostMessage) { 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, cancellation) => { const handle = generateUuid(); const resource = webviewInput.resource; webviewInput.webview.origin = this._webviewOriginStore.getOrigin(viewType, extension.id); this.mainThreadWebviewPanels.addWebviewInput(handle, webviewInput, { serializeBuffersForPostMessage }); webviewInput.webview.options = options; webviewInput.webview.extension = extension; let backupId = webviewInput.backupId; if (webviewInput.oldResource && !webviewInput.backupId) { const backup = this._editorRenameBackups.get(( (webviewInput.oldResource.toString()))); backupId = backup?.backupId; this._editorRenameBackups.delete(( (webviewInput.oldResource.toString()))); } let modelRef; try { modelRef = await this.getOrCreateCustomEditorModel(modelType, resource, viewType, { backupId }, cancellation); } catch (error) { onUnexpectedError(error); webviewInput.webview.setHtml(this.mainThreadWebview.getWebviewResolvedFailedContent(viewType)); return; } if (cancellation.isCancellationRequested) { modelRef.dispose(); return; } webviewInput.webview.onDidDispose(() => { 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) => { const oldModel = modelRef; modelRef = await this.getOrCreateCustomEditorModel(modelType, newResource, viewType, {}, CancellationToken.None); this._proxyCustomEditors.$onMoveCustomEditor(handle, newResource, viewType); oldModel.dispose(); }); } try { await this._proxyCustomEditors.$resolveCustomEditor(resource, handle, viewType, { title: webviewInput.getTitle(), contentOptions: webviewInput.webview.contentOptions, options: webviewInput.webview.options, active: webviewInput === this._editorService.activeEditor, }, editorGroupToColumn(this._editorGroupService, webviewInput.group || 0), cancellation); } catch (error) { onUnexpectedError(error); webviewInput.webview.setHtml(this.mainThreadWebview.getWebviewResolvedFailedContent(viewType)); modelRef.dispose(); return; } } })); this._editorProviders.set(viewType, disposables); } $unregisterEditorProvider(viewType) { if (!( (this._editorProviders.has(viewType)))) { throw ( (new Error(`No provider for ${viewType} registered`))); } this._editorProviders.deleteAndDispose(viewType); this._customEditorService.models.disposeAllModelsForView(viewType); } async getOrCreateCustomEditorModel(modelType, resource, viewType, options, cancellation) { 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.mainThreadWebviewPanels.webviewInputs) .filter(editor => editor instanceof CustomEditorInput && isEqual(editor.resource, resource)); }, cancellation); return this._customEditorService.models.add(resource, viewType, model); } } } async $onDidEdit(resourceComponents, viewType, editId, label) { const model = await this.getCustomEditorModel(resourceComponents, viewType); model.pushEdit(editId, label); } async $onContentChange(resourceComponents, viewType) { const model = await this.getCustomEditorModel(resourceComponents, viewType); model.changeContent(); } async getCustomEditorModel(resourceComponents, viewType) { 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; } async onWillRunWorkingCopyFileOperation(e) { if (e.operation !== FileOperation.MOVE) { return; } e.waitUntil((async () => { const models = []; for (const file of e.files) { if (file.source) { models.push(...(await this._customEditorService.models.getAllModels(file.source))); } } for (const model of models) { if (model instanceof MainThreadCustomEditorModel && model.isDirty()) { const workingCopy = await model.backup(CancellationToken.None); if (workingCopy.meta) { this._editorRenameBackups.set(( (model.editorResource.toString())), workingCopy.meta); } } } })()); } }; MainThreadCustomEditors = ( (__decorate([ ( (__param(3, IExtensionService))), ( (__param(4, IStorageService))), ( (__param(5, IWorkingCopyService))), ( (__param(6, IWorkingCopyFileService))), ( (__param(7, ICustomEditorService))), ( (__param(8, IEditorGroupsService))), ( (__param(9, IEditorService))), ( (__param(10, IInstantiationService))), ( (__param(11, IWebviewWorkbenchService))) ], MainThreadCustomEditors))); var HotExitState; ( ((function(HotExitState) { let Type; ( ((function(Type) { Type[Type["Allowed"] = 0] = "Allowed"; Type[Type["NotAllowed"] = 1] = "NotAllowed"; Type[Type["Pending"] = 2] = "Pending"; })(Type = HotExitState.Type || (HotExitState.Type = {})))); HotExitState.Allowed = ( (Object.freeze({ type: Type.Allowed }))); HotExitState.NotAllowed = ( (Object.freeze({ type: Type.NotAllowed }))); class Pending { constructor(operation) { this.operation = operation; this.type = Type.Pending; } } HotExitState.Pending = Pending; })(HotExitState || (HotExitState = {})))); let MainThreadCustomEditorModel = MainThreadCustomEditorModel_1 = class MainThreadCustomEditorModel extends ResourceWorkingCopy { static async create(instantiationService, proxy, viewType, resource, options, getEditors, cancellation) { const editors = getEditors(); let untitledDocumentData; if (editors.length !== 0) { untitledDocumentData = editors[0].untitledDocumentData; } const { editable } = await proxy.$createCustomDocument(resource, viewType, options.backupId, untitledDocumentData, cancellation); return instantiationService.createInstance(MainThreadCustomEditorModel_1, proxy, viewType, resource, !!options.backupId, editable, !!untitledDocumentData, getEditors); } constructor(_proxy, _viewType, _editorResource, fromBackup, _editable, startDirty, _getEditors, _fileDialogService, fileService, _labelService, _undoService, _environmentService, workingCopyService, _pathService, extensionService) { super(MainThreadCustomEditorModel_1.toWorkingCopyResource(_viewType, _editorResource), fileService); this._proxy = _proxy; this._viewType = _viewType; this._editorResource = _editorResource; this._editable = _editable; this._getEditors = _getEditors; this._fileDialogService = _fileDialogService; this._labelService = _labelService; this._undoService = _undoService; this._environmentService = _environmentService; this._pathService = _pathService; this._fromBackup = false; this._hotExitState = HotExitState.Allowed; this._currentEditIndex = -1; this._savePoint = -1; this._edits = []; this._isDirtyFromContentChange = false; this.typeId = NO_TYPE_ID; this._onDidChangeDirty = this._register(( (new Emitter()))); this.onDidChangeDirty = this._onDidChangeDirty.event; this._onDidChangeContent = this._register(( (new Emitter()))); this.onDidChangeContent = this._onDidChangeContent.event; this._onDidSave = this._register(( (new Emitter()))); this.onDidSave = this._onDidSave.event; this.onDidChangeReadonly = Event.None; this._fromBackup = fromBackup; if (_editable) { this._register(workingCopyService.registerWorkingCopy(this)); this._register(extensionService.onWillStop(e => { if (!this.isDirty()) { return; } e.veto((async () => { const didSave = await this.save(); if (!didSave) { return true; } return false; })(), ( localize(9831, "Custom editor '{0}' could not be saved.", this.name))); })); } if (startDirty) { this._isDirtyFromContentChange = true; } } get editorResource() { return this._editorResource; } dispose() { if (this._editable) { this._undoService.removeElements(this._editorResource); } this._proxy.$disposeCustomDocument(this._editorResource, this._viewType); super.dispose(); } static toWorkingCopyResource(viewType, resource) { const authority = viewType.replace(/[^a-z0-9\-_]/gi, '-'); const path = `/${multibyteAwareBtoa(( (resource.with({ query: null, fragment: null }).toString(true))))}`; return ( (URI.from({ scheme: Schemas.vscodeCustomEditor, authority: authority, path: path, query: JSON.stringify(resource.toJSON()), })) ); } get name() { return basename(this._labelService.getUriLabel(this._editorResource)); } get capabilities() { return this.isUntitled() ? WorkingCopyCapabilities.Untitled : WorkingCopyCapabilities.None; } isDirty() { if (this._isDirtyFromContentChange) { return true; } if (this._edits.length > 0) { return this._savePoint !== this._currentEditIndex; } return this._fromBackup; } isUntitled() { return this._editorResource.scheme === Schemas.untitled; } isReadonly() { return !this._editable; } get viewType() { return this._viewType; } get backupId() { return this._backupId; } pushEdit(editId, label) { 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(9832, "Edit")), code: 'undoredo.customEditorEdit', undo: () => this.undo(), redo: () => this.redo(), }); } changeContent() { this.change(() => { this._isDirtyFromContentChange = true; }); } async undo() { if (!this._editable) { return; } if (this._currentEditIndex < 0) { return; } const undoneEdit = this._edits[this._currentEditIndex]; this.change(() => { --this._currentEditIndex; }); await this._proxy.$undo(this._editorResource, this.viewType, undoneEdit, this.isDirty()); } async redo() { if (!this._editable) { return; } if (this._currentEditIndex >= this._edits.length - 1) { return; } const redoneEdit = this._edits[this._currentEditIndex + 1]; this.change(() => { ++this._currentEditIndex; }); await this._proxy.$redo(this._editorResource, this.viewType, redoneEdit, this.isDirty()); } spliceEdits(editToInsert) { 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); } } change(makeEdit) { const wasDirty = this.isDirty(); makeEdit(); this._onDidChangeContent.fire(); if (this.isDirty() !== wasDirty) { this._onDidChangeDirty.fire(); } } async revert(options) { if (!this._editable) { return; } if (this._currentEditIndex === this._savePoint && !this._isDirtyFromContentChange && !this._fromBackup) { return; } if (!options?.soft) { this._proxy.$revert(this._editorResource, this.viewType, CancellationToken.None); } this.change(() => { this._isDirtyFromContentChange = false; this._fromBackup = false; this._currentEditIndex = this._savePoint; this.spliceEdits(); }); } async save(options) { const result = !!(await this.saveCustomEditor(options)); if (result) { this._onDidSave.fire({ reason: options?.reason, source: options?.source }); } return result; } async saveCustomEditor(options) { if (!this._editable) { return undefined; } if (this.isUntitled()) { 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; try { await savePromise; if (this._ongoingSave === savePromise) { this.change(() => { this._isDirtyFromContentChange = false; this._savePoint = this._currentEditIndex; this._fromBackup = false; }); } } finally { if (this._ongoingSave === savePromise) { this._ongoingSave = undefined; } } return this._editorResource; } suggestUntitledSavePath(options) { if (!this.isUntitled()) { throw ( (new Error('Resource is not untitled'))); } const remoteAuthority = this._environmentService.remoteAuthority; const localResource = toLocalResource(this._editorResource, remoteAuthority, this._pathService.defaultUriScheme); return this._fileDialogService.pickFileToSave(localResource, options?.availableFileSystems); } async saveCustomEditorAs(resource, targetResource, _options) { if (this._editable) { await createCancelablePromise(token => this._proxy.$onSaveAs(this._editorResource, this.viewType, targetResource, token)); this.change(() => { this._savePoint = this._currentEditIndex; }); return true; } else { await this.fileService.copy(resource, targetResource, false ); return true; } } get canHotExit() { return typeof this._backupId === 'string' && this._hotExitState.type === HotExitState.Type.Allowed; } async backup(token) { const editors = this._getEditors(); if (!editors.length) { throw ( (new Error('No editors found for resource, cannot back up'))); } const primaryEditor = editors[0]; const backupMeta = { viewType: this.viewType, editorResource: this._editorResource, backupId: '', extension: primaryEditor.extension ? { id: primaryEditor.extension.id.value, location: primaryEditor.extension.location, } : undefined, webview: { origin: primaryEditor.webview.origin, options: primaryEditor.webview.options, state: primaryEditor.webview.state, } }; const backupData = { meta: backupMeta }; 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; token.onCancellationRequested(() => { pendingState.operation.cancel(); }); let errorMessage = ''; try { const backupId = await pendingState.operation; if (this._hotExitState === pendingState) { this._hotExitState = HotExitState.Allowed; backupData.meta.backupId = backupId; this._backupId = backupId; } } catch (e) { if (isCancellationError(e)) { throw e; } if (this._hotExitState === pendingState) { this._hotExitState = HotExitState.NotAllowed; } if (e.message) { errorMessage = e.message; } } if (this._hotExitState === HotExitState.Allowed) { return backupData; } throw ( (new Error(`Cannot backup in this state: ${errorMessage}`))); } }; MainThreadCustomEditorModel = MainThreadCustomEditorModel_1 = ( (__decorate([ ( (__param(7, IFileDialogService))), ( (__param(8, IFileService))), ( (__param(9, ILabelService))), ( (__param(10, IUndoRedoService))), ( (__param(11, IWorkbenchEnvironmentService))), ( (__param(12, IWorkingCopyService))), ( (__param(13, IPathService))), ( (__param(14, IExtensionService))) ], MainThreadCustomEditorModel))); export { MainThreadCustomEditors };