From ac72f7a4c5eb2f7e10c0ba4a9ecf6ddc190e6c48 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 10 Feb 2016 09:22:26 +1100 Subject: [PATCH] experimental debugging of django and support for stopOnEntry --- .vscode/launch.json | 36 +- README.md | 5 +- package.json | 595 ++++++++++-------- src/client/debugger/vs/BaseDebugger.ts | 536 ++++++++++++++++ src/client/debugger/vs/Common/Contracts.ts | 2 + .../debugger/vs/Common/OnPortOpenedHandler.ts | 46 ++ src/client/debugger/vs/Common/SocketStream.ts | 2 + src/client/debugger/vs/Common/TryParser.ts | 2 + src/client/debugger/vs/Common/Utils.ts | 2 + src/client/debugger/vs/DjangoDebugger.ts | 70 +++ src/client/debugger/vs/ProxyCommands.ts | 2 + src/client/debugger/vs/PythonDebugger.ts | 6 + src/client/debugger/vs/PythonProcess.ts | 5 +- .../vs/PythonProcessCallbackHandler.ts | 3 + src/client/debugger/vs/VSDebugger.ts | 61 +- 15 files changed, 1093 insertions(+), 280 deletions(-) create mode 100644 src/client/debugger/vs/BaseDebugger.ts create mode 100644 src/client/debugger/vs/Common/OnPortOpenedHandler.ts create mode 100644 src/client/debugger/vs/DjangoDebugger.ts create mode 100644 src/client/debugger/vs/PythonDebugger.ts diff --git a/.vscode/launch.json b/.vscode/launch.json index 3ee6613ad13a..3b2be517d04b 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -12,7 +12,7 @@ ], "stopOnEntry": false, "sourceMaps": true, - "outDir": "out/src", + "outDir": "${workspaceRoot}/out/src", "preLaunchTask": "npm" }, { @@ -29,13 +29,43 @@ "runtimeArgs": [ "--harmony" ], - "program": "./out/client/debugger/vs/vsdebugger.js", + "program": "${workspaceRoot}/out/client/debugger/vs/PythonDebugger.js", "stopOnEntry": false, "args": [ "--server=4711" ], "sourceMaps": true, - "outDir": "./out/client" + "outDir": "${workspaceRoot}/out/client" + }, + { + "name": "Attach to python_django debug server", + "type": "node", + "request": "launch", + "runtimeArgs": [ + "--harmony" + ], + "program": "${workspaceRoot}/out/client/debugger/vs/DjangoDebugger.js", + "stopOnEntry": false, + "args": [ + "--server=4711" + ], + "sourceMaps": true, + "outDir": "${workspaceRoot}/out/client" + }, + { + "name": "Attach to old vs debug server", + "type": "node", + "request": "launch", + "runtimeArgs": [ + "--harmony" + ], + "program": "${workspaceRoot}/out/client/debugger/vs/VSDebugger.js", + "stopOnEntry": false, + "args": [ + "--server=4711" + ], + "sourceMaps": true, + "outDir": "${workspaceRoot}/out/client" } ] } \ No newline at end of file diff --git a/README.md b/README.md index 7e300448d40b..29f6c0743392 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,6 @@ Works on both Windows and Mac. ## Issues and Feature Requests [Github Issues](https://github.com/DonJayamanne/pythonVSCode/issues) * Remote Debugging (coming soon) -* Debugging django (in development) ## Feature Details (with confiuration) * IDE Features @@ -99,6 +98,10 @@ Works on both Windows and Mac. ## Change Log +### Version 0.2.4 +* Introduced experimental debugging of django (debugging templates not supported) +* Added support for optionally breaking into python code as soon as debugger starts + ### Version 0.2.4 * Fixed issue where debugger would break into all exceptions * Added support for breaking on all and uncaught exceptions diff --git a/package.json b/package.json index 2390749cdf6f..97e8d591fa94 100644 --- a/package.json +++ b/package.json @@ -1,274 +1,335 @@ { - "name": "python", - "displayName": "Python", - "description": "Linting, Debugging (multi-threaded), Intellisense, auto-completion, code formatting, snippets, and more.", - "version": "0.2.4", - "publisher": "donjayamanne", - "license": "SEE LICENSE IN LICENSE or README.MD", - "homepage": "https://github.com/DonJayamanne/pythonVSCode/blob/master/README.md", - "repository": { - "type": "git", - "url": "https://github.com/DonJayamanne/pythonVSCode" - }, - "bugs": { - "url": "https://github.com/DonJayamanne/pythonVSCode/issues" - }, - "icon": "images/icon.png", - "galleryBanner": { - "color": "#0000FF", - "theme": "dark" - }, - "engines": { - "vscode": "^0.10.1" - }, - "categories": [ - "Languages", - "Debuggers", - "Linters", - "Snippets", - "Other" - ], - "activationEvents": [ - "onLanguage:python", - "onCommand:python.sortImports", - "onCommand:python.runtests" - ], - "main": "./out/client/extension", - "contributes": { - "snippets": [ - { - "language": "python", - "path": "./snippets/python.json" - } + "name": "python", + "displayName": "Python", + "description": "Linting, Debugging (multi-threaded), Intellisense, auto-completion, code formatting, snippets, and more.", + "version": "0.2.4", + "publisher": "donjayamanne", + "license": "SEE LICENSE IN LICENSE or README.MD", + "homepage": "https://github.com/DonJayamanne/pythonVSCode/blob/master/README.md", + "repository": { + "type": "git", + "url": "https://github.com/DonJayamanne/pythonVSCode" + }, + "bugs": { + "url": "https://github.com/DonJayamanne/pythonVSCode/issues" + }, + "icon": "images/icon.png", + "galleryBanner": { + "color": "#0000FF", + "theme": "dark" + }, + "engines": { + "vscode": "^0.10.1" + }, + "categories": [ + "Languages", + "Debuggers", + "Linters", + "Snippets", + "Other" ], - "commands": [ - { - "command": "python.sortImports", - "title": "Python: Sort Imports" - }, - { - "command": "python.runtests", - "title": "Python: Run Unit Tests" - } + "activationEvents": [ + "onLanguage:python", + "onCommand:python.sortImports", + "onCommand:python.runtests" ], - "debuggers": [ - { - "type": "python", - "label": "Python", - "enableBreakpointsFor": { - "languageIds": [ - "python" - ] - }, - "program": "./out/client/debugger/vs/VSDebugger.js", - "runtime": "node", - "configurationAttributes": { - "launch": { - "required": [ - "program" - ], + "main": "./out/client/extension", + "contributes": { + "snippets": [ + { + "language": "python", + "path": "./snippets/python.json" + } + ], + "commands": [ + { + "command": "python.sortImports", + "title": "Python: Sort Imports" + }, + { + "command": "python.runtests", + "title": "Python: Run Unit Tests" + } + ], + "debuggers": [ + { + "type": "python", + "label": "Python", + "enableBreakpointsFor": { + "languageIds": [ + "python" + ] + }, + "program": "./out/client/debugger/vs/PythonDebugger.js", + "runtime": "node", + "configurationAttributes": { + "launch": { + "required": [ + "program" + ], + "properties": { + "program": { + "type": "string", + "description": "Workspace relative path to a text file.", + "default": "${workspaceRoot}/__init__.py" + }, + "pythonPath": { + "type": "string", + "description": "Path (fully qualified) to python executable. Use this if you want to use a custom pthon executable version.", + "default": "" + }, + "args": { + "type": "array", + "description": "List of arguments for the program", + "default": [] + }, + "stopOnEntry": { + "type": "boolean", + "description": "Automatically stop after launch.", + "default": true + } + } + } + }, + "initialConfigurations": [ + { + "name": "Python", + "type": "python", + "request": "launch", + "stopOnEntry": true, + "program": "${workspaceRoot}/__init__.py" + } + ] + }, + { + "type": "python_django", + "label": "Django", + "enableBreakpointsFor": { + "languageIds": [ + "python" + ] + }, + "program": "./out/client/debugger/vs/vsDebugger.js", + "runtime": "node", + "configurationAttributes": { + "launch": { + "required": [ + "program" + ], + "properties": { + "program": { + "type": "string", + "description": "Workspace relative path to a text file.", + "default": "${workspaceRoot}/manage.py" + }, + "pythonPath": { + "type": "string", + "description": "Path (fully qualified) to python executable. Use this if you want to use a custom pthon executable version.", + "default": "" + }, + "port": { + "type": "number", + "description": "Port number, leave empty or 0 for dyanamic generation of ports.", + "default": 0 + }, + "noReload": { + "type": "boolean", + "description": "Whether not to reload.", + "default": true + }, + "settings": { + "type": "string", + "description": "Settings", + "default": "" + } + } + } + }, + "initialConfigurations": [ + { + "name": "Python", + "type": "python", + "request": "launch", + "program": "${workspaceRoot}/manage.py", + "port": 5002, + "noReload": true, + "settings": "" + } + ] + } + ], + "configuration": { + "type": "object", + "title": "Python Configuration", "properties": { - "program": { - "type": "string", - "description": "Workspace relative path to a text file.", - "default": "__init__.py" - }, - "pythonPath": { - "type": "string", - "description": "Path (fully qualified) to python executable. Use this if you want to use a custom pthon executable version.", - "default": "" - }, - "args": { - "type": "array", - "description": "List of arguments for the program", - "default": [] - } + "python.pythonPath": { + "type": "string", + "default": "python", + "description": "Path to Python, you can use a custom version of Python by modifying this setting to include the full path." + }, + "python.linting.enabled": { + "type": "boolean", + "default": true, + "description": "Whether to lint Python files." + }, + "python.linting.pylintEnabled": { + "type": "boolean", + "default": true, + "description": "Whether to lint Python files using pylint." + }, + "python.linting.pep8Enabled": { + "type": "boolean", + "default": false, + "description": "Whether to lint Python files using pep8" + }, + "python.linting.flake8Enabled": { + "type": "boolean", + "default": false, + "description": "Whether to lint Python files using flake8" + }, + "python.linting.lintOnTextChange": { + "type": "boolean", + "default": true, + "description": "Whether to lint Python files when modified." + }, + "python.linting.lintOnSave": { + "type": "boolean", + "default": true, + "description": "Whether to lint Python files when saved." + }, + "python.linting.maxNumberOfProblems": { + "type": "number", + "default": 100, + "description": "Controls the maximum number of problems produced by the server." + }, + "python.linting.pylintCategorySeverity.convention": { + "type": "string", + "default": "Hint", + "description": "Severity of Pylint message type 'Convention/C'.", + "enum": [ + "Hint", + "Error", + "Information", + "Warning" + ] + }, + "python.linting.pylintCategorySeverity.refactor": { + "type": "string", + "default": "Hint", + "description": "Severity of Pylint message type 'Refactor/R'.", + "enum": [ + "Hint", + "Error", + "Information", + "Warning" + ] + }, + "python.linting.pylintCategorySeverity.warning": { + "type": "string", + "default": "Warning", + "description": "Severity of Pylint message type 'Warning/W'.", + "enum": [ + "Hint", + "Error", + "Information", + "Warning" + ] + }, + "python.linting.pylintCategorySeverity.error": { + "type": "string", + "default": "Error", + "description": "Severity of Pylint message type 'Error/E'.", + "enum": [ + "Hint", + "Error", + "Information", + "Warning" + ] + }, + "python.linting.pylintCategorySeverity.fatal": { + "type": "string", + "default": "Error", + "description": "Severity of Pylint message type 'Fatal/F'.", + "enum": [ + "Hint", + "Error", + "Information", + "Warning" + ] + }, + "python.linting.pylintPath": { + "type": "string", + "default": "pylint", + "description": "Path to Pylint, you can use a custom version of pylint by modifying this setting to include the full path." + }, + "python.linting.pep8Path": { + "type": "string", + "default": "pep8", + "description": "Path to pep8, you can use a custom version of pep8 by modifying this setting to include the full path." + }, + "python.linting.flake8Path": { + "type": "string", + "default": "flake8", + "description": "Path to flake8, you can use a custom version of flake8 by modifying this setting to include the full path." + }, + "python.formatting.provider": { + "type": "string", + "default": "autopep8", + "description": "Provider for formatting. Possible options include 'autopep8' and 'yapf'.", + "enum": [ + "autopep8", + "yapf" + ] + }, + "python.formatting.autopep8Path": { + "type": "string", + "default": "autopep8", + "description": "Path to autopep8, you can use a custom version of autopep8 by modifying this setting to include the full path." + }, + "python.formatting.yapfPath": { + "type": "string", + "default": "yapf", + "description": "Path to yapf, you can use a custom version of yapf by modifying this setting to include the full path." + }, + "python.autoComplete.extraPaths": { + "type": "array", + "default": [], + "description": "List of paths to libraries and the like that need to be imported by auto complete engine. E.g. when using Google App SDK, the paths are not in system path, hence need to be added into this list." + }, + "python.unitTest.nosetestsEnabled": { + "type": "boolean", + "default": false, + "description": "Whether to enable or disable unit testing using nosetests." + }, + "python.unitTest.nosetestPath": { + "type": "string", + "default": "nosetests", + "description": "Path to nosetests, you can use a custom version of nosetests by modifying this setting to include the full path." + }, + "python.unitTest.unittestEnabled": { + "type": "boolean", + "default": true, + "description": "Whether to enable or disable unit testing using standard unittest (built into Python)." + } } - } - }, - "initialConfigurations": [ - { - "name": "Python", - "type": "python", - "request": "launch", - "stopOnEntry": true, - "program": "__init__.py" - } - ] - } - ], - "configuration": { - "type": "object", - "title": "Python Configuration", - "properties": { - "python.pythonPath": { - "type": "string", - "default": "python", - "description": "Path to Python, you can use a custom version of Python by modifying this setting to include the full path." - }, - "python.linting.enabled": { - "type": "boolean", - "default": true, - "description": "Whether to lint Python files." - }, - "python.linting.pylintEnabled": { - "type": "boolean", - "default": true, - "description": "Whether to lint Python files using pylint." - }, - "python.linting.pep8Enabled": { - "type": "boolean", - "default": false, - "description": "Whether to lint Python files using pep8" - }, - "python.linting.flake8Enabled": { - "type": "boolean", - "default": false, - "description": "Whether to lint Python files using flake8" - }, - "python.linting.lintOnTextChange": { - "type": "boolean", - "default": true, - "description": "Whether to lint Python files when modified." - }, - "python.linting.lintOnSave": { - "type": "boolean", - "default": true, - "description": "Whether to lint Python files when saved." - }, - "python.linting.maxNumberOfProblems": { - "type": "number", - "default": 100, - "description": "Controls the maximum number of problems produced by the server." - }, - "python.linting.pylintCategorySeverity.convention": { - "type": "string", - "default": "Hint", - "description": "Severity of Pylint message type 'Convention/C'.", - "enum": [ - "Hint", - "Error", - "Information", - "Warning" - ] - }, - "python.linting.pylintCategorySeverity.refactor": { - "type": "string", - "default": "Hint", - "description": "Severity of Pylint message type 'Refactor/R'.", - "enum": [ - "Hint", - "Error", - "Information", - "Warning" - ] - }, - "python.linting.pylintCategorySeverity.warning": { - "type": "string", - "default": "Warning", - "description": "Severity of Pylint message type 'Warning/W'.", - "enum": [ - "Hint", - "Error", - "Information", - "Warning" - ] - }, - "python.linting.pylintCategorySeverity.error": { - "type": "string", - "default": "Error", - "description": "Severity of Pylint message type 'Error/E'.", - "enum": [ - "Hint", - "Error", - "Information", - "Warning" - ] - }, - "python.linting.pylintCategorySeverity.fatal": { - "type": "string", - "default": "Error", - "description": "Severity of Pylint message type 'Fatal/F'.", - "enum": [ - "Hint", - "Error", - "Information", - "Warning" - ] - }, - "python.linting.pylintPath": { - "type": "string", - "default": "pylint", - "description": "Path to Pylint, you can use a custom version of pylint by modifying this setting to include the full path." - }, - "python.linting.pep8Path": { - "type": "string", - "default": "pep8", - "description": "Path to pep8, you can use a custom version of pep8 by modifying this setting to include the full path." - }, - "python.linting.flake8Path": { - "type": "string", - "default": "flake8", - "description": "Path to flake8, you can use a custom version of flake8 by modifying this setting to include the full path." - }, - "python.formatting.provider": { - "type": "string", - "default": "autopep8", - "description": "Provider for formatting. Possible options include 'autopep8' and 'yapf'.", - "enum": [ - "autopep8", - "yapf" - ] - }, - "python.formatting.autopep8Path": { - "type": "string", - "default": "autopep8", - "description": "Path to autopep8, you can use a custom version of autopep8 by modifying this setting to include the full path." - }, - "python.formatting.yapfPath": { - "type": "string", - "default": "yapf", - "description": "Path to yapf, you can use a custom version of yapf by modifying this setting to include the full path." - }, - "python.autoComplete.extraPaths": { - "type": "array", - "default": [], - "description": "List of paths to libraries and the like that need to be imported by auto complete engine. E.g. when using Google App SDK, the paths are not in system path, hence need to be added into this list." - }, - "python.unitTest.nosetestsEnabled": { - "type": "boolean", - "default": false, - "description": "Whether to enable or disable unit testing using nosetests." - }, - "python.unitTest.nosetestPath": { - "type": "string", - "default": "nosetests", - "description": "Path to nosetests, you can use a custom version of nosetests by modifying this setting to include the full path." - }, - "python.unitTest.unittestEnabled": { - "type": "boolean", - "default": true, - "description": "Whether to enable or disable unit testing using standard unittest (built into Python)." } - } + }, + "scripts": { + "vscode:prepublish": "node ./node_modules/vscode/bin/compile", + "compile": "node ./node_modules/vscode/bin/compile -watch -p ./ && installServerIntoExtension ./out ./src/server/package.json ./src/server/tsconfig.json" + }, + "dependencies": { + "line-by-line": "^0.1.4", + "named-js-regexp": "^1.3.1", + "tmp": "0.0.28", + "uint64be": "^1.0.1", + "vscode-debugadapter": "^1.0.1", + "vscode-debugprotocol": "^1.0.1", + "vscode-languageclient": "^1.1.0", + "vscode-languageserver": "^1.1.0" + }, + "devDependencies": { + "typescript": "^1.6.2", + "vscode": "0.10.x" } - }, - "scripts": { - "vscode:prepublish": "node ./node_modules/vscode/bin/compile", - "compile": "node ./node_modules/vscode/bin/compile -watch -p ./ && installServerIntoExtension ./out ./src/server/package.json ./src/server/tsconfig.json" - }, - "dependencies": { - "line-by-line": "^0.1.4", - "named-js-regexp": "^1.3.1", - "tmp": "0.0.28", - "uint64be": "^1.0.1", - "vscode-debugadapter": "^1.0.1", - "vscode-debugprotocol": "^1.0.1", - "vscode-languageclient": "^1.1.0", - "vscode-languageserver": "^1.1.0" - }, - "devDependencies": { - "typescript": "^1.6.2", - "vscode": "0.10.x" - } -} +} \ No newline at end of file diff --git a/src/client/debugger/vs/BaseDebugger.ts b/src/client/debugger/vs/BaseDebugger.ts new file mode 100644 index 000000000000..2b5742a4893d --- /dev/null +++ b/src/client/debugger/vs/BaseDebugger.ts @@ -0,0 +1,536 @@ +'use strict'; + +import {Variable, DebugSession, InitializedEvent, TerminatedEvent, StoppedEvent, OutputEvent, Thread, StackFrame, Scope, Source, Handles} from 'vscode-debugadapter'; +import {ThreadEvent} from 'vscode-debugadapter'; +import {DebugProtocol} from 'vscode-debugprotocol'; +import {readFileSync} from 'fs'; +import {basename} from 'path'; +import * as path from 'path'; +import * as os from 'os'; +import * as fs from 'fs'; +import * as child_process from 'child_process'; +import * as StringDecoder from 'string_decoder'; +import * as net from 'net'; +import {PythonProcess} from './PythonProcess'; +import {FrameKind, IPythonProcess, IPythonThread, IPythonModule, IPythonEvaluationResult, IPythonStackFrame, IDebugServer} from './Common/Contracts'; +import {IPythonBreakpoint, PythonBreakpointConditionKind, PythonBreakpointPassCountKind, IPythonException, PythonEvaluationResultReprKind, enum_EXCEPTION_STATE} from './Common/Contracts'; + +const CHILD_ENUMEARATION_TIMEOUT = 5000; + +export interface LaunchRequestArguments { + /** An absolute path to the program to debug. */ + program: string; + /** Automatically stop target after launch. If not specified, target does not stop. */ + stopOnEntry?: boolean; + args: string[]; + pythonPath: string; +} + +interface IDebugVariable { + variables: IPythonEvaluationResult[]; + evaluateChildren?: Boolean; +} + +export class BaseDebugger extends DebugSession { + private _variableHandles: Handles; + protected breakPointCounter: number = 0; + private registeredBreakpoints: Map; + private registeredBreakpointsByFileName: Map; + private debuggerLoaded: Promise; + protected debuggerLoadedPromiseResolve: () => void; + public constructor(debuggerLinesStartAt1: boolean, isServer: boolean) { + super(debuggerLinesStartAt1, isServer === true); + this._variableHandles = new Handles(); + this.registeredBreakpoints = new Map(); + this.registeredBreakpointsByFileName = new Map(); + } + + protected initializeRequest(response: DebugProtocol.InitializeResponse, args: DebugProtocol.InitializeRequestArguments): void { + this.sendResponse(response); + + // now we are ready to accept breakpoints -> fire the initialized event to give UI a chance to set breakpoints + // this.sendEvent(new InitializedEvent()); + } + private startedHandlingMessages: Boolean; + protected pythonProcess: PythonProcess; + private debugSocketServer: net.Server; + private startDebugServer(): Promise { + return new Promise((resolve, reject) => { + var that = this; + var programDirectory = path.dirname(this.launchArgs.program) + this.pythonProcess = new PythonProcess(0, "", programDirectory); + this.InitializeEventHandlers(); + this.debugSocketServer = net.createServer(c => { //'connection' listener + var pythonProcess: PythonProcess; + var connected = false; + console.log('client connected'); + c.on('end', (ex) => { + var msg = "Debugger client disconneced, " + ex; + that.sendEvent(new OutputEvent(msg + "\n", "stderr")); + console.log(msg); + }); + c.on("data", (buffer: Buffer) => { + if (!connected) { + connected = true; + that.pythonProcess.Connect(buffer, c); + } + else { + that.pythonProcess.HandleIncomingData(buffer) + that.startedHandlingMessages = true; + } + }); + c.on("close", d=> { + var msg = "Debugger client closed, " + d; + console.log(msg); + that.emit("detach", d); + that.onDetachDebugger(); + }); + c.on("error", d=> { + // var msg = "Debugger client error, " + d; + // that.sendEvent(new OutputEvent(msg + "\n", "Python")); + // console.log(msg); + // // that.onDetachDebugger(); + }); + c.on("timeout", d=> { + var msg = "Debugger client timedout, " + d; + that.sendEvent(new OutputEvent(msg + "\n", "stderr")); + console.log(msg); + }); + }); + this.debugSocketServer.on("error", ex=> { + var exMessage = JSON.stringify(ex); + var msg = ""; + if (ex.code === "EADDRINUSE") { + msg = `The port used for debugging is in use, please try again or try restarting Visual Studio Code, Error = ${exMessage}`; + } + else { + msg = `There was an error in starting the debug server. Error = ${exMessage}`; + } + that.sendEvent(new OutputEvent(msg + "\n", "stderr")); + console.log(msg); + reject(msg); + }); + this.debugSocketServer.listen(0, () => { + var server = that.debugSocketServer.address(); + console.log(`Debug server started, listening on port ${server.port}`); + resolve({ port: server.port }); + }); + }); + } + private stopDebugServer() { + try { + this.debugSocketServer.close(); + } + catch (ex) { } + // try { + // // this.pythonProcess.Terminate(); + // } + // catch (ex) { } + try { + this.pyProc.send("EXIT"); + } + catch (ex) { } + try { + this.pyProc.stdin.write("EXIT"); + } + catch (ex) { } + try { + this.pyProc.disconnect(); + } + catch (ex) { } + this.debugSocketServer = null; + this.pyProc = null; + } + private InitializeEventHandlers() { + this.pythonProcess.on("threadExited", arg => this.onPythonThreadExited(arg)); + this.pythonProcess.on("moduleLoaded", arg=> this.onPythonModuleLoaded(arg)); + this.pythonProcess.on("threadCreated", arg=> this.onPythonThreadCreated(arg)); + this.pythonProcess.on("processLoaded", arg=> this.onPythonProcessLoaded(arg)); + this.pythonProcess.on("output", (pyThread, output) => this.onDebuggerOutput(pyThread, output)); + this.pythonProcess.on("exceptionRaised", (pyThread, ex) => this.onPythonException(pyThread, ex)); + this.pythonProcess.on("breakpointHit", (pyThread, breakpointId) => this.onBreakpointHit(pyThread, breakpointId)); + this.pythonProcess.on("detach", () => this.onDetachDebugger()); + this.pythonProcess.on("error", ex => this.sendEvent(new OutputEvent(ex, "stderr"))); + this.pythonProcess.on("asyncBreakCompleted", arg=> this.onPythonProcessPaused(arg)); + } + private onDetachDebugger() { + this.stopDebugServer(); + this.sendEvent(new TerminatedEvent()); + this.shutdown(); + } + private onPythonThreadCreated(pyThread: IPythonThread) { + this.sendEvent(new ThreadEvent("started", pyThread.Id)); + } + private onPythonException(pyThread: IPythonThread, ex: IPythonException) { + this.sendEvent(new StoppedEvent("exception", pyThread.Id, `${ex.TypeName}, ${ex.Description}`)); + this.sendEvent(new OutputEvent(`${ex.TypeName}, ${ex.Description}\n`, "stderr")); + } + private onPythonThreadExited(pyThread: IPythonThread) { + this.sendEvent(new ThreadEvent("exited", pyThread.Id)); + } + private onPythonProcessPaused(pyThread: IPythonThread) { + this.sendEvent(new StoppedEvent("pause", pyThread.Id)); + } + private onPythonModuleLoaded(module: IPythonModule) { + } + protected onPythonProcessLoaded(pyThread: IPythonThread) { + this.sendResponse(this.entryResponse); + if (this.launchArgs.stopOnEntry === true) { + this.sendEvent(new StoppedEvent("entry", pyThread.Id)); + } + else { + this.pythonProcess.SendResumeThread(pyThread.Id); + } + this.debuggerLoadedPromiseResolve(); + } + private onDebuggerOutput(pyThread: IPythonThread, output: string) { + this.sendEvent(new OutputEvent(output)); + } + private pyProc: child_process.ChildProcess; + protected entryResponse: DebugProtocol.LaunchResponse; + protected launchArgs: LaunchRequestArguments; + protected canStartDebugger(): Promise { + return Promise.resolve(true); + } + protected launchRequest(response: DebugProtocol.LaunchResponse, args: LaunchRequestArguments): void { + this.launchArgs = args; + var fileDir = path.dirname(args.program); + var fileNameWithoutPath = path.basename(args.program); + var pythonPath = "python"; + if (typeof args.pythonPath === "string" && args.pythonPath.trim().length > 0) { + pythonPath = args.pythonPath; + } + + this.debuggerLoaded = new Promise(resolve=> { + this.debuggerLoadedPromiseResolve = resolve; + }); + this.entryResponse = response; + + var that = this; + + this.canStartDebugger().then(() => { + this.startDebugServer().then(dbgServer => { + //GUID is hardcoded for now, will have to be fixed + var currentFileName = module.filename; + var ptVSToolsFilePath = path.join(path.dirname(currentFileName), "..", "..", "..", "..", "pythonFiles", "PythonTools", "visualstudio_py_launcher.py"); + //ptVSToolsFilePath = "C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\Common7\\IDE\\Extensions\\Microsoft\\Python Tools for Visual Studio\\2.2\\visualstudio_py_launcher.py"; + var launcherArgs = this.buildLauncherArguments(args); + var commandLine = `\"${pythonPath}\" \"${ptVSToolsFilePath}\" \"${fileDir}" ${dbgServer.port} 34806ad9-833a-4524-8cd6-18ca4aa74f14 ${launcherArgs}`; + console.log(commandLine); + that.pyProc = child_process.exec(commandLine, { cwd: fileDir }, (error, stdout, stderr) => { + if (that.startedHandlingMessages) { + return; + } + var hasErrors = (error && error.message.length > 0) || (stderr && stderr.length > 0); + if (hasErrors && (typeof stdout !== "string" || stdout.length === 0)) { + var errorMsg = (error && error.message) ? error.message : (stderr && stderr.length > 0 ? stderr.toString("utf-8") : ""); + that.sendEvent(new OutputEvent(errorMsg + "\n", "stderr")); + that.sendErrorResponse(that.entryResponse, 2000, errorMsg); + console.error(errorMsg); + } + }); + }); + }, error=> { + this.sendEvent(new OutputEvent(error + "\n", "stderr")); + this.sendErrorResponse(that.entryResponse, 2000, error); + }); + } + protected buildLauncherArguments(args: LaunchRequestArguments): string { + var vsDebugOptions = "RedirectOutput"; + vsDebugOptions = "WaitOnAbnormalExit, WaitOnNormalExit, RedirectOutput, DjangoDebugging"; + + var programArgs = Array.isArray(args.args) && args.args.length > 0 ? args.args.join(" ") : ""; + + return `\"${vsDebugOptions}\" \"${args.program}\" ${programArgs}`; + } + private onBreakpointHit(pyThread: IPythonThread, breakpointId: number) { + if (this.registeredBreakpoints.has(breakpointId)) { + this.sendEvent(new StoppedEvent("breakpoint", pyThread.Id)); + } + else { + this.pythonProcess.SendResumeThread(pyThread.Id); + } + } + protected buildBreakpointDetails(filePath: string, line: number): IPythonBreakpoint { + return { + Condition: "", + ConditionKind: PythonBreakpointConditionKind.Always, + Filename: filePath, + Id: this.breakPointCounter++, + LineNo: line, + PassCount: 0, + PassCountKind: PythonBreakpointPassCountKind.Always + }; + } + protected setBreakPointsRequest(response: DebugProtocol.SetBreakpointsResponse, args: DebugProtocol.SetBreakpointsArguments): void { + this.debuggerLoaded.then(() => { + if (!this.registeredBreakpointsByFileName.has(args.source.path)) { + this.registeredBreakpointsByFileName.set(args.source.path, []); + } + + var breakpoints: { verified: boolean, line: number }[] = []; + var breakpointsToRemove = []; + var linesToAdd: number[] = []; + if (Array.isArray(args.breakpoints)) { + linesToAdd = args.breakpoints.map(b=> b.line); + } + else { + linesToAdd = args.lines; + } + + var linesToRemove = []; + var registeredBks = this.registeredBreakpointsByFileName.get(args.source.path); + linesToRemove = registeredBks.map(b=> b.LineNo).filter(oldLine=> linesToAdd.indexOf(oldLine) === -1); + + var linesToAddPromises = linesToAdd.map(line=> { + return new Promise(resolve=> { + var breakpoint: IPythonBreakpoint = this.buildBreakpointDetails(args.source.path, line); + + this.pythonProcess.BindBreakpoint(breakpoint).then(() => { + this.registeredBreakpoints.set(breakpoint.Id, breakpoint); + breakpoints.push({ verified: true, line: line }); + registeredBks.push(breakpoint); + resolve(); + }, reason=> { + this.registeredBreakpoints.set(breakpoint.Id, breakpoint); + breakpoints.push({ verified: false, line: line }); + resolve(); + }); + }); + }); + + var linesToRemovePromises = linesToRemove.map(line=> { + return new Promise(resolve=> { + var registeredBks = this.registeredBreakpointsByFileName.get(args.source.path); + var bk = registeredBks.filter(b=> b.LineNo === line)[0]; + this.pythonProcess.DisableBreakPoint(bk); + }); + }); + + var promises = linesToAddPromises.concat(linesToRemovePromises); + Promise.all(promises).then(() => { + response.body = { + breakpoints: breakpoints + }; + + this.sendResponse(response); + }); + }); + } + + protected threadsRequest(response: DebugProtocol.ThreadsResponse): void { + var threads = []; + this.pythonProcess.Threads.forEach(t=> { + threads.push(new Thread(t.Id, t.Name)); + }); + + response.body = { + threads: threads + }; + this.sendResponse(response); + } + + private currentStackThread: IPythonThread; + protected stackTraceRequest(response: DebugProtocol.StackTraceResponse, args: DebugProtocol.StackTraceArguments): void { + this.debuggerLoaded.then(() => { + if (!this.pythonProcess.Threads.has(args.threadId)) { + response.body = { + stackFrames: [] + }; + this.currentStackThread = null; + this.sendResponse(response); + } + + var pyThread = this.pythonProcess.Threads.get(args.threadId); + this.currentStackThread = pyThread; + var maxFrames = typeof args.levels === "number" && args.levels > 0 ? args.levels : pyThread.Frames.length - 1; + maxFrames = maxFrames < pyThread.Frames.length ? maxFrames : pyThread.Frames.length; + + var frames = []; + for (var counter = 0; counter < maxFrames; counter++) { + var frame = pyThread.Frames[counter]; + frames.push(new StackFrame(counter, frame.FunctionName, + new Source(path.basename(frame.FileName), this.convertDebuggerPathToClient(frame.FileName)), + this.convertDebuggerLineToClient(frame.LineNo - 1), + 0)); + } + + response.body = { + stackFrames: frames + }; + + this.sendResponse(response); + }); + } + protected stepInRequest(response: DebugProtocol.StepInResponse): void { + this.pythonProcess.SendStepInto(this.pythonProcess.LastExecutedThread.Id).then(pyThread => { + this.sendResponse(response); + this.sendEvent(new StoppedEvent("step", pyThread.Id)); + }); + } + protected stepOutRequest(response: DebugProtocol.StepInResponse): void { + this.pythonProcess.SendStepOut(this.pythonProcess.LastExecutedThread.Id).then(pyThread => { + this.sendResponse(response); + this.sendEvent(new StoppedEvent("return", pyThread.Id)); + }); + } + protected continueRequest(response: DebugProtocol.ContinueResponse, args: DebugProtocol.ContinueArguments): void { + this.pythonProcess.SendContinue().then(() => { + this.sendResponse(response); + }); + } + protected nextRequest(response: DebugProtocol.NextResponse, args: DebugProtocol.NextArguments): void { + this.pythonProcess.SendStepOver(this.pythonProcess.LastExecutedThread.Id).then(pyThread => { + this.sendResponse(response); + this.sendEvent(new StoppedEvent("next", pyThread.Id)); + }); + } + protected evaluateRequest(response: DebugProtocol.EvaluateResponse, args: DebugProtocol.EvaluateArguments): void { + this.debuggerLoaded.then(() => { + //Find the last thread for which we display the stack frames + if (!this.currentStackThread) { + response.body = { + result: null, + variablesReference: 0 + }; + return this.sendResponse(response); + } + + var frame = this.currentStackThread.Frames[args.frameId]; + this.pythonProcess.ExecuteText(args.expression, PythonEvaluationResultReprKind.Normal, frame).then(result=> { + let variablesReference = 0; + //If this value can be expanded, then create a vars ref for user to expand it + if (result.IsExpandable) { + const parentVariable: IDebugVariable = { + variables: [result], + evaluateChildren: true + }; + variablesReference = this._variableHandles.create(parentVariable); + } + + response.body = { + result: result.StringRepr, + variablesReference: variablesReference + }; + this.sendResponse(response); + }, + error => { + // this.sendResponse(response); + this.sendErrorResponse(response, 2000, error); + } + ); + }); + } + protected scopesRequest(response: DebugProtocol.ScopesResponse, args: DebugProtocol.ScopesArguments): void { + this.debuggerLoaded.then(() => { + //Find the last thread for which we dislay the stack frames + if (!this.currentStackThread) { + response.body = { + scopes: [] + }; + return this.sendResponse(response); + } + + var frame = this.currentStackThread.Frames[args.frameId]; + frame.Locals + var scopes = []; + if (Array.isArray(frame.Locals) && frame.Locals.length > 0) { + let values: IDebugVariable = { variables: frame.Locals }; + scopes.push(new Scope("Local", this._variableHandles.create(values), false)); + } + if (Array.isArray(frame.Parameters) && frame.Parameters.length > 0) { + let values: IDebugVariable = { variables: frame.Parameters }; + scopes.push(new Scope("Arguments", this._variableHandles.create(values), false)); + } + response.body = { scopes }; + this.sendResponse(response); + }); + } + protected variablesRequest(response: DebugProtocol.VariablesResponse, args: DebugProtocol.VariablesArguments): void { + var varRef = this._variableHandles.get(args.variablesReference); + + if (varRef.evaluateChildren !== true) { + let variables = []; + varRef.variables.forEach(variable=> { + let variablesReference = 0; + //If this value can be expanded, then create a vars ref for user to expand it + if (variable.IsExpandable) { + const parentVariable: IDebugVariable = { + variables: [variable], + evaluateChildren: true + }; + variablesReference = this._variableHandles.create(parentVariable); + } + + variables.push({ + name: variable.Expression, + value: variable.StringRepr, + variablesReference: variablesReference + }); + }); + + response.body = { + variables: variables + }; + + return this.sendResponse(response); + } + + + + //Ok, we need to evaluate the children of the current variable + var variables = []; + var promises = varRef.variables.map(variable=> { + return variable.Process.EnumChildren(variable.Expression, variable.Frame, CHILD_ENUMEARATION_TIMEOUT).then(children=> { + children.forEach(child=> { + let variablesReference = 0; + //If this value can be expanded, then create a vars ref for user to expand it + if (child.IsExpandable) { + const childVariable: IDebugVariable = { + variables: [child], + evaluateChildren: true + }; + variablesReference = this._variableHandles.create(childVariable); + } + + variables.push({ + name: child.ChildName, + value: child.StringRepr, + variablesReference: variablesReference + }); + }); + }, error=> { + this.sendErrorResponse(response, 2001, error); + }); + }); + + Promise.all(promises).then(() => { + response.body = { + variables: variables + }; + + return this.sendResponse(response); + }); + } + protected pauseRequest(response: DebugProtocol.PauseResponse): void { + this.pythonProcess.Break(); + this.sendResponse(response); + } + protected setExceptionBreakPointsRequest(response: DebugProtocol.SetExceptionBreakpointsResponse, args: DebugProtocol.SetExceptionBreakpointsArguments): void { + this.debuggerLoaded.then(() => { + var mode = enum_EXCEPTION_STATE.BREAK_MODE_NEVER; + if (args.filters.indexOf("uncaught") >= 0) { + mode = enum_EXCEPTION_STATE.BREAK_MODE_UNHANDLED; + } + if (args.filters.indexOf("all") >= 0) { + mode = enum_EXCEPTION_STATE.BREAK_MODE_ALWAYS; + } + this.pythonProcess.SendExceptionInfo(mode, null); + this.sendResponse(response); + }); + } + protected disconnectRequest(response: DebugProtocol.DisconnectResponse, args: DebugProtocol.DisconnectArguments) { + this.stopDebugServer(); + this.sendResponse(response); + } +} diff --git a/src/client/debugger/vs/Common/Contracts.ts b/src/client/debugger/vs/Common/Contracts.ts index 8e160123cfa9..41a32ef42370 100644 --- a/src/client/debugger/vs/Common/Contracts.ts +++ b/src/client/debugger/vs/Common/Contracts.ts @@ -1,3 +1,5 @@ +'use strict'; + export interface IDebugServer { port: number; } diff --git a/src/client/debugger/vs/Common/OnPortOpenedHandler.ts b/src/client/debugger/vs/Common/OnPortOpenedHandler.ts new file mode 100644 index 000000000000..13f23fc191f0 --- /dev/null +++ b/src/client/debugger/vs/Common/OnPortOpenedHandler.ts @@ -0,0 +1,46 @@ +'use strict'; + +import * as net from 'net'; + +export function WaitForPortToOpen(port: number, timeout: number): Promise { + return new Promise((resolve, reject) => { + var timedOut = false; + const handle = setTimeout(() => { + timedOut = true; + reject(`Timeout after ${timeout} milli-seconds`); + }, timeout); + + tryToConnect(); + + function tryToConnect() { + if (timedOut) { + return; + } + + var socket = net.connect(port, () => { + if (timedOut) { + return; + } + + resolve(); + socket.end(); + clearTimeout(handle); + }); + socket.on("error", error=> { + if (timedOut) { + return; + } + + if (error.code === "ECONNREFUSED" && !timedOut) { + setTimeout(() => { + tryToConnect(); + }, 10); + return; + } + + clearTimeout(handle); + reject(`Connection failed due to ${JSON.stringify(error)}`); + }); + } + }); +} \ No newline at end of file diff --git a/src/client/debugger/vs/Common/SocketStream.ts b/src/client/debugger/vs/Common/SocketStream.ts index 3c09ea981597..ec6729ee7532 100644 --- a/src/client/debugger/vs/Common/SocketStream.ts +++ b/src/client/debugger/vs/Common/SocketStream.ts @@ -1,3 +1,5 @@ +'use strict'; + import * as net from 'net'; var uint64be = require("uint64be"); diff --git a/src/client/debugger/vs/Common/TryParser.ts b/src/client/debugger/vs/Common/TryParser.ts index 6f7351fffc80..800d5406a6ca 100644 --- a/src/client/debugger/vs/Common/TryParser.ts +++ b/src/client/debugger/vs/Common/TryParser.ts @@ -1,3 +1,5 @@ +'use strict'; + import * as path from 'path'; var LineByLineReader = require('line-by-line'); diff --git a/src/client/debugger/vs/Common/Utils.ts b/src/client/debugger/vs/Common/Utils.ts index d75e00342970..015f0393d5c8 100644 --- a/src/client/debugger/vs/Common/Utils.ts +++ b/src/client/debugger/vs/Common/Utils.ts @@ -1,3 +1,5 @@ +'use strict'; + import {IPythonProcess, IPythonThread, IPythonModule, IPythonEvaluationResult} from './Contracts'; import * as path from 'path'; diff --git a/src/client/debugger/vs/DjangoDebugger.ts b/src/client/debugger/vs/DjangoDebugger.ts new file mode 100644 index 000000000000..00416fa81fcc --- /dev/null +++ b/src/client/debugger/vs/DjangoDebugger.ts @@ -0,0 +1,70 @@ +'use strict'; + +import {IPythonThread, IPythonBreakpoint, PythonBreakpointConditionKind, PythonBreakpointPassCountKind} from './Common/Contracts'; +import {BaseDebugger, LaunchRequestArguments} from './BaseDebugger'; +import {DebugSession, StoppedEvent, OutputEvent, InitializedEvent} from 'vscode-debugadapter'; +import {WaitForPortToOpen} from './Common/OnPortOpenedHandler'; + + +export interface LaunchDjangoRequestArguments extends LaunchRequestArguments { + port: number; + noReload: boolean; + settings?: string; +} + +export class DjangoDebugSession extends BaseDebugger { + protected canStartDebugger(): Promise { + return new Promise((resolve, reject) => { + //Check if the port is already in use + var djangoArgs = this.launchArgs; + if (typeof djangoArgs.port !== "number" || djangoArgs.port <= 0) { + return Promise.resolve(true); + } + + WaitForPortToOpen(djangoArgs.port, 5000).then(() => { + reject(`Port is already in use, please terminate relevant process`); + }, error=> { + resolve(true); + }); + }); + } + protected onPythonProcessLoaded(pyThread: IPythonThread) { + var djangoArgs = this.launchArgs; + + this.sendResponse(this.entryResponse); + this.pythonProcess.SendResumeThread(pyThread.Id); + + //Wait for the port to open before loading breakpoints + var djangoArgs = this.launchArgs; + WaitForPortToOpen(djangoArgs.port, 60000).then(() => { + this.debuggerLoadedPromiseResolve(); + // now we are ready to accept breakpoints -> fire the initialized event to give UI a chance to set breakpoints + this.sendEvent(new InitializedEvent()); + }, error=> { + this.sendEvent(new OutputEvent(error, "stderr")); + }); + } + + protected buildLauncherArguments(args: LaunchRequestArguments): string { + var djangoArgs = args; + var vsDebugOptions = "RedirectOutput"; + vsDebugOptions = "WaitOnAbnormalExit, WaitOnNormalExit, RedirectOutput, DjangoDebugging"; + + var argsList = ["runserver"]; + if (djangoArgs.noReload === true) { + argsList.push("--noreload"); + } + if (typeof djangoArgs.settings === "string" && djangoArgs.settings.length > 0) { + argsList.push("--settings"); + argsList.push(djangoArgs.settings); + } + if (typeof djangoArgs.port === "number" && djangoArgs.port > 0) { + argsList.push(djangoArgs.port.toString()); + } + + var programArgs = argsList.join(" "); + return `\"${vsDebugOptions}\" \"${args.program}\" ${programArgs}`; + } +} + +DebugSession.run(DjangoDebugSession); \ No newline at end of file diff --git a/src/client/debugger/vs/ProxyCommands.ts b/src/client/debugger/vs/ProxyCommands.ts index 537b8b2cca64..9069556115f3 100644 --- a/src/client/debugger/vs/ProxyCommands.ts +++ b/src/client/debugger/vs/ProxyCommands.ts @@ -1,3 +1,5 @@ +'use strict'; + export class Commands { public static ExitCommandBytes: Buffer = new Buffer("exit"); public static StepIntoCommandBytes: Buffer = new Buffer("stpi"); diff --git a/src/client/debugger/vs/PythonDebugger.ts b/src/client/debugger/vs/PythonDebugger.ts new file mode 100644 index 000000000000..b2c2c1219f32 --- /dev/null +++ b/src/client/debugger/vs/PythonDebugger.ts @@ -0,0 +1,6 @@ +'use strict'; + +import {DebugSession} from 'vscode-debugadapter'; +import {BaseDebugger} from './BaseDebugger'; + +DebugSession.run(BaseDebugger); diff --git a/src/client/debugger/vs/PythonProcess.ts b/src/client/debugger/vs/PythonProcess.ts index 1a84316f69d8..aa0c1e3261a5 100644 --- a/src/client/debugger/vs/PythonProcess.ts +++ b/src/client/debugger/vs/PythonProcess.ts @@ -1,4 +1,4 @@ -"use string"; +'use strict'; import * as net from 'net'; import {EventEmitter} from 'events'; @@ -47,7 +47,7 @@ export class PythonProcess extends EventEmitter implements IPythonProcess { private programDirectory: string; public get ProgramDirectory(): string { return this.programDirectory; - } + } constructor(id: number, guid: string, programDirectory: string) { super(); this.id = id; @@ -60,6 +60,7 @@ export class PythonProcess extends EventEmitter implements IPythonProcess { } public Terminate() { + this.stream.Write(Commands.ExitCommandBytes); } public Connect(buffer: Buffer, socket: net.Socket) { diff --git a/src/client/debugger/vs/PythonProcessCallbackHandler.ts b/src/client/debugger/vs/PythonProcessCallbackHandler.ts index a84431d09dbe..8c1ff6d1ebcb 100644 --- a/src/client/debugger/vs/PythonProcessCallbackHandler.ts +++ b/src/client/debugger/vs/PythonProcessCallbackHandler.ts @@ -1,3 +1,5 @@ +'use strict'; + import {FrameKind, IPythonProcess, IPythonThread, IPythonModule, IPythonEvaluationResult, IPythonStackFrame} from './Common/Contracts'; import {IDjangoStackFrame, PythonEvaluationResultFlags, PythonLanguageVersion, IChildEnumCommand, IPythonException, IExecutionCommand} from './Common/Contracts'; import * as utils from './Common/Utils'; @@ -47,6 +49,7 @@ export class PythonProcessCallbackHandler extends EventEmitter { case "EXCE": this.HandleExecutionException(); break; case "ASBR": this.HandleAsyncBreak(); break; default: { + console.error("Uhandled command = " + cmd); this.emit("error", `Unhandled command '${cmd}'`); } diff --git a/src/client/debugger/vs/VSDebugger.ts b/src/client/debugger/vs/VSDebugger.ts index 5e32ffe16908..c86af83f3db2 100644 --- a/src/client/debugger/vs/VSDebugger.ts +++ b/src/client/debugger/vs/VSDebugger.ts @@ -1,4 +1,8 @@ -"use string"; +'use strict'; + +//Will be deleted, left for debugging and testing against old code +// import {DebugSession} from 'vscode-debugadapter'; +// import {PythonDebugSession} from './BaseDebugger'; import {Variable, DebugSession, InitializedEvent, TerminatedEvent, StoppedEvent, OutputEvent, Thread, StackFrame, Scope, Source, Handles} from 'vscode-debugadapter'; import {ThreadEvent} from 'vscode-debugadapter'; @@ -14,6 +18,7 @@ import * as net from 'net'; import {PythonProcess} from './PythonProcess'; import {FrameKind, IPythonProcess, IPythonThread, IPythonModule, IPythonEvaluationResult, IPythonStackFrame, IDebugServer} from './Common/Contracts'; import {IPythonBreakpoint, PythonBreakpointConditionKind, PythonBreakpointPassCountKind, IPythonException, PythonEvaluationResultReprKind, enum_EXCEPTION_STATE} from './Common/Contracts'; +import {WaitForPortToOpen} from './Common/OnPortOpenedHandler'; const CHILD_ENUMEARATION_TIMEOUT = 5000; @@ -29,6 +34,18 @@ export interface LaunchRequestArguments { pythonPath: string; } +export interface LaunchDjangoRequestArguments extends LaunchRequestArguments { + /** An absolute path to the program to debug. */ + program: string; + /** Automatically stop target after launch. If not specified, target does not stop. */ + stopOnEntry?: boolean; + args: string[]; + pythonPath: string; + port: number; + noreload: boolean; + settings?: string; +} + interface IDebugVariable { variables: IPythonEvaluationResult[]; evaluateChildren?: Boolean; @@ -53,13 +70,14 @@ class PythonDebugSession extends DebugSession { protected initializeRequest(response: DebugProtocol.InitializeResponse, args: DebugProtocol.InitializeRequestArguments): void { this.sendResponse(response); - - // now we are ready to accept breakpoints -> fire the initialized event to give UI a chance to set breakpoints - this.sendEvent(new InitializedEvent()); + // + // // now we are ready to accept breakpoints -> fire the initialized event to give UI a chance to set breakpoints + // this.sendEvent(new InitializedEvent()); } private startedHandlingMessages: Boolean; private pythonProcess: PythonProcess; private debugSocketServer: net.Server; + private isDjango: boolean; private startDebugServer(): Promise { return new Promise((resolve, reject) => { var that = this; @@ -173,9 +191,26 @@ class PythonDebugSession extends DebugSession { private onPythonModuleLoaded(module: IPythonModule) { } private onPythonProcessLoaded(pyThread: IPythonThread) { + // WaitForPortToOpen(5002, 60000).then(() => { + // this.sendResponse(this.entryResponse); + // this.sendEvent(new StoppedEvent("entry", pyThread.Id)); + // this.debuggerLoadedPromiseResolve(); + // }, error=> { + // this.sendErrorResponse(this.entryResponse, 2000, error); + // this.sendEvent(new OutputEvent(error, "stderr")); + // }); + // return; this.sendResponse(this.entryResponse); this.sendEvent(new StoppedEvent("entry", pyThread.Id)); - this.debuggerLoadedPromiseResolve(); + + //Wait for the port to open before loading breakpoints + WaitForPortToOpen(5002, 60000).then(() => { + this.debuggerLoadedPromiseResolve(); + // now we are ready to accept breakpoints -> fire the initialized event to give UI a chance to set breakpoints + this.sendEvent(new InitializedEvent()); + }, error=> { + this.sendEvent(new OutputEvent(error, "stderr")); + }); } private onDebuggerOutput(pyThread: IPythonThread, output: string) { this.sendEvent(new OutputEvent(output)); @@ -202,12 +237,17 @@ class PythonDebugSession extends DebugSession { this.startDebugServer().then(dbgServer => { dbgServer.port var vsDebugOptions = "RedirectOutput"; + vsDebugOptions = "WaitOnAbnormalExit, WaitOnNormalExit, RedirectOutput, DjangoDebugging"; + //GUID is hardcoded for now, will have to be fixed var currentFileName = module.filename; var ptVSToolsFilePath = path.join(path.dirname(currentFileName), "..", "..", "..", "..", "pythonFiles", "PythonTools", "visualstudio_py_launcher.py");// ""; //C:\Users\djayamanne\.vscode\extensions\pythonVSCode\pythonFiles\PythonTools + ptVSToolsFilePath = "C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\Common7\\IDE\\Extensions\\Microsoft\\Python Tools for Visual Studio\\2.2\\visualstudio_py_launcher.py"; var programArgs = Array.isArray(args.args) && args.args.length > 0 ? args.args.join(" ") : ""; - - var commandLine = `${pythonPath} \"${ptVSToolsFilePath}\" \"${fileDir}" ${dbgServer.port} 34806ad9-833a-4524-8cd6-18ca4aa74f14 \"${vsDebugOptions}\" ${fileNameWithoutPath} ${programArgs}`; + programArgs = "runserver --noreload --settings DjangoWebProject1.settings 5002"; + // var commandLine = `${pythonPath} \"${ptVSToolsFilePath}\" \"${fileDir}" ${dbgServer.port} 34806ad9-833a-4524-8cd6-18ca4aa74f14 \"${vsDebugOptions}\" ${fileNameWithoutPath} ${programArgs}`; + var commandLine = `\"${pythonPath}\" \"${ptVSToolsFilePath}\" \"${fileDir}" ${dbgServer.port} 34806ad9-833a-4524-8cd6-18ca4aa74f14 \"${vsDebugOptions}\" \"${args.program}\" ${programArgs}`; + console.log(commandLine); that.pyProc = child_process.exec(commandLine, { cwd: fileDir }, (error, stdout, stderr) => { if (that.startedHandlingMessages) { return; @@ -273,6 +313,13 @@ class PythonDebugSession extends DebugSession { breakpoints.push({ verified: false, line: line }); resolve(); }); + + if (breakpoint.IsDjangoBreakpoint) { + this.registeredBreakpoints.set(breakpoint.Id, breakpoint); + breakpoints.push({ verified: true, line: line }); + registeredBks.push(breakpoint); + resolve(); + } }); });