diff --git a/src/sql/media/icons/common-icons.css b/src/sql/media/icons/common-icons.css
index 5f18584766..621fbe45f3 100644
--- a/src/sql/media/icons/common-icons.css
+++ b/src/sql/media/icons/common-icons.css
@@ -524,6 +524,15 @@ Includes non-masked style declarations. */
background-image: url('start-outline.svg');
}
+.codicon:not(.masked-icon).icon-split-cell {
+ background-image: url("split_cell.svg");
+}
+
+.codicon.masked-icon.icon-split-cell:before {
+ -webkit-mask-image: url("split_cell.svg");
+ mask-image: url("split_cell.svg");
+}
+
/* Masked element inside pseudo element */
.masked-pseudo {
background-image: none !important;
diff --git a/src/sql/media/icons/split_cell.svg b/src/sql/media/icons/split_cell.svg
new file mode 100644
index 0000000000..cab46f46ec
--- /dev/null
+++ b/src/sql/media/icons/split_cell.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/sql/workbench/contrib/notebook/browser/cellToolbarActions.ts b/src/sql/workbench/contrib/notebook/browser/cellToolbarActions.ts
index 23f06b3575..5247ebeadc 100644
--- a/src/sql/workbench/contrib/notebook/browser/cellToolbarActions.ts
+++ b/src/sql/workbench/contrib/notebook/browser/cellToolbarActions.ts
@@ -18,7 +18,6 @@ import { INotebookService } from 'sql/workbench/services/notebook/browser/notebo
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { MoveDirection } from 'sql/workbench/services/notebook/browser/models/modelInterfaces';
-
const moreActionsLabel = localize('moreActionsLabel', "More");
export class EditCellAction extends ToggleableAction {
@@ -58,6 +57,29 @@ export class EditCellAction extends ToggleableAction {
}
}
+export class SplitCellAction extends CellActionBase {
+ public cellType: CellType;
+
+ constructor(
+ id: string,
+ label: string,
+ cssClass: string,
+ @INotificationService notificationService: INotificationService,
+ @INotebookService private notebookService: INotebookService,
+ ) {
+ super(id, label, cssClass, notificationService);
+ this._cssClass = cssClass;
+ this._tooltip = label;
+ this._label = '';
+ }
+ doRun(context: CellContext): Promise {
+ let model = context.model;
+ let index = model.cells.findIndex((cell) => cell.id === context.cell.id);
+ context.model?.splitCell(context.cell.cellType, this.notebookService, index);
+ return Promise.resolve();
+ }
+}
+
export class MoveCellAction extends CellActionBase {
constructor(
id: string,
diff --git a/src/sql/workbench/contrib/notebook/browser/cellViews/cellToolbar.component.ts b/src/sql/workbench/contrib/notebook/browser/cellViews/cellToolbar.component.ts
index 61885a0db8..f6b0f6537c 100644
--- a/src/sql/workbench/contrib/notebook/browser/cellViews/cellToolbar.component.ts
+++ b/src/sql/workbench/contrib/notebook/browser/cellViews/cellToolbar.component.ts
@@ -10,7 +10,7 @@ import { localize } from 'vs/nls';
import { Taskbar, ITaskbarContent } from 'sql/base/browser/ui/taskbar/taskbar';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
-import { DeleteCellAction, EditCellAction, CellToggleMoreActions, MoveCellAction } from 'sql/workbench/contrib/notebook/browser/cellToolbarActions';
+import { DeleteCellAction, EditCellAction, CellToggleMoreActions, MoveCellAction, SplitCellAction } from 'sql/workbench/contrib/notebook/browser/cellToolbarActions';
import { AddCellAction } from 'sql/workbench/contrib/notebook/browser/notebookActions';
import { CellTypes } from 'sql/workbench/services/notebook/common/contracts';
import { DropdownMenuActionViewItem } from 'sql/base/browser/ui/buttonMenu/buttonMenu';
@@ -33,6 +33,7 @@ export class CellToolbarComponent {
public buttonMoveDown = localize('buttonMoveDown', "Move cell down");
public buttonMoveUp = localize('buttonMoveUp', "Move cell up");
public buttonDelete = localize('buttonDelete', "Delete");
+ public buttonSplitCell = localize('splitCell', "Split cell");
@Input() cellModel: ICellModel;
@Input() model: NotebookModel;
@@ -58,6 +59,8 @@ export class CellToolbarComponent {
this._actionBar = new Taskbar(taskbar);
this._actionBar.context = context;
+ let splitCellButton = this.instantiationService.createInstance(SplitCellAction, 'notebook.SplitCellAtCursor', this.buttonSplitCell, 'masked-icon icon-split-cell');
+
let addCellsButton = this.instantiationService.createInstance(AddCellAction, 'notebook.AddCodeCell', localize('codeCellsPreview', "Add cell"), 'masked-pseudo code');
let addCodeCellButton = this.instantiationService.createInstance(AddCellAction, 'notebook.AddCodeCell', localize('codePreview', "Code cell"), 'masked-pseudo code');
@@ -96,9 +99,12 @@ export class CellToolbarComponent {
let taskbarContent: ITaskbarContent[] = [];
if (this.cellModel?.cellType === CellTypes.Markdown) {
- taskbarContent.push({ action: this._editCellAction });
+ taskbarContent.push(
+ { action: this._editCellAction }
+ );
}
taskbarContent.push(
+ { action: splitCellButton },
{ element: addCellDropdownContainer },
{ action: moveCellDownButton },
{ action: moveCellUpButton },
diff --git a/src/sql/workbench/contrib/notebook/test/browser/notebookViewModel.test.ts b/src/sql/workbench/contrib/notebook/test/browser/notebookViewModel.test.ts
index 8004bf16cd..9eb229415b 100644
--- a/src/sql/workbench/contrib/notebook/test/browser/notebookViewModel.test.ts
+++ b/src/sql/workbench/contrib/notebook/test/browser/notebookViewModel.test.ts
@@ -271,7 +271,7 @@ suite('NotebookViewModel', function (): void {
mockContentManager.setup(c => c.loadContent()).returns(() => Promise.resolve(contents));
defaultModelOptions.contentLoader = mockContentManager.object;
- let model = new NotebookModel(defaultModelOptions, undefined, logService, undefined, new NullAdsTelemetryService(), queryConnectionService.object, configurationService);
+ let model = new NotebookModel(defaultModelOptions, undefined, logService, undefined, new NullAdsTelemetryService(), queryConnectionService.object, configurationService, undefined);
await model.loadContents();
await model.requestModelLoad();
diff --git a/src/sql/workbench/contrib/notebook/test/browser/notebookViewsActions.test.ts b/src/sql/workbench/contrib/notebook/test/browser/notebookViewsActions.test.ts
index a2ee031ece..68aa29f59e 100644
--- a/src/sql/workbench/contrib/notebook/test/browser/notebookViewsActions.test.ts
+++ b/src/sql/workbench/contrib/notebook/test/browser/notebookViewsActions.test.ts
@@ -196,7 +196,7 @@ suite('Notebook Views Actions', function (): void {
mockContentManager.setup(c => c.loadContent()).returns(() => Promise.resolve(contents));
defaultModelOptions.contentLoader = mockContentManager.object;
- let model = new NotebookModel(defaultModelOptions, undefined, logService, undefined, new NullAdsTelemetryService(), queryConnectionService.object, configurationService);
+ let model = new NotebookModel(defaultModelOptions, undefined, logService, undefined, new NullAdsTelemetryService(), queryConnectionService.object, configurationService, undefined);
await model.loadContents();
await model.requestModelLoad();
diff --git a/src/sql/workbench/contrib/notebook/test/browser/notebookViewsExtension.test.ts b/src/sql/workbench/contrib/notebook/test/browser/notebookViewsExtension.test.ts
index bf630b4445..38d3864833 100644
--- a/src/sql/workbench/contrib/notebook/test/browser/notebookViewsExtension.test.ts
+++ b/src/sql/workbench/contrib/notebook/test/browser/notebookViewsExtension.test.ts
@@ -163,7 +163,7 @@ suite('NotebookViews', function (): void {
mockContentManager.setup(c => c.loadContent()).returns(() => Promise.resolve(initialNotebookContent));
defaultModelOptions.contentLoader = mockContentManager.object;
- let model = new NotebookModel(defaultModelOptions, undefined, logService, undefined, new NullAdsTelemetryService(), queryConnectionService.object, configurationService);
+ let model = new NotebookModel(defaultModelOptions, undefined, logService, undefined, new NullAdsTelemetryService(), queryConnectionService.object, configurationService, undefined);
await model.loadContents();
await model.requestModelLoad();
diff --git a/src/sql/workbench/contrib/notebook/test/electron-browser/notebookEditorModel.test.ts b/src/sql/workbench/contrib/notebook/test/electron-browser/notebookEditorModel.test.ts
index 1a8da8e6aa..da0157e02e 100644
--- a/src/sql/workbench/contrib/notebook/test/electron-browser/notebookEditorModel.test.ts
+++ b/src/sql/workbench/contrib/notebook/test/electron-browser/notebookEditorModel.test.ts
@@ -978,7 +978,7 @@ suite('Notebook Editor Model', function (): void {
let options: INotebookModelOptions = Object.assign({}, defaultModelOptions, >{
factory: mockModelFactory.object
});
- notebookModel = new NotebookModel(options, undefined, logService, undefined, new NullAdsTelemetryService(), queryConnectionService.object, configurationService);
+ notebookModel = new NotebookModel(options, undefined, logService, undefined, new NullAdsTelemetryService(), queryConnectionService.object, configurationService, undefined);
await notebookModel.loadContents();
}
diff --git a/src/sql/workbench/contrib/notebook/test/electron-browser/notebookFindModel.test.ts b/src/sql/workbench/contrib/notebook/test/electron-browser/notebookFindModel.test.ts
index 84a70698e6..94a5adb7e3 100644
--- a/src/sql/workbench/contrib/notebook/test/electron-browser/notebookFindModel.test.ts
+++ b/src/sql/workbench/contrib/notebook/test/electron-browser/notebookFindModel.test.ts
@@ -439,7 +439,7 @@ suite('Notebook Find Model', function (): void {
mockContentManager.setup(c => c.loadContent()).returns(() => Promise.resolve(contents));
defaultModelOptions.contentLoader = mockContentManager.object;
// Initialize the model
- model = new NotebookModel(defaultModelOptions, undefined, logService, undefined, new NullAdsTelemetryService(), queryConnectionService.object, configurationService);
+ model = new NotebookModel(defaultModelOptions, undefined, logService, undefined, new NullAdsTelemetryService(), queryConnectionService.object, configurationService, undefined);
await model.loadContents();
await model.requestModelLoad();
}
diff --git a/src/sql/workbench/services/notebook/browser/models/notebookModel.ts b/src/sql/workbench/services/notebook/browser/models/notebookModel.ts
index 10b29642ec..9f3e436a93 100644
--- a/src/sql/workbench/services/notebook/browser/models/notebookModel.ts
+++ b/src/sql/workbench/services/notebook/browser/models/notebookModel.ts
@@ -14,7 +14,7 @@ import { NotebookChangeType, CellType, CellTypes } from 'sql/workbench/services/
import { KernelsLanguage, nbversion } from 'sql/workbench/services/notebook/common/notebookConstants';
import * as notebookUtils from 'sql/workbench/services/notebook/browser/models/notebookUtils';
import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys';
-import { IExecuteManager, SQL_NOTEBOOK_PROVIDER, DEFAULT_NOTEBOOK_PROVIDER, ISerializationManager } from 'sql/workbench/services/notebook/browser/notebookService';
+import { IExecuteManager, SQL_NOTEBOOK_PROVIDER, DEFAULT_NOTEBOOK_PROVIDER, ISerializationManager, INotebookService } from 'sql/workbench/services/notebook/browser/notebookService';
import { NotebookContexts } from 'sql/workbench/services/notebook/browser/models/notebookContexts';
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
import { INotification, Severity, INotificationService } from 'vs/platform/notification/common/notification';
@@ -32,6 +32,9 @@ import { IConnectionManagementService } from 'sql/platform/connection/common/con
import { values } from 'vs/base/common/collections';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { isUUID } from 'vs/base/common/uuid';
+import { TextModel } from 'vs/editor/common/model/textModel';
+import { QueryTextEditor } from 'sql/workbench/browser/modelComponents/queryTextEditor';
+import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';
/*
* Used to control whether a message in a dialog/wizard is displayed as an error,
@@ -121,7 +124,6 @@ export class NotebookModel extends Disposable implements INotebookModel {
@IConnectionManagementService private connectionManagementService: IConnectionManagementService,
@IConfigurationService private configurationService: IConfigurationService,
@ICapabilitiesService private _capabilitiesService?: ICapabilitiesService
-
) {
super();
if (!_notebookOptions || !_notebookOptions.notebookUri || !_notebookOptions.executeManagers) {
@@ -538,8 +540,127 @@ export class NotebookModel extends Disposable implements INotebookModel {
if (this.inErrorState) {
return undefined;
}
- let cell = this.createCell(cellType);
+ let cell = this.createCell(cellType);
+ return this.insertCell(cell, index);
+ }
+
+ public splitCell(cellType: CellType, notebookService: INotebookService, index?: number): ICellModel | undefined {
+ if (this.inErrorState) {
+ return undefined;
+ }
+
+ let notebookEditor = notebookService.findNotebookEditor(this.notebookUri);
+ let cellEditorProvider = notebookEditor.cellEditors.find(e => e.cellGuid() === this.cells[index].cellGuid);
+ //Only split the cell if the markdown editor is open.
+ //TODO: Need to handle splitting of cell if the selection is on webview
+ if (cellEditorProvider) {
+ let editor = cellEditorProvider.getEditor() as QueryTextEditor;
+ if (editor) {
+ let editorControl = editor.getControl() as CodeEditorWidget;
+
+ let model = editorControl.getModel() as TextModel;
+ let range = model.getFullModelRange();
+ let selection = editorControl.getSelection();
+ let source = this.cells[index].source;
+ let newCell = undefined, tailCell = undefined, partialSource = undefined;
+ let newCellIndex = index;
+ let tailCellIndex = index;
+
+ // Save UI state
+ let showMarkdown = this.cells[index].showMarkdown;
+ let showPreview = this.cells[index].showPreview;
+
+ //Get selection value from current cell
+ let newCellContent = model.getValueInRange(selection);
+
+ //Get content after selection
+ let tailRange = range.setStartPosition(selection.endLineNumber, selection.endColumn);
+ let tailCellContent = model.getValueInRange(tailRange);
+
+ //Get content before selection
+ let headRange = range.setEndPosition(selection.startLineNumber, selection.startColumn);
+ let headContent = model.getValueInRange(headRange);
+
+ // If the selection is equal to entire content then do nothing
+ if (headContent.length === 0 && tailCellContent.length === 0) {
+ return undefined;
+ }
+
+ //Set content before selection if the selection is not the same as original content
+ if (headContent.length) {
+ let headsource = source.slice(range.startLineNumber - 1, selection.startLineNumber - 1);
+ if (selection.startColumn > 1) {
+ partialSource = source.slice(selection.startLineNumber - 1, selection.startLineNumber)[0].slice(0, selection.startColumn - 1);
+ headsource = headsource.concat(partialSource.toString());
+ }
+ this.cells[index].source = headsource;
+ }
+
+ if (newCellContent.length) {
+ let newSource = source.slice(selection.startLineNumber - 1, selection.endLineNumber) as string[];
+ if (selection.startColumn > 1) {
+ partialSource = source.slice(selection.startLineNumber - 1)[0].slice(selection.startColumn - 1);
+ newSource.splice(0, 1, partialSource);
+ }
+ if (selection.endColumn !== source[selection.endLineNumber - 1].length) {
+ let splicestart = 0;
+ if (selection.startLineNumber === selection.endLineNumber) {
+ splicestart = selection.startColumn - 1;
+ }
+ let partial = source.slice(selection.endLineNumber - 1, selection.endLineNumber)[0].slice(splicestart, selection.endColumn - 1);
+ newSource.splice(newSource.length - 1, 1, partial);
+ }
+ //If the selection is not from the start of the cell, create a new cell.
+ if (headContent.length) {
+ newCell = this.createCell(cellType);
+ newCell.source = newSource;
+ newCellIndex++;
+ this.insertCell(newCell, newCellIndex);
+ }
+ else { //update the existing cell
+ this.cells[index].source = newSource;
+ }
+ }
+
+ if (tailCellContent.length) {
+ //tail cell will be of original cell type.
+ tailCell = this.createCell(this._cells[index].cellType);
+ let tailSource = source.slice(tailRange.startLineNumber - 1) as string[];
+ if (selection.endColumn > 1) {
+ partialSource = source.slice(tailRange.startLineNumber - 1, tailRange.startLineNumber)[0].slice(tailRange.startColumn - 1);
+ tailSource.splice(0, 1, partialSource);
+ }
+ tailCell.source = tailSource;
+ tailCellIndex = newCellIndex + 1;
+ this.insertCell(tailCell, tailCellIndex);
+ }
+
+ let activeCell = newCell ? newCell : (headContent.length ? tailCell : this.cells[index]);
+ let activeCellIndex = newCell ? newCellIndex : (headContent.length ? tailCellIndex : index);
+
+ //make new cell Active
+ this.updateActiveCell(activeCell);
+ activeCell.isEditMode = true;
+ this._contentChangedEmitter.fire({
+ changeType: NotebookChangeType.CellsModified,
+ cells: [activeCell],
+ cellIndex: activeCellIndex
+ });
+ activeCell.showMarkdown = showMarkdown;
+ activeCell.showPreview = showPreview;
+
+ //return inserted cell
+ return activeCell;
+ }
+ }
+ return undefined;
+ }
+
+ public insertCell(cell: ICellModel, index?: number): ICellModel | undefined {
+ if (this.inErrorState) {
+ return undefined;
+ }
if (index !== undefined && index !== null && index >= 0 && index < this._cells.length) {
this._cells.splice(index, 0, cell);
} else {
@@ -554,7 +675,6 @@ export class NotebookModel extends Disposable implements INotebookModel {
cells: [cell],
cellIndex: index
});
-
return cell;
}