don't duplicate lines, only keep prefix sums

This commit is contained in:
Johannes Rieken
2020-04-21 09:59:26 +02:00
parent fc20cdf5e7
commit 06e1bbac2f
2 changed files with 227 additions and 118 deletions

View File

@@ -9,29 +9,23 @@ import { Event, Emitter } from 'vs/base/common/event';
import { ExtHostNotebookDocument, 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 { ExtHostDocumentData } from 'vs/workbench/api/common/extHostDocumentData';
import { Range, LanguageSelector } from 'vs/workbench/api/common/extHostTypeConverters';
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 { NotImplementedProxy } from 'vs/base/common/types';
import { MainThreadDocumentsShape } from 'vs/workbench/api/common/extHost.protocol';
//todo@jrieken ConcatDiagnosticsCollection...
import { isEqual } from 'vs/base/common/resources';
export class ExtHostNotebookConcatDocument implements vscode.NotebookConcatTextDocument {
private _disposables = new DisposableStore();
private _cells!: ExtHostCell[];
private _cellLengths!: PrefixSumComputer;
private _cellLines!: PrefixSumComputer;
private _versionId = 0;
private readonly _onDidChange = new Emitter<this>();
readonly onDidChange: Event<this> = this._onDidChange.event;
private _versionId = 0;
private _delegate!: ExtHostDocumentData;
private _selectedCells!: ExtHostCell[];
private _cellStarts!: PrefixSumComputer;
constructor(
extHostNotebooks: ExtHostNotebookController,
extHostDocuments: ExtHostDocuments,
@@ -40,133 +34,119 @@ export class ExtHostNotebookConcatDocument implements vscode.NotebookConcatTextD
) {
this._init();
extHostDocuments.onDidChangeDocument(e => {
const cellIdx = this._selectedCells.findIndex(candidate => candidate.uri.toString() === e.document.uri.toString());
if (cellIdx < 0) {
return;
this._disposables.add(extHostDocuments.onDidChangeDocument(e => {
let cellIdx = this._cells.findIndex(cell => isEqual(cell.uri, e.document.uri));
if (cellIdx >= 0) {
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(this);
}
// todo@jrieken reuse raw event!
this._versionId += 1;
this._delegate.onEvents({
versionId: this._versionId,
eol: '\n',
changes: e.contentChanges.map(change => {
return {
range: Range.from(change.range),
rangeOffset: change.rangeOffset,
rangeLength: change.rangeLength,
text: change.text,
};
})
});
this._cellStarts.changeValue(cellIdx, e.document.getText().length + 1);
this._onDidChange.fire(this);
}, undefined, this._disposables);
extHostNotebooks.onDidChangeNotebookDocument(e => {
if (e.document !== this._notebook) {
return;
}));
this._disposables.add(extHostNotebooks.onDidChangeNotebookDocument(e => {
if (e.document === this._notebook) {
this._init();
this._versionId += 1;
this._onDidChange.fire(this);
}
//todo@jrieken update instead of flushing...
this._versionId += 1;
this._init();
this._onDidChange.fire(this);
}, undefined, this._disposables);
}));
}
dispose(): void {
this._disposables.dispose();
this._delegate.dispose();
}
private _init() {
// only allow Code-cells and those that are selected by the language selector
this._selectedCells = this._notebook.cells
.filter(cell => cell.cellKind === CellKind.Code && (!this._selector || score(LanguageSelector.from(this._selector), cell.uri, cell.language, true)));
const lines: string[] = [];
const cellLengths = new Uint32Array(this._selectedCells.length);
for (let i = 0; i < this._selectedCells.length; i++) {
const cell = this._selectedCells[i];
// update prefix sum
cellLengths[i] = cell.document.getText().length + 1; // 1 is newline
//todo@jrieken reuse lines!
for (let line = 0; line < cell.document.lineCount; line++) {
lines.push(cell.document.lineAt(line).text);
this._cells = [];
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._cells.push(cell);
cellLengths.push(cell.document.getText().length + 1);
cellLineCounts.push(cell.document.lineCount);
}
}
this._cellStarts = new PrefixSumComputer(cellLengths);
this._delegate = new ExtHostDocumentData(
new class extends NotImplementedProxy<MainThreadDocumentsShape>('MainThreadDocumentsShape') { },
this._notebook.uri.with({ scheme: 'vscode-concatdoc' }),
lines,
'\n',
this._notebook.languages[0],
this._versionId,
false
);
this._cellLengths = new PrefixSumComputer(new Uint32Array(cellLengths));
this._cellLines = new PrefixSumComputer(new Uint32Array(cellLineCounts));
}
get version() {
get version(): number {
return this._versionId;
}
getText(range?: vscode.Range) {
return this._delegate.document.getText(range);
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.find(cell => isEqual(cell.uri, start.uri));
const endCell = this._cells.find(cell => isEqual(cell.uri, end.uri));
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;
}
}
locationAt(positionOrRange: vscode.Position | vscode.Range): vscode.Location {
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._cells.findIndex(cell => isEqual(cell.uri, locationOrOffset.uri));
if (idx >= 0) {
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(<types.Position>positionOrRange, <types.Position>positionOrRange);
}
const start = this._delegate.document.offsetAt(positionOrRange.start);
const startIndex = this._cellStarts.getIndexOf(start);
const startCell = this._selectedCells[startIndex.index];
if (!startCell) {
// do better? throw an error insead? return undefined?
return new types.Location(this._notebook.uri, new types.Position(0, 0));
}
let endCell = startCell;
let endIndex = startIndex;
const startIdx = this._cellLines.getIndexOf(positionOrRange.start.line);
let endIdx = startIdx;
if (!positionOrRange.isEmpty) {
const end = this._delegate.document.offsetAt(positionOrRange.end);
endIndex = this._cellStarts.getIndexOf(end);
endCell = this._selectedCells[endIndex.index];
endIdx = this._cellLines.getIndexOf(positionOrRange.end.line);
}
const startPos = startCell.document.positionAt(startIndex.remainder);
let endPos = startPos;
if (endCell && endCell.handle === startCell.handle) {
endPos = endCell.document.positionAt(endIndex.remainder);
}
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);
return new types.Location(startCell.uri, new types.Range(startPos.line, startPos.character, endPos.line, endPos.character));
}
positionAt(offsetOrLocation: number | vscode.Location): vscode.Position {
if (typeof offsetOrLocation === 'number') {
return this._delegate.document.positionAt(offsetOrLocation);
}
const idx = this._selectedCells.findIndex(candidate => candidate.uri.toString() === offsetOrLocation.uri.toString());
if (idx < 0) {
// do better?
// return undefined;
return new types.Position(0, 0);
}
const docOffset = this._selectedCells[idx].document.offsetAt(offsetOrLocation.range.start);
const cellOffset = this._cellStarts.getAccumulatedValue(idx - 1);
return this._delegate.document.positionAt(docOffset + cellOffset);
}
offsetAt(position: vscode.Position): number {
return this._delegate.document.offsetAt(position);
const startCell = this._cells[startIdx.index];
return new types.Location(startCell.uri, <types.Range>startCell.document.validateRange(range));
}
}