diff --git a/news/2 Fixes/6738.md b/news/2 Fixes/6738.md new file mode 100644 index 000000000000..e3a1e6d38210 --- /dev/null +++ b/news/2 Fixes/6738.md @@ -0,0 +1 @@ +Debugging an untitled file causes an error 'Untitled-1 cannot be opened'. diff --git a/package-lock.json b/package-lock.json index c2c3e0e506bf..9cef74f373c3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1885,13 +1885,13 @@ }, "@types/json5": { "version": "0.0.29", - "resolved": "http://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", "dev": true }, "@types/loader-utils": { "version": "1.1.3", - "resolved": "http://registry.npmjs.org/@types/loader-utils/-/loader-utils-1.1.3.tgz", + "resolved": "https://registry.npmjs.org/@types/loader-utils/-/loader-utils-1.1.3.tgz", "integrity": "sha512-euKGFr2oCB3ASBwG39CYJMR3N9T0nanVqXdiH7Zu/Nqddt6SmFRxytq/i2w9LQYNQekEtGBz+pE3qG6fQTNvRg==", "dev": true, "requires": { @@ -2012,7 +2012,7 @@ }, "@types/relateurl": { "version": "0.2.28", - "resolved": "http://registry.npmjs.org/@types/relateurl/-/relateurl-0.2.28.tgz", + "resolved": "https://registry.npmjs.org/@types/relateurl/-/relateurl-0.2.28.tgz", "integrity": "sha1-a9p9uGU/piZD9e5p6facEaOS46Y=", "dev": true }, @@ -3261,7 +3261,7 @@ }, "babel-plugin-syntax-jsx": { "version": "6.18.0", - "resolved": "http://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", "integrity": "sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY=", "dev": true }, @@ -3672,7 +3672,7 @@ }, "browserify-aes": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", "dev": true, "requires": { @@ -3724,7 +3724,7 @@ }, "browserify-rsa": { "version": "4.0.1", - "resolved": "http://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", "dev": true, "requires": { @@ -5231,7 +5231,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "requires": { "core-util-is": "~1.0.0", @@ -5687,7 +5687,7 @@ }, "create-hash": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", "dev": true, "requires": { @@ -5700,7 +5700,7 @@ }, "create-hmac": { "version": "1.1.7", - "resolved": "http://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", "dev": true, "requires": { @@ -6638,7 +6638,7 @@ }, "diffie-hellman": { "version": "5.0.3", - "resolved": "http://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", "dev": true, "requires": { @@ -7606,7 +7606,7 @@ }, "external-editor": { "version": "2.2.0", - "resolved": "http://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==", "dev": true, "requires": { @@ -9143,7 +9143,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -10178,7 +10178,7 @@ }, "html-webpack-plugin": { "version": "3.2.0", - "resolved": "http://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-3.2.0.tgz", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-3.2.0.tgz", "integrity": "sha1-sBq71yOsqqeze2r0SS69oD2d03s=", "dev": true, "requires": { @@ -12673,7 +12673,7 @@ }, "media-typer": { "version": "0.3.0", - "resolved": "http://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", "dev": true }, @@ -13242,7 +13242,7 @@ }, "moment": { "version": "2.21.0", - "resolved": "http://registry.npmjs.org/moment/-/moment-2.21.0.tgz", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.21.0.tgz", "integrity": "sha512-TCZ36BjURTeFTM/CwRcViQlfkMvL1/vFISuNLO5GkcVm1+QHfbSiNqZuWeMFjj1/3+uAjXswgRk30j1kkLYJBQ==" }, "monaco-editor": { @@ -14594,7 +14594,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -16856,7 +16856,7 @@ }, "sha.js": { "version": "2.4.11", - "resolved": "http://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", "dev": true, "requires": { @@ -16941,7 +16941,7 @@ }, "simple-html-tokenizer": { "version": "0.1.1", - "resolved": "http://registry.npmjs.org/simple-html-tokenizer/-/simple-html-tokenizer-0.1.1.tgz", + "resolved": "https://registry.npmjs.org/simple-html-tokenizer/-/simple-html-tokenizer-0.1.1.tgz", "integrity": "sha1-BcLuxXn//+FFoDCsJs/qYbmA+r4=", "dev": true }, @@ -17588,7 +17588,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -19016,7 +19016,7 @@ "dependencies": { "@types/react": { "version": "0.14.57", - "resolved": "http://registry.npmjs.org/@types/react/-/react-0.14.57.tgz", + "resolved": "https://registry.npmjs.org/@types/react/-/react-0.14.57.tgz", "integrity": "sha1-GHioZU+v3R04G4RXKStkM0mMW2I=", "dev": true } @@ -19808,7 +19808,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -20434,7 +20434,7 @@ }, "webpack-node-externals": { "version": "1.7.2", - "resolved": "http://registry.npmjs.org/webpack-node-externals/-/webpack-node-externals-1.7.2.tgz", + "resolved": "https://registry.npmjs.org/webpack-node-externals/-/webpack-node-externals-1.7.2.tgz", "integrity": "sha512-ajerHZ+BJKeCLviLUUmnyd5B4RavLF76uv3cs6KNuO8W+HuQaEs0y0L7o40NQxdPy5w0pcv8Ew7yPUAQG0UdCg==", "dev": true }, @@ -20666,7 +20666,7 @@ }, "wrap-ansi": { "version": "2.1.0", - "resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", "dev": true, "requires": { diff --git a/src/client/datascience/interactive-window/interactiveWindow.ts b/src/client/datascience/interactive-window/interactiveWindow.ts index 0134932a356d..d0a93dd4392b 100644 --- a/src/client/datascience/interactive-window/interactiveWindow.ts +++ b/src/client/datascience/interactive-window/interactiveWindow.ts @@ -188,9 +188,38 @@ export class InteractiveWindow extends WebViewHost im return this.submitCode(code, file, line, undefined, editor, false); } - public debugCode(code: string, file: string, line: number, editor?: TextEditor): Promise { - // Call the internal method. - return this.submitCode(code, file, line, undefined, editor, true); + public async debugCode(code: string, file: string, line: number, editor?: TextEditor): Promise { + let saved = true; + // Make sure the file is saved before debugging + const doc = this.documentManager.textDocuments.find(d => d.fileName === file); + if (doc && doc.isUntitled) { + // Before we start, get the list of documents + const beforeSave = [...this.documentManager.textDocuments]; + + saved = await doc.save(); + + // If that worked, we have to open the new document. It should be + // the new entry in the list + if (saved) { + const diff = this.documentManager.textDocuments.filter(f => beforeSave.indexOf(f) === -1); + if (diff && diff.length > 0) { + file = diff[0].fileName; + + // Open the new document + await this.documentManager.openTextDocument(file); + + // Change the editor to the new file + editor = this.documentManager.visibleTextEditors.find(e => e.document.fileName === file); + } + } + } + + // Call the internal method if we were able to save + if (saved) { + return this.submitCode(code, file, line, undefined, editor, true); + } + + return false; } // tslint:disable-next-line: no-any no-empty cyclomatic-complexity max-func-body-length diff --git a/src/test/datascience/debugger.functional.test.tsx b/src/test/datascience/debugger.functional.test.tsx index 492bd6381132..5e25b8349357 100644 --- a/src/test/datascience/debugger.functional.test.tsx +++ b/src/test/datascience/debugger.functional.test.tsx @@ -28,6 +28,7 @@ import { MockDebuggerService } from './mockDebugService'; import { MockDocumentManager } from './mockDocumentManager'; //import { asyncDump } from '../common/asyncDump'; +import { MockDocument } from './mockDocument'; // tslint:disable-next-line:max-func-body-length no-any suite('DataScience Debugger tests', () => { const disposables: Disposable[] = []; @@ -240,4 +241,42 @@ suite('DataScience Debugger tests', () => { await debugCell('#%%\nprint("bar")', undefined, undefined, true); } }); + + test('Debug temporary file', async () => { + ioc.getSettings().datascience.stopOnFirstLineWhileDebugging = true; + const code = '#%%\nprint("bar")'; + + // Create a dummy document with just this code + const docManager = ioc.get(IDocumentManager) as MockDocumentManager; + const fileName = 'Untitled-1'; + docManager.addDocument(code, fileName); + const mockDoc = docManager.textDocuments[0] as MockDocument; + mockDoc.forceUntitled(); + + // Start the jupyter server + const history = await getOrCreateInteractiveWindow(); + const expectedBreakLine = 2; // 2 because of the 'breakpoint()' that gets added + + // Debug this code. We should either hit the breakpoint or stop on entry + const resultPromise = getCellResults(ioc.wrapper!, 5, async () => { + const breakPromise = createDeferred(); + disposables.push(mockDebuggerService!.onBreakpointHit(() => breakPromise.resolve())); + const done = history.debugCode(code, fileName, 0, docManager.activeTextEditor); + await waitForPromise(Promise.race([done, breakPromise.promise]), 60000); + assert.ok(breakPromise.resolved, 'Breakpoint event did not fire'); + assert.ok(!lastErrorMessage, `Error occurred ${lastErrorMessage}`); + const stackTrace = await mockDebuggerService!.getStackTrace(); + assert.ok(stackTrace, 'Stack trace not computable'); + assert.ok(stackTrace!.body.stackFrames.length >= 1, 'Not enough frames'); + assert.equal(stackTrace!.body.stackFrames[0].line, expectedBreakLine, 'Stopped on wrong line number'); + assert.equal(stackTrace!.body.stackFrames[0].source!.path, path.join(EXTENSION_ROOT_DIR, 'baz.py'), 'Stopped on wrong file name. Name should have been saved'); + // Verify break location + await mockDebuggerService!.continue(); + }); + + const cellResults = await resultPromise; + assert.ok(cellResults, 'No cell results after finishing debugging'); + await history.dispose(); + }); + }); diff --git a/src/test/datascience/mockDocument.ts b/src/test/datascience/mockDocument.ts index e1ced53db4b3..f7bd94fabd10 100644 --- a/src/test/datascience/mockDocument.ts +++ b/src/test/datascience/mockDocument.ts @@ -55,11 +55,20 @@ export class MockDocument implements TextDocument { private _version: number = 0; private _lines: MockLine[] = []; private _contents: string = ''; + private _isUntitled = false; + private _isDirty = false; + private _onSave: (doc: TextDocument) => Promise; - constructor(contents: string, fileName: string) { + constructor(contents: string, fileName: string, onSave: (doc: TextDocument) => Promise) { this._uri = Uri.file(fileName); this._contents = contents; this._lines = this.createLines(); + this._onSave = onSave; + } + + public forceUntitled(): void { + this._isUntitled = true; + this._isDirty = true; } public get uri(): Uri { @@ -70,7 +79,7 @@ export class MockDocument implements TextDocument { } public get isUntitled(): boolean { - return true; + return this._isUntitled; } public get languageId(): string { return 'python'; @@ -79,13 +88,13 @@ export class MockDocument implements TextDocument { return this._version; } public get isDirty(): boolean { - return true; + return this._isDirty; } public get isClosed(): boolean { return false; } public save(): Thenable { - return Promise.resolve(true); + return this._onSave(this); } public get eol(): EndOfLine { return EndOfLine.LF; diff --git a/src/test/datascience/mockDocumentManager.ts b/src/test/datascience/mockDocumentManager.ts index d1c2dd887b44..0c904fe15d23 100644 --- a/src/test/datascience/mockDocumentManager.ts +++ b/src/test/datascience/mockDocumentManager.ts @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. 'use strict'; +import * as path from 'path'; import { DecorationRenderOptions, Event, @@ -20,8 +21,10 @@ import { } from 'vscode'; import { IDocumentManager } from '../../client/common/application/types'; +import { EXTENSION_ROOT_DIR } from '../../client/constants'; import { MockDocument } from './mockDocument'; import { MockEditor } from './mockTextEditor'; + // tslint:disable:no-any no-http-string no-multiline-string max-func-body-length export class MockDocumentManager implements IDocumentManager { @@ -81,7 +84,7 @@ export class MockDocumentManager implements IDocumentManager { } public addDocument(code: string, file: string) { - const mockDoc = new MockDocument(code, file); + const mockDoc = new MockDocument(code, file, this.saveDocument); this.textDocuments.push(mockDoc); } @@ -118,4 +121,10 @@ export class MockDocumentManager implements IDocumentManager { } throw new Error('No documents in MockDocumentManager'); } + + private saveDocument = (doc: TextDocument): Promise => { + // Create a new document with the contents of the doc passed in + this.addDocument(doc.getText(), path.join(EXTENSION_ROOT_DIR, 'baz.py')); + return Promise.resolve(true); + } }