mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-22 09:38:38 +01:00
439 lines
12 KiB
TypeScript
439 lines
12 KiB
TypeScript
/*---------------------------------------------------------------------------------------------
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
*--------------------------------------------------------------------------------------------*/
|
|
|
|
import * as vscode from 'vscode';
|
|
import { ExtHostNotebookShape, IMainContext, MainThreadNotebookShape, MainContext, MainThreadDocumentsShape } from 'vs/workbench/api/common/extHost.protocol';
|
|
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
|
import { Disposable as VSCodeDisposable } from './extHostTypes';
|
|
import { URI } from 'vs/base/common/uri';
|
|
import { DisposableStore } from 'vs/base/common/lifecycle';
|
|
import { readonly } from 'vs/base/common/errors';
|
|
import { Emitter, Event } from 'vs/base/common/event';
|
|
import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors';
|
|
import { ICell } from 'vs/editor/common/modes';
|
|
// import { ExtHostDocumentData } from 'vs/workbench/api/common/extHostDocumentData';
|
|
|
|
export class ExtHostCell implements vscode.NotebookCell {
|
|
|
|
private static _handlePool: number = 0;
|
|
readonly handle = ExtHostCell._handlePool++;
|
|
public source: string[];
|
|
private _outputs: any[];
|
|
private _onDidChangeOutputs = new Emitter<void>();
|
|
onDidChangeOutputs: Event<void> = this._onDidChangeOutputs.event;
|
|
private _textDocument: vscode.TextDocument | undefined;
|
|
private _initalVersion: number = -1;
|
|
|
|
constructor(
|
|
private _content: string,
|
|
public cell_type: 'markdown' | 'code',
|
|
public language: string,
|
|
outputs: any[]
|
|
) {
|
|
this.source = this._content.split(/\r|\n|\r\n/g);
|
|
this._outputs = outputs;
|
|
}
|
|
|
|
get outputs() {
|
|
return this._outputs;
|
|
}
|
|
|
|
set outputs(newOutputs: any[]) {
|
|
this._outputs = newOutputs;
|
|
this._onDidChangeOutputs.fire();
|
|
}
|
|
|
|
getContent(): string {
|
|
if (this._textDocument && this._initalVersion !== this._textDocument?.version) {
|
|
return this._textDocument.getText();
|
|
} else {
|
|
return this.source.join('\n');
|
|
}
|
|
}
|
|
|
|
attachTextDocument(document: vscode.TextDocument) {
|
|
this._textDocument = document;
|
|
this._initalVersion = this._textDocument.version;
|
|
}
|
|
|
|
detachTextDocument(document: vscode.TextDocument) {
|
|
if (this._textDocument && this._textDocument.version !== this._initalVersion) {
|
|
this.source = this._textDocument.getText().split(/\r|\n|\r\n/g);
|
|
}
|
|
|
|
this._textDocument = undefined;
|
|
this._initalVersion = -1;
|
|
}
|
|
}
|
|
|
|
export class ExtHostNotebookDocument implements vscode.NotebookDocument {
|
|
private static _handlePool: number = 0;
|
|
readonly handle = ExtHostNotebookDocument._handlePool++;
|
|
|
|
private _cells: ExtHostCell[] = [];
|
|
|
|
private _cellDisposableMapping = new Map<number, DisposableStore>();
|
|
|
|
get cells() {
|
|
return this._cells;
|
|
}
|
|
|
|
set cells(newCells: ExtHostCell[]) {
|
|
this._cells = newCells;
|
|
this._cells.forEach(cell => {
|
|
if (!this._cellDisposableMapping.has(cell.handle)) {
|
|
this._cellDisposableMapping.set(cell.handle, new DisposableStore());
|
|
}
|
|
|
|
let store = this._cellDisposableMapping.get(cell.handle)!;
|
|
store.add(cell.onDidChangeOutputs(() => {
|
|
this._proxy.$updateNotebookCell(this.viewType, this.uri, {
|
|
handle: cell.handle,
|
|
source: cell.source,
|
|
language: cell.language,
|
|
cell_type: cell.cell_type,
|
|
outputs: cell.outputs,
|
|
isDirty: false
|
|
});
|
|
}));
|
|
});
|
|
}
|
|
|
|
private _languages: string[] = [];
|
|
|
|
get languages() {
|
|
return this._languages = [];
|
|
}
|
|
|
|
set languages(newLanguages: string[]) {
|
|
this._languages = newLanguages;
|
|
this._proxy.$updateNotebookLanguages(this.viewType, this.uri, this._languages);
|
|
}
|
|
|
|
constructor(
|
|
private readonly _proxy: MainThreadNotebookShape,
|
|
public viewType: string,
|
|
public uri: URI
|
|
) {
|
|
|
|
}
|
|
|
|
get fileName() { return this.uri.fsPath; }
|
|
|
|
get isDirty() { return false; }
|
|
|
|
async $updateCells(): Promise<void> {
|
|
return await this._proxy.$updateNotebookCells(this.viewType, this.uri, this.cells.map(cell => ({
|
|
handle: cell.handle,
|
|
source: cell.source,
|
|
language: cell.language,
|
|
cell_type: cell.cell_type,
|
|
outputs: cell.outputs,
|
|
isDirty: false
|
|
})));
|
|
}
|
|
|
|
insertRawCell(index: number, cell: ExtHostCell) {
|
|
this.cells.splice(index, 0, cell);
|
|
|
|
if (!this._cellDisposableMapping.has(cell.handle)) {
|
|
this._cellDisposableMapping.set(cell.handle, new DisposableStore());
|
|
}
|
|
|
|
let store = this._cellDisposableMapping.get(cell.handle)!;
|
|
|
|
store.add(cell.onDidChangeOutputs(() => {
|
|
this._proxy.$updateNotebookCell(this.viewType, this.uri, {
|
|
handle: cell.handle,
|
|
source: cell.source,
|
|
language: cell.language,
|
|
cell_type: cell.cell_type,
|
|
outputs: cell.outputs,
|
|
isDirty: false
|
|
});
|
|
}));
|
|
}
|
|
|
|
deleteCell(index: number): boolean {
|
|
if (index >= this.cells.length) {
|
|
return false;
|
|
}
|
|
|
|
let cell = this.cells[index];
|
|
this._cellDisposableMapping.get(cell.handle)?.dispose();
|
|
this._cellDisposableMapping.delete(cell.handle);
|
|
|
|
this.cells.splice(index, 1);
|
|
return true;
|
|
}
|
|
|
|
getActiveCell(cellHandle: number) {
|
|
return this.cells.find(cell => cell.handle === cellHandle);
|
|
}
|
|
|
|
attachCellTextDocument(cellHandle: number, textDocument: vscode.TextDocument) {
|
|
let cell = this.cells.find(cell => cell.handle === cellHandle);
|
|
|
|
if (cell) {
|
|
cell.attachTextDocument(textDocument);
|
|
}
|
|
}
|
|
|
|
detachCellTextDocument(cellHandle: number, textDocument: vscode.TextDocument) {
|
|
let cell = this.cells.find(cell => cell.handle === cellHandle);
|
|
|
|
if (cell) {
|
|
cell.detachTextDocument(textDocument);
|
|
}
|
|
}
|
|
}
|
|
|
|
export class ExtHostNotebookEditor implements vscode.NotebookEditor {
|
|
private _viewColumn: vscode.ViewColumn | undefined;
|
|
|
|
constructor(
|
|
private viewType: string,
|
|
private readonly _proxy: MainThreadNotebookShape,
|
|
readonly id: string,
|
|
public uri: URI,
|
|
public document: ExtHostNotebookDocument,
|
|
private readonly documentsProxy: MainThreadDocumentsShape,
|
|
private _documentsAndEditors: ExtHostDocumentsAndEditors
|
|
) {
|
|
let regex = new RegExp(`notebook\\+${viewType}-(\\d+)-(\\d+)`);
|
|
this._documentsAndEditors.onDidAddDocuments(documents => {
|
|
for (const data of documents) {
|
|
let textDocument = data.document;
|
|
let authority = textDocument.uri.authority;
|
|
|
|
if (authority !== '') {
|
|
let matches = regex.exec(authority);
|
|
if (matches) {
|
|
const notebookHandle = matches[1];
|
|
const cellHandle = matches[2];
|
|
|
|
if (Number(notebookHandle) === this.document.handle) {
|
|
document.attachCellTextDocument(Number(cellHandle), textDocument);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
this._documentsAndEditors.onDidRemoveDocuments(documents => {
|
|
for (const data of documents) {
|
|
let textDocument = data.document;
|
|
let authority = textDocument.uri.authority;
|
|
|
|
if (authority !== '') {
|
|
let matches = regex.exec(authority);
|
|
if (matches) {
|
|
const notebookHandle = matches[1];
|
|
const cellHandle = matches[2];
|
|
|
|
if (Number(notebookHandle) === this.document.handle) {
|
|
document.detachCellTextDocument(Number(cellHandle), textDocument);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
createCell(content: string, language: string, type: 'markdown' | 'code', outputs: vscode.CellOutput[]): vscode.NotebookCell {
|
|
let cell = new ExtHostCell(content, type, language, outputs);
|
|
return cell;
|
|
}
|
|
|
|
get viewColumn(): vscode.ViewColumn | undefined {
|
|
return this._viewColumn;
|
|
}
|
|
|
|
set viewColumn(value) {
|
|
throw readonly('viewColumn');
|
|
}
|
|
}
|
|
|
|
export class ExtHostNotebookController implements ExtHostNotebookShape {
|
|
private static _handlePool: number = 0;
|
|
|
|
private readonly _proxy: MainThreadNotebookShape;
|
|
private readonly _documentsProxy: MainThreadDocumentsShape;
|
|
private readonly _notebookProviders = new Map<string, { readonly provider: vscode.NotebookProvider, readonly extension: IExtensionDescription }>();
|
|
private readonly _localStore = new DisposableStore();
|
|
private readonly _documents = new Map<string, ExtHostNotebookDocument>();
|
|
private readonly _editors = new Map<string, ExtHostNotebookEditor>();
|
|
|
|
|
|
constructor(mainContext: IMainContext, private _documentsAndEditors: ExtHostDocumentsAndEditors) {
|
|
this._proxy = mainContext.getProxy(MainContext.MainThreadNotebook);
|
|
this._documentsProxy = mainContext.getProxy(MainContext.MainThreadDocuments);
|
|
|
|
}
|
|
|
|
private _activeNotebookDocument: ExtHostNotebookDocument | undefined;
|
|
|
|
get activeNotebookDocument() {
|
|
return this._activeNotebookDocument;
|
|
}
|
|
|
|
public registerNotebookProvider(
|
|
extension: IExtensionDescription,
|
|
viewType: string,
|
|
provider: vscode.NotebookProvider,
|
|
): vscode.Disposable {
|
|
|
|
if (this._notebookProviders.has(viewType)) {
|
|
throw new Error(`Notebook provider for '${viewType}' already registered`);
|
|
}
|
|
|
|
this._notebookProviders.set(viewType, { extension, provider });
|
|
this._proxy.$registerNotebookProvider({ id: extension.identifier, location: extension.extensionLocation }, viewType);
|
|
return new VSCodeDisposable(() => {
|
|
this._notebookProviders.delete(viewType);
|
|
this._proxy.$unregisterNotebookProvider(viewType);
|
|
});
|
|
}
|
|
|
|
async $resolveNotebook(viewType: string, uri: URI): Promise<number | undefined> {
|
|
let provider = this._notebookProviders.get(viewType);
|
|
|
|
if (provider) {
|
|
if (!this._documents.has(URI.revive(uri).toString())) {
|
|
let document = new ExtHostNotebookDocument(this._proxy, viewType, uri);
|
|
await this._proxy.$createNotebookDocument(
|
|
document.handle,
|
|
viewType,
|
|
uri
|
|
);
|
|
|
|
this._documents.set(URI.revive(uri).toString(), document);
|
|
}
|
|
|
|
let editor = new ExtHostNotebookEditor(
|
|
viewType,
|
|
this._proxy,
|
|
`${ExtHostNotebookController._handlePool++}`,
|
|
uri,
|
|
this._documents.get(URI.revive(uri).toString())!,
|
|
this._documentsProxy,
|
|
this._documentsAndEditors
|
|
);
|
|
|
|
this._editors.set(URI.revive(uri).toString(), editor);
|
|
await provider.provider.resolveNotebook(editor);
|
|
await editor.document.$updateCells();
|
|
return editor.document.handle;
|
|
}
|
|
|
|
return Promise.resolve(undefined);
|
|
}
|
|
|
|
async $executeNotebook(viewType: string, uri: URI): Promise<void> {
|
|
let provider = this._notebookProviders.get(viewType);
|
|
|
|
if (provider) {
|
|
let document = this._documents.get(URI.revive(uri).toString());
|
|
|
|
return provider.provider.executeCell(document!, undefined);
|
|
}
|
|
}
|
|
|
|
async $executeNotebookCell(viewType: string, uri: URI, cellHandle: number): Promise<void> {
|
|
let provider = this._notebookProviders.get(viewType);
|
|
|
|
if (provider) {
|
|
let document = this._documents.get(URI.revive(uri).toString());
|
|
let cell = document?.getActiveCell(cellHandle);
|
|
|
|
if (cell) {
|
|
return provider.provider.executeCell(document!, cell!);
|
|
}
|
|
}
|
|
}
|
|
|
|
async $createRawCell(viewType: string, uri: URI, index: number, language: string, type: 'markdown' | 'code'): Promise<ICell | undefined> {
|
|
let provider = this._notebookProviders.get(viewType);
|
|
|
|
if (provider) {
|
|
let editor = this._editors.get(URI.revive(uri).toString());
|
|
let document = this._documents.get(URI.revive(uri).toString());
|
|
|
|
let rawCell = editor?.createCell('', language, type, []) as ExtHostCell;
|
|
document?.insertRawCell(index, rawCell!);
|
|
return {
|
|
handle: rawCell.handle,
|
|
source: rawCell.source,
|
|
language: rawCell.language,
|
|
cell_type: rawCell.cell_type,
|
|
outputs: rawCell.outputs,
|
|
isDirty: false
|
|
};
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
async $deleteCell(viewType: string, uri: URI, index: number): Promise<boolean> {
|
|
let provider = this._notebookProviders.get(viewType);
|
|
|
|
if (provider) {
|
|
let document = this._documents.get(URI.revive(uri).toString());
|
|
|
|
if (document) {
|
|
return document.deleteCell(index);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
async $saveNotebook(viewType: string, uri: URI): Promise<boolean> {
|
|
let provider = this._notebookProviders.get(viewType);
|
|
let document = this._documents.get(URI.revive(uri).toString());
|
|
|
|
if (provider && document) {
|
|
return await provider.provider.save(document);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
async $updateActiveEditor(viewType: string, uri: URI): Promise<void> {
|
|
let document = this._documents.get(URI.revive(uri).toString());
|
|
|
|
if (document) {
|
|
this._activeNotebookDocument = document;
|
|
} else {
|
|
this._activeNotebookDocument = undefined;
|
|
}
|
|
}
|
|
|
|
async $destoryNotebookDocument(viewType: string, uri: URI): Promise<boolean> {
|
|
let provider = this._notebookProviders.get(viewType);
|
|
|
|
if (provider) {
|
|
let document = this._documents.get(URI.revive(uri).toString());
|
|
|
|
if (document) {
|
|
this._documents.delete(URI.revive(uri).toString());
|
|
}
|
|
|
|
let editor = this._editors.get(URI.revive(uri).toString());
|
|
|
|
if (editor) {
|
|
this._editors.delete(URI.revive(uri).toString());
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
}
|