Skip to content

Implement undo support for the custom editor #9946

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 24 commits into from
Feb 7, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions news/1 Enhancements/9821.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add undo/redo support to notebooks.
13 changes: 1 addition & 12 deletions src/client/common/application/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,7 @@
import { CancellationToken, Position, TextDocument, Uri } from 'vscode';
import { Commands as LSCommands } from '../../activation/languageServer/constants';
import { Commands as DSCommands } from '../../datascience/constants';
import { IEditCell, IInsertCell, ISwapCells } from '../../datascience/interactive-common/interactiveWindowTypes';
import { LiveKernelModel } from '../../datascience/jupyter/kernels/types';
import { ICell, IJupyterKernelSpec, INotebook } from '../../datascience/types';
import { PythonInterpreter } from '../../interpreter/contracts';
import { INotebook } from '../../datascience/types';
import { CommandSource } from '../../testing/common/constants';
import { TestFunction, TestsToRun } from '../../testing/common/types';
import { TestDataItem, TestWorkspaceFolder } from '../../testing/types';
Expand Down Expand Up @@ -148,12 +145,4 @@ export interface ICommandNameArgumentTypeMapping extends ICommandNameWithoutArgu
[DSCommands.ScrollToCell]: [string, string];
[DSCommands.ViewJupyterOutput]: [];
[DSCommands.SwitchJupyterKernel]: [INotebook | undefined];
[DSCommands.NotebookStorage_DeleteAllCells]: [Uri];
[DSCommands.NotebookStorage_ModifyCells]: [Uri, ICell[]];
[DSCommands.NotebookStorage_EditCell]: [Uri, IEditCell];
[DSCommands.NotebookStorage_InsertCell]: [Uri, IInsertCell];
[DSCommands.NotebookStorage_RemoveCell]: [Uri, string];
[DSCommands.NotebookStorage_SwapCells]: [Uri, ISwapCells];
[DSCommands.NotebookStorage_ClearCellOutputs]: [Uri];
[DSCommands.NotebookStorage_UpdateVersion]: [Uri, PythonInterpreter | undefined, IJupyterKernelSpec | LiveKernelModel | undefined];
}
10 changes: 0 additions & 10 deletions src/client/datascience/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,16 +62,6 @@ export namespace Commands {
export const ScrollToCell = 'python.datascience.scrolltocell';
export const CreateNewNotebook = 'python.datascience.createnewnotebook';
export const ViewJupyterOutput = 'python.datascience.viewJupyterOutput';

// Make sure to put these into the package .json
export const NotebookStorage_DeleteAllCells = 'python.datascience.notebook.deleteall';
export const NotebookStorage_ModifyCells = 'python.datascience.notebook.modifycells';
export const NotebookStorage_EditCell = 'python.datascience.notebook.editcell';
export const NotebookStorage_InsertCell = 'python.datascience.notebook.insertcell';
export const NotebookStorage_RemoveCell = 'python.datascience.notebook.removecell';
export const NotebookStorage_SwapCells = 'python.datascience.notebook.swapcells';
export const NotebookStorage_ClearCellOutputs = 'python.datascience.notebook.clearoutputs';
export const NotebookStorage_UpdateVersion = 'python.datascience.notebook.updateversion';
}

