/* -------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. * ------------------------------------------------------------------------------------------ */ 'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); exports.NotebookDocuments = exports.NotebookSyncFeature = void 0; const vscode_languageserver_protocol_1 = require("vscode-languageserver-protocol"); const textDocuments_1 = require("./textDocuments"); const NotebookSyncFeature = (Base) => { return class extends Base { get synchronization() { return { onDidOpenNotebookDocument: (handler) => { return this.connection.onNotification(vscode_languageserver_protocol_1.DidOpenNotebookDocumentNotification.type, (params) => { handler(params); }); }, onDidChangeNotebookDocument: (handler) => { return this.connection.onNotification(vscode_languageserver_protocol_1.DidChangeNotebookDocumentNotification.type, (params) => { handler(params); }); }, onDidSaveNotebookDocument: (handler) => { return this.connection.onNotification(vscode_languageserver_protocol_1.DidSaveNotebookDocumentNotification.type, (params) => { handler(params); }); }, onDidCloseNotebookDocument: (handler) => { return this.connection.onNotification(vscode_languageserver_protocol_1.DidCloseNotebookDocumentNotification.type, (params) => { handler(params); }); } }; } }; }; exports.NotebookSyncFeature = NotebookSyncFeature; class CellTextDocumentConnection { onDidOpenTextDocument(handler) { this.openHandler = handler; return vscode_languageserver_protocol_1.Disposable.create(() => { this.openHandler = undefined; }); } openTextDocument(params) { this.openHandler && this.openHandler(params); } onDidChangeTextDocument(handler) { this.changeHandler = handler; return vscode_languageserver_protocol_1.Disposable.create(() => { this.changeHandler = handler; }); } changeTextDocument(params) { this.changeHandler && this.changeHandler(params); } onDidCloseTextDocument(handler) { this.closeHandler = handler; return vscode_languageserver_protocol_1.Disposable.create(() => { this.closeHandler = undefined; }); } closeTextDocument(params) { this.closeHandler && this.closeHandler(params); } onWillSaveTextDocument() { return CellTextDocumentConnection.NULL_DISPOSE; } onWillSaveTextDocumentWaitUntil() { return CellTextDocumentConnection.NULL_DISPOSE; } onDidSaveTextDocument() { return CellTextDocumentConnection.NULL_DISPOSE; } } CellTextDocumentConnection.NULL_DISPOSE = Object.freeze({ dispose: () => { } }); class NotebookDocuments { constructor(configurationOrTextDocuments) { if (configurationOrTextDocuments instanceof textDocuments_1.TextDocuments) { this._cellTextDocuments = configurationOrTextDocuments; } else { this._cellTextDocuments = new textDocuments_1.TextDocuments(configurationOrTextDocuments); } this.notebookDocuments = new Map(); this.notebookCellMap = new Map(); this._onDidOpen = new vscode_languageserver_protocol_1.Emitter(); this._onDidChange = new vscode_languageserver_protocol_1.Emitter(); this._onDidSave = new vscode_languageserver_protocol_1.Emitter(); this._onDidClose = new vscode_languageserver_protocol_1.Emitter(); } get cellTextDocuments() { return this._cellTextDocuments; } getCellTextDocument(cell) { return this._cellTextDocuments.get(cell.document); } getNotebookDocument(uri) { return this.notebookDocuments.get(uri); } getNotebookCell(uri) { const value = this.notebookCellMap.get(uri); return value && value[0]; } findNotebookDocumentForCell(cell) { const key = typeof cell === 'string' ? cell : cell.document; const value = this.notebookCellMap.get(key); return value && value[1]; } get onDidOpen() { return this._onDidOpen.event; } get onDidSave() { return this._onDidSave.event; } get onDidChange() { return this._onDidChange.event; } get onDidClose() { return this._onDidClose.event; } /** * Listens for `low level` notification on the given connection to * update the notebook documents managed by this instance. * * Please note that the connection only provides handlers not an event model. Therefore * listening on a connection will overwrite the following handlers on a connection: * `onDidOpenNotebookDocument`, `onDidChangeNotebookDocument`, `onDidSaveNotebookDocument`, * and `onDidCloseNotebookDocument`. * * @param connection The connection to listen on. */ listen(connection) { const cellTextDocumentConnection = new CellTextDocumentConnection(); const disposables = []; disposables.push(this.cellTextDocuments.listen(cellTextDocumentConnection)); disposables.push(connection.notebooks.synchronization.onDidOpenNotebookDocument((params) => { this.notebookDocuments.set(params.notebookDocument.uri, params.notebookDocument); for (const cellTextDocument of params.cellTextDocuments) { cellTextDocumentConnection.openTextDocument({ textDocument: cellTextDocument }); } this.updateCellMap(params.notebookDocument); this._onDidOpen.fire(params.notebookDocument); })); disposables.push(connection.notebooks.synchronization.onDidChangeNotebookDocument((params) => { const notebookDocument = this.notebookDocuments.get(params.notebookDocument.uri); if (notebookDocument === undefined) { return; } notebookDocument.version = params.notebookDocument.version; const oldMetadata = notebookDocument.metadata; let metadataChanged = false; const change = params.change; if (change.metadata !== undefined) { metadataChanged = true; notebookDocument.metadata = change.metadata; } const opened = []; const closed = []; const data = []; const text = []; if (change.cells !== undefined) { const changedCells = change.cells; if (changedCells.structure !== undefined) { const array = changedCells.structure.array; notebookDocument.cells.splice(array.start, array.deleteCount, ...(array.cells !== undefined ? array.cells : [])); // Additional open cell text documents. if (changedCells.structure.didOpen !== undefined) { for (const open of changedCells.structure.didOpen) { cellTextDocumentConnection.openTextDocument({ textDocument: open }); opened.push(open.uri); } } // Additional closed cell test documents. if (changedCells.structure.didClose) { for (const close of changedCells.structure.didClose) { cellTextDocumentConnection.closeTextDocument({ textDocument: close }); closed.push(close.uri); } } } if (changedCells.data !== undefined) { const cellUpdates = new Map(changedCells.data.map(cell => [cell.document, cell])); for (let i = 0; i <= notebookDocument.cells.length; i++) { const change = cellUpdates.get(notebookDocument.cells[i].document); if (change !== undefined) { const old = notebookDocument.cells.splice(i, 1, change); data.push({ old: old[0], new: change }); cellUpdates.delete(change.document); if (cellUpdates.size === 0) { break; } } } } if (changedCells.textContent !== undefined) { for (const cellTextDocument of changedCells.textContent) { cellTextDocumentConnection.changeTextDocument({ textDocument: cellTextDocument.document, contentChanges: cellTextDocument.changes }); text.push(cellTextDocument.document.uri); } } } // Update internal data structure. this.updateCellMap(notebookDocument); const changeEvent = { notebookDocument }; if (metadataChanged) { changeEvent.metadata = { old: oldMetadata, new: notebookDocument.metadata }; } const added = []; for (const open of opened) { added.push(this.getNotebookCell(open)); } const removed = []; for (const close of closed) { removed.push(this.getNotebookCell(close)); } const textContent = []; for (const change of text) { textContent.push(this.getNotebookCell(change)); } if (added.length > 0 || removed.length > 0 || data.length > 0 || textContent.length > 0) { changeEvent.cells = { added, removed, changed: { data, textContent } }; } if (changeEvent.metadata !== undefined || changeEvent.cells !== undefined) { this._onDidChange.fire(changeEvent); } })); disposables.push(connection.notebooks.synchronization.onDidSaveNotebookDocument((params) => { const notebookDocument = this.notebookDocuments.get(params.notebookDocument.uri); if (notebookDocument === undefined) { return; } this._onDidSave.fire(notebookDocument); })); disposables.push(connection.notebooks.synchronization.onDidCloseNotebookDocument((params) => { const notebookDocument = this.notebookDocuments.get(params.notebookDocument.uri); if (notebookDocument === undefined) { return; } this._onDidClose.fire(notebookDocument); for (const cellTextDocument of params.cellTextDocuments) { cellTextDocumentConnection.closeTextDocument({ textDocument: cellTextDocument }); } this.notebookDocuments.delete(params.notebookDocument.uri); for (const cell of notebookDocument.cells) { this.notebookCellMap.delete(cell.document); } })); return vscode_languageserver_protocol_1.Disposable.create(() => { disposables.forEach(disposable => disposable.dispose()); }); } updateCellMap(notebookDocument) { for (const cell of notebookDocument.cells) { this.notebookCellMap.set(cell.document, [cell, notebookDocument]); } } } exports.NotebookDocuments = NotebookDocuments;