提交 9887d9ba 编写于 作者: R rebornix

NotebookViewModel and tests

上级 c5f08080
......@@ -124,7 +124,7 @@ registerAction2(class extends Action2 {
let activeCell = editor.getActiveCell();
if (activeCell) {
editor.editNotebookCell(undefined, activeCell);
editor.editNotebookCell(activeCell);
}
}
});
......
......@@ -16,10 +16,10 @@ export const KEYBINDING_CONTEXT_NOTEBOOK_FIND_WIDGET_FOCUSED = new RawContextKey
export interface INotebookEditor {
viewType: string | undefined;
insertEmptyNotebookCell(index: number | undefined, cell: CellViewModel, type: 'markdown' | 'code', direction: 'above' | 'below'): Promise<void>;
deleteNotebookCell(index: number | undefined, cell: CellViewModel): void;
editNotebookCell(index: number | undefined, cell: CellViewModel): void;
saveNotebookCell(index: number | undefined, cell: CellViewModel): void;
insertEmptyNotebookCell(cell: CellViewModel, type: 'markdown' | 'code', direction: 'above' | 'below'): Promise<void>;
deleteNotebookCell(cell: CellViewModel): void;
editNotebookCell(cell: CellViewModel): void;
saveNotebookCell(cell: CellViewModel): void;
focusNotebookCell(cell: CellViewModel, focusEditor: boolean): void;
getActiveCell(): CellViewModel | undefined;
layoutNotebookCell(cell: CellViewModel, height: number): void;
......
......@@ -38,6 +38,7 @@ import { IResourceEditorInput } from 'vs/platform/editor/common/editor';
import { Emitter, Event } from 'vs/base/common/event';
import { NotebookCellList } from 'vs/workbench/contrib/notebook/browser/notebookCellList';
import { NotebookFindWidget, NotebookFindDelegate, CellFindMatch } from 'vs/workbench/contrib/notebook/browser/notebookFindWidget';
import { NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/notebookViewModel';
const $ = DOM.$;
const NOTEBOOK_EDITOR_VIEW_STATE_PREFERENCE_KEY = 'NotebookEditorViewState';
......@@ -112,19 +113,14 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor, Noteb
static readonly ID: string = 'workbench.editor.notebook';
private rootElement!: HTMLElement;
private body!: HTMLElement;
private contentWidgets!: HTMLElement;
private webview: BackLayerWebView | null = null;
private list: NotebookCellList<CellViewModel> | undefined;
private control: ICompositeCodeEditor | undefined;
private renderedEditors: Map<CellViewModel, ICodeEditor | undefined> = new Map();
private model: NotebookEditorModel | undefined;
viewType: string | undefined;
private viewCells: CellViewModel[] = [];
private notebookViewModel: NotebookViewModel | undefined;
private localStore: DisposableStore = this._register(new DisposableStore());
private editorMemento: IEditorMemento<INotebookEditorViewState>;
private fontInfo: BareFontInfo | undefined;
// private relayoutDisposable: IDisposable | null = null;
private dimension: DOM.Dimension | null = null;
private editorFocus: IContextKey<boolean> | null = null;
private outputRenderer: OutputRenderer;
......@@ -157,6 +153,8 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor, Noteb
set minimumWidth(value: number) { /*noop*/ }
set maximumWidth(value: number) { /*noop*/ }
get viewType() { return this.notebookViewModel?.viewType; }
//#region Editor
......@@ -184,10 +182,6 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor, Noteb
DOM.addClass(this.body, 'cell-list-container');
this.createCellList();
DOM.append(parent, this.body);
this.contentWidgets = document.createElement('div');
DOM.addClass(this.contentWidgets, 'notebook-content-widgets');
DOM.append(this.body, this.contentWidgets);
DOM.append(this.body, this.findWidget.getDomNode());
}
......@@ -255,10 +249,9 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor, Noteb
this.list?.splice(0, this.list?.length);
if (this.model && !this.model.isDirty()) {
this.notebookService.destoryNotebookDocument(this.viewType!, this.model!.notebook!);
this.model = undefined;
this.viewType = undefined;
if (this.notebookViewModel && !this.notebookViewModel.isDirty()) {
this.notebookService.destoryNotebookDocument(this.viewType!, this.notebookViewModel!.notebookDocument);
this.notebookViewModel = undefined;
}
super.onHide();
......@@ -267,11 +260,7 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor, Noteb
setVisible(visible: boolean, group?: IEditorGroup): void {
super.setVisible(visible, group);
if (!visible) {
this.viewCells.forEach(cell => {
if (cell.getText() !== '') {
cell.isEditing = false;
}
});
this.notebookViewModel?.hide();
}
}
......@@ -280,97 +269,91 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor, Noteb
this.editorFocus?.set(true);
}
setInput(input: NotebookEditorInput, options: EditorOptions | undefined, token: CancellationToken): Promise<void> {
async setInput(input: NotebookEditorInput, options: EditorOptions | undefined, token: CancellationToken): Promise<void> {
if (this.input instanceof NotebookEditorInput) {
this.saveTextEditorViewState(this.input);
}
return super.setInput(input, options, token)
.then(() => {
return input.resolve();
})
.then(async model => {
if (this.model !== undefined && this.model === model && this.webview !== null) {
return;
}
await super.setInput(input, options, token);
const model = await input.resolve();
this.localStore.clear();
this.viewCells.forEach(cell => {
cell.save();
});
if (this.notebookViewModel !== undefined && this.notebookViewModel.equal(model) && this.webview !== null) {
return;
}
if (this.webview) {
this.webview?.clearInsets();
this.webview?.clearPreloadsCache();
} else {
this.webview = new BackLayerWebView(this.webviewService, this.notebookService, this, this.environmentSerice);
this.list?.rowsContainer.insertAdjacentElement('afterbegin', this.webview!.element);
}
this.detachModel();
await this.attachModel(input, model);
}
this.model = model;
this.localStore.add(this.model.onDidChangeCells((e) => {
this.updateViewCells(e);
}));
private detachModel() {
this.localStore.clear();
this.notebookViewModel?.dispose();
this.notebookViewModel = undefined;
this.webview?.clearInsets();
this.webview?.clearPreloadsCache();
}
let viewState = this.loadTextEditorViewState(input);
this.webview.updateRendererPreloads(this.model!.notebook.renderers);
this.viewType = input.viewType;
this.viewCells = await Promise.all(this.model!.notebook!.cells.map(async cell => {
const isEditing = viewState && viewState.editingCells[cell.handle];
const viewCell = this.instantiationService.createInstance(CellViewModel, input.viewType!, this.model!.notebook!.handle, cell, !!isEditing);
this.localStore.add(viewCell);
return viewCell;
}));
private async attachModel(input: NotebookEditorInput, model: NotebookEditorModel) {
if (!this.webview) {
this.webview = new BackLayerWebView(this.webviewService, this.notebookService, this, this.environmentSerice);
this.list?.rowsContainer.insertAdjacentElement('afterbegin', this.webview!.element);
}
const updateScrollPosition = () => {
const scrollTop = this.list?.scrollTop || 0;
const scrollHeight = this.list?.scrollHeight || 0;
this.webview!.element.style.height = `${scrollHeight}px`;
let updateItems: { cell: CellViewModel, output: IOutput, cellTop: number }[] = [];
// const date = new Date();
if (this.webview?.insetMapping) {
this.webview?.insetMapping.forEach((value, key) => {
let cell = value.cell;
let index = this.model!.getNotebook().cells.indexOf(cell.cell);
let cellTop = this.list?.getAbsoluteTop(index) || 0;
if (this.webview!.shouldUpdateInset(cell, key, cellTop)) {
updateItems.push({
cell: cell,
output: key,
cellTop: cellTop
});
}
});
this.notebookViewModel = this.instantiationService.createInstance(NotebookViewModel, input.viewType!, model);
const viewState = this.loadTextEditorViewState(input);
await this.notebookViewModel.initialize(viewState);
if (updateItems.length) {
// console.log('----- did scroll ---- ', date.getMinutes() + ':' + date.getSeconds() + ':' + date.getMilliseconds());
this.webview?.updateViewScrollTop(-scrollTop, updateItems);
}
}
};
this.localStore.add(this.list!.onWillScroll(e => {
// const date = new Date();
// console.log('----- will scroll ---- ', date.getMinutes() + ':' + date.getSeconds() + ':' + date.getMilliseconds());
this.webview!.updateViewScrollTop(-e.scrollTop, []);
}));
this.localStore.add(this.list!.onDidChangeContentHeight(() => updateScrollPosition()));
this.localStore.add(this.list!.onFocusChange((e) => {
if (e.elements.length > 0) {
this.notebookService.updateNotebookActiveCell(input.viewType!, input.resource!, e.elements[0].cell.handle);
this.localStore.add(this.notebookViewModel.onDidChangeCells((e) => {
this.updateViewCells(e);
}));
this.webview?.updateRendererPreloads(this.notebookViewModel.renderers);
this.localStore.add(this.list!.onWillScroll(e => {
this.webview!.updateViewScrollTop(-e.scrollTop, []);
}));
this.localStore.add(this.list!.onDidChangeContentHeight(() => {
const scrollTop = this.list?.scrollTop || 0;
const scrollHeight = this.list?.scrollHeight || 0;
this.webview!.element.style.height = `${scrollHeight}px`;
let updateItems: { cell: CellViewModel, output: IOutput, cellTop: number }[] = [];
if (this.webview?.insetMapping) {
this.webview?.insetMapping.forEach((value, key) => {
let cell = value.cell;
let index = this.notebookViewModel!.getViewCellIndex(cell);
let cellTop = this.list?.getAbsoluteTop(index) || 0;
if (this.webview!.shouldUpdateInset(cell, key, cellTop)) {
updateItems.push({
cell: cell,
output: key,
cellTop: cellTop
});
}
}));
});
this.list?.splice(0, this.list?.length);
this.list?.splice(0, 0, this.viewCells);
this.list?.layout();
});
if (updateItems.length) {
this.webview?.updateViewScrollTop(-scrollTop, updateItems);
}
}
}));
this.localStore.add(this.list!.onFocusChange((e) => {
if (e.elements.length > 0) {
this.notebookService.updateNotebookActiveCell(input.viewType!, input.resource!, e.elements[0].cell.handle);
}
}));
this.list?.splice(0, this.list?.length);
this.list?.splice(0, 0, this.notebookViewModel!.viewCells);
this.list?.layout();
}
private saveTextEditorViewState(input: NotebookEditorInput): void {
if (this.group) {
let state: { [key: number]: boolean } = {};
this.viewCells.filter(cell => cell.isEditing).forEach(cell => state[cell.cell.handle] = true);
this.notebookViewModel!.viewCells.filter(cell => cell.isEditing).forEach(cell => state[cell.cell.handle] = true);
this.editorMemento.saveEditorState(this.group, input, {
editingCells: state
});
......@@ -406,7 +389,7 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor, Noteb
//#region Find Delegate
startFind(value: string): CellFindMatch[] {
let matches: CellFindMatch[] = [];
this.viewCells.forEach(cell => {
this.notebookViewModel!.viewCells.forEach(cell => {
let cellMatches = cell.startFind(value);
matches.push(...cellMatches);
});
......@@ -419,7 +402,7 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor, Noteb
focusNext(match: CellFindMatch) {
let cell = match.cell;
let index = this.viewCells.indexOf(cell);
let index = this.notebookViewModel!.viewCells.indexOf(cell);
this.list?.reveal(index);
}
......@@ -437,7 +420,7 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor, Noteb
//#region Cell operations
layoutNotebookCell(cell: CellViewModel, height: number) {
let relayout = (cell: CellViewModel, height: number) => {
let index = this.model!.getNotebook().cells.indexOf(cell.cell);
let index = this.notebookViewModel!.getViewCellIndex(cell);
if (index >= 0) {
this.list?.updateElementHeight(index, height);
}
......@@ -445,38 +428,28 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor, Noteb
DOM.scheduleAtNextAnimationFrame(() => {
relayout(cell, height);
// this.relayoutDisposable = null;
});
}
updateViewCells(splices: NotebookCellsSplice[]) {
let update = () => {
DOM.scheduleAtNextAnimationFrame(() => {
splices.reverse().forEach((diff) => {
this.list?.splice(diff[0], diff[1], diff[2].map(cell => {
return this.instantiationService.createInstance(CellViewModel, this.viewType!, this.model!.notebook!.handle, cell, false);
return this.instantiationService.createInstance(CellViewModel, this.viewType!, this.notebookViewModel!.handle, cell, false);
}));
});
};
DOM.scheduleAtNextAnimationFrame(() => {
update();
});
}
async insertEmptyNotebookCell(listIndex: number | undefined, cell: CellViewModel, type: 'code' | 'markdown', direction: 'above' | 'below'): Promise<void> {
let newLanguages = this.model!.notebook!.languages;
let language = 'markdown';
if (newLanguages && newLanguages.length) {
language = newLanguages[0];
}
let index = listIndex ? listIndex : this.model!.getNotebook().cells.indexOf(cell.cell);
async insertEmptyNotebookCell(cell: CellViewModel, type: 'code' | 'markdown', direction: 'above' | 'below'): Promise<void> {
const newLanguages = this.notebookViewModel!.languages;
const language = newLanguages && newLanguages.length ? newLanguages[0] : 'markdown';
const index = this.notebookViewModel!.getViewCellIndex(cell);
const insertIndex = direction === 'above' ? index : index + 1;
const newModeCell = await this.notebookService.createNotebookCell(this.viewType!, this.notebookViewModel!.uri, insertIndex, language, type);
const newCell = this.instantiationService.createInstance(CellViewModel, this.viewType!, this.notebookViewModel!.handle, newModeCell!, false);
let newModeCell = await this.notebookService.createNotebookCell(this.viewType!, this.model!.notebook!.uri, insertIndex, language, type);
let newCell = this.instantiationService.createInstance(CellViewModel, this.viewType!, this.model!.notebook!.handle, newModeCell!, false);
this.viewCells!.splice(insertIndex, 0, newCell);
this.notebookViewModel!.insertCell(insertIndex, newCell);
this.list?.splice(insertIndex, 0, [newCell]);
this.list?.setFocus([insertIndex]);
......@@ -489,11 +462,18 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor, Noteb
});
}
editNotebookCell(listIndex: number | undefined, cell: CellViewModel): void {
async deleteNotebookCell(cell: CellViewModel): Promise<void> {
const index = this.notebookViewModel!.getViewCellIndex(cell);
await this.notebookService.deleteNotebookCell(this.viewType!, this.notebookViewModel!.uri, index);
this.notebookViewModel!.deleteCell(index);
this.list?.splice(index, 1);
}
editNotebookCell(cell: CellViewModel): void {
cell.isEditing = true;
}
saveNotebookCell(listIndex: number | undefined, cell: CellViewModel): void {
saveNotebookCell(cell: CellViewModel): void {
cell.isEditing = false;
}
......@@ -508,7 +488,7 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor, Noteb
}
focusNotebookCell(cell: CellViewModel, focusEditor: boolean) {
let index = this.model!.getNotebook().cells.indexOf(cell.cell);
const index = this.notebookViewModel!.getViewCellIndex(cell);
if (focusEditor) {
......@@ -525,15 +505,6 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor, Noteb
this.list?.focusView();
}
async deleteNotebookCell(listIndex: number | undefined, cell: CellViewModel): Promise<void> {
let index = this.model!.getNotebook().cells.indexOf(cell.cell);
// await this.notebookService.createNotebookCell(this.viewType!, this.model!.notebook!.uri, insertIndex, language, type);
await this.notebookService.deleteNotebookCell(this.viewType!, this.model!.notebook!.uri, index);
this.viewCells!.splice(index, 1);
this.list?.splice(index, 1);
}
//#endregion
//#region MISC
......@@ -555,15 +526,15 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor, Noteb
return;
}
let preloads = this.model!.notebook!.renderers;
let preloads = this.notebookViewModel!.renderers;
if (!this.webview!.insetMapping.has(output)) {
let index = this.model!.getNotebook().cells.indexOf(cell.cell);
let index = this.notebookViewModel!.getViewCellIndex(cell);
let cellTop = this.list?.getAbsoluteTop(index) || 0;
this.webview!.createInset(cell, output, cellTop, offset, shadowContent, preloads);
} else {
let index = this.model!.getNotebook().cells.indexOf(cell.cell);
let index = this.notebookViewModel!.getViewCellIndex(cell);
let cellTop = this.list?.getAbsoluteTop(index) || 0;
let scrollTop = this.list?.scrollTop || 0;
......
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { NotebookEditorModel } from 'vs/workbench/contrib/notebook/browser/notebookEditorInput';
import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/renderers/cellViewModel';
import { DisposableStore, Disposable } from 'vs/base/common/lifecycle';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { Emitter, Event } from 'vs/base/common/event';
import { NotebookCellsSplice } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { CellFindMatch } from 'vs/workbench/contrib/notebook/browser/notebookFindWidget';
export interface INotebookEditorViewState {
editingCells: { [key: number]: boolean };
}
export class NotebookViewModel extends Disposable {
private _localStore: DisposableStore = this._register(new DisposableStore());
private _viewCells: CellViewModel[] = [];
get viewCells() {
return this._viewCells;
}
get notebookDocument() {
return this._model.notebook;
}
get renderers() {
return this._model.notebook!.renderers;
}
get handle() {
return this._model.notebook.handle;
}
get languages() {
return this._model.notebook.languages;
}
get uri() {
return this._model.notebook.uri;
}
private readonly _onDidChangeCells = new Emitter<NotebookCellsSplice[]>();
get onDidChangeCells(): Event<NotebookCellsSplice[]> { return this._onDidChangeCells.event; }
constructor(
public viewType: string,
private _model: NotebookEditorModel,
@IInstantiationService private readonly instantiationService: IInstantiationService,
) {
super();
this._register(this._model.onDidChangeCells(e => this._onDidChangeCells.fire(e)));
}
initialize(viewState: INotebookEditorViewState | undefined) {
this._viewCells = this._model!.notebook!.cells.map(cell => {
const isEditing = viewState && viewState.editingCells[cell.handle];
const viewCell = this.instantiationService.createInstance(CellViewModel, this.viewType, this._model!.notebook!.handle, cell, !!isEditing);
this._localStore.add(viewCell);
return viewCell;
});
return;
}
isDirty() {
return this._model.isDirty();
}
hide() {
this.viewCells.forEach(cell => {
if (cell.getText() !== '') {
cell.isEditing = false;
}
});
}
getViewCellIndex(cell: CellViewModel) {
return this.viewCells.indexOf(cell);
}
find(value: string): CellFindMatch[] {
let matches: CellFindMatch[] = [];
this.viewCells.forEach(cell => {
let cellMatches = cell.startFind(value);
matches.push(...cellMatches);
});
return matches;
}
insertCell(index: number, newCell: CellViewModel) {
this.viewCells!.splice(index, 0, newCell);
this._model.insertCell(newCell.cell, index);
}
deleteCell(index: number) {
let viewCell = this.viewCells[index];
this.viewCells.splice(index, 1);
this._model.deleteCell(viewCell.cell);
}
equal(model: NotebookEditorModel) {
return this._model === model;
}
dispose() {
this._localStore.clear();
this._viewCells.forEach(cell => {
cell.save();
});
super.dispose();
}
}
......@@ -89,7 +89,7 @@ class AbstractCellRenderer {
undefined,
true,
async () => {
await this.notebookEditor.insertEmptyNotebookCell(listIndex, element, 'code', 'above');
await this.notebookEditor.insertEmptyNotebookCell(element, 'code', 'above');
}
);
actions.push(insertAbove);
......@@ -100,7 +100,7 @@ class AbstractCellRenderer {
undefined,
true,
async () => {
await this.notebookEditor.insertEmptyNotebookCell(listIndex, element, 'code', 'below');
await this.notebookEditor.insertEmptyNotebookCell(element, 'code', 'below');
}
);
actions.push(insertBelow);
......@@ -111,7 +111,7 @@ class AbstractCellRenderer {
undefined,
true,
async () => {
await this.notebookEditor.insertEmptyNotebookCell(listIndex, element, 'markdown', 'above');
await this.notebookEditor.insertEmptyNotebookCell(element, 'markdown', 'above');
}
);
actions.push(insertMarkdownAbove);
......@@ -122,7 +122,7 @@ class AbstractCellRenderer {
undefined,
true,
async () => {
await this.notebookEditor.insertEmptyNotebookCell(listIndex, element, 'markdown', 'below');
await this.notebookEditor.insertEmptyNotebookCell(element, 'markdown', 'below');
}
);
actions.push(insertMarkdownBelow);
......@@ -134,7 +134,7 @@ class AbstractCellRenderer {
undefined,
true,
async () => {
this.notebookEditor.editNotebookCell(listIndex, element);
this.notebookEditor.editNotebookCell(element);
}
);
......@@ -146,7 +146,7 @@ class AbstractCellRenderer {
undefined,
true,
async () => {
this.notebookEditor.saveNotebookCell(listIndex, element);
this.notebookEditor.saveNotebookCell(element);
}
);
......@@ -159,7 +159,7 @@ class AbstractCellRenderer {
undefined,
true,
async () => {
this.notebookEditor.deleteNotebookCell(listIndex, element);
this.notebookEditor.deleteNotebookCell(element);
}
);
......
......@@ -104,7 +104,7 @@ export class CodeCell extends Disposable {
this._register(templateData.editor!.onDidContentSizeChange((e) => {
if (e.contentHeightChanged) {
if (viewCell.editorHeight !== e.contentHeight) {
if (this.viewCell.editorHeight !== e.contentHeight) {
templateData.editor?.layout(
{
width: e.contentWidth,
......@@ -112,13 +112,13 @@ export class CodeCell extends Disposable {
}
);
viewCell.editorHeight = e.contentHeight;
this.viewCell.editorHeight = e.contentHeight;
if (viewCell.outputs.length) {
let outputHeight = viewCell.getOutputTotalHeight();
notebookEditor.layoutNotebookCell(viewCell, viewCell.editorHeight + 32 + outputHeight);
if (this.viewCell.outputs.length) {
let outputHeight = this.viewCell.getOutputTotalHeight();
notebookEditor.layoutNotebookCell(this.viewCell, viewCell.editorHeight + 32 + outputHeight);
} else {
notebookEditor.layoutNotebookCell(viewCell, viewCell.editorHeight + 32);
notebookEditor.layoutNotebookCell(this.viewCell, viewCell.editorHeight + 32);
}
}
......
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { PieceTreeTextBufferFactory } from 'vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBufferBuilder';
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
import { NotebookEditorModel } from 'vs/workbench/contrib/notebook/browser/notebookEditorInput';
import { NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/notebookViewModel';
import { generateCellPath, ICell, INotebook, IOutput, NotebookCellOutputsSplice, NotebookCellsSplice } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/renderers/cellViewModel';
class MockCell implements ICell {
uri: URI;
private _onDidChangeOutputs = new Emitter<NotebookCellOutputsSplice[]>();
onDidChangeOutputs: Event<NotebookCellOutputsSplice[]> = this._onDidChangeOutputs.event;
private _isDirty: boolean = false;
private _outputs: IOutput[];
get outputs(): IOutput[] {
return this._outputs;
}
get isDirty() {
return this._isDirty;
}
set isDirty(newState: boolean) {
this._isDirty = newState;
}
constructor(
public viewType: string,
public handle: number,
public source: string[],
public language: string,
public cell_type: 'markdown' | 'code',
outputs: IOutput[]
) {
this._outputs = outputs;
this.uri = URI.from({
scheme: 'vscode-notebook',
authority: viewType,
path: generateCellPath(cell_type, handle),
query: ''
});
}
resolveTextBufferFactory(): PieceTreeTextBufferFactory {
throw new Error('Method not implemented.');
}
}
class MockNotebook extends Disposable implements INotebook {
private readonly _onDidChangeCells = new Emitter<NotebookCellsSplice[]>();
get onDidChangeCells(): Event<NotebookCellsSplice[]> { return this._onDidChangeCells.event; }
private _onDidChangeDirtyState = new Emitter<boolean>();
onDidChangeDirtyState: Event<boolean> = this._onDidChangeDirtyState.event;
private readonly _onWillDispose: Emitter<void> = this._register(new Emitter<void>());
readonly onWillDispose: Event<void> = this._onWillDispose.event;
cells: MockCell[];
activeCell: MockCell | undefined;
languages: string[] = [];
renderers = new Set<number>();
constructor(
public handle: number,
public viewType: string,
public uri: URI
) {
super();
this.cells = [];
}
save(): Promise<boolean> {
throw new Error('Method not implemented.');
}
}
suite('NotebookViewModel', () => {
const instantiationService = new TestInstantiationService();
const createCellViewModel = (viewType: string, notebookHandle: number, cellhandle: number, source: string[], language: string, cell_type: 'markdown' | 'code', outputs: IOutput[]) => {
const mockCell = new MockCell(viewType, cellhandle, source, language, cell_type, outputs);
return instantiationService.createInstance(CellViewModel, viewType, notebookHandle, mockCell, false);
};
const withNotebookDocument = (cells: [string[], string, 'markdown' | 'code', IOutput[]][], callback: (viewModel: NotebookViewModel) => void) => {
const viewType = 'notebook';
const notebook = new MockNotebook(0, viewType, URI.parse('test'));
notebook.cells = cells.map((cell, index) => {
return new MockCell(viewType, index, cell[0], cell[1], cell[2], cell[3]);
});
const model = new NotebookEditorModel(notebook);
const viewModel = new NotebookViewModel(viewType, model, instantiationService);
viewModel.initialize(undefined);
callback(viewModel);
viewModel.dispose();
return;
};
test('ctor', function () {
const notebook = new MockNotebook(0, 'notebook', URI.parse('test'));
const model = new NotebookEditorModel(notebook);
const viewModel = new NotebookViewModel('notebook', model, instantiationService);
assert.equal(viewModel.viewType, 'notebook');
});
test('insert/delete', function () {
withNotebookDocument(
[
[['var a = 1;'], 'javascript', 'code', []],
[['var b = 2;'], 'javascript', 'code', []]
],
(viewModel) => {
const cell = createCellViewModel(viewModel.viewType, viewModel.handle, 0, ['var c = 3;'], 'javascript', 'code', []);
viewModel.insertCell(1, cell);
assert.equal(viewModel.viewCells.length, 3);
assert.equal(viewModel.notebookDocument.cells.length, 3);
assert.equal(viewModel.getViewCellIndex(cell), 1);
viewModel.deleteCell(1);
assert.equal(viewModel.viewCells.length, 2);
assert.equal(viewModel.notebookDocument.cells.length, 2);
assert.equal(viewModel.getViewCellIndex(cell), -1);
}
);
});
test('index', function () {
withNotebookDocument(
[
[['var a = 1;'], 'javascript', 'code', []],
[['var b = 2;'], 'javascript', 'code', []]
],
(viewModel) => {
const firstViewCell = viewModel.viewCells[0];
const lastViewCell = viewModel.viewCells[viewModel.viewCells.length - 1];
const insertIndex = viewModel.getViewCellIndex(firstViewCell) + 1;
const cell = createCellViewModel(viewModel.viewType, viewModel.handle, 3, ['var c = 3;'], 'javascript', 'code', []);
viewModel.insertCell(insertIndex, cell);
const addedCellIndex = viewModel.getViewCellIndex(cell);
viewModel.deleteCell(addedCellIndex);
const secondInsertIndex = viewModel.getViewCellIndex(lastViewCell) + 1;
const cell2 = createCellViewModel(viewModel.viewType, viewModel.handle, 4, ['var d = 4;'], 'javascript', 'code', []);
viewModel.insertCell(secondInsertIndex, cell2);
assert.equal(viewModel.viewCells.length, 3);
assert.equal(viewModel.notebookDocument.cells.length, 3);
assert.equal(viewModel.getViewCellIndex(cell2), 2);
}
);
});
});
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册