export namespace CodeLensCommands {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
'use strict';
import '../../../common/extensions';

import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api';
import { EndOfLine, Position, Range, TextDocument, TextDocumentContentChangeEvent, TextLine, Uri } from 'vscode';
import * as vscodeLanguageClient from 'vscode-languageclient';

import { PYTHON_LANGUAGE } from '../../../common/constants';
import { Identifiers } from '../../constants';
import { IEditorContentChange } from '../interactiveWindowTypes';
import { DefaultWordPattern, ensureValidWordDefinition, getWordAtText, regExpLeadsToEndlessLoop } from './wordHelper';

class IntellisenseLine implements TextLine {
Expand Down Expand Up @@ -201,54 +201,57 @@ export class IntellisenseDocument implements TextDocument {
}

public loadAllCells(cells: { code: string; id: string }[]): TextDocumentContentChangeEvent[] {
let changes: TextDocumentContentChangeEvent[] = [];
if (!this.inEditMode) {
this.inEditMode = true;
this._version += 1;
return this.reloadCells(cells);
}
return [];
}

// Normalize all of the cells, removing \r and separating each
// with a newline
const normalized = cells.map(c => {
return {
id: c.id,
code: `${c.code.replace(/\r/g, '')}\n`
};
});
public reloadCells(cells: { code: string; id: string }[]): TextDocumentContentChangeEvent[] {
this._version += 1;

// Normalize all of the cells, removing \r and separating each
// with a newline
const normalized = cells.map(c => {
return {
id: c.id,
code: `${c.code.replace(/\r/g, '')}\n`
};
});

// Contents are easy, just load all of the code in a row
this._contents = normalized
.map(c => c.code)
.reduce((p, c) => {
return `${p}${c}`;
});

// Cell ranges are slightly more complicated
let prev: number = 0;
this._cellRanges = normalized.map(c => {
const result = {
id: c.id,
start: prev,
fullEnd: prev + c.code.length,
currentEnd: prev + c.code.length
};
prev += c.code.length;
return result;
// Contents are easy, just load all of the code in a row
this._contents = normalized
.map(c => c.code)
.reduce((p, c) => {
return `${p}${c}`;
});

// Then create the lines.
this._lines = this.createLines();
// Cell ranges are slightly more complicated
let prev: number = 0;
this._cellRanges = normalized.map(c => {
const result = {
id: c.id,
start: prev,
fullEnd: prev + c.code.length,
currentEnd: prev + c.code.length
};
prev += c.code.length;
return result;
});

// Return our changes
changes = [
{
range: this.createSerializableRange(new Position(0, 0), new Position(0, 0)),
rangeOffset: 0,
rangeLength: 0, // Adds are always zero
text: this._contents
}
];
}
return changes;
// Then create the lines.
this._lines = this.createLines();

// Return our changes
return [
{
range: this.createSerializableRange(new Position(0, 0), new Position(0, 0)),
rangeOffset: 0,
rangeLength: 0, // Adds are always zero
text: this._contents
}
];
}

public addCell(fullCode: string, currentCode: string, id: string): TextDocumentContentChangeEvent[] {
Expand Down Expand Up @@ -293,16 +296,33 @@ export class IntellisenseDocument implements TextDocument {
];
}

public insertCell(id: string, code: string, codeCellAbove: string | undefined): TextDocumentContentChangeEvent[] {
public reloadCell(id: string, code: string): TextDocumentContentChangeEvent[] {
this._version += 1;

// Make sure to put a newline between this code and the next code
const newCode = `${code.replace(/\r/g, '')}\n`;

// Figure where this goes
const index = this._cellRanges.findIndex(r => r.id === id);
if (index >= 0) {
const start = this.positionAt(this._cellRanges[index].start);
const end = this.positionAt(this._cellRanges[index].currentEnd);
return this.removeRange(newCode, start, end, index);
}

return [];
}

public insertCell(id: string, code: string, codeCellAboveOrIndex: string | undefined | number): TextDocumentContentChangeEvent[] {
// This should only happen once for each cell.
this._version += 1;

// Make sure to put a newline between this code and the next code
const newCode = `${code.replace(/\r/g, '')}\n`;

// Figure where this goes
const aboveIndex = this._cellRanges.findIndex(r => r.id === codeCellAbove);
const insertIndex = aboveIndex + 1;
const aboveIndex = this._cellRanges.findIndex(r => r.id === codeCellAboveOrIndex);
const insertIndex = typeof codeCellAboveOrIndex === 'number' ? codeCellAboveOrIndex : aboveIndex + 1;

// Compute where we start from.
const fromOffset = insertIndex < this._cellRanges.length ? this._cellRanges[insertIndex].start : this._contents.length;
Expand Down Expand Up @@ -356,7 +376,7 @@ export class IntellisenseDocument implements TextDocument {
return [];
}

public edit(editorChanges: monacoEditor.editor.IModelContentChange[], id: string): TextDocumentContentChangeEvent[] {
public editCell(editorChanges: IEditorContentChange[], id: string): TextDocumentContentChangeEvent[] {
this._version += 1;

// Convert the range to local (and remove 1 based)
Expand Down
Loading