mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
* Fast update WYSIWYG support for source update * Do bracket matching over hardcoding line offsets
This commit is contained in:
@@ -105,6 +105,25 @@ export class NotebookTextFileModel {
|
|||||||
}]);
|
}]);
|
||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
|
} else if (contentChange && areRangePropertiesPopulated(cellGuidRange)) {
|
||||||
|
// If no modelContentChanged event, then we're replacing the entire source for that cell
|
||||||
|
let sourceEnd = this.getSourceEndRange(textEditorModel, contentChange.cells[0].cellGuid);
|
||||||
|
if (sourceEnd) {
|
||||||
|
// Need to subtract one because we're going from 1-based to 0-based
|
||||||
|
let startSpaces: string = repeat(' ', cellGuidRange.startColumn - 1);
|
||||||
|
let escapedQuotesAndBackslashes = contentChange.cells[0].source.join('\n').replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
||||||
|
|
||||||
|
// The text here transforms a string from 'This is a string\n this is another string' to:
|
||||||
|
// This is a string
|
||||||
|
// this is another string
|
||||||
|
|
||||||
|
// Note: Adding 1 to startColumn to avoid overwriting first "
|
||||||
|
textEditorModel.textEditorModel.applyEdits([{
|
||||||
|
range: new Range(this._sourceBeginRange.startLineNumber, this._sourceBeginRange.startColumn + 1, sourceEnd.endLineNumber, sourceEnd.endColumn),
|
||||||
|
text: escapedQuotesAndBackslashes.split(/[\r\n]+/gm).join('\\n\",'.concat(this._eol).concat(startSpaces).concat('\"'))
|
||||||
|
}]);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -209,6 +228,45 @@ export class NotebookTextFileModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getSourceEndRange(textEditorModel: ITextEditorModel, cellGuid: string): IRange | undefined {
|
||||||
|
if (!cellGuid) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
let cellGuidMatches = findOrSetCellGuidMatch(textEditorModel, cellGuid);
|
||||||
|
if (cellGuidMatches?.length > 0) {
|
||||||
|
if (!this._sourceBeginRange) {
|
||||||
|
this.updateSourceBeginRange(textEditorModel, cellGuid);
|
||||||
|
}
|
||||||
|
// Source begin range tracks where the first " in exists.
|
||||||
|
// The line before that will always include '"source": ['
|
||||||
|
let sourceBeforeLineNumber = this._sourceBeginRange?.startLineNumber - 1;
|
||||||
|
if (sourceBeforeLineNumber) {
|
||||||
|
// The 2nd to last column (ie before newline) is guaranteed to be [
|
||||||
|
let sourceBeforeColumn = textEditorModel.textEditorModel.getLineMaxColumn(sourceBeforeLineNumber);
|
||||||
|
if (sourceBeforeColumn) {
|
||||||
|
// Match the end of the source array
|
||||||
|
let sourceEnd = textEditorModel.textEditorModel.matchBracket({ column: sourceBeforeColumn - 1, lineNumber: sourceBeforeLineNumber });
|
||||||
|
if (sourceEnd?.length === 2) {
|
||||||
|
// Last quote in the source array will end the line before the source array
|
||||||
|
// e.g.
|
||||||
|
// "source": [
|
||||||
|
// "SELECT 12" <-- Looking for this " position
|
||||||
|
// ],
|
||||||
|
let lineForSourceEnd = sourceEnd[1].endLineNumber - 1;
|
||||||
|
let lastCharacterPosition = textEditorModel.textEditorModel.getLineLength(lineForSourceEnd);
|
||||||
|
return {
|
||||||
|
startColumn: lastCharacterPosition,
|
||||||
|
startLineNumber: lineForSourceEnd,
|
||||||
|
endLineNumber: lineForSourceEnd,
|
||||||
|
endColumn: lastCharacterPosition
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
// Find the beginning of a cell's outputs in the text editor model
|
// Find the beginning of a cell's outputs in the text editor model
|
||||||
private updateOutputBeginRange(textEditorModel: ITextEditorModel, cellGuid: string): void {
|
private updateOutputBeginRange(textEditorModel: ITextEditorModel, cellGuid: string): void {
|
||||||
if (!cellGuid) {
|
if (!cellGuid) {
|
||||||
|
|||||||
@@ -400,6 +400,87 @@ suite('Notebook Editor Model', function (): void {
|
|||||||
assert(!notebookEditorModel.lastEditFullReplacement);
|
assert(!notebookEditorModel.lastEditFullReplacement);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should not replace entire text model but replace entire source when no modelContentChangedEvent passed in', async function (): Promise<void> {
|
||||||
|
await createNewNotebookModel();
|
||||||
|
let notebookEditorModel = await createTextEditorModel(this);
|
||||||
|
notebookEditorModel.replaceEntireTextEditorModel(notebookModel, undefined);
|
||||||
|
|
||||||
|
let newCell = notebookModel.addCell(CellTypes.Code);
|
||||||
|
|
||||||
|
let contentChange: NotebookContentChange = {
|
||||||
|
changeType: NotebookChangeType.CellsModified,
|
||||||
|
cells: [newCell],
|
||||||
|
cellIndex: 0
|
||||||
|
};
|
||||||
|
notebookEditorModel.updateModel(contentChange, NotebookChangeType.CellsModified);
|
||||||
|
assert(notebookEditorModel.lastEditFullReplacement);
|
||||||
|
|
||||||
|
assert.equal(notebookEditorModel.editorModel.textEditorModel.getLineContent(14), ' "outputs": [');
|
||||||
|
|
||||||
|
newCell.source = 'This is a test';
|
||||||
|
|
||||||
|
contentChange = {
|
||||||
|
changeType: NotebookChangeType.CellSourceUpdated,
|
||||||
|
cells: [newCell],
|
||||||
|
cellIndex: 0,
|
||||||
|
modelContentChangedEvent: undefined
|
||||||
|
};
|
||||||
|
|
||||||
|
notebookEditorModel.updateModel(contentChange, NotebookChangeType.CellSourceUpdated);
|
||||||
|
|
||||||
|
assert(!notebookEditorModel.lastEditFullReplacement, 'should not do a full replacement for a source update');
|
||||||
|
|
||||||
|
assert.equal(notebookEditorModel.editorModel.textEditorModel.getLineContent(8), ' "source": [');
|
||||||
|
assert.equal(notebookEditorModel.editorModel.textEditorModel.getLineContent(9), ' "This is a test"');
|
||||||
|
assert.equal(notebookEditorModel.editorModel.textEditorModel.getLineContent(10), ' ],');
|
||||||
|
assert.equal(notebookEditorModel.editorModel.textEditorModel.getLineContent(12), ' "azdata_cell_guid": "' + newCell.cellGuid + '"');
|
||||||
|
assert.equal(notebookEditorModel.editorModel.textEditorModel.getLineContent(14), ' "outputs": [');
|
||||||
|
assert.equal(notebookEditorModel.editorModel.textEditorModel.getLineContent(25), ' "execution_count": null');
|
||||||
|
assert.equal(notebookEditorModel.editorModel.textEditorModel.getLineContent(26), ' }');
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not replace entire text model but replace entire source when no modelContentChangedEvent passed in multiline change', async function (): Promise<void> {
|
||||||
|
await createNewNotebookModel();
|
||||||
|
let notebookEditorModel = await createTextEditorModel(this);
|
||||||
|
notebookEditorModel.replaceEntireTextEditorModel(notebookModel, undefined);
|
||||||
|
|
||||||
|
let newCell = notebookModel.addCell(CellTypes.Code);
|
||||||
|
|
||||||
|
let contentChange: NotebookContentChange = {
|
||||||
|
changeType: NotebookChangeType.CellsModified,
|
||||||
|
cells: [newCell],
|
||||||
|
cellIndex: 0
|
||||||
|
};
|
||||||
|
notebookEditorModel.updateModel(contentChange, NotebookChangeType.CellsModified);
|
||||||
|
assert(notebookEditorModel.lastEditFullReplacement);
|
||||||
|
|
||||||
|
assert.equal(notebookEditorModel.editorModel.textEditorModel.getLineContent(14), ' "outputs": [');
|
||||||
|
|
||||||
|
newCell.source = 'This is a test' + os.EOL + 'Line 2 test' + os.EOL + 'Line 3 test';
|
||||||
|
|
||||||
|
contentChange = {
|
||||||
|
changeType: NotebookChangeType.CellSourceUpdated,
|
||||||
|
cells: [newCell],
|
||||||
|
cellIndex: 0,
|
||||||
|
modelContentChangedEvent: undefined
|
||||||
|
};
|
||||||
|
|
||||||
|
notebookEditorModel.updateModel(contentChange, NotebookChangeType.CellSourceUpdated);
|
||||||
|
|
||||||
|
assert(!notebookEditorModel.lastEditFullReplacement, 'should not do a full replacement for a source update');
|
||||||
|
|
||||||
|
assert.equal(notebookEditorModel.editorModel.textEditorModel.getLineContent(8), ' "source": [');
|
||||||
|
assert.equal(notebookEditorModel.editorModel.textEditorModel.getLineContent(9), ' "This is a test\\n",');
|
||||||
|
assert.equal(notebookEditorModel.editorModel.textEditorModel.getLineContent(10), ' "Line 2 test\\n",');
|
||||||
|
assert.equal(notebookEditorModel.editorModel.textEditorModel.getLineContent(11), ' "Line 3 test"');
|
||||||
|
assert.equal(notebookEditorModel.editorModel.textEditorModel.getLineContent(12), ' ],');
|
||||||
|
assert.equal(notebookEditorModel.editorModel.textEditorModel.getLineContent(14), ' "azdata_cell_guid": "' + newCell.cellGuid + '"');
|
||||||
|
assert.equal(notebookEditorModel.editorModel.textEditorModel.getLineContent(16), ' "outputs": [');
|
||||||
|
assert.equal(notebookEditorModel.editorModel.textEditorModel.getLineContent(27), ' "execution_count": null');
|
||||||
|
assert.equal(notebookEditorModel.editorModel.textEditorModel.getLineContent(28), ' }');
|
||||||
|
});
|
||||||
|
|
||||||
test('should not replace entire text model for single line source change then delete', async function (): Promise<void> {
|
test('should not replace entire text model for single line source change then delete', async function (): Promise<void> {
|
||||||
await createNewNotebookModel();
|
await createNewNotebookModel();
|
||||||
let notebookEditorModel = await createTextEditorModel(this);
|
let notebookEditorModel = await createTextEditorModel(this);
|
||||||
|
|||||||
Reference in New Issue
Block a user