/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ 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 { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; import { PrefixSumComputer } from 'vs/editor/common/viewModel/prefixSumComputer'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { score } from 'vs/editor/common/modes/languageSelector'; import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { ResourceMap } from 'vs/base/common/map'; export class ExtHostNotebookConcatDocument implements vscode.NotebookConcatTextDocument { private _disposables = new DisposableStore(); private _isClosed = false; private _cells!: ExtHostCell[]; private _cellUris!: ResourceMap; private _cellLengths!: PrefixSumComputer; private _cellLines!: PrefixSumComputer; private _versionId = 0; private readonly _onDidChange = new Emitter(); readonly onDidChange: Event = this._onDidChange.event; constructor( extHostNotebooks: ExtHostNotebookController, extHostDocuments: ExtHostDocuments, private readonly _notebook: vscode.NotebookDocument, private readonly _selector: vscode.DocumentSelector | undefined, ) { this._init(); this._disposables.add(extHostDocuments.onDidChangeDocument(e => { let cellIdx = this._cellUris.get(e.document.uri); if (cellIdx !== undefined) { this._cellLengths.changeValue(cellIdx, this._cells[cellIdx].document.getText().length + 1); this._cellLines.changeValue(cellIdx, this._cells[cellIdx].document.lineCount); this._versionId += 1; this._onDidChange.fire(undefined); } })); const documentChange = (document: vscode.NotebookDocument) => { if (document === this._notebook) { this._init(); this._versionId += 1; this._onDidChange.fire(undefined); } }; this._disposables.add(extHostNotebooks.onDidChangeCellLanguage(e => documentChange(e.document))); this._disposables.add(extHostNotebooks.onDidChangeNotebookCells(e => documentChange(e.document))); } dispose(): void { this._disposables.dispose(); this._isClosed = true; } get isClosed() { return this._isClosed; } private _init() { this._cells = []; this._cellUris = new ResourceMap(); const cellLengths: number[] = []; const cellLineCounts: number[] = []; for (let 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(cell); cellLengths.push(cell.document.getText().length + 1); cellLineCounts.push(cell.document.lineCount); } } this._cellLengths = new PrefixSumComputer(new Uint32Array(cellLengths)); this._cellLines = new PrefixSumComputer(new Uint32Array(cellLineCounts)); } get version(): number { return this._versionId; } getText(range?: vscode.Range): string { if (!range) { let result = ''; for (let cell of this._cells) { result += cell.document.getText() + '\n'; } // remove last newline again result = result.slice(0, -1); return result; } if (range.isEmpty) { return ''; } // get start and end locations and create substrings const start = this.locationAt(range.start); const end = this.locationAt(range.end); const startCell = this._cells[this._cellUris.get(start.uri) ?? -1]; const endCell = this._cells[this._cellUris.get(end.uri) ?? -1]; if (!startCell || !endCell) { return ''; } else if (startCell === endCell) { return startCell.document.getText(new types.Range(start.range.start, end.range.end)); } else { let a = startCell.document.getText(new types.Range(start.range.start, new types.Position(startCell.document.lineCount, 0))); let b = endCell.document.getText(new types.Range(new types.Position(0, 0), end.range.end)); return a + '\n' + b; } } offsetAt(position: vscode.Position): number { const idx = this._cellLines.getIndexOf(position.line); const offset1 = this._cellLengths.getAccumulatedValue(idx.index - 1); const offset2 = this._cells[idx.index].document.offsetAt(position.with(idx.remainder)); return offset1 + offset2; } positionAt(locationOrOffset: vscode.Location | number): vscode.Position { if (typeof locationOrOffset === 'number') { const idx = this._cellLengths.getIndexOf(locationOrOffset); const lineCount = this._cellLines.getAccumulatedValue(idx.index - 1); return this._cells[idx.index].document.positionAt(idx.remainder).translate(lineCount); } const idx = this._cellUris.get(locationOrOffset.uri); if (idx !== undefined) { let line = this._cellLines.getAccumulatedValue(idx - 1); return new types.Position(line + locationOrOffset.range.start.line, locationOrOffset.range.start.character); } // do better? // return undefined; return new types.Position(0, 0); } locationAt(positionOrRange: vscode.Range | vscode.Position): types.Location { if (!types.Range.isRange(positionOrRange)) { positionOrRange = new types.Range(positionOrRange, positionOrRange); } const startIdx = this._cellLines.getIndexOf(positionOrRange.start.line); let endIdx = startIdx; if (!positionOrRange.isEmpty) { endIdx = this._cellLines.getIndexOf(positionOrRange.end.line); } let startPos = new types.Position(startIdx.remainder, positionOrRange.start.character); let endPos = new types.Position(endIdx.remainder, positionOrRange.end.character); let range = new types.Range(startPos, endPos); const startCell = this._cells[startIdx.index]; return new types.Location(startCell.uri, startCell.document.validateRange(range)); } contains(uri: vscode.Uri): boolean { return this._cellUris.has(uri); } validateRange(range: vscode.Range): vscode.Range { const start = this.validatePosition(range.start); const end = this.validatePosition(range.end); return range.with(start, end); } validatePosition(position: vscode.Position): vscode.Position { const startIdx = this._cellLines.getIndexOf(position.line); const cellPosition = new types.Position(startIdx.remainder, position.character); const validCellPosition = this._cells[startIdx.index].document.validatePosition(cellPosition); const line = this._cellLines.getAccumulatedValue(startIdx.index - 1); return new types.Position(line + validCellPosition.line, validCellPosition.character); } }