add tests for joining single cell.
This commit is contained in:
parent
3f7651fed0
commit
881e370539
6 changed files with 172 additions and 151 deletions
|
@ -9,12 +9,16 @@ import { MenuId, registerAction2 } from 'vs/platform/actions/common/actions';
|
|||
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { InputFocusedContext } from 'vs/platform/contextkey/common/contextkeys';
|
||||
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { CellOverflowToolbarGroups, CellToolbarOrder, CELL_TITLE_CELL_GROUP_ID, INotebookCellActionContext, NotebookCellAction } from 'vs/workbench/contrib/notebook/browser/contrib/coreActions';
|
||||
import { expandCellRangesWithHiddenCells, NOTEBOOK_CELL_EDITABLE, NOTEBOOK_CELL_EDITOR_FOCUSED, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_FOCUSED } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
|
||||
import { expandCellRangesWithHiddenCells, ICellViewModel, NOTEBOOK_CELL_EDITABLE, NOTEBOOK_CELL_EDITOR_FOCUSED, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_FOCUSED } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
|
||||
import * as icons from 'vs/workbench/contrib/notebook/browser/notebookIcons';
|
||||
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { CellEditType, cellRangeContains, cellRangesToIndexes, NOTEBOOK_EDITOR_CURSOR_BEGIN_END, SelectionStateType } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
import { CellEditType, CellKind, cellRangeContains, cellRangesToIndexes, NOTEBOOK_EDITOR_CURSOR_BEGIN_END, SelectionStateType } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
import { cloneNotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel';
|
||||
import { CellViewModel, NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel';
|
||||
import { IBulkEditService, ResourceEdit, ResourceTextEdit } from 'vs/editor/browser/services/bulkEditService';
|
||||
import { ResourceNotebookCellEdit } from 'vs/workbench/contrib/bulkEdit/browser/bulkCellEdits';
|
||||
|
||||
const MOVE_CELL_UP_COMMAND_ID = 'notebook.cell.moveUp';
|
||||
const MOVE_CELL_DOWN_COMMAND_ID = 'notebook.cell.moveDown';
|
||||
|
@ -282,11 +286,108 @@ registerAction2(class extends NotebookCellAction {
|
|||
}
|
||||
});
|
||||
|
||||
export async function joinCells(context: INotebookCellActionContext, direction: 'above' | 'below'): Promise<void> {
|
||||
const cell = await context.notebookEditor.joinNotebookCells(context.cell, direction);
|
||||
if (cell) {
|
||||
context.notebookEditor.focusNotebookCell(cell, 'editor');
|
||||
export async function joinNotebookCells(viewModel: NotebookViewModel, cell: ICellViewModel, direction: 'above' | 'below', constraint?: CellKind): Promise<{ edits: ResourceEdit[], cell: ICellViewModel } | null> {
|
||||
if (!viewModel) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!viewModel.metadata.editable) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const index = viewModel.getCellIndex(cell);
|
||||
|
||||
if (!cell.getEvaluatedMetadata(viewModel.notebookDocument.metadata).editable) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (constraint && cell.cellKind !== constraint) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (index === 0 && direction === 'above') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (index === viewModel.length - 1 && direction === 'below') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (direction === 'above') {
|
||||
const above = viewModel.viewCells[index - 1] as CellViewModel;
|
||||
if (constraint && above.cellKind !== constraint) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!above.getEvaluatedMetadata(viewModel.notebookDocument.metadata).editable) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// const endSelections = [above.handle];
|
||||
const insertContent = (cell.textBuffer.getEOL() ?? '') + cell.getText();
|
||||
const aboveCellLineCount = above.textBuffer.getLineCount();
|
||||
const aboveCellLastLineEndColumn = above.textBuffer.getLineLength(aboveCellLineCount);
|
||||
|
||||
return {
|
||||
edits: [
|
||||
new ResourceTextEdit(above.uri, { range: new Range(aboveCellLineCount, aboveCellLastLineEndColumn + 1, aboveCellLineCount, aboveCellLastLineEndColumn + 1), text: insertContent }),
|
||||
new ResourceNotebookCellEdit(viewModel.notebookDocument.uri,
|
||||
{
|
||||
editType: CellEditType.Replace,
|
||||
index: index,
|
||||
count: 1,
|
||||
cells: []
|
||||
}
|
||||
)
|
||||
],
|
||||
cell: above
|
||||
};
|
||||
} else {
|
||||
const below = viewModel.viewCells[index + 1] as CellViewModel;
|
||||
if (constraint && below.cellKind !== constraint) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!below.getEvaluatedMetadata(viewModel.notebookDocument.metadata).editable) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const insertContent = (cell.textBuffer.getEOL() ?? '') + below.getText();
|
||||
|
||||
const cellLineCount = cell.textBuffer.getLineCount();
|
||||
const cellLastLineEndColumn = cell.textBuffer.getLineLength(cellLineCount);
|
||||
|
||||
return {
|
||||
edits: [
|
||||
new ResourceTextEdit(cell.uri, { range: new Range(cellLineCount, cellLastLineEndColumn + 1, cellLineCount, cellLastLineEndColumn + 1), text: insertContent }),
|
||||
new ResourceNotebookCellEdit(viewModel.notebookDocument.uri,
|
||||
{
|
||||
editType: CellEditType.Replace,
|
||||
index: index + 1,
|
||||
count: 1,
|
||||
cells: []
|
||||
}
|
||||
)
|
||||
],
|
||||
cell
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export async function joinCells(bulkEditService: IBulkEditService, context: INotebookCellActionContext, direction: 'above' | 'below'): Promise<void> {
|
||||
const ret = await joinNotebookCells(context.notebookEditor.viewModel, context.cell, direction);
|
||||
if (!ret) {
|
||||
return;
|
||||
}
|
||||
|
||||
await bulkEditService.apply(
|
||||
ret?.edits,
|
||||
{ quotableLabel: 'Join Notebook Cells' }
|
||||
);
|
||||
context.notebookEditor.focusNotebookCell(ret.cell, 'editor');
|
||||
// TODO
|
||||
// viewModel.selectionHandles = endSelections;
|
||||
|
||||
}
|
||||
|
||||
registerAction2(class extends NotebookCellAction {
|
||||
|
@ -310,7 +411,8 @@ registerAction2(class extends NotebookCellAction {
|
|||
}
|
||||
|
||||
async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) {
|
||||
return joinCells(context, 'above');
|
||||
const bulkEditService = accessor.get(IBulkEditService);
|
||||
return joinCells(bulkEditService, context, 'above');
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -335,6 +437,7 @@ registerAction2(class extends NotebookCellAction {
|
|||
}
|
||||
|
||||
async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) {
|
||||
return joinCells(context, 'below');
|
||||
const bulkEditService = accessor.get(IBulkEditService);
|
||||
return joinCells(bulkEditService, context, 'below');
|
||||
}
|
||||
});
|
||||
|
|
|
@ -4,9 +4,10 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { copyCellRange, moveCellRange } from 'vs/workbench/contrib/notebook/browser/contrib/cellOperations/cellOperations';
|
||||
import { ResourceNotebookCellEdit } from 'vs/workbench/contrib/bulkEdit/browser/bulkCellEdits';
|
||||
import { copyCellRange, joinNotebookCells, moveCellRange } from 'vs/workbench/contrib/notebook/browser/contrib/cellOperations/cellOperations';
|
||||
import { FoldingModel, updateFoldingStateAtIndex } from 'vs/workbench/contrib/notebook/browser/contrib/fold/foldingModel';
|
||||
import { CellKind, SelectionStateType } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
import { CellEditType, CellKind, SelectionStateType } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
import { withTestNotebook } from 'vs/workbench/contrib/notebook/test/testNotebookEditor';
|
||||
|
||||
suite('CellOperations', () => {
|
||||
|
@ -141,5 +142,56 @@ suite('CellOperations', () => {
|
|||
assert.strictEqual(viewModel.viewCells[3].getText(), 'var b = 1;');
|
||||
});
|
||||
});
|
||||
|
||||
test('Join cell with below - single cell', async function () {
|
||||
await withTestNotebook(
|
||||
[
|
||||
['# header a', 'markdown', CellKind.Markdown, [], {}],
|
||||
['var b = 1;', 'javascript', CellKind.Code, [], {}],
|
||||
['# header b', 'markdown', CellKind.Markdown, [], {}],
|
||||
['var b = 2;', 'javascript', CellKind.Code, [], {}],
|
||||
['var c = 3;', 'javascript', CellKind.Code, [], {}]
|
||||
],
|
||||
async (editor, accessor) => {
|
||||
const viewModel = editor.viewModel;
|
||||
viewModel.updateSelectionsState({ kind: SelectionStateType.Index, focus: { start: 3, end: 4 }, selections: [{ start: 3, end: 4 }] });
|
||||
const ret = await joinNotebookCells(editor.viewModel, viewModel.viewCells[3], 'below');
|
||||
assert.strictEqual(ret?.edits.length, 2);
|
||||
assert.deepStrictEqual(ret?.edits[1], new ResourceNotebookCellEdit(viewModel.notebookDocument.uri,
|
||||
{
|
||||
editType: CellEditType.Replace,
|
||||
index: 4,
|
||||
count: 1,
|
||||
cells: []
|
||||
}
|
||||
));
|
||||
});
|
||||
});
|
||||
|
||||
test('Join cell with above - single cell', async function () {
|
||||
await withTestNotebook(
|
||||
[
|
||||
['# header a', 'markdown', CellKind.Markdown, [], {}],
|
||||
['var b = 1;', 'javascript', CellKind.Code, [], {}],
|
||||
['# header b', 'markdown', CellKind.Markdown, [], {}],
|
||||
['var b = 2;', 'javascript', CellKind.Code, [], {}],
|
||||
['var c = 3;', 'javascript', CellKind.Code, [], {}]
|
||||
],
|
||||
async (editor, accessor) => {
|
||||
const viewModel = editor.viewModel;
|
||||
viewModel.updateSelectionsState({ kind: SelectionStateType.Index, focus: { start: 3, end: 4 }, selections: [{ start: 3, end: 4 }] });
|
||||
const ret = await joinNotebookCells(editor.viewModel, viewModel.viewCells[4], 'above');
|
||||
assert.strictEqual(ret?.edits.length, 2);
|
||||
assert.deepStrictEqual(ret?.edits[1], new ResourceNotebookCellEdit(viewModel.notebookDocument.uri,
|
||||
{
|
||||
editType: CellEditType.Replace,
|
||||
index: 4,
|
||||
count: 1,
|
||||
cells: []
|
||||
}
|
||||
));
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
|
|
@ -410,11 +410,6 @@ export interface INotebookEditor extends ICommonNotebookEditor {
|
|||
*/
|
||||
splitNotebookCell(cell: ICellViewModel): Promise<CellViewModel[] | null>;
|
||||
|
||||
/**
|
||||
* Joins the given cell either with the cell above or the one below depending on the given direction.
|
||||
*/
|
||||
joinNotebookCells(cell: ICellViewModel, direction: 'above' | 'below', constraint?: CellKind): Promise<ICellViewModel | null>;
|
||||
|
||||
/**
|
||||
* Delete a cell from the notebook
|
||||
*/
|
||||
|
|
|
@ -1669,31 +1669,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
|
|||
return this.viewModel.splitNotebookCell(index);
|
||||
}
|
||||
|
||||
async joinNotebookCells(cell: ICellViewModel, direction: 'above' | 'below', constraint?: CellKind): Promise<ICellViewModel | null> {
|
||||
if (!this.viewModel) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!this.viewModel.metadata.editable) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const index = this.viewModel.getCellIndex(cell);
|
||||
const ret = await this.viewModel.joinNotebookCells(index, direction, constraint);
|
||||
|
||||
if (ret) {
|
||||
ret.deletedCells.forEach(cell => {
|
||||
if (this._pendingLayouts.has(cell)) {
|
||||
this._pendingLayouts.get(cell)!.dispose();
|
||||
}
|
||||
});
|
||||
|
||||
return ret.cell;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async deleteNotebookCell(cell: ICellViewModel): Promise<boolean> {
|
||||
if (!this.viewModel) {
|
||||
return false;
|
||||
|
|
|
@ -878,106 +878,6 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD
|
|||
return null;
|
||||
}
|
||||
|
||||
async joinNotebookCells(index: number, direction: 'above' | 'below', constraint?: CellKind): Promise<{ cell: ICellViewModel, deletedCells: ICellViewModel[] } | null> {
|
||||
const cell = this.viewCells[index] as CellViewModel;
|
||||
|
||||
if (!this.metadata.editable) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!cell.getEvaluatedMetadata(this.notebookDocument.metadata).editable) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (constraint && cell.cellKind !== constraint) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (index === 0 && direction === 'above') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (index === this.length - 1 && direction === 'below') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (direction === 'above') {
|
||||
const above = this.viewCells[index - 1] as CellViewModel;
|
||||
if (constraint && above.cellKind !== constraint) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!above.getEvaluatedMetadata(this.notebookDocument.metadata).editable) {
|
||||
return null;
|
||||
}
|
||||
|
||||
await above.resolveTextModel();
|
||||
if (!above.hasModel()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const endSelections = [above.handle];
|
||||
const insertContent = (cell.textModel?.getEOL() ?? '') + cell.getText();
|
||||
const aboveCellLineCount = above.textModel.getLineCount();
|
||||
const aboveCellLastLineEndColumn = above.textModel.getLineLength(aboveCellLineCount);
|
||||
|
||||
await this._bulkEditService.apply(
|
||||
[
|
||||
new ResourceTextEdit(above.uri, { range: new Range(aboveCellLineCount, aboveCellLastLineEndColumn + 1, aboveCellLineCount, aboveCellLastLineEndColumn + 1), text: insertContent }),
|
||||
new ResourceNotebookCellEdit(this._notebook.uri,
|
||||
{
|
||||
editType: CellEditType.Replace,
|
||||
index: index,
|
||||
count: 1,
|
||||
cells: []
|
||||
}
|
||||
)
|
||||
],
|
||||
{ quotableLabel: 'Join Notebook Cells' }
|
||||
);
|
||||
|
||||
this.selectionHandles = endSelections;
|
||||
|
||||
return { cell: above, deletedCells: [cell] };
|
||||
} else {
|
||||
const below = this.viewCells[index + 1] as CellViewModel;
|
||||
if (constraint && below.cellKind !== constraint) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!below.getEvaluatedMetadata(this.notebookDocument.metadata).editable) {
|
||||
return null;
|
||||
}
|
||||
|
||||
await cell.resolveTextModel();
|
||||
if (!cell.hasModel()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const insertContent = (cell.textModel?.getEOL() ?? '') + below.getText();
|
||||
|
||||
const cellLineCount = cell.textModel.getLineCount();
|
||||
const cellLastLineEndColumn = cell.textModel.getLineLength(cellLineCount);
|
||||
|
||||
await this._bulkEditService.apply(
|
||||
[
|
||||
new ResourceTextEdit(cell.uri, { range: new Range(cellLineCount, cellLastLineEndColumn + 1, cellLineCount, cellLastLineEndColumn + 1), text: insertContent }),
|
||||
new ResourceNotebookCellEdit(this._notebook.uri,
|
||||
{
|
||||
editType: CellEditType.Replace,
|
||||
index: index + 1,
|
||||
count: 1,
|
||||
cells: []
|
||||
}
|
||||
)
|
||||
],
|
||||
{ quotableLabel: 'Join Notebook Cells' }
|
||||
);
|
||||
|
||||
return { cell, deletedCells: [below] };
|
||||
}
|
||||
}
|
||||
|
||||
getEditorViewState(): INotebookEditorViewState {
|
||||
const editingCells: { [key: number]: boolean } = {};
|
||||
this._viewCells.forEach((cell, i) => {
|
||||
|
|
|
@ -9,7 +9,6 @@ import { NotImplementedError } from 'vs/base/common/errors';
|
|||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService';
|
||||
import { ITextModelService } from 'vs/editor/common/services/resolverService';
|
||||
import { ContextKeyService } from 'vs/platform/contextkey/browser/contextKeyService';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
|
@ -35,6 +34,7 @@ import { IFileStatWithMetadata } from 'vs/platform/files/common/files';
|
|||
import { NotebookCellList } from 'vs/workbench/contrib/notebook/browser/view/notebookCellList';
|
||||
import { ListViewInfoAccessor } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidget';
|
||||
import { mock } from 'vs/base/test/common/mock';
|
||||
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
||||
export class TestCell extends NotebookCellTextModel {
|
||||
constructor(
|
||||
|
@ -125,7 +125,6 @@ export class NotebookEditorTestModel extends EditorModel implements INotebookEdi
|
|||
|
||||
export function setupInstantiationService() {
|
||||
const instantiationService = new TestInstantiationService();
|
||||
|
||||
instantiationService.stub(IUndoRedoService, instantiationService.createInstance(UndoRedoService));
|
||||
instantiationService.stub(IConfigurationService, new TestConfigurationService());
|
||||
instantiationService.stub(IThemeService, new TestThemeService());
|
||||
|
@ -137,14 +136,10 @@ export function setupInstantiationService() {
|
|||
return instantiationService;
|
||||
}
|
||||
|
||||
export async function withTestNotebook<R = any>(cells: [source: string, lang: string, kind: CellKind, output?: IOutputDto[], metadata?: NotebookCellMetadata][], callback: (editor: IActiveNotebookEditor, textModel: NotebookTextModel) => Promise<R> | R): Promise<R> {
|
||||
export async function withTestNotebook<R = any>(cells: [source: string, lang: string, kind: CellKind, output?: IOutputDto[], metadata?: NotebookCellMetadata][], callback: (editor: IActiveNotebookEditor, accessor: ServicesAccessor) => Promise<R> | R): Promise<R> {
|
||||
const instantiationService = setupInstantiationService();
|
||||
const undoRedoService = instantiationService.get(IUndoRedoService);
|
||||
const textModelService = instantiationService.get(ITextModelService);
|
||||
const bulkEditService = instantiationService.get(IBulkEditService);
|
||||
|
||||
const viewType = 'notebook';
|
||||
const notebook = new NotebookTextModel(viewType, URI.parse('test'), cells.map(cell => {
|
||||
const notebook = instantiationService.createInstance(NotebookTextModel, viewType, URI.parse('test'), cells.map(cell => {
|
||||
return {
|
||||
source: cell[0],
|
||||
language: cell[1],
|
||||
|
@ -152,10 +147,11 @@ export async function withTestNotebook<R = any>(cells: [source: string, lang: st
|
|||
outputs: cell[3] ?? [],
|
||||
metadata: cell[4]
|
||||
};
|
||||
}), notebookDocumentMetadataDefaults, { transientMetadata: {}, transientOutputs: false }, undoRedoService, textModelService);
|
||||
}), notebookDocumentMetadataDefaults, { transientMetadata: {}, transientOutputs: false });
|
||||
|
||||
const model = new NotebookEditorTestModel(notebook);
|
||||
const eventDispatcher = new NotebookEventDispatcher();
|
||||
const viewModel = new NotebookViewModel(viewType, model.notebook, eventDispatcher, null, instantiationService, bulkEditService, undoRedoService);
|
||||
const viewModel: NotebookViewModel = instantiationService.createInstance(NotebookViewModel, viewType, model.notebook, eventDispatcher, null);
|
||||
|
||||
const cellList = createNotebookCellList(instantiationService);
|
||||
cellList.attachViewModel(viewModel);
|
||||
|
@ -177,7 +173,7 @@ export async function withTestNotebook<R = any>(cells: [source: string, lang: st
|
|||
}
|
||||
};
|
||||
|
||||
const res = await callback(notebookEditor, notebook);
|
||||
const res = await callback(notebookEditor, instantiationService);
|
||||
if (res instanceof Promise) {
|
||||
res.finally(() => viewModel.dispose());
|
||||
} else {
|
||||
|
|
Loading…
Reference in a new issue