Skip to content

Commit b19eb75

Browse files
committed
Don't look for a jupyter interpreter when creating blank notebook (#8596)
* Creating a new blank notebook should not require a search for jupyter. * Fix bug caused by other fix.
1 parent 6110d15 commit b19eb75

File tree

5 files changed

+82
-47
lines changed

5 files changed

+82
-47
lines changed

news/2 Fixes/8481.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Creating a new blank notebook should not require a search for jupyter.

news/2 Fixes/8594.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Typing 'z' in a cell causes the cell to disappear.

src/client/datascience/interactive-ipynb/nativeEditor.ts

Lines changed: 37 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ export class NativeEditor extends InteractiveBase implements INotebookEditor {
164164
return this.close();
165165
}
166166

167-
public get contents(): string {
167+
public getContents(): Promise<string> {
168168
return this.generateNotebookContent(this.visibleCells);
169169
}
170170

@@ -470,31 +470,18 @@ export class NativeEditor extends InteractiveBase implements INotebookEditor {
470470
* @memberof NativeEditor
471471
*/
472472
private async updateVersionInfoInNotebook(): Promise<void> {
473+
// Make sure we have notebook json
474+
await this.ensureNotebookJson();
475+
473476
// Use the active interpreter
474477
const usableInterpreter = await this.jupyterExecution.getUsableJupyterPython();
475478
if (usableInterpreter && usableInterpreter.version && this.notebookJson.metadata && this.notebookJson.metadata.language_info) {
476479
this.notebookJson.metadata.language_info.version = `${usableInterpreter.version.major}.${usableInterpreter.version.minor}.${usableInterpreter.version.patch}`;
477480
}
478481
}
479482

480-
private async loadContents(contents: string | undefined, forceDirty: boolean): Promise<void> {
481-
// tslint:disable-next-line: no-any
482-
const json = contents ? JSON.parse(contents) as any : undefined;
483-
484-
// Double check json (if we have any)
485-
if (json && !json.cells) {
486-
throw new InvalidNotebookFileError(this.file.fsPath);
487-
}
488-
489-
// Then compute indent. It's computed from the contents
490-
if (contents) {
491-
this.indentAmount = detectIndent(contents).indent;
492-
}
493-
494-
// Then save the contents. We'll stick our cells back into this format when we save
495-
if (json) {
496-
this.notebookJson = json;
497-
} else {
483+
private async ensureNotebookJson(): Promise<void> {
484+
if (!this.notebookJson || !this.notebookJson.metadata) {
498485
const pythonNumber = await this.extractPythonMainVersion(this.notebookJson);
499486
// Use this to build our metadata object
500487
// Use these as the defaults unless we have been given some in the options.
@@ -522,6 +509,26 @@ export class NativeEditor extends InteractiveBase implements INotebookEditor {
522509
metadata: metadata
523510
};
524511
}
512+
}
513+
514+
private async loadContents(contents: string | undefined, forceDirty: boolean): Promise<void> {
515+
// tslint:disable-next-line: no-any
516+
const json = contents ? JSON.parse(contents) as any : undefined;
517+
518+
// Double check json (if we have any)
519+
if (json && !json.cells) {
520+
throw new InvalidNotebookFileError(this.file.fsPath);
521+
}
522+
523+
// Then compute indent. It's computed from the contents
524+
if (contents) {
525+
this.indentAmount = detectIndent(contents).indent;
526+
}
527+
528+
// Then save the contents. We'll stick our cells back into this format when we save
529+
if (json) {
530+
this.notebookJson = json;
531+
}
525532

526533
// Extract cells from the json
527534
const cells = contents ? json.cells as (nbformat.ICodeCell | nbformat.IRawCell | nbformat.IMarkdownCell)[] : [];
@@ -743,9 +750,12 @@ export class NativeEditor extends InteractiveBase implements INotebookEditor {
743750
}
744751

745752
private async setDirty(): Promise<void> {
746-
// Always update storage. Don't wait for results.
747-
this.storeContents(this.generateNotebookContent(this.visibleCells))
748-
.catch(ex => traceError('Failed to generate notebook content to store in state', ex));
753+
// Update storage if not untitled. Don't wait for results.
754+
if (!this.isUntitled) {
755+
this.generateNotebookContent(this.visibleCells)
756+
.then(c => this.storeContents(c).catch(ex => traceError('Failed to generate notebook content to store in state', ex)))
757+
.ignoreErrors();
758+
}
749759

750760
// Then update dirty flag.
751761
if (!this._dirty) {
@@ -827,7 +837,10 @@ export class NativeEditor extends InteractiveBase implements INotebookEditor {
827837
return usableInterpreter && usableInterpreter.version ? usableInterpreter.version.major : 3;
828838
}
829839

830-
private generateNotebookContent(cells: ICell[]): string {
840+
private async generateNotebookContent(cells: ICell[]): Promise<string> {
841+
// Make sure we have some
842+
await this.ensureNotebookJson();
843+
831844
// Reuse our original json except for the cells.
832845
const json = {
833846
...(this.notebookJson as nbformat.INotebookContent),
@@ -863,7 +876,7 @@ export class NativeEditor extends InteractiveBase implements INotebookEditor {
863876

864877
if (fileToSaveTo && isDirty) {
865878
// Write out our visible cells
866-
await this.fileSystem.writeFile(fileToSaveTo.fsPath, this.generateNotebookContent(this.visibleCells));
879+
await this.fileSystem.writeFile(fileToSaveTo.fsPath, await this.generateNotebookContent(this.visibleCells));
867880

868881
// Update our file name and dirty state
869882
this._file = fileToSaveTo;

src/datascience-ui/native-editor/nativeEditor.tsx

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -367,14 +367,16 @@ export class NativeEditor extends React.Component<INativeEditorProps, IMainState
367367
}
368368
case 'z':
369369
case 'Z':
370-
if (event.shiftKey && !event.ctrlKey && !event.altKey && this.stateController.canRedo()) {
371-
event.stopPropagation();
372-
this.stateController.redo();
373-
this.stateController.sendCommand(NativeCommandType.Redo, 'keyboard');
374-
} else if (!event.shiftKey && !event.altKey && !event.ctrlKey && this.stateController.canUndo()) {
375-
event.stopPropagation();
376-
this.stateController.undo();
377-
this.stateController.sendCommand(NativeCommandType.Undo, 'keyboard');
370+
if (this.stateController.getState().focusedCellId === undefined) {
371+
if (event.shiftKey && !event.ctrlKey && !event.altKey && this.stateController.canRedo()) {
372+
event.stopPropagation();
373+
this.stateController.redo();
374+
this.stateController.sendCommand(NativeCommandType.Redo, 'keyboard');
375+
} else if (!event.shiftKey && !event.altKey && !event.ctrlKey && this.stateController.canUndo()) {
376+
event.stopPropagation();
377+
this.stateController.undo();
378+
this.stateController.sendCommand(NativeCommandType.Undo, 'keyboard');
379+
}
378380
}
379381
break;
380382
default:

src/test/datascience/interactive-ipynb/nativeEditor.unit.test.ts

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,7 @@ suite('Data Science - Native Editor', () => {
263263
test('Create new editor and add some cells', async () => {
264264
const editor = createEditor();
265265
await editor.load(baseFile, Uri.parse('file:///foo.ipynb'));
266-
expect(editor.contents).to.be.equal(baseFile);
266+
expect(await editor.getContents()).to.be.equal(baseFile);
267267
editor.onMessage(InteractiveWindowMessages.InsertCell, { index: 0, cell: createEmptyCell('1', 1) });
268268
expect(editor.cells).to.be.lengthOf(4);
269269
expect(editor.isDirty).to.be.equal(true, 'Editor should be dirty');
@@ -273,7 +273,7 @@ suite('Data Science - Native Editor', () => {
273273
test('Move cells around', async () => {
274274
const editor = createEditor();
275275
await editor.load(baseFile, Uri.parse('file:///foo.ipynb'));
276-
expect(editor.contents).to.be.equal(baseFile);
276+
expect(await editor.getContents()).to.be.equal(baseFile);
277277
editor.onMessage(InteractiveWindowMessages.SwapCells, { firstCellId: 'NotebookImport#0', secondCellId: 'NotebookImport#1' });
278278
expect(editor.cells).to.be.lengthOf(3);
279279
expect(editor.isDirty).to.be.equal(true, 'Editor should be dirty');
@@ -283,7 +283,7 @@ suite('Data Science - Native Editor', () => {
283283
test('Edit/delete cells', async () => {
284284
const editor = createEditor();
285285
await editor.load(baseFile, Uri.parse('file:///foo.ipynb'));
286-
expect(editor.contents).to.be.equal(baseFile);
286+
expect(await editor.getContents()).to.be.equal(baseFile);
287287
expect(editor.isDirty).to.be.equal(false, 'Editor should not be dirty');
288288
editor.onMessage(InteractiveWindowMessages.EditCell, {
289289
changes: [{
@@ -313,7 +313,7 @@ suite('Data Science - Native Editor', () => {
313313
async function loadEditorAddCellAndWaitForMementoUpdate(file: Uri) {
314314
const editor = createEditor();
315315
await editor.load(baseFile, file);
316-
expect(editor.contents).to.be.equal(baseFile);
316+
expect(await editor.getContents()).to.be.equal(baseFile);
317317
storageUpdateSpy.resetHistory();
318318
editor.onMessage(InteractiveWindowMessages.InsertCell, { index: 0, cell: createEmptyCell('1', 1) });
319319
expect(editor.cells).to.be.lengthOf(4);
@@ -325,7 +325,7 @@ suite('Data Science - Native Editor', () => {
325325

326326
// Confirm contents were saved.
327327
expect(storage.get(`notebook-storage-${file.toString()}`)).not.to.be.undefined;
328-
expect(editor.contents).not.to.be.equal(baseFile);
328+
expect(await editor.getContents()).not.to.be.equal(baseFile);
329329

330330
return editor;
331331
}
@@ -359,8 +359,8 @@ suite('Data Science - Native Editor', () => {
359359

360360
// Verify contents are different.
361361
// Meaning it was not loaded from file, but loaded from our storage.
362-
expect(newEditor.contents).not.to.be.equal(baseFile);
363-
const notebook = JSON.parse(newEditor.contents);
362+
expect(await newEditor.getContents()).not.to.be.equal(baseFile);
363+
const notebook = JSON.parse(await newEditor.getContents());
364364
// 4 cells (1 extra for what was added)
365365
expect(notebook.cells).to.be.lengthOf(4);
366366
});
@@ -387,8 +387,8 @@ suite('Data Science - Native Editor', () => {
387387

388388
// Verify contents are different.
389389
// Meaning it was not loaded from file, but loaded from our storage.
390-
expect(newEditor.contents).not.to.be.equal(baseFile);
391-
const notebook = JSON.parse(newEditor.contents);
390+
expect(await newEditor.getContents()).not.to.be.equal(baseFile);
391+
const notebook = JSON.parse(await newEditor.getContents());
392392
// 4 cells (1 extra for what was added)
393393
expect(notebook.cells).to.be.lengthOf(4);
394394
});
@@ -417,18 +417,36 @@ suite('Data Science - Native Editor', () => {
417417

418418
// Verify contents are different.
419419
// Meaning it was not loaded from file, but loaded from our storage.
420-
expect(newEditor.contents).to.be.equal(baseFile);
420+
expect(await newEditor.getContents()).to.be.equal(baseFile);
421421
expect(newEditor.cells).to.be.lengthOf(3);
422422
});
423423

424+
test('Python version info is not queried on creating a blank editor', async () => {
425+
const file = Uri.parse('file:///Untitled1.ipynb');
426+
427+
// When a cell is executed, then ensure we store the python version info in the notebook data.
428+
when(executionProvider.getUsableJupyterPython()).thenReject();
429+
430+
const editor = createEditor();
431+
await editor.load('', file);
432+
433+
try {
434+
await editor.getContents();
435+
expect(false, 'Did not throw an error');
436+
} catch {
437+
// This should throw an error
438+
noop();
439+
}
440+
});
441+
424442
test('Pyton version info will be updated in notebook when a cell has been executed', async () => {
425443
const file = Uri.parse('file:///foo.ipynb');
426444

427445
const editor = createEditor();
428446
await editor.load(baseFile, file);
429-
expect(editor.contents).to.be.equal(baseFile);
447+
expect(await editor.getContents()).to.be.equal(baseFile);
430448
// At the begining version info is NOT in the file (at least not the same as what we are using to run cells).
431-
let contents = JSON.parse(editor.contents) as nbformat.INotebookContent;
449+
let contents = JSON.parse(await editor.getContents()) as nbformat.INotebookContent;
432450
expect(contents.metadata!.language_info!.version).to.not.equal('10.11.12');
433451

434452
// When a cell is executed, then ensure we store the python version info in the notebook data.
@@ -453,7 +471,7 @@ suite('Data Science - Native Editor', () => {
453471
}, 5_000, 'Timeout');
454472

455473
// Verify the version info is in the notbook.
456-
contents = JSON.parse(editor.contents) as nbformat.INotebookContent;
474+
contents = JSON.parse(await editor.getContents()) as nbformat.INotebookContent;
457475
expect(contents.metadata!.language_info!.version).to.equal('10.11.12');
458476
});
459477

@@ -471,7 +489,7 @@ suite('Data Science - Native Editor', () => {
471489
await editor.load('', file);
472490

473491
// It should load with that value
474-
expect(editor.contents).to.be.equal(baseFile);
492+
expect(await editor.getContents()).to.be.equal(baseFile);
475493
expect(editor.cells).to.be.lengthOf(3);
476494
});
477495

@@ -496,7 +514,7 @@ suite('Data Science - Native Editor', () => {
496514

497515
const editor = createEditor();
498516
await editor.load(baseFile, file);
499-
expect(editor.contents).to.be.equal(baseFile);
517+
expect(await editor.getContents()).to.be.equal(baseFile);
500518

501519
// Make our call to actually export
502520
editor.onMessage(InteractiveWindowMessages.Export, editor.cells);

0 commit comments

Comments
 (0)