diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index ab820b1891749d418cf8f13996bf23afd3e4cc95..b98f9591efdf8a80b0ed502ea30aa289033a6d18 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -1454,7 +1454,7 @@ declare module 'vscode' { metadata: NotebookDocumentMetadata; } - export interface NotebookConcatTextDocument { + export interface NotebookConcatTextDocument extends TextDocument { isClosed: boolean; dispose(): void; onDidChange: Event; diff --git a/src/vs/workbench/api/common/extHostNotebookConcatDocument.ts b/src/vs/workbench/api/common/extHostNotebookConcatDocument.ts index c23541ca7d23aae7bf6b33af6ba271f8faea9bd0..1d69692e007eccd36bb4af8c2d5fcf7754d912a4 100644 --- a/src/vs/workbench/api/common/extHostNotebookConcatDocument.ts +++ b/src/vs/workbench/api/common/extHostNotebookConcatDocument.ts @@ -12,14 +12,17 @@ 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 { isEqual } from 'vs/base/common/resources'; +import { basename } from 'vs/base/common/resources'; +import { ResourceMap } from 'vs/base/common/map'; +import { ExtHostDocumentLine } from 'vs/workbench/api/common/extHostDocumentData'; -export class ExtHostNotebookConcatDocument implements vscode.NotebookConcatTextDocument { +export class ExtHostNotebookConcatDocument implements vscode.NotebookConcatTextDocument, vscode.TextDocument { private _disposables = new DisposableStore(); private _isClosed = false; private _cells!: ExtHostCell[]; + private _cellByUri!: ResourceMap; private _cellLengths!: PrefixSumComputer; private _cellLines!: PrefixSumComputer; private _versionId = 0; @@ -27,17 +30,27 @@ export class ExtHostNotebookConcatDocument implements vscode.NotebookConcatTextD private readonly _onDidChange = new Emitter(); readonly onDidChange: Event = this._onDidChange.event; + readonly uri: vscode.Uri; + readonly fileName: string; + readonly languageId: string; + readonly isUntitled: boolean = false; + readonly isDirty: boolean = false; + constructor( extHostNotebooks: ExtHostNotebookController, extHostDocuments: ExtHostDocuments, private readonly _notebook: vscode.NotebookDocument, private readonly _selector: vscode.DocumentSelector | undefined, ) { + this.uri = _notebook.uri.with({ scheme: 'vscode-notebook-concat-doc' }); + this.fileName = basename(this.uri); + this.languageId = this._createLanguageId(); + this._init(); this._disposables.add(extHostDocuments.onDidChangeDocument(e => { - let cellIdx = this._cells.findIndex(cell => isEqual(cell.uri, e.document.uri)); - if (cellIdx >= 0) { + const cellIdx = this._cellByUri.get(e.document.uri); + if (typeof cellIdx === 'number') { this._cellLengths.changeValue(cellIdx, this._cells[cellIdx].document.getText().length + 1); this._cellLines.changeValue(cellIdx, this._cells[cellIdx].document.lineCount); this._versionId += 1; @@ -68,10 +81,12 @@ export class ExtHostNotebookConcatDocument implements vscode.NotebookConcatTextD private _init() { this._cells = []; + this._cellByUri = 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._cellByUri.set(cell.uri, this._cells.length); this._cells.push(cell); cellLengths.push(cell.document.getText().length + 1); cellLineCounts.push(cell.document.lineCount); @@ -81,6 +96,67 @@ export class ExtHostNotebookConcatDocument implements vscode.NotebookConcatTextD this._cellLines = new PrefixSumComputer(new Uint32Array(cellLineCounts)); } + private _createLanguageId(): string { + const languageIds = new Set(); + (function fillInLanguageIds(selector: vscode.DocumentSelector | undefined) { + if (Array.isArray(selector)) { + selector.forEach(fillInLanguageIds); + } else if (typeof selector === 'string') { + languageIds.add(selector); + } else if (selector?.language) { + languageIds.add(selector.language); + } + })(this._selector); + + if (languageIds.size === 0) { + return 'unknown'; + } + return [...languageIds.values()].sort().join(';'); + } + + save(): Thenable { + // todo@jrieken throw error instead? + return Promise.resolve(false); + } + + get eol(): vscode.EndOfLine { + return types.EndOfLine.LF; + } + + get lineCount(): number { + let total = 0; + for (let cell of this._cells) { + total += cell.document.lineCount; + } + return total; + } + + lineAt(lineOrPosition: number | vscode.Position): vscode.TextLine { + const line = typeof lineOrPosition === 'number' ? lineOrPosition : lineOrPosition.line; + const cellIdx = this._cellLines.getIndexOf(line); + return new ExtHostDocumentLine( + line, + this._cells[cellIdx.index].document.lineAt(cellIdx.remainder).text, + line >= this.lineCount + ); + } + + getWordRangeAtPosition(position: vscode.Position, regex?: RegExp | undefined): vscode.Range | undefined { + const cellIdx = this._cellLines.getIndexOf(position.line); + return this._cells[cellIdx.index].document.getWordRangeAtPosition(position.with({ line: cellIdx.remainder }), regex); + } + + 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 cellIdx = this._cellLines.getIndexOf(position.line); + return this._cells[cellIdx.index].document.validatePosition(position.with({ line: cellIdx.remainder })); + } + get version(): number { return this._versionId; } @@ -103,8 +179,8 @@ export class ExtHostNotebookConcatDocument implements vscode.NotebookConcatTextD // get start and end locations and create substrings const start = this.locationAt(range.start); const end = this.locationAt(range.end); - const startCell = this._cells.find(cell => isEqual(cell.uri, start.uri)); - const endCell = this._cells.find(cell => isEqual(cell.uri, end.uri)); + const startCell = this._cells[this._cellByUri.get(start.uri) ?? -1]; + const endCell = this._cells[this._cellByUri.get(end.uri) ?? -1]; if (!startCell || !endCell) { return ''; @@ -131,8 +207,8 @@ export class ExtHostNotebookConcatDocument implements vscode.NotebookConcatTextD return this._cells[idx.index].document.positionAt(idx.remainder).translate(lineCount); } - const idx = this._cells.findIndex(cell => isEqual(cell.uri, locationOrOffset.uri)); - if (idx >= 0) { + const idx = this._cellByUri.get(locationOrOffset.uri); + if (typeof idx === 'number') { let line = this._cellLines.getAccumulatedValue(idx - 1); return new types.Position(line + locationOrOffset.range.start.line, locationOrOffset.range.start.character); } diff --git a/src/vs/workbench/test/browser/api/extHostNotebookConcatDocument.test.ts b/src/vs/workbench/test/browser/api/extHostNotebookConcatDocument.test.ts index 3c7b6919f565b7d166ecd2d928ed1ccdd462fffd..4dd7c7b51c461d9dc55e352a4698a948fac95c98 100644 --- a/src/vs/workbench/test/browser/api/extHostNotebookConcatDocument.test.ts +++ b/src/vs/workbench/test/browser/api/extHostNotebookConcatDocument.test.ts @@ -12,7 +12,7 @@ import { ExtHostNotebookConcatDocument } from 'vs/workbench/api/common/extHostNo import { ExtHostNotebookDocument, ExtHostNotebookController } from 'vs/workbench/api/common/extHostNotebook'; import { URI } from 'vs/base/common/uri'; import { CellKind, CellUri, NotebookCellsChangeType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { Position, Location, Range } from 'vs/workbench/api/common/extHostTypes'; +import { Position, Location, Range, EndOfLine } from 'vs/workbench/api/common/extHostTypes'; import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; import { nullExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; import * as vscode from 'vscode'; @@ -28,7 +28,7 @@ suite('NotebookConcatDocument', function () { let extHostDocumentsAndEditors: ExtHostDocumentsAndEditors; let extHostDocuments: ExtHostDocuments; let extHostNotebooks: ExtHostNotebookController; - const notebookUri = URI.parse('test:///notebook.file'); + const notebookUri = URI.parse('test:/path/notebook.file'); const disposables = new DisposableStore(); setup(async function () { @@ -80,6 +80,19 @@ suite('NotebookConcatDocument', function () { disposables.add(extHostDocuments); }); + test('basics', async function () { + let doc = new ExtHostNotebookConcatDocument(extHostNotebooks, extHostDocuments, notebook, undefined); + assert.equal(doc.uri.toString(), 'vscode-notebook-concat-doc:/path/notebook.file'); + assert.equal(doc.fileName, 'notebook.file'); + assert.equal(doc.isUntitled, false); + assert.equal(doc.isDirty, false); + assert.equal(await doc.save(), false); + assert.equal(doc.isClosed, false); + assert.equal(doc.eol, EndOfLine.LF); + doc.dispose(); + assert.equal(doc.isClosed, true); + }); + test('empty', function () { let doc = new ExtHostNotebookConcatDocument(extHostNotebooks, extHostDocuments, notebook, undefined); assert.equal(doc.getText(), ''); @@ -109,6 +122,11 @@ suite('NotebookConcatDocument', function () { function assertLines(doc: vscode.NotebookConcatTextDocument, ...lines: string[]) { let actual = doc.getText().split(/\r\n|\n|\r/); assert.deepStrictEqual(actual, lines); + assert.equal(doc.lineCount, lines.length); + for (let i = 0; i < lines.length; i++) { + assert.equal(doc.lineAt(i).text, lines[i]); + assert.equal(doc.lineAt(i).range.start.line, i); + } } test('location, position mapping', function () { @@ -302,6 +320,10 @@ suite('NotebookConcatDocument', function () { const fooLangDoc = new ExtHostNotebookConcatDocument(extHostNotebooks, extHostDocuments, notebook, 'fooLang'); const barLangDoc = new ExtHostNotebookConcatDocument(extHostNotebooks, extHostDocuments, notebook, 'barLang'); + assert.equal(mixedDoc.languageId, 'unknown'); + assert.equal(fooLangDoc.languageId, 'fooLang'); + assert.equal(barLangDoc.languageId, 'barLang'); + assertLines(mixedDoc, 'fooLang-document', 'barLang-document'); assertLines(fooLangDoc, 'fooLang-document'); assertLines(barLangDoc, 'barLang-document');