diff --git a/.vscode/launch.json b/.vscode/launch.json index 3cc9f6c671c6..3fbf982ae0b3 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -8,12 +8,12 @@ "request": "launch", "runtimeExecutable": "${execPath}", "args": [ - "--extensionDevelopmentPath=${workspaceRoot}" + "--extensionDevelopmentPath=${workspaceFolder}" ], "stopOnEntry": false, "sourceMaps": true, "outFiles": [ - "${workspaceRoot}/out/**/*.js" + "${workspaceFolder}/out/**/*.js" ], "preLaunchTask": "compile" }, @@ -21,16 +21,16 @@ "name": "Launch Extension as debugServer", // https://code.visualstudio.com/docs/extensions/example-debuggers "type": "node", "request": "launch", - "program": "${workspaceRoot}/out/client/debugger/Main.js", + "program": "${workspaceFolder}/out/client/debugger/Main.js", "stopOnEntry": false, "args": [ "--server=4711" ], "sourceMaps": true, "outFiles": [ - "${workspaceRoot}/out/client/**/*.js" + "${workspaceFolder}/out/client/**/*.js" ], - "cwd": "${workspaceRoot}" + "cwd": "${workspaceFolder}" }, { "name": "Launch Tests", @@ -38,14 +38,14 @@ "request": "launch", "runtimeExecutable": "${execPath}", "args": [ - "${workspaceRoot}/src/test", - "--extensionDevelopmentPath=${workspaceRoot}", - "--extensionTestsPath=${workspaceRoot}/out/test" + "${workspaceFolder}/src/test", + "--extensionDevelopmentPath=${workspaceFolder}", + "--extensionTestsPath=${workspaceFolder}/out/test" ], "stopOnEntry": false, "sourceMaps": true, "outFiles": [ - "${workspaceRoot}/out/**/*.js" + "${workspaceFolder}/out/**/*.js" ], "preLaunchTask": "compile" }, @@ -55,14 +55,14 @@ "request": "launch", "runtimeExecutable": "${execPath}", "args": [ - "${workspaceRoot}/src/testMultiRootWkspc/multi.code-workspace", - "--extensionDevelopmentPath=${workspaceRoot}", - "--extensionTestsPath=${workspaceRoot}/out/test" + "${workspaceFolder}/src/testMultiRootWkspc/multi.code-workspace", + "--extensionDevelopmentPath=${workspaceFolder}", + "--extensionTestsPath=${workspaceFolder}/out/test" ], "stopOnEntry": false, "sourceMaps": true, "outFiles": [ - "${workspaceRoot}/out/**/*.js" + "${workspaceFolder}/out/**/*.js" ], "preLaunchTask": "compile" } diff --git a/.vscode/tasks.json b/.vscode/tasks.json index bfcb80a350f7..fb0c28d572fd 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -65,6 +65,24 @@ "fileLocation": "relative" } ] + }, + { + "type": "gulp", + "task": "hygiene-watch", + "isBackground": true, + "presentation": { + "echo": true, + "reveal": "always", + "focus": false, + "panel": "shared" + }, + "problemMatcher": [ + "$tsc", + { + "base": "$tslint5", + "fileLocation": "relative" + } + ] } ] } diff --git a/gulpfile.js b/gulpfile.js index f284ad9edc91..f23540ca044a 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -12,6 +12,8 @@ const tsfmt = require('typescript-formatter'); const tslint = require('tslint'); const relative = require('relative'); const ts = require('gulp-typescript'); +const watch = require('gulp-watch'); +const cp = require('child_process'); /** * Hygiene works by creating cascading subsets of all our files and @@ -212,47 +214,51 @@ const hygiene = exports.hygiene = (some, options) => { gulp.task('hygiene', () => hygiene()); -// this allows us to run hygiene as a git pre-commit hook. -if (require.main === module) { - const cp = require('child_process'); +gulp.task('hygiene-watch', function () { + return watch(all, function () { + run(true, true); + }); +}); +function run(lintOnlyModifiedFiles, doNotExit) { + function exitProcessOnError(ex) { + console.error(); + console.error(ex); + if (!doNotExit) { + process.exit(1); + } + } process.on('unhandledRejection', (reason, p) => { console.log('Unhandled Rejection at: Promise', p, 'reason:', reason); - process.exit(1); + exitProcessOnError(); }); cp.exec('git config core.autocrlf', (err, out) => { const skipEOL = out.trim() === 'true'; - - if (process.argv.length > 2) { + if (!lintOnlyModifiedFiles && process.argv.length > 2) { return hygiene(process.argv.slice(2), { skipEOL: skipEOL - }).on('error', err => { - console.error(); - console.error(err); - process.exit(1); - }); + }).on('error', exitProcessOnError); } - cp.exec('git diff --cached --name-only', { + const cmd = lintOnlyModifiedFiles ? 'git diff --name-only' : 'git diff --cached --name-only'; + cp.exec(cmd, { maxBuffer: 2000 * 1024 }, (err, out) => { if (err) { - console.error(); - console.error(err); - process.exit(1); + exitProcessOnError(err); } - const some = out .split(/\r?\n/) .filter(l => !!l); + hygiene(some, { skipEOL: skipEOL - }).on('error', err => { - console.error(); - console.error(err); - process.exit(1); - }); + }).on('error', exitProcessOnError); }); }); } +// this allows us to run hygiene as a git pre-commit hook. +if (require.main === module) { + run(); +} diff --git a/package.json b/package.json index 053c5370522e..7602332878cd 100644 --- a/package.json +++ b/package.json @@ -1,1654 +1,1599 @@ { - "name": "python", - "displayName": "Python", - "description": "Linting, Debugging (multi-threaded, remote), Intellisense, code formatting, refactoring, unit tests, snippets, and more.", - "version": "0.8.0", - "publisher": "ms-python", - "author": { - "name": "Microsoft Corporation" - }, - "license": "MIT", - "homepage": "https://github.com/Microsoft/vscode-python", - "repository": { - "type": "git", - "url": "https://github.com/Microsoft/vscode-python" - }, - "bugs": { - "url": "https://github.com/Microsoft/vscode-python/issues" - }, - "icon": "icon.png", - "galleryBanner": { - "color": "#1e415e", - "theme": "dark" - }, - "engines": { - "vscode": "^1.17.0" - }, - "recommendations": [ - "donjayamanne.jupyter" - ], - "keywords": [ - "python", - "django", - "unittest", - "multi-root ready" - ], - "categories": [ - "Languages", - "Debuggers", - "Linters", - "Snippets", - "Formatters", - "Other" - ], - "activationEvents": [ - "onLanguage:python", - "onCommand:python.execInTerminal", - "onCommand:python.sortImports", - "onCommand:python.runtests", - "onCommand:python.debugtests", - "onCommand:python.setInterpreter", - "onCommand:python.setShebangInterpreter", - "onCommand:python.viewTestUI", - "onCommand:python.viewTestOutput", - "onCommand:python.selectAndRunTestMethod", - "onCommand:python.selectAndDebugTestMethod", - "onCommand:python.selectAndRunTestFile", - "onCommand:python.runCurrentTestFile", - "onCommand:python.runFailedTests", - "onCommand:python.execSelectionInTerminal", - "onCommand:python.execSelectionInDjangoShell", - "onCommand:jupyter.runSelectionLine", - "onCommand:jupyter.execCurrentCell", - "onCommand:jupyter.execCurrentCellAndAdvance", - "onCommand:python.buildWorkspaceSymbols", - "onCommand:python.updateSparkLibrary", - "onCommand:python.startREPL", - "onCommand:python.goToPythonObject" - ], - "main": "./out/client/extension", - "contributes": { - "snippets": [ - { - "language": "python", - "path": "./snippets/python.json" - } + "name": "python", + "displayName": "Python", + "description": "Linting, Debugging (multi-threaded, remote), Intellisense, code formatting, refactoring, unit tests, snippets, and more.", + "version": "0.8.0", + "publisher": "ms-python", + "author": { + "name": "Microsoft Corporation" + }, + "license": "MIT", + "homepage": "https://github.com/Microsoft/vscode-python", + "repository": { + "type": "git", + "url": "https://github.com/Microsoft/vscode-python" + }, + "bugs": { + "url": "https://github.com/Microsoft/vscode-python/issues" + }, + "icon": "icon.png", + "galleryBanner": { + "color": "#1e415e", + "theme": "dark" + }, + "engines": { + "vscode": "^1.17.0" + }, + "recommendations": [ + "donjayamanne.jupyter" ], - "keybindings": [ - { - "command": "jupyter.runSelectionLine", - "key": "ctrl+alt+enter", - "when": "editorFocus && !replaceActive && !searchViewletVisible && !findWidgetVisible" - } + "keywords": [ + "python", + "django", + "unittest", + "multi-root ready" ], - "commands": [ - { - "command": "python.sortImports", - "title": "Sort Imports", - "category": "Python Refactor" - }, - { - "command": "python.startREPL", - "title": "Start REPL", - "category": "Python" - }, - { - "command": "python.buildWorkspaceSymbols", - "title": "Build Workspace Symbols", - "category": "Python" - }, - { - "command": "python.runtests", - "title": "Run All Unit Tests", - "category": "Python" - }, - { - "command": "python.debugtests", - "title": "Debug All Unit Tests", - "category": "Python" - }, - { - "command": "python.execInTerminal", - "title": "Run Python File in Terminal", - "category": "Python" - }, - { - "command": "python.setInterpreter", - "title": "Select Interpreter", - "category": "Python" - }, - { - "command": "python.updateSparkLibrary", - "title": "Update Workspace PySpark Libraries", - "category": "Python" - }, - { - "command": "python.refactorExtractVariable", - "title": "Extract Variable", - "category": "Python Refactor" - }, - { - "command": "python.refactorExtractMethod", - "title": "Extract Method", - "category": "Python Refactor" - }, - { - "command": "python.viewTestOutput", - "title": "Show Unit Test Output", - "category": "Python" - }, - { - "command": "python.selectAndRunTestMethod", - "title": "Run Unit Test Method ...", - "category": "Python" - }, - { - "command": "python.selectAndDebugTestMethod", - "title": "Debug Unit Test Method ...", - "category": "Python" - }, - { - "command": "python.selectAndRunTestFile", - "title": "Run Unit Test File ...", - "category": "Python" - }, - { - "command": "python.runCurrentTestFile", - "title": "Run Current Unit Test File", - "category": "Python" - }, - { - "command": "python.runFailedTests", - "title": "Run Failed Unit Tests", - "category": "Python" - }, - { - "command": "python.execSelectionInTerminal", - "title": "Run Selection/Line in Python Terminal", - "category": "Python" - }, - { - "command": "python.execSelectionInDjangoShell", - "title": "Run Selection/Line in Django Shell", - "category": "Python" - }, - { - "command": "jupyter.runSelectionLine", - "title": "Run Selection/Line", - "category": "Jupyter" - }, - { - "command": "jupyter.execCurrentCell", - "title": "Run Cell", - "category": "Jupyter" - }, - { - "command": "jupyter.execCurrentCellAndAdvance", - "title": "Run Cell and Advance", - "category": "Jupyter" - }, - { - "command": "jupyter.gotToPreviousCell", - "title": "Go to Previous Cell", - "category": "Jupyter" - }, - { - "command": "jupyter.gotToNextCell", - "title": "Go to Next Cell", - "category": "Jupyter" - }, - { - "command": "python.goToPythonObject", - "title": "Go to Python Object", - "category": "Python" - } + "categories": [ + "Languages", + "Debuggers", + "Linters", + "Snippets", + "Formatters", + "Other" ], - "menus": { - "editor/context": [ - { - "command": "python.refactorExtractVariable", - "title": "Refactor: Extract Variable", - "group": "Refactor", - "when": "editorHasSelection && editorLangId == python" - }, - { - "command": "python.refactorExtractMethod", - "title": "Refactor: Extract Method", - "group": "Refactor", - "when": "editorHasSelection && editorLangId == python" - }, - { - "command": "python.sortImports", - "title": "Refactor: Sort Imports", - "group": "Refactor", - "when": "editorLangId == python" - }, - { - "command": "python.execSelectionInTerminal", - "group": "Python", - "when": "editorHasSelection && editorLangId == python" - }, - { - "command": "python.execSelectionInDjangoShell", - "group": "Python", - "when": "editorHasSelection && editorLangId == python && python.isDjangoProject" - }, - { - "when": "resourceLangId == python", - "command": "python.execInTerminal", - "group": "Python" - }, - { - "when": "resourceLangId == python", - "command": "python.runCurrentTestFile", - "group": "Python" - } - ], - "explorer/context": [ - { - "when": "resourceLangId == python", - "command": "python.runtests", - "group": "Python" - }, - { - "when": "resourceLangId == python", - "command": "python.debugtests", - "group": "Python" - }, - { - "when": "resourceLangId == python", - "command": "python.execInTerminal", - "group": "Python" - } - ] - }, - "debuggers": [ - { - "type": "python", - "label": "Python", - "languages": [ - "python" - ], - "enableBreakpointsFor": { - "languageIds": [ - "python", - "html" - ] - }, - "aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217", - "program": "./out/client/debugger/Main.js", - "runtime": "node", - "configurationSnippets": [ - { - "label": "%python.snippet.launch.standard.label%", - "description": "%python.snippet.launch.standard.description%", - "body": { - "name": "Python", - "type": "python", - "request": "launch", - "stopOnEntry": true, - "pythonPath": "^\"\\${config:python.pythonPath}\"", - "program": "^\"\\${file}\"", - "cwd": "^\"\\${workspaceRoot}\"", - "env": {}, - "envFile": "^\"\\${workspaceRoot}/.env\"", - "debugOptions": [ - "WaitOnAbnormalExit", - "WaitOnNormalExit", - "RedirectOutput" - ] - } - }, - { - "label": "%python.snippet.launch.pyspark.label%", - "description": "%python.snippet.launch.pyspark.description%", - "body": { - "name": "PySpark", - "type": "python", - "request": "launch", - "stopOnEntry": true, - "osx": { - "pythonPath": "^\"\\${env:SPARK_HOME}/bin/spark-submit\"" - }, - "windows": { - "pythonPath": "^\"\\${env:SPARK_HOME}/bin/spark-submit.cmd\"" - }, - "linux": { - "pythonPath": "^\"\\${env:SPARK_HOME}/bin/spark-submit\"" - }, - "program": "^\"\\${file}\"", - "cwd": "^\"\\${workspaceRoot}\"", - "env": {}, - "envFile": "^\"\\${workspaceRoot}/.env\"", - "debugOptions": [ - "WaitOnAbnormalExit", - "WaitOnNormalExit", - "RedirectOutput" - ] - } - }, - { - "label": "%python.snippet.launch.module.label%", - "description": "%python.snippet.launch.module.description%", - "body": { - "name": "Python Module", - "type": "python", - "request": "launch", - "stopOnEntry": true, - "pythonPath": "^\"\\${config:python.pythonPath}\"", - "module": "module.name", - "cwd": "^\"\\${workspaceRoot}\"", - "env": {}, - "envFile": "^\"\\${workspaceRoot}/.env\"", - "debugOptions": [ - "WaitOnAbnormalExit", - "WaitOnNormalExit", - "RedirectOutput" - ] - } - }, - { - "label": "%python.snippet.launch.terminal.label%", - "description": "%python.snippet.launch.terminal.description%", - "body": { - "name": "Integrated Terminal/Console", - "type": "python", - "request": "launch", - "stopOnEntry": true, - "pythonPath": "^\"\\${config:python.pythonPath}\"", - "program": "^\"\\${file}\"", - "cwd": "", - "console": "integratedTerminal", - "env": {}, - "envFile": "^\"\\${workspaceRoot}/.env\"", - "debugOptions": [ - "WaitOnAbnormalExit", - "WaitOnNormalExit" - ] - } - }, - { - "label": "%python.snippet.launch.externalTerminal.label%", - "description": "%python.snippet.launch.externalTerminal.description%", - "body": { - "name": "External Terminal/Console", - "type": "python", - "request": "launch", - "stopOnEntry": true, - "pythonPath": "^\"\\${config:python.pythonPath}\"", - "program": "^\"\\${file}\"", - "cwd": "", - "console": "externalTerminal", - "env": {}, - "envFile": "^\"\\${workspaceRoot}/.env\"", - "debugOptions": [ - "WaitOnAbnormalExit", - "WaitOnNormalExit" - ] - } - }, - { - "label": "%python.snippet.launch.django.label%", - "description": "%python.snippet.launch.django.description%", - "body": { - "name": "Django", - "type": "python", - "request": "launch", - "stopOnEntry": true, - "pythonPath": "^\"\\${config:python.pythonPath}\"", - "program": "^\"\\${workspaceRoot}/manage.py\"", - "cwd": "^\"\\${workspaceRoot}\"", - "args": [ - "runserver", - "--noreload", - "--nothreading" - ], - "env": {}, - "envFile": "^\"\\${workspaceRoot}/.env\"", - "debugOptions": [ - "WaitOnAbnormalExit", - "WaitOnNormalExit", - "RedirectOutput", - "DjangoDebugging" - ] - } - }, - { - "label": "%python.snippet.launch.flask.label%", - "description": "%python.snippet.launch.flask.description%", - "body": { - "name": "Flask", - "type": "python", - "request": "launch", - "stopOnEntry": false, - "pythonPath": "^\"\\${config:python.pythonPath}\"", - "program": "fully qualified path fo 'flask' executable. Generally located along with python interpreter", - "cwd": "^\"\\${workspaceRoot}\"", - "env": { - "FLASK_APP": "^\"\\${workspaceRoot}/quickstart/app.py\"" - }, - "args": [ - "run", - "--no-debugger", - "--no-reload" - ], - "envFile": "^\"\\${workspaceRoot}/.env\"", - "debugOptions": [ - "WaitOnAbnormalExit", - "WaitOnNormalExit", - "RedirectOutput" - ] - } - }, - { - "label": "%python.snippet.launch.flaskOld.label%", - "description": "%python.snippet.launch.flaskOld.description%", - "body": { - "name": "Flask (old)", - "type": "python", - "request": "launch", - "stopOnEntry": false, - "pythonPath": "^\"\\${config:python.pythonPath}\"", - "program": "^\"\\${workspaceRoot}/run.py\"", - "cwd": "^\"\\${workspaceRoot}\"", - "args": [], - "env": {}, - "envFile": "^\"\\${workspaceRoot}/.env\"", - "debugOptions": [ - "WaitOnAbnormalExit", - "WaitOnNormalExit", - "RedirectOutput" - ] - } - }, - { - "label": "%python.snippet.launch.pyramid.label%", - "description": "%python.snippet.launch.pyramid.description%", - "body": { - "name": "Pyramid", - "type": "python", - "request": "launch", - "stopOnEntry": true, - "pythonPath": "^\"\\${config:python.pythonPath}\"", - "cwd": "^\"\\${workspaceRoot}\"", - "env": {}, - "envFile": "^\"\\${workspaceRoot}/.env\"", - "args": [ - "^\"\\${workspaceRoot}/development.ini\"" - ], - "debugOptions": [ - "WaitOnAbnormalExit", - "WaitOnNormalExit", - "RedirectOutput", - "Pyramid" - ] - } - }, - { - "label": "%python.snippet.launch.watson.label%", - "description": "%python.snippet.launch.watson.description%", - "body": { - "name": "Watson", - "type": "python", - "request": "launch", - "stopOnEntry": true, - "pythonPath": "^\"\\${config:python.pythonPath}\"", - "program": "^\"\\${workspaceRoot}/console.py\"", - "cwd": "^\"\\${workspaceRoot}\"", - "args": [ - "dev", - "runserver", - "--noreload=True" - ], - "env": {}, - "envFile": "^\"\\${workspaceRoot}/.env\"", - "debugOptions": [ - "WaitOnAbnormalExit", - "WaitOnNormalExit", - "RedirectOutput" - ] + "activationEvents": [ + "onLanguage:python", + "onCommand:python.execInTerminal", + "onCommand:python.sortImports", + "onCommand:python.runtests", + "onCommand:python.debugtests", + "onCommand:python.setInterpreter", + "onCommand:python.setShebangInterpreter", + "onCommand:python.viewTestUI", + "onCommand:python.viewTestOutput", + "onCommand:python.selectAndRunTestMethod", + "onCommand:python.selectAndDebugTestMethod", + "onCommand:python.selectAndRunTestFile", + "onCommand:python.runCurrentTestFile", + "onCommand:python.runFailedTests", + "onCommand:python.execSelectionInTerminal", + "onCommand:python.execSelectionInDjangoShell", + "onCommand:jupyter.runSelectionLine", + "onCommand:jupyter.execCurrentCell", + "onCommand:jupyter.execCurrentCellAndAdvance", + "onCommand:python.buildWorkspaceSymbols", + "onCommand:python.updateSparkLibrary", + "onCommand:python.startREPL", + "onCommand:python.goToPythonObject" + ], + "main": "./out/client/extension", + "contributes": { + "snippets": [ + { + "language": "python", + "path": "./snippets/python.json" } - }, - { - "label": "%python.snippet.launch.scrapy.label%", - "description": "%python.snippet.launch.scrapy.description%", - "body": { - "name": "Scrapy", - "type": "python", - "request": "launch", - "stopOnEntry": true, - "pythonPath": "^\"\\${config:python.pythonPath}\"", - "program": "~/.virtualenvs/scrapy/bin/scrapy", - "cwd": "^\"\\${workspaceRoot}\"", - "args": [ - "crawl", - "specs", - "-o", - "bikes.json" - ], - "console": "integratedTerminal", - "env": {}, - "envFile": "^\"\\${workspaceRoot}/.env\"", - "debugOptions": [ - "WaitOnAbnormalExit", - "WaitOnNormalExit" - ] + ], + "keybindings": [ + { + "command": "jupyter.runSelectionLine", + "key": "ctrl+alt+enter", + "when": "editorFocus && !replaceActive && !searchViewletVisible && !findWidgetVisible" } - }, - { - "label": "%python.snippet.launch.attach.label%", - "description": "%python.snippet.launch.attach.description%", - "body": { - "name": "Attach (Remote Debug)", - "type": "python", - "request": "attach", - "localRoot": "^\"\\${workspaceRoot}\"", - "remoteRoot": "^\"\\${workspaceRoot}\"", - "port": 3000, - "secret": "my_secret", - "host": "localhost" + ], + "commands": [ + { + "command": "python.sortImports", + "title": "Sort Imports", + "category": "Python Refactor" + }, + { + "command": "python.startREPL", + "title": "Start REPL", + "category": "Python" + }, + { + "command": "python.buildWorkspaceSymbols", + "title": "Build Workspace Symbols", + "category": "Python" + }, + { + "command": "python.runtests", + "title": "Run All Unit Tests", + "category": "Python" + }, + { + "command": "python.debugtests", + "title": "Debug All Unit Tests", + "category": "Python" + }, + { + "command": "python.execInTerminal", + "title": "Run Python File in Terminal", + "category": "Python" + }, + { + "command": "python.setInterpreter", + "title": "Select Interpreter", + "category": "Python" + }, + { + "command": "python.updateSparkLibrary", + "title": "Update Workspace PySpark Libraries", + "category": "Python" + }, + { + "command": "python.refactorExtractVariable", + "title": "Extract Variable", + "category": "Python Refactor" + }, + { + "command": "python.refactorExtractMethod", + "title": "Extract Method", + "category": "Python Refactor" + }, + { + "command": "python.viewTestOutput", + "title": "Show Unit Test Output", + "category": "Python" + }, + { + "command": "python.selectAndRunTestMethod", + "title": "Run Unit Test Method ...", + "category": "Python" + }, + { + "command": "python.selectAndDebugTestMethod", + "title": "Debug Unit Test Method ...", + "category": "Python" + }, + { + "command": "python.selectAndRunTestFile", + "title": "Run Unit Test File ...", + "category": "Python" + }, + { + "command": "python.runCurrentTestFile", + "title": "Run Current Unit Test File", + "category": "Python" + }, + { + "command": "python.runFailedTests", + "title": "Run Failed Unit Tests", + "category": "Python" + }, + { + "command": "python.execSelectionInTerminal", + "title": "Run Selection/Line in Python Terminal", + "category": "Python" + }, + { + "command": "python.execSelectionInDjangoShell", + "title": "Run Selection/Line in Django Shell", + "category": "Python" + }, + { + "command": "jupyter.runSelectionLine", + "title": "Run Selection/Line", + "category": "Jupyter" + }, + { + "command": "jupyter.execCurrentCell", + "title": "Run Cell", + "category": "Jupyter" + }, + { + "command": "jupyter.execCurrentCellAndAdvance", + "title": "Run Cell and Advance", + "category": "Jupyter" + }, + { + "command": "jupyter.gotToPreviousCell", + "title": "Go to Previous Cell", + "category": "Jupyter" + }, + { + "command": "jupyter.gotToNextCell", + "title": "Go to Next Cell", + "category": "Jupyter" + }, + { + "command": "python.goToPythonObject", + "title": "Go to Python Object", + "category": "Python" } - } ], - "configurationAttributes": { - "launch": { - "properties": { - "module": { - "type": "string", - "description": "Name of the module to be debugged.", - "default": "" - }, - "program": { - "type": "string", - "description": "Absolute path to the program.", - "default": "${file}" - }, - "pythonPath": { - "type": "string", - "description": "Path (fully qualified) to python executable. Defaults to the value in settings.json", - "default": "${config:python.pythonPath}" - }, - "args": { - "type": "array", - "description": "Command line arguments passed to the program", - "default": [], - "items": { - "type": "string" + "menus": { + "editor/context": [ + { + "command": "python.refactorExtractVariable", + "title": "Refactor: Extract Variable", + "group": "Refactor", + "when": "editorHasSelection && editorLangId == python" + }, + { + "command": "python.refactorExtractMethod", + "title": "Refactor: Extract Method", + "group": "Refactor", + "when": "editorHasSelection && editorLangId == python" + }, + { + "command": "python.sortImports", + "title": "Refactor: Sort Imports", + "group": "Refactor", + "when": "editorLangId == python" + }, + { + "command": "python.execSelectionInTerminal", + "group": "Python", + "when": "editorHasSelection && editorLangId == python" + }, + { + "command": "python.execSelectionInDjangoShell", + "group": "Python", + "when": "editorHasSelection && editorLangId == python && python.isDjangoProject" + }, + { + "when": "resourceLangId == python", + "command": "python.execInTerminal", + "group": "Python" + }, + { + "when": "resourceLangId == python", + "command": "python.runCurrentTestFile", + "group": "Python" + } + ], + "explorer/context": [ + { + "when": "resourceLangId == python", + "command": "python.runtests", + "group": "Python" + }, + { + "when": "resourceLangId == python", + "command": "python.debugtests", + "group": "Python" + }, + { + "when": "resourceLangId == python", + "command": "python.execInTerminal", + "group": "Python" } - }, - "stopOnEntry": { - "type": "boolean", - "description": "Automatically stop after launch.", - "default": true - }, - "externalConsole": { - "type": "boolean", - "description": "Deprecated: use 'console' attribute instead.", - "default": false - }, - "console": { - "enum": [ - "none", - "integratedTerminal", - "externalTerminal" + ] + }, + "debuggers": [ + { + "type": "python", + "label": "Python", + "languages": [ + "python" ], - "description": "Where to launch the debug target: internal console, integrated terminal, or external terminal.", - "default": "none" - }, - "cwd": { - "type": "string", - "description": "Absolute path to the working directory of the program being debugged. Default is the root directory of the file (leave empty).", - "default": "" - }, - "debugOptions": { - "type": "array", - "description": "Advanced options, view read me for further details.", - "items": { - "type": "string", - "enum": [ - "WaitOnAbnormalExit", - "WaitOnNormalExit", - "RedirectOutput", - "DebugStdLib", - "BreakOnSystemExitZero", - "DjangoDebugging", - "Sudo", - "IgnoreDjangoTemplateWarnings", - "Pyramid" - ] - }, - "default": [ - "WaitOnAbnormalExit", - "WaitOnNormalExit", - "RedirectOutput" + "enableBreakpointsFor": { + "languageIds": [ + "python", + "html" + ] + }, + "aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217", + "program": "./out/client/debugger/Main.js", + "runtime": "node", + "configurationSnippets": [ + { + "label": "%python.snippet.launch.standard.label%", + "description": "%python.snippet.launch.standard.description%", + "body": { + "name": "Python", + "type": "python", + "request": "launch", + "stopOnEntry": true, + "pythonPath": "^\"\\${config:python.pythonPath}\"", + "program": "^\"\\${file}\"", + "cwd": "^\"\\${workspaceFolder}\"", + "env": {}, + "envFile": "^\"\\${workspaceFolder}/.env\"", + "debugOptions": [ + "RedirectOutput" + ] + } + }, + { + "label": "%python.snippet.launch.pyspark.label%", + "description": "%python.snippet.launch.pyspark.description%", + "body": { + "name": "PySpark", + "type": "python", + "request": "launch", + "stopOnEntry": true, + "osx": { + "pythonPath": "^\"\\${env:SPARK_HOME}/bin/spark-submit\"" + }, + "windows": { + "pythonPath": "^\"\\${env:SPARK_HOME}/bin/spark-submit.cmd\"" + }, + "linux": { + "pythonPath": "^\"\\${env:SPARK_HOME}/bin/spark-submit\"" + }, + "program": "^\"\\${file}\"", + "cwd": "^\"\\${workspaceFolder}\"", + "env": {}, + "envFile": "^\"\\${workspaceFolder}/.env\"", + "debugOptions": [ + "RedirectOutput" + ] + } + }, + { + "label": "%python.snippet.launch.module.label%", + "description": "%python.snippet.launch.module.description%", + "body": { + "name": "Python Module", + "type": "python", + "request": "launch", + "stopOnEntry": true, + "pythonPath": "^\"\\${config:python.pythonPath}\"", + "module": "module.name", + "cwd": "^\"\\${workspaceFolder}\"", + "env": {}, + "envFile": "^\"\\${workspaceFolder}/.env\"", + "debugOptions": [ + "RedirectOutput" + ] + } + }, + { + "label": "%python.snippet.launch.terminal.label%", + "description": "%python.snippet.launch.terminal.description%", + "body": { + "name": "Integrated Terminal/Console", + "type": "python", + "request": "launch", + "stopOnEntry": true, + "pythonPath": "^\"\\${config:python.pythonPath}\"", + "program": "^\"\\${file}\"", + "cwd": "", + "console": "integratedTerminal", + "env": {}, + "envFile": "^\"\\${workspaceFolder}/.env\"", + "debugOptions": [] + } + }, + { + "label": "%python.snippet.launch.externalTerminal.label%", + "description": "%python.snippet.launch.externalTerminal.description%", + "body": { + "name": "External Terminal/Console", + "type": "python", + "request": "launch", + "stopOnEntry": true, + "pythonPath": "^\"\\${config:python.pythonPath}\"", + "program": "^\"\\${file}\"", + "cwd": "", + "console": "externalTerminal", + "env": {}, + "envFile": "^\"\\${workspaceFolder}/.env\"", + "debugOptions": [] + } + }, + { + "label": "%python.snippet.launch.django.label%", + "description": "%python.snippet.launch.django.description%", + "body": { + "name": "Django", + "type": "python", + "request": "launch", + "stopOnEntry": true, + "pythonPath": "^\"\\${config:python.pythonPath}\"", + "program": "^\"\\${workspaceFolder}/manage.py\"", + "cwd": "^\"\\${workspaceFolder}\"", + "args": [ + "runserver", + "--noreload", + "--nothreading" + ], + "env": {}, + "envFile": "^\"\\${workspaceFolder}/.env\"", + "debugOptions": [ + "RedirectOutput", + "DjangoDebugging" + ] + } + }, + { + "label": "%python.snippet.launch.flask.label%", + "description": "%python.snippet.launch.flask.description%", + "body": { + "name": "Flask", + "type": "python", + "request": "launch", + "stopOnEntry": false, + "pythonPath": "^\"\\${config:python.pythonPath}\"", + "program": "fully qualified path fo 'flask' executable. Generally located along with python interpreter", + "cwd": "^\"\\${workspaceFolder}\"", + "env": { + "FLASK_APP": "^\"\\${workspaceFolder}/quickstart/app.py\"" + }, + "args": [ + "run", + "--no-debugger", + "--no-reload" + ], + "envFile": "^\"\\${workspaceFolder}/.env\"", + "debugOptions": [ + "RedirectOutput" + ] + } + }, + { + "label": "%python.snippet.launch.flaskOld.label%", + "description": "%python.snippet.launch.flaskOld.description%", + "body": { + "name": "Flask (old)", + "type": "python", + "request": "launch", + "stopOnEntry": false, + "pythonPath": "^\"\\${config:python.pythonPath}\"", + "program": "^\"\\${workspaceFolder}/run.py\"", + "cwd": "^\"\\${workspaceFolder}\"", + "args": [], + "env": {}, + "envFile": "^\"\\${workspaceFolder}/.env\"", + "debugOptions": [ + "RedirectOutput" + ] + } + }, + { + "label": "%python.snippet.launch.pyramid.label%", + "description": "%python.snippet.launch.pyramid.description%", + "body": { + "name": "Pyramid", + "type": "python", + "request": "launch", + "stopOnEntry": true, + "pythonPath": "^\"\\${config:python.pythonPath}\"", + "cwd": "^\"\\${workspaceFolder}\"", + "env": {}, + "envFile": "^\"\\${workspaceFolder}/.env\"", + "args": [ + "^\"\\${workspaceFolder}/development.ini\"" + ], + "debugOptions": [ + "RedirectOutput", + "Pyramid" + ] + } + }, + { + "label": "%python.snippet.launch.watson.label%", + "description": "%python.snippet.launch.watson.description%", + "body": { + "name": "Watson", + "type": "python", + "request": "launch", + "stopOnEntry": true, + "pythonPath": "^\"\\${config:python.pythonPath}\"", + "program": "^\"\\${workspaceFolder}/console.py\"", + "cwd": "^\"\\${workspaceFolder}\"", + "args": [ + "dev", + "runserver", + "--noreload=True" + ], + "env": {}, + "envFile": "^\"\\${workspaceFolder}/.env\"", + "debugOptions": [ + "RedirectOutput" + ] + } + }, + { + "label": "%python.snippet.launch.scrapy.label%", + "description": "%python.snippet.launch.scrapy.description%", + "body": { + "name": "Scrapy", + "type": "python", + "request": "launch", + "stopOnEntry": true, + "pythonPath": "^\"\\${config:python.pythonPath}\"", + "program": "~/.virtualenvs/scrapy/bin/scrapy", + "cwd": "^\"\\${workspaceFolder}\"", + "args": [ + "crawl", + "specs", + "-o", + "bikes.json" + ], + "console": "integratedTerminal", + "env": {}, + "envFile": "^\"\\${workspaceFolder}/.env\"", + "debugOptions": [] + } + }, + { + "label": "%python.snippet.launch.attach.label%", + "description": "%python.snippet.launch.attach.description%", + "body": { + "name": "Attach (Remote Debug)", + "type": "python", + "request": "attach", + "localRoot": "^\"\\${workspaceFolder}\"", + "remoteRoot": "^\"\\${workspaceFolder}\"", + "port": 3000, + "secret": "my_secret", + "host": "localhost" + } + } + ], + "configurationAttributes": { + "launch": { + "properties": { + "module": { + "type": "string", + "description": "Name of the module to be debugged.", + "default": "" + }, + "program": { + "type": "string", + "description": "Absolute path to the program.", + "default": "${file}" + }, + "pythonPath": { + "type": "string", + "description": "Path (fully qualified) to python executable. Defaults to the value in settings.json", + "default": "${config:python.pythonPath}" + }, + "args": { + "type": "array", + "description": "Command line arguments passed to the program", + "default": [], + "items": { + "type": "string" + } + }, + "stopOnEntry": { + "type": "boolean", + "description": "Automatically stop after launch.", + "default": true + }, + "console": { + "enum": [ + "none", + "integratedTerminal", + "externalTerminal" + ], + "description": "Where to launch the debug target: internal console, integrated terminal, or external terminal.", + "default": "none" + }, + "cwd": { + "type": "string", + "description": "Absolute path to the working directory of the program being debugged. Default is the root directory of the file (leave empty).", + "default": "" + }, + "debugOptions": { + "type": "array", + "description": "Advanced options, view read me for further details.", + "items": { + "type": "string", + "enum": [ + "RedirectOutput", + "DebugStdLib", + "BreakOnSystemExitZero", + "DjangoDebugging", + "Sudo", + "IgnoreDjangoTemplateWarnings", + "Pyramid" + ] + }, + "default": [ + "RedirectOutput" + ] + }, + "exceptionHandling": { + "description": "List of exception types and how they are handled during debugging (ignore, always break or break only if unhandled).", + "properties": { + "ignore": { + "type": "array", + "description": "Never break into these exceptions, e.g. 'copy.Error'", + "default": [], + "items": { + "type": "string" + } + }, + "always": { + "type": "array", + "description": "Always break into these exceptions, e.g. 'copy.Error'", + "default": [], + "items": { + "type": "string" + } + }, + "unhandled": { + "type": "array", + "description": "Break into these exceptions if they aren't handled, e.g. 'copy.Error'", + "default": [], + "items": { + "type": "string" + } + } + } + }, + "env": { + "type": "object", + "description": "Environment variables defined as a key value pair. Property ends up being the Environment Variable and the value of the property ends up being the value of the Env Variable.", + "default": {} + }, + "envFile": { + "type": "string", + "description": "Absolute path to a file containing environment variable definitions.", + "default": "" + } + } + }, + "attach": { + "required": [ + "localRoot", + "remoteRoot" + ], + "properties": { + "localRoot": { + "type": "string", + "description": "Local source root that corrresponds to the 'remoteRoot'.", + "default": "${workspaceFolder}" + }, + "remoteRoot": { + "type": "string", + "description": "The source root of the remote host.", + "default": "" + }, + "port": { + "type": "number", + "description": "Debug port to attach", + "default": 0 + }, + "host": { + "type": "string", + "description": "IP Address of the of remote server (default is localhost or use 127.0.0.1).", + "default": "localhost" + }, + "secret": { + "type": "string", + "description": "Secret used to authenticate for remote debugging.", + "default": "" + } + } + } + }, + "initialConfigurations": [ + { + "name": "Python", + "type": "python", + "request": "launch", + "stopOnEntry": true, + "pythonPath": "${config:python.pythonPath}", + "program": "${file}", + "cwd": "${workspaceFolder}", + "env": {}, + "envFile": "${workspaceFolder}/.env", + "debugOptions": [ + "RedirectOutput" + ] + }, + { + "name": "Python: Attach", + "type": "python", + "request": "attach", + "localRoot": "${workspaceFolder}", + "remoteRoot": "${workspaceFolder}", + "port": 3000, + "secret": "my_secret", + "host": "localhost" + }, + { + "name": "Python: Terminal (integrated)", + "type": "python", + "request": "launch", + "stopOnEntry": true, + "pythonPath": "${config:python.pythonPath}", + "program": "${file}", + "cwd": "", + "console": "integratedTerminal", + "env": {}, + "envFile": "${workspaceFolder}/.env", + "debugOptions": [] + }, + { + "name": "Python: Terminal (external)", + "type": "python", + "request": "launch", + "stopOnEntry": true, + "pythonPath": "${config:python.pythonPath}", + "program": "${file}", + "cwd": "", + "console": "externalTerminal", + "env": {}, + "envFile": "${workspaceFolder}/.env", + "debugOptions": [] + }, + { + "name": "Python: Django", + "type": "python", + "request": "launch", + "stopOnEntry": true, + "pythonPath": "${config:python.pythonPath}", + "program": "${workspaceFolder}/manage.py", + "cwd": "${workspaceFolder}", + "args": [ + "runserver", + "--noreload", + "--nothreading" + ], + "env": {}, + "envFile": "${workspaceFolder}/.env", + "debugOptions": [ + "RedirectOutput", + "DjangoDebugging" + ] + }, + { + "name": "Python: Flask (0.11.x or later)", + "type": "python", + "request": "launch", + "stopOnEntry": false, + "pythonPath": "${config:python.pythonPath}", + "program": "fully qualified path fo 'flask' executable. Generally located along with python interpreter", + "cwd": "${workspaceFolder}", + "env": { + "FLASK_APP": "${workspaceFolder}/quickstart/app.py" + }, + "args": [ + "run", + "--no-debugger", + "--no-reload" + ], + "envFile": "${workspaceFolder}/.env", + "debugOptions": [ + "RedirectOutput" + ] + }, + { + "name": "Python: Flask (0.10.x or earlier)", + "type": "python", + "request": "launch", + "stopOnEntry": false, + "pythonPath": "${config:python.pythonPath}", + "program": "${workspaceFolder}/run.py", + "cwd": "${workspaceFolder}", + "args": [], + "env": {}, + "envFile": "${workspaceFolder}/.env", + "debugOptions": [ + "RedirectOutput" + ] + }, + { + "name": "Python: PySpark", + "type": "python", + "request": "launch", + "stopOnEntry": true, + "osx": { + "pythonPath": "${env:SPARK_HOME}/bin/spark-submit" + }, + "windows": { + "pythonPath": "${env:SPARK_HOME}/bin/spark-submit.cmd" + }, + "linux": { + "pythonPath": "${env:SPARK_HOME}/bin/spark-submit" + }, + "program": "${file}", + "cwd": "${workspaceFolder}", + "env": {}, + "envFile": "${workspaceFolder}/.env", + "debugOptions": [ + "RedirectOutput" + ] + }, + { + "name": "Python: Module", + "type": "python", + "request": "launch", + "stopOnEntry": true, + "pythonPath": "${config:python.pythonPath}", + "module": "module.name", + "cwd": "${workspaceFolder}", + "env": {}, + "envFile": "${workspaceFolder}/.env", + "debugOptions": [ + "RedirectOutput" + ] + }, + { + "name": "Python: Pyramid", + "type": "python", + "request": "launch", + "stopOnEntry": true, + "pythonPath": "${config:python.pythonPath}", + "cwd": "${workspaceFolder}", + "env": {}, + "envFile": "${workspaceFolder}/.env", + "args": [ + "${workspaceFolder}/development.ini" + ], + "debugOptions": [ + "RedirectOutput", + "Pyramid" + ] + }, + { + "name": "Python: Watson", + "type": "python", + "request": "launch", + "stopOnEntry": true, + "pythonPath": "${config:python.pythonPath}", + "program": "${workspaceFolder}/console.py", + "cwd": "${workspaceFolder}", + "args": [ + "dev", + "runserver", + "--noreload=True" + ], + "env": {}, + "envFile": "${workspaceFolder}/.env", + "debugOptions": [ + "RedirectOutput" + ] + } ] - }, - "exceptionHandling": { - "description": "List of exception types and how they are handled during debugging (ignore, always break or break only if unhandled).", - "properties": { - "ignore": { + } + ], + "configuration": { + "type": "object", + "title": "Python Configuration", + "properties": { + "python.promptToInstallJupyter": { + "type": "boolean", + "default": true, + "description": "Display prompt to install Jupyter Extension.", + "scope": "resource" + }, + "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.", + "scope": "resource" + }, + "python.venvPath": { + "type": "string", + "default": "", + "description": "Path to folder with a list of Virtual Environments (e.g. ~/.pyenv, ~/Envs, ~/.virtualenvs).", + "scope": "resource" + }, + "python.envFile": { + "type": "string", + "description": "Absolute path to a file containing environment variable definitions.", + "default": "${workspaceFolder}/.env", + "scope": "resource" + }, + "python.jediPath": { + "type": "string", + "default": "", + "description": "Path to directory containing the Jedi library (this path will contain the 'Jedi' sub directory).", + "scope": "resource" + }, + "python.sortImports.path": { + "type": "string", + "description": "Path to isort script, default using inner version", + "default": "", + "scope": "resource" + }, + "python.sortImports.args": { "type": "array", - "description": "Never break into these exceptions, e.g. 'copy.Error'", + "description": "Arguments passed in. Each argument is a separate item in the array.", "default": [], "items": { - "type": "string" - } - }, - "always": { + "type": "string" + }, + "scope": "resource" + }, + "python.disablePromptForFeatures": { "type": "array", - "description": "Always break into these exceptions, e.g. 'copy.Error'", "default": [], + "description": "Do not display a prompt to install these features", "items": { - "type": "string" - } - }, - "unhandled": { + "type": "string", + "default": "pylint", + "description": "Feature", + "enum": [ + "flake8", + "mypy", + "pep8", + "pylama", + "prospector", + "pydocstyle", + "pylint" + ] + }, + "scope": "resource" + }, + "python.linting.enabled": { + "type": "boolean", + "default": true, + "description": "Whether to lint Python files.", + "scope": "resource" + }, + "python.linting.enabledWithoutWorkspace": { + "type": "boolean", + "default": true, + "description": "Whether to lint Python files when no workspace is opened.", + "scope": "resource" + }, + "python.linting.prospectorEnabled": { + "type": "boolean", + "default": false, + "description": "Whether to lint Python files using prospector.", + "scope": "resource" + }, + "python.linting.pylintEnabled": { + "type": "boolean", + "default": true, + "description": "Whether to lint Python files using pylint.", + "scope": "resource" + }, + "python.linting.pep8Enabled": { + "type": "boolean", + "default": false, + "description": "Whether to lint Python files using pep8", + "scope": "resource" + }, + "python.linting.flake8Enabled": { + "type": "boolean", + "default": false, + "description": "Whether to lint Python files using flake8", + "scope": "resource" + }, + "python.linting.pydocstyleEnabled": { + "type": "boolean", + "default": false, + "description": "Whether to lint Python files using pydocstyle", + "scope": "resource" + }, + "python.linting.mypyEnabled": { + "type": "boolean", + "default": false, + "description": "Whether to lint Python files using mypy.", + "scope": "resource" + }, + "python.linting.lintOnTextChange": { + "type": "boolean", + "default": true, + "description": "Whether to lint Python files when modified.", + "scope": "resource" + }, + "python.linting.lintOnSave": { + "type": "boolean", + "default": true, + "description": "Whether to lint Python files when saved.", + "scope": "resource" + }, + "python.linting.maxNumberOfProblems": { + "type": "number", + "default": 100, + "description": "Controls the maximum number of problems produced by the server.", + "scope": "resource" + }, + "python.linting.pylintCategorySeverity.convention": { + "type": "string", + "default": "Information", + "description": "Severity of Pylint message type 'Convention/C'.", + "enum": [ + "Hint", + "Error", + "Information", + "Warning" + ], + "scope": "resource" + }, + "python.linting.pylintCategorySeverity.refactor": { + "type": "string", + "default": "Hint", + "description": "Severity of Pylint message type 'Refactor/R'.", + "enum": [ + "Hint", + "Error", + "Information", + "Warning" + ], + "scope": "resource" + }, + "python.linting.pylintCategorySeverity.warning": { + "type": "string", + "default": "Warning", + "description": "Severity of Pylint message type 'Warning/W'.", + "enum": [ + "Hint", + "Error", + "Information", + "Warning" + ], + "scope": "resource" + }, + "python.linting.pylintCategorySeverity.error": { + "type": "string", + "default": "Error", + "description": "Severity of Pylint message type 'Error/E'.", + "enum": [ + "Hint", + "Error", + "Information", + "Warning" + ], + "scope": "resource" + }, + "python.linting.pylintCategorySeverity.fatal": { + "type": "string", + "default": "Error", + "description": "Severity of Pylint message type 'Fatal/F'.", + "enum": [ + "Hint", + "Error", + "Information", + "Warning" + ], + "scope": "resource" + }, + "python.linting.pep8CategorySeverity.W": { + "type": "string", + "default": "Warning", + "description": "Severity of Pep8 message type 'W'.", + "enum": [ + "Hint", + "Error", + "Information", + "Warning" + ], + "scope": "resource" + }, + "python.linting.pep8CategorySeverity.E": { + "type": "string", + "default": "Error", + "description": "Severity of Pep8 message type 'E'.", + "enum": [ + "Hint", + "Error", + "Information", + "Warning" + ], + "scope": "resource" + }, + "python.linting.flake8CategorySeverity.F": { + "type": "string", + "default": "Error", + "description": "Severity of Flake8 message type 'F'.", + "enum": [ + "Hint", + "Error", + "Information", + "Warning" + ], + "scope": "resource" + }, + "python.linting.flake8CategorySeverity.E": { + "type": "string", + "default": "Error", + "description": "Severity of Flake8 message type 'E'.", + "enum": [ + "Hint", + "Error", + "Information", + "Warning" + ], + "scope": "resource" + }, + "python.linting.flake8CategorySeverity.W": { + "type": "string", + "default": "Warning", + "description": "Severity of Flake8 message type 'W'.", + "enum": [ + "Hint", + "Error", + "Information", + "Warning" + ], + "scope": "resource" + }, + "python.linting.mypyCategorySeverity.error": { + "type": "string", + "default": "Error", + "description": "Severity of Mypy message type 'Error'.", + "enum": [ + "Hint", + "Error", + "Information", + "Warning" + ], + "scope": "resource" + }, + "python.linting.mypyCategorySeverity.note": { + "type": "string", + "default": "Information", + "description": "Severity of Mypy message type 'Note'.", + "enum": [ + "Hint", + "Error", + "Information", + "Warning" + ], + "scope": "resource" + }, + "python.linting.prospectorPath": { + "type": "string", + "default": "prospector", + "description": "Path to Prospector, you can use a custom version of prospector by modifying this setting to include the full path.", + "scope": "resource" + }, + "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.", + "scope": "resource" + }, + "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.", + "scope": "resource" + }, + "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.", + "scope": "resource" + }, + "python.linting.pydocstylePath": { + "type": "string", + "default": "pydocstyle", + "description": "Path to pydocstyle, you can use a custom version of pydocstyle by modifying this setting to include the full path.", + "scope": "resource" + }, + "python.linting.mypyPath": { + "type": "string", + "default": "mypy", + "description": "Path to mypy, you can use a custom version of mypy by modifying this setting to include the full path.", + "scope": "resource" + }, + "python.linting.prospectorArgs": { "type": "array", - "description": "Break into these exceptions if they aren't handled, e.g. 'copy.Error'", + "description": "Arguments passed in. Each argument is a separate item in the array.", "default": [], "items": { - "type": "string" - } - } + "type": "string" + }, + "scope": "resource" + }, + "python.linting.pylintArgs": { + "type": "array", + "description": "Arguments passed in. Each argument is a separate item in the array.", + "default": [], + "items": { + "type": "string" + }, + "scope": "resource" + }, + "python.linting.pep8Args": { + "type": "array", + "description": "Arguments passed in. Each argument is a separate item in the array.", + "default": [], + "items": { + "type": "string" + }, + "scope": "resource" + }, + "python.linting.flake8Args": { + "type": "array", + "description": "Arguments passed in. Each argument is a separate item in the array.", + "default": [], + "items": { + "type": "string" + }, + "scope": "resource" + }, + "python.linting.pydocstyleArgs": { + "type": "array", + "description": "Arguments passed in. Each argument is a separate item in the array.", + "default": [], + "items": { + "type": "string" + }, + "scope": "resource" + }, + "python.linting.mypyArgs": { + "type": "array", + "description": "Arguments passed in. Each argument is a separate item in the array.", + "default": [ + "--ignore-missing-imports", + "--follow-imports=silent" + ], + "items": { + "type": "string" + }, + "scope": "resource" + }, + "python.linting.outputWindow": { + "type": "string", + "default": "Python", + "description": "The output window name for the linting messages, defaults to Python output window.", + "scope": "resource" + }, + "python.formatting.provider": { + "type": "string", + "default": "autopep8", + "description": "Provider for formatting. Possible options include 'autopep8' and 'yapf'.", + "enum": [ + "autopep8", + "yapf", + "none" + ], + "scope": "resource" + }, + "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.", + "scope": "resource" + }, + "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.", + "scope": "resource" + }, + "python.formatting.autopep8Args": { + "type": "array", + "description": "Arguments passed in. Each argument is a separate item in the array.", + "default": [], + "items": { + "type": "string" + }, + "scope": "resource" + }, + "python.formatting.yapfArgs": { + "type": "array", + "description": "Arguments passed in. Each argument is a separate item in the array.", + "default": [], + "items": { + "type": "string" + }, + "scope": "resource" + }, + "python.formatting.formatOnSave": { + "type": "boolean", + "default": false, + "description": "Format the document upon saving.", + "scope": "resource" + }, + "python.formatting.outputWindow": { + "type": "string", + "default": "Python", + "description": "The output window name for the formatting messages, defaults to Python output window.", + "scope": "resource" + }, + "python.autoComplete.preloadModules": { + "type": "array", + "items": { + "type": "string" + }, + "default": [], + "description": "Comma delimited list of modules preloaded to speed up Auto Complete (e.g. add Numpy, Pandas, etc, items slow to load when autocompleting).", + "scope": "resource" + }, + "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.", + "scope": "resource" + }, + "python.autoComplete.addBrackets": { + "type": "boolean", + "default": false, + "description": "Automatically add brackets for functions.", + "scope": "resource" + }, + "python.workspaceSymbols.tagFilePath": { + "type": "string", + "default": "${workspaceFolder}/.vscode/tags", + "description": "Fully qualified path to tag file (exuberant ctag file), used to provide workspace symbols.", + "scope": "resource" + }, + "python.workspaceSymbols.enabled": { + "type": "boolean", + "default": true, + "description": "Set to 'false' to disable Workspace Symbol provider using ctags.", + "scope": "resource" + }, + "python.workspaceSymbols.rebuildOnStart": { + "type": "boolean", + "default": true, + "description": "Whether to re-build the tags file on start (defaults to true).", + "scope": "resource" + }, + "python.workspaceSymbols.rebuildOnFileSave": { + "type": "boolean", + "default": true, + "description": "Whether to re-build the tags file on when changes made to python files are saved.", + "scope": "resource" + }, + "python.workspaceSymbols.ctagsPath": { + "type": "string", + "default": "ctags", + "description": "Fully qualilified path to the ctags executable (else leave as ctags, assuming it is in current path).", + "scope": "resource" + }, + "python.workspaceSymbols.exclusionPatterns": { + "type": "array", + "default": [ + "**/site-packages/**" + ], + "items": { + "type": "string" + }, + "description": "Pattern used to exclude files and folders from ctags See http://ctags.sourceforge.net/ctags.html.", + "scope": "resource" + }, + "python.unitTest.promptToConfigure": { + "type": "boolean", + "default": true, + "description": "Where to prompt to configure a test framework if potential tests directories are discovered.", + "scope": "resource" + }, + "python.unitTest.debugPort": { + "type": "number", + "default": 3000, + "description": "Port number used for debugging of unittests.", + "scope": "resource" + }, + "python.unitTest.cwd": { + "type": "string", + "default": null, + "description": "Optional working directory for unit tests.", + "scope": "resource" + }, + "python.unitTest.nosetestsEnabled": { + "type": "boolean", + "default": false, + "description": "Whether to enable or disable unit testing using nosetests.", + "scope": "resource" + }, + "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.", + "scope": "resource" + }, + "python.unitTest.pyTestEnabled": { + "type": "boolean", + "default": false, + "description": "Whether to enable or disable unit testing using pytest.", + "scope": "resource" + }, + "python.unitTest.pyTestPath": { + "type": "string", + "default": "py.test", + "description": "Path to pytest (py.test), you can use a custom version of pytest by modifying this setting to include the full path.", + "scope": "resource" + }, + "python.unitTest.nosetestArgs": { + "type": "array", + "description": "Arguments passed in. Each argument is a separate item in the array.", + "default": [], + "items": { + "type": "string" + }, + "scope": "resource" + }, + "python.unitTest.pyTestArgs": { + "type": "array", + "description": "Arguments passed in. Each argument is a separate item in the array.", + "default": [], + "items": { + "type": "string" + }, + "scope": "resource" + }, + "python.unitTest.unittestEnabled": { + "type": "boolean", + "default": false, + "description": "Whether to enable or disable unit testing using unittest.", + "scope": "resource" + }, + "python.unitTest.unittestArgs": { + "type": "array", + "description": "Arguments passed in. Each argument is a separate item in the array.", + "default": [ + "-v", + "-s", + ".", + "-p", + "*test*.py" + ], + "items": { + "type": "string" + }, + "scope": "resource" + }, + "python.linting.ignorePatterns": { + "type": "array", + "description": "Patterns used to exclude files or folders from being linted.", + "default": [ + ".vscode/*.py", + "**/site-packages/**/*.py" + ], + "items": { + "type": "string" + }, + "scope": "resource" + }, + "python.linting.pylamaEnabled": { + "type": "boolean", + "default": false, + "description": "Whether to lint Python files using pylama.", + "scope": "resource" + }, + "python.linting.pylamaPath": { + "type": "string", + "default": "pylama", + "description": "Path to pylama, you can use a custom version of pylama by modifying this setting to include the full path.", + "scope": "resource" + }, + "python.linting.pylamaArgs": { + "type": "array", + "description": "Arguments passed in. Each argument is a separate item in the array.", + "default": [], + "items": { + "type": "string" + }, + "scope": "resource" + }, + "python.unitTest.outputWindow": { + "type": "string", + "default": "Python Test Log", + "description": "The output window name for the unit test messages, defaults to Python output window.", + "scope": "resource" + }, + "python.terminal.executeInFileDir": { + "type": "boolean", + "default": false, + "description": "When executing a file in the terminal, whether to use execute in the file's directory, instead of the current open folder.", + "scope": "resource" + }, + "python.terminal.launchArgs": { + "type": "array", + "default": [], + "description": "Python launch arguments to use when executing a file in the terminal.", + "scope": "resource" + }, + "python.jupyter.appendResults": { + "type": "boolean", + "default": true, + "description": "Whether to appen the results to results window, else clear and display.", + "scope": "resource" + }, + "python.jupyter.defaultKernel": { + "type": "string", + "default": "", + "description": "Default kernel to be used. By default the first available kernel is used.", + "scope": "resource" + }, + "python.jupyter.startupCode": { + "type": "array", + "items": { + "type": "string" + }, + "default": [ + "%matplotlib inline" + ], + "description": "Code executed when the kernel starts. Such as the default of '%matplotlib inline'. Individual lines can be placed in separate items of the array.", + "scope": "resource" } - }, - "env": { - "type": "object", - "description": "Environment variables defined as a key value pair. Property ends up being the Environment Variable and the value of the property ends up being the value of the Env Variable.", - "default": {} - }, - "envFile": { - "type": "string", - "description": "Absolute path to a file containing environment variable definitions.", - "default": "" - } - } - }, - "attach": { - "required": [ - "localRoot", - "remoteRoot" - ], - "properties": { - "localRoot": { - "type": "string", - "description": "Local source root that corrresponds to the 'remoteRoot'.", - "default": "${workspaceRoot}" - }, - "remoteRoot": { - "type": "string", - "description": "The source root of the remote host.", - "default": "" - }, - "port": { - "type": "number", - "description": "Debug port to attach", - "default": 0 - }, - "host": { - "type": "string", - "description": "IP Address of the of remote server (default is localhost or use 127.0.0.1).", - "default": "localhost" - }, - "secret": { - "type": "string", - "description": "Secret used to authenticate for remote debugging.", - "default": "" - } } - } }, - "initialConfigurations": [ - { - "name": "Python", - "type": "python", - "request": "launch", - "stopOnEntry": true, - "pythonPath": "${config:python.pythonPath}", - "program": "${file}", - "cwd": "${workspaceRoot}", - "env": {}, - "envFile": "${workspaceRoot}/.env", - "debugOptions": [ - "WaitOnAbnormalExit", - "WaitOnNormalExit", - "RedirectOutput" - ] - }, - { - "name": "Python: Attach", - "type": "python", - "request": "attach", - "localRoot": "${workspaceRoot}", - "remoteRoot": "${workspaceRoot}", - "port": 3000, - "secret": "my_secret", - "host": "localhost" - }, - { - "name": "Python: Terminal (integrated)", - "type": "python", - "request": "launch", - "stopOnEntry": true, - "pythonPath": "${config:python.pythonPath}", - "program": "${file}", - "cwd": "", - "console": "integratedTerminal", - "env": {}, - "envFile": "${workspaceRoot}/.env", - "debugOptions": [ - "WaitOnAbnormalExit", - "WaitOnNormalExit" - ] - }, - { - "name": "Python: Terminal (external)", - "type": "python", - "request": "launch", - "stopOnEntry": true, - "pythonPath": "${config:python.pythonPath}", - "program": "${file}", - "cwd": "", - "console": "externalTerminal", - "env": {}, - "envFile": "${workspaceRoot}/.env", - "debugOptions": [ - "WaitOnAbnormalExit", - "WaitOnNormalExit" - ] - }, - { - "name": "Python: Django", - "type": "python", - "request": "launch", - "stopOnEntry": true, - "pythonPath": "${config:python.pythonPath}", - "program": "${workspaceRoot}/manage.py", - "cwd": "${workspaceRoot}", - "args": [ - "runserver", - "--noreload", - "--nothreading" - ], - "env": {}, - "envFile": "${workspaceRoot}/.env", - "debugOptions": [ - "WaitOnAbnormalExit", - "WaitOnNormalExit", - "RedirectOutput", - "DjangoDebugging" - ] - }, - { - "name": "Python: Flask (0.11.x or later)", - "type": "python", - "request": "launch", - "stopOnEntry": false, - "pythonPath": "${config:python.pythonPath}", - "program": "fully qualified path fo 'flask' executable. Generally located along with python interpreter", - "cwd": "${workspaceRoot}", - "env": { - "FLASK_APP": "${workspaceRoot}/quickstart/app.py" - }, - "args": [ - "run", - "--no-debugger", - "--no-reload" - ], - "envFile": "${workspaceRoot}/.env", - "debugOptions": [ - "WaitOnAbnormalExit", - "WaitOnNormalExit", - "RedirectOutput" - ] - }, - { - "name": "Python: Flask (0.10.x or earlier)", - "type": "python", - "request": "launch", - "stopOnEntry": false, - "pythonPath": "${config:python.pythonPath}", - "program": "${workspaceRoot}/run.py", - "cwd": "${workspaceRoot}", - "args": [], - "env": {}, - "envFile": "${workspaceRoot}/.env", - "debugOptions": [ - "WaitOnAbnormalExit", - "WaitOnNormalExit", - "RedirectOutput" - ] - }, - { - "name": "Python: PySpark", - "type": "python", - "request": "launch", - "stopOnEntry": true, - "osx": { - "pythonPath": "${env:SPARK_HOME}/bin/spark-submit" - }, - "windows": { - "pythonPath": "${env:SPARK_HOME}/bin/spark-submit.cmd" - }, - "linux": { - "pythonPath": "${env:SPARK_HOME}/bin/spark-submit" - }, - "program": "${file}", - "cwd": "${workspaceRoot}", - "env": {}, - "envFile": "${workspaceRoot}/.env", - "debugOptions": [ - "WaitOnAbnormalExit", - "WaitOnNormalExit", - "RedirectOutput" - ] - }, - { - "name": "Python: Module", - "type": "python", - "request": "launch", - "stopOnEntry": true, - "pythonPath": "${config:python.pythonPath}", - "module": "module.name", - "cwd": "${workspaceRoot}", - "env": {}, - "envFile": "${workspaceRoot}/.env", - "debugOptions": [ - "WaitOnAbnormalExit", - "WaitOnNormalExit", - "RedirectOutput" - ] - }, - { - "name": "Python: Pyramid", - "type": "python", - "request": "launch", - "stopOnEntry": true, - "pythonPath": "${config:python.pythonPath}", - "cwd": "${workspaceRoot}", - "env": {}, - "envFile": "${workspaceRoot}/.env", - "args": [ - "${workspaceRoot}/development.ini" - ], - "debugOptions": [ - "WaitOnAbnormalExit", - "WaitOnNormalExit", - "RedirectOutput", - "Pyramid" - ] - }, - { - "name": "Python: Watson", - "type": "python", - "request": "launch", - "stopOnEntry": true, - "pythonPath": "${config:python.pythonPath}", - "program": "${workspaceRoot}/console.py", - "cwd": "${workspaceRoot}", - "args": [ - "dev", - "runserver", - "--noreload=True" - ], - "env": {}, - "envFile": "${workspaceRoot}/.env", - "debugOptions": [ - "WaitOnAbnormalExit", - "WaitOnNormalExit", - "RedirectOutput" - ] - } + "languages": [ + { + "id": "pip-requirements", + "aliases": [ + "pip requirements", + "requirements.txt" + ], + "filenames": [ + "requirements.txt" + ], + "filenamePatterns": [ + "*-requirements.txt", + "requirements-*.txt" + ], + "configuration": "./languages/pip-requirements.json" + } + ], + "grammars": [ + { + "language": "pip-requirements", + "scopeName": "source.pip-requirements", + "path": "./syntaxes/pip-requirements.tmLanguage.json" + } ] - } - ], - "configuration": { - "type": "object", - "title": "Python Configuration", - "properties": { - "python.promptToInstallJupyter": { - "type": "boolean", - "default": true, - "description": "Display prompt to install Jupyter Extension.", - "scope": "resource" - }, - "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.", - "scope": "resource" - }, - "python.venvPath": { - "type": "string", - "default": "", - "description": "Path to folder with a list of Virtual Environments (e.g. ~/.pyenv, ~/Envs, ~/.virtualenvs).", - "scope": "resource" - }, - "python.envFile": { - "type": "string", - "description": "Absolute path to a file containing environment variable definitions.", - "default": "${workspaceRoot}/.env", - "scope": "resource" - }, - "python.jediPath": { - "type": "string", - "default": "", - "description": "Path to directory containing the Jedi library (this path will contain the 'Jedi' sub directory).", - "scope": "resource" - }, - "python.sortImports.path": { - "type": "string", - "description": "Path to isort script, default using inner version", - "default": "", - "scope": "resource" - }, - "python.sortImports.args": { - "type": "array", - "description": "Arguments passed in. Each argument is a separate item in the array.", - "default": [], - "items": { - "type": "string" - }, - "scope": "resource" - }, - "python.disablePromptForFeatures": { - "type": "array", - "default": [], - "description": "Do not display a prompt to install these features", - "items": { - "type": "string", - "default": "pylint", - "description": "Feature", - "enum": [ - "flake8", - "mypy", - "pep8", - "pylama", - "prospector", - "pydocstyle", - "pylint" - ] - }, - "scope": "resource" - }, - "python.linting.enabled": { - "type": "boolean", - "default": true, - "description": "Whether to lint Python files.", - "scope": "resource" - }, - "python.linting.enabledWithoutWorkspace": { - "type": "boolean", - "default": true, - "description": "Whether to lint Python files when no workspace is opened.", - "scope": "resource" - }, - "python.linting.prospectorEnabled": { - "type": "boolean", - "default": false, - "description": "Whether to lint Python files using prospector.", - "scope": "resource" - }, - "python.linting.pylintEnabled": { - "type": "boolean", - "default": true, - "description": "Whether to lint Python files using pylint.", - "scope": "resource" - }, - "python.linting.pep8Enabled": { - "type": "boolean", - "default": false, - "description": "Whether to lint Python files using pep8", - "scope": "resource" - }, - "python.linting.flake8Enabled": { - "type": "boolean", - "default": false, - "description": "Whether to lint Python files using flake8", - "scope": "resource" - }, - "python.linting.pydocstyleEnabled": { - "type": "boolean", - "default": false, - "description": "Whether to lint Python files using pydocstyle", - "scope": "resource" - }, - "python.linting.mypyEnabled": { - "type": "boolean", - "default": false, - "description": "Whether to lint Python files using mypy.", - "scope": "resource" - }, - "python.linting.lintOnTextChange": { - "type": "boolean", - "default": true, - "description": "Whether to lint Python files when modified.", - "scope": "resource" - }, - "python.linting.lintOnSave": { - "type": "boolean", - "default": true, - "description": "Whether to lint Python files when saved.", - "scope": "resource" - }, - "python.linting.maxNumberOfProblems": { - "type": "number", - "default": 100, - "description": "Controls the maximum number of problems produced by the server.", - "scope": "resource" - }, - "python.linting.pylintCategorySeverity.convention": { - "type": "string", - "default": "Information", - "description": "Severity of Pylint message type 'Convention/C'.", - "enum": [ - "Hint", - "Error", - "Information", - "Warning" - ], - "scope": "resource" - }, - "python.linting.pylintCategorySeverity.refactor": { - "type": "string", - "default": "Hint", - "description": "Severity of Pylint message type 'Refactor/R'.", - "enum": [ - "Hint", - "Error", - "Information", - "Warning" - ], - "scope": "resource" - }, - "python.linting.pylintCategorySeverity.warning": { - "type": "string", - "default": "Warning", - "description": "Severity of Pylint message type 'Warning/W'.", - "enum": [ - "Hint", - "Error", - "Information", - "Warning" - ], - "scope": "resource" - }, - "python.linting.pylintCategorySeverity.error": { - "type": "string", - "default": "Error", - "description": "Severity of Pylint message type 'Error/E'.", - "enum": [ - "Hint", - "Error", - "Information", - "Warning" - ], - "scope": "resource" - }, - "python.linting.pylintCategorySeverity.fatal": { - "type": "string", - "default": "Error", - "description": "Severity of Pylint message type 'Fatal/F'.", - "enum": [ - "Hint", - "Error", - "Information", - "Warning" - ], - "scope": "resource" - }, - "python.linting.pep8CategorySeverity.W": { - "type": "string", - "default": "Warning", - "description": "Severity of Pep8 message type 'W'.", - "enum": [ - "Hint", - "Error", - "Information", - "Warning" - ], - "scope": "resource" - }, - "python.linting.pep8CategorySeverity.E": { - "type": "string", - "default": "Error", - "description": "Severity of Pep8 message type 'E'.", - "enum": [ - "Hint", - "Error", - "Information", - "Warning" - ], - "scope": "resource" - }, - "python.linting.flake8CategorySeverity.F": { - "type": "string", - "default": "Error", - "description": "Severity of Flake8 message type 'F'.", - "enum": [ - "Hint", - "Error", - "Information", - "Warning" - ], - "scope": "resource" - }, - "python.linting.flake8CategorySeverity.E": { - "type": "string", - "default": "Error", - "description": "Severity of Flake8 message type 'E'.", - "enum": [ - "Hint", - "Error", - "Information", - "Warning" - ], - "scope": "resource" - }, - "python.linting.flake8CategorySeverity.W": { - "type": "string", - "default": "Warning", - "description": "Severity of Flake8 message type 'W'.", - "enum": [ - "Hint", - "Error", - "Information", - "Warning" - ], - "scope": "resource" - }, - "python.linting.mypyCategorySeverity.error": { - "type": "string", - "default": "Error", - "description": "Severity of Mypy message type 'Error'.", - "enum": [ - "Hint", - "Error", - "Information", - "Warning" - ], - "scope": "resource" - }, - "python.linting.mypyCategorySeverity.note": { - "type": "string", - "default": "Information", - "description": "Severity of Mypy message type 'Note'.", - "enum": [ - "Hint", - "Error", - "Information", - "Warning" - ], - "scope": "resource" - }, - "python.linting.prospectorPath": { - "type": "string", - "default": "prospector", - "description": "Path to Prospector, you can use a custom version of prospector by modifying this setting to include the full path.", - "scope": "resource" - }, - "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.", - "scope": "resource" - }, - "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.", - "scope": "resource" - }, - "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.", - "scope": "resource" - }, - "python.linting.pydocstylePath": { - "type": "string", - "default": "pydocstyle", - "description": "Path to pydocstyle, you can use a custom version of pydocstyle by modifying this setting to include the full path.", - "scope": "resource" - }, - "python.linting.mypyPath": { - "type": "string", - "default": "mypy", - "description": "Path to mypy, you can use a custom version of mypy by modifying this setting to include the full path.", - "scope": "resource" - }, - "python.linting.prospectorArgs": { - "type": "array", - "description": "Arguments passed in. Each argument is a separate item in the array.", - "default": [], - "items": { - "type": "string" - }, - "scope": "resource" - }, - "python.linting.pylintArgs": { - "type": "array", - "description": "Arguments passed in. Each argument is a separate item in the array.", - "default": [], - "items": { - "type": "string" - }, - "scope": "resource" - }, - "python.linting.pep8Args": { - "type": "array", - "description": "Arguments passed in. Each argument is a separate item in the array.", - "default": [], - "items": { - "type": "string" - }, - "scope": "resource" - }, - "python.linting.flake8Args": { - "type": "array", - "description": "Arguments passed in. Each argument is a separate item in the array.", - "default": [], - "items": { - "type": "string" - }, - "scope": "resource" - }, - "python.linting.pydocstyleArgs": { - "type": "array", - "description": "Arguments passed in. Each argument is a separate item in the array.", - "default": [], - "items": { - "type": "string" - }, - "scope": "resource" - }, - "python.linting.mypyArgs": { - "type": "array", - "description": "Arguments passed in. Each argument is a separate item in the array.", - "default": [ - "--ignore-missing-imports", - "--follow-imports=silent" - ], - "items": { - "type": "string" - }, - "scope": "resource" - }, - "python.linting.outputWindow": { - "type": "string", - "default": "Python", - "description": "The output window name for the linting messages, defaults to Python output window.", - "scope": "resource" - }, - "python.formatting.provider": { - "type": "string", - "default": "autopep8", - "description": "Provider for formatting. Possible options include 'autopep8' and 'yapf'.", - "enum": [ - "autopep8", - "yapf", - "none" - ], - "scope": "resource" - }, - "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.", - "scope": "resource" - }, - "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.", - "scope": "resource" - }, - "python.formatting.autopep8Args": { - "type": "array", - "description": "Arguments passed in. Each argument is a separate item in the array.", - "default": [], - "items": { - "type": "string" - }, - "scope": "resource" - }, - "python.formatting.yapfArgs": { - "type": "array", - "description": "Arguments passed in. Each argument is a separate item in the array.", - "default": [], - "items": { - "type": "string" - }, - "scope": "resource" - }, - "python.formatting.formatOnSave": { - "type": "boolean", - "default": false, - "description": "Format the document upon saving.", - "scope": "resource" - }, - "python.formatting.outputWindow": { - "type": "string", - "default": "Python", - "description": "The output window name for the formatting messages, defaults to Python output window.", - "scope": "resource" - }, - "python.autoComplete.preloadModules": { - "type": "array", - "items": { - "type": "string" - }, - "default": [], - "description": "Comma delimited list of modules preloaded to speed up Auto Complete (e.g. add Numpy, Pandas, etc, items slow to load when autocompleting).", - "scope": "resource" - }, - "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.", - "scope": "resource" - }, - "python.autoComplete.addBrackets": { - "type": "boolean", - "default": false, - "description": "Automatically add brackets for functions.", - "scope": "resource" - }, - "python.workspaceSymbols.tagFilePath": { - "type": "string", - "default": "${workspaceRoot}/.vscode/tags", - "description": "Fully qualified path to tag file (exuberant ctag file), used to provide workspace symbols.", - "scope": "resource" - }, - "python.workspaceSymbols.enabled": { - "type": "boolean", - "default": true, - "description": "Set to 'false' to disable Workspace Symbol provider using ctags.", - "scope": "resource" - }, - "python.workspaceSymbols.rebuildOnStart": { - "type": "boolean", - "default": true, - "description": "Whether to re-build the tags file on start (defaults to true).", - "scope": "resource" - }, - "python.workspaceSymbols.rebuildOnFileSave": { - "type": "boolean", - "default": true, - "description": "Whether to re-build the tags file on when changes made to python files are saved.", - "scope": "resource" - }, - "python.workspaceSymbols.ctagsPath": { - "type": "string", - "default": "ctags", - "description": "Fully qualilified path to the ctags executable (else leave as ctags, assuming it is in current path).", - "scope": "resource" - }, - "python.workspaceSymbols.exclusionPatterns": { - "type": "array", - "default": [ - "**/site-packages/**" - ], - "items": { - "type": "string" - }, - "description": "Pattern used to exclude files and folders from ctags See http://ctags.sourceforge.net/ctags.html.", - "scope": "resource" - }, - "python.unitTest.promptToConfigure": { - "type": "boolean", - "default": true, - "description": "Where to prompt to configure a test framework if potential tests directories are discovered.", - "scope": "resource" - }, - "python.unitTest.debugPort": { - "type": "number", - "default": 3000, - "description": "Port number used for debugging of unittests.", - "scope": "resource" - }, - "python.unitTest.cwd": { - "type": "string", - "default": null, - "description": "Optional working directory for unit tests.", - "scope": "resource" - }, - "python.unitTest.nosetestsEnabled": { - "type": "boolean", - "default": false, - "description": "Whether to enable or disable unit testing using nosetests.", - "scope": "resource" - }, - "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.", - "scope": "resource" - }, - "python.unitTest.pyTestEnabled": { - "type": "boolean", - "default": false, - "description": "Whether to enable or disable unit testing using pytest.", - "scope": "resource" - }, - "python.unitTest.pyTestPath": { - "type": "string", - "default": "py.test", - "description": "Path to pytest (py.test), you can use a custom version of pytest by modifying this setting to include the full path.", - "scope": "resource" - }, - "python.unitTest.nosetestArgs": { - "type": "array", - "description": "Arguments passed in. Each argument is a separate item in the array.", - "default": [], - "items": { - "type": "string" - }, - "scope": "resource" - }, - "python.unitTest.pyTestArgs": { - "type": "array", - "description": "Arguments passed in. Each argument is a separate item in the array.", - "default": [], - "items": { - "type": "string" - }, - "scope": "resource" - }, - "python.unitTest.unittestEnabled": { - "type": "boolean", - "default": false, - "description": "Whether to enable or disable unit testing using unittest.", - "scope": "resource" - }, - "python.unitTest.unittestArgs": { - "type": "array", - "description": "Arguments passed in. Each argument is a separate item in the array.", - "default": [ - "-v", - "-s", - ".", - "-p", - "*test*.py" - ], - "items": { - "type": "string" - }, - "scope": "resource" - }, - "python.linting.ignorePatterns": { - "type": "array", - "description": "Patterns used to exclude files or folders from being linted.", - "default": [ - ".vscode/*.py", - "**/site-packages/**/*.py" - ], - "items": { - "type": "string" - }, - "scope": "resource" - }, - "python.linting.pylamaEnabled": { - "type": "boolean", - "default": false, - "description": "Whether to lint Python files using pylama.", - "scope": "resource" - }, - "python.linting.pylamaPath": { - "type": "string", - "default": "pylama", - "description": "Path to pylama, you can use a custom version of pylama by modifying this setting to include the full path.", - "scope": "resource" - }, - "python.linting.pylamaArgs": { - "type": "array", - "description": "Arguments passed in. Each argument is a separate item in the array.", - "default": [], - "items": { - "type": "string" - }, - "scope": "resource" - }, - "python.unitTest.outputWindow": { - "type": "string", - "default": "Python Test Log", - "description": "The output window name for the unit test messages, defaults to Python output window.", - "scope": "resource" - }, - "python.terminal.executeInFileDir": { - "type": "boolean", - "default": false, - "description": "When executing a file in the terminal, whether to use execute in the file's directory, instead of the current open folder.", - "scope": "resource" - }, - "python.terminal.launchArgs": { - "type": "array", - "default": [], - "description": "Python launch arguments to use when executing a file in the terminal.", - "scope": "resource" - }, - "python.jupyter.appendResults": { - "type": "boolean", - "default": true, - "description": "Whether to appen the results to results window, else clear and display.", - "scope": "resource" - }, - "python.jupyter.defaultKernel": { - "type": "string", - "default": "", - "description": "Default kernel to be used. By default the first available kernel is used.", - "scope": "resource" - }, - "python.jupyter.startupCode": { - "type": "array", - "items": { - "type": "string" - }, - "default": [ - "%matplotlib inline" - ], - "description": "Code executed when the kernel starts. Such as the default of '%matplotlib inline'. Individual lines can be placed in separate items of the array.", - "scope": "resource" - } - } }, - "languages": [ - { - "id": "pip-requirements", - "aliases": [ - "pip requirements", - "requirements.txt" - ], - "filenames": [ - "requirements.txt" - ], - "filenamePatterns": [ - "*-requirements.txt", - "requirements-*.txt" - ], - "configuration": "./languages/pip-requirements.json" - } - ], - "grammars": [ - { - "language": "pip-requirements", - "scopeName": "source.pip-requirements", - "path": "./syntaxes/pip-requirements.tmLanguage.json" - } - ] - }, - "scripts": { - "vscode:prepublish": "tsc -p ./ && webpack", - "compile": "webpack && tsc -watch -p ./", - "postinstall": "node ./node_modules/vscode/bin/install", - "test": "node ./out/test/standardTest.js && node ./out/test/multiRootTest.js", - "precommit": "node gulpfile.js", - "lint-staged": "node gulpfile.js", - "lint": "tslint src/**/*.ts -t verbose" - }, - "dependencies": { - "diff-match-patch": "^1.0.0", - "fs-extra": "^4.0.2", - "fuzzy": "^0.1.3", - "line-by-line": "^0.1.5", - "lodash": "^4.17.4", - "minimatch": "^3.0.3", - "named-js-regexp": "^1.3.1", - "rx": "^4.1.0", - "semver": "^5.4.1", - "socket.io": "^1.4.8", - "tmp": "0.0.29", - "tree-kill": "^1.1.0", - "uint64be": "^1.0.1", - "untildify": "^3.0.2", - "vscode-debugadapter": "^1.0.1", - "vscode-debugprotocol": "^1.0.1", - "vscode-extension-telemetry": "^0.0.5", - "vscode-languageclient": "^3.1.0", - "vscode-languageserver": "^3.1.0", - "winreg": "^1.2.4", - "xml2js": "^0.4.17" - }, - "devDependencies": { - "@types/chai": "^4.0.4", - "@types/chai-as-promised": "^7.1.0", - "@types/fs-extra": "^4.0.2", - "@types/jquery": "^1.10.31", - "@types/lodash": "^4.14.74", - "@types/mocha": "^2.2.43", - "@types/node": "^6.0.40", - "@types/rx": "^2.5.33", - "@types/semver": "^5.4.0", - "@types/sinon": "^2.3.2", - "@types/socket.io": "^1.4.27", - "@types/socket.io-client": "^1.4.27", - "@types/uuid": "^3.3.27", - "@types/winreg": "^1.2.30", - "@types/xml2js": "^0.4.0", - "babel-core": "^6.14.0", - "babel-loader": "^6.2.5", - "babel-preset-es2015": "^6.14.0", - "chai": "^4.1.2", - "chai-as-promised": "^7.1.1", - "event-stream": "^3.3.4", - "gulp": "^3.9.1", - "gulp-filter": "^5.0.1", - "gulp-typescript": "^3.2.2", - "husky": "^0.14.3", - "ignore-loader": "^0.1.1", - "mocha": "^2.3.3", - "relative": "^3.0.2", - "retyped-diff-match-patch-tsd-ambient": "^1.0.0-0", - "sinon": "^2.3.6", - "transformime": "^3.1.2", - "transformime-marked": "0.0.1", - "ts-loader": "^2.3.4", - "tslint": "^5.7.0", - "tslint-eslint-rules": "^4.1.1", - "tslint-microsoft-contrib": "^5.0.1", - "typescript": "^2.5.2", - "typescript-formatter": "^6.0.0", - "webpack": "^1.13.2", - "vscode": "^1.1.5" - } + "scripts": { + "vscode:prepublish": "tsc -p ./ && webpack", + "compile": "webpack && tsc -watch -p ./", + "postinstall": "node ./node_modules/vscode/bin/install", + "test": "node ./out/test/standardTest.js && node ./out/test/multiRootTest.js", + "precommit": "node gulpfile.js", + "lint-staged": "node gulpfile.js", + "lint": "tslint src/**/*.ts -t verbose" + }, + "dependencies": { + "diff-match-patch": "^1.0.0", + "fs-extra": "^4.0.2", + "fuzzy": "^0.1.3", + "line-by-line": "^0.1.5", + "lodash": "^4.17.4", + "minimatch": "^3.0.3", + "named-js-regexp": "^1.3.1", + "rx": "^4.1.0", + "semver": "^5.4.1", + "socket.io": "^1.4.8", + "tmp": "0.0.29", + "tree-kill": "^1.1.0", + "uint64be": "^1.0.1", + "untildify": "^3.0.2", + "vscode-debugadapter": "^1.0.1", + "vscode-debugprotocol": "^1.0.1", + "vscode-extension-telemetry": "^0.0.5", + "vscode-languageclient": "^3.1.0", + "vscode-languageserver": "^3.1.0", + "winreg": "^1.2.4", + "xml2js": "^0.4.17" + }, + "devDependencies": { + "@types/chai": "^4.0.4", + "@types/chai-as-promised": "^7.1.0", + "@types/fs-extra": "^4.0.2", + "@types/jquery": "^1.10.31", + "@types/lodash": "^4.14.74", + "@types/mocha": "^2.2.43", + "@types/node": "^6.0.40", + "@types/rx": "^2.5.33", + "@types/semver": "^5.4.0", + "@types/sinon": "^2.3.2", + "@types/socket.io": "^1.4.27", + "@types/socket.io-client": "^1.4.27", + "@types/uuid": "^3.3.27", + "@types/winreg": "^1.2.30", + "@types/xml2js": "^0.4.0", + "babel-core": "^6.14.0", + "babel-loader": "^6.2.5", + "babel-preset-es2015": "^6.14.0", + "chai": "^4.1.2", + "chai-as-promised": "^7.1.1", + "event-stream": "^3.3.4", + "gulp": "^3.9.1", + "gulp-filter": "^5.0.1", + "gulp-typescript": "^3.2.2", + "gulp-watch": "^4.3.11", + "husky": "^0.14.3", + "ignore-loader": "^0.1.1", + "mocha": "^2.3.3", + "relative": "^3.0.2", + "retyped-diff-match-patch-tsd-ambient": "^1.0.0-0", + "sinon": "^2.3.6", + "transformime": "^3.1.2", + "transformime-marked": "0.0.1", + "ts-loader": "^2.3.4", + "tslint": "^5.7.0", + "tslint-eslint-rules": "^4.1.1", + "tslint-microsoft-contrib": "^5.0.1", + "typescript": "^2.5.2", + "typescript-formatter": "^6.0.0", + "vscode": "^1.1.5", + "webpack": "^1.13.2" + } } diff --git a/pythonFiles/PythonTools/visualstudio_py_launcher_nodebug.py b/pythonFiles/PythonTools/visualstudio_py_launcher_nodebug.py new file mode 100644 index 000000000000..79184dfa1e0d --- /dev/null +++ b/pythonFiles/PythonTools/visualstudio_py_launcher_nodebug.py @@ -0,0 +1,146 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +"""Run a block of code or Python file.""" + +import sys +import os.path +import traceback +import time +import socket +try: + import visualstudio_py_util as _vspu +except: + traceback.print_exc() + print("""Internal error detected. Please copy the above traceback and report at +https://github.com/Microsoft/vscode-python/issues""") + sys.exit(1) + +LAST = _vspu.to_bytes('LAST') +OUTP = _vspu.to_bytes('OUTP') +LOAD = _vspu.to_bytes('LOAD') + +def parse_argv(): + """Parses arguments for use with the launcher. + Arguments are: + 1. Working directory. + 2. VS debugger port to connect to. + 3. GUID for the debug session. + 4. Debug options (not used). + 5. '-m' or '-c' to override the default run-as mode. [optional]. + 6. Startup script name. + 7. Script arguments. + """ + + # Change to directory we expected to start from. + os.chdir(sys.argv[1]) + + port_num = int(sys.argv[2]) + debug_id = sys.argv[3] + + del sys.argv[:5] + + # Set run_as mode appropriately + run_as = 'script' + if sys.argv and sys.argv[0] == '-m': + run_as = 'module' + del sys.argv[0] + elif sys.argv and sys.argv[0] == '-c': + run_as = 'code' + del sys.argv[0] + + # Preserve filename before we del sys. + filename = sys.argv[0] + + # Fix sys.path to be the script file dir. + sys.path[0] = '' + + pid = os.getpid() + + return (filename, port_num, debug_id, pid, run_as) + +def run(file, port_num, debug_id, pid, run_as='script'): + attach_process(port_num, pid, debug_id) + + # Now execute main file. + globals_obj = {'__name__': '__main__'} + + try: + if run_as == 'module': + _vspu.exec_module(file, globals_obj) + elif run_as == 'code': + _vspu.exec_code(file, '', globals_obj) + else: + _vspu.exec_file(file, globals_obj) + except: + exc_type, exc_value, exc_tb = sys.exc_info() + handle_exception(exc_type, exc_value, exc_tb) + + _vspu.write_bytes(conn, LAST) + # Wait for message to be received by debugger. + time.sleep(0.5) + + +def attach_process(port_num, pid, debug_id): + global conn + for i in xrange(50): + try: + conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + conn.connect(('127.0.0.1', port_num)) + # Initial handshake. + _vspu.write_string(conn, debug_id) + _vspu.write_int(conn, 0) + _vspu.write_int(conn, pid) + + # Notify debugger that process has launched. + _vspu.write_bytes(conn, LOAD) + _vspu.write_int(conn, 0) + break + except: + time.sleep(50./1000) + else: + raise Exception('failed to attach') + + +def handle_exception(exc_type, exc_value, exc_tb): + # Specifies list of files not to display in stack trace. + do_not_debug = [__file__, _vspu.__file__] + if sys.version_info >= (3, 3): + do_not_debug.append('') + if sys.version_info >= (3, 5): + do_not_debug.append('') + + # Remove debugger frames from the top and bottom of the traceback. + tb = traceback.extract_tb(exc_tb) + for i in [0, -1]: + while tb: + frame_file = path.normcase(tb[i][0]) + if not any(is_same_py_file(frame_file, f) for f in do_not_debug): + break + del tb[i] + + # Print the traceback. + if tb: + sys.stderr.write('Traceback (most recent call last):') + for out in traceback.format_list(tb): + sys.stderr.write(out) + sys.stderr.flush() + + # Print the exception. + for out in traceback.format_exception_only(exc_type, exc_value): + sys.stderr.write(out) + sys.stderr.flush() + + +def is_same_py_file(file_1, file_2): + """Compares 2 filenames accounting for .pyc files.""" + if file_1.endswith('.pyc') or file_1.endswith('.pyo'): + file_1 = file_1[:-1] + if file_2.endswith('.pyc') or file_2.endswith('.pyo'): + file_2 = file_2[:-1] + + return file_1 == file_2 + +if __name__ == '__main__': + filename, port_num, debug_id, pid, run_as = parse_argv() + run(filename, port_num, debug_id, pid, run_as) diff --git a/src/client/common/envFileParser.ts b/src/client/common/envFileParser.ts index 9322742bec7a..d6ca7a7024e1 100644 --- a/src/client/common/envFileParser.ts +++ b/src/client/common/envFileParser.ts @@ -1,7 +1,9 @@ import * as fs from 'fs'; import * as path from 'path'; -export function parseEnvFile(envFile: string): any { +type EnvVars = Object & { [key: string]: string }; + +export function parseEnvFile(envFile: string, mergeWithProcessEnvVars: boolean = true): EnvVars { const buffer = fs.readFileSync(envFile, 'utf8'); const env = {}; buffer.split('\n').forEach(line => { @@ -14,28 +16,43 @@ export function parseEnvFile(envFile: string): any { env[r[1]] = value.replace(/(^['"]|['"]$)/g, ''); } }); - return mergeEnvVariables(env); + return mergeWithProcessEnvVars ? mergeEnvVariables(env, process.env) : mergePythonPath(env, process.env.PYTHONPATH); } -export function mergeEnvVariables(newVariables: { [key: string]: string }, mergeWith: any = process.env): any { - for (let setting in mergeWith) { - if (setting === 'PYTHONPATH') { - let PYTHONPATH: string = newVariables['PYTHONPATH']; - if (typeof PYTHONPATH !== 'string') { - PYTHONPATH = ''; - } - if (mergeWith['PYTHONPATH']) { - PYTHONPATH += (PYTHONPATH.length > 0 ? path.delimiter : '') + mergeWith['PYTHONPATH']; - } - if (PYTHONPATH.length > 0) { - newVariables[setting] = PYTHONPATH; - } - continue; - } - if (!newVariables[setting]) { - newVariables[setting] = mergeWith[setting]; +/** + * Merge the target environment variables into the source. + * Note: The source variables are modified and returned (i.e. it modifies value passed in). + * @export + * @param {EnvVars} targetEnvVars target environment variables. + * @param {EnvVars} [sourceEnvVars=process.env] source environment variables (defaults to current process variables). + * @returns {EnvVars} + */ +export function mergeEnvVariables(targetEnvVars: EnvVars, sourceEnvVars: EnvVars = process.env): EnvVars { + Object.keys(sourceEnvVars).forEach(setting => { + if (targetEnvVars[setting] === undefined) { + targetEnvVars[setting] = sourceEnvVars[setting]; } + }); + return mergePythonPath(targetEnvVars, sourceEnvVars.PYTHONPATH); +} + +/** + * Merge the target PYTHONPATH value into the env variables passed. + * Note: The env variables passed in are modified and returned (i.e. it modifies value passed in). + * @export + * @param {EnvVars} env target environment variables. + * @param {string | undefined} [currentPythonPath] PYTHONPATH value. + * @returns {EnvVars} + */ +export function mergePythonPath(env: EnvVars, currentPythonPath: string | undefined): EnvVars { + if (typeof currentPythonPath !== 'string' || currentPythonPath.length === 0) { + return env; } - return newVariables; + if (typeof env.PYTHONPATH === 'string' && env.PYTHONPATH.length > 0) { + env.PYTHONPATH = env.PYTHONPATH + path.delimiter + currentPythonPath; + } else { + env.PYTHONPATH = currentPythonPath; + } + return env; } diff --git a/src/client/common/registry.ts b/src/client/common/registry.ts index 3f0bb1c907d8..1814f8bdc5ba 100644 --- a/src/client/common/registry.ts +++ b/src/client/common/registry.ts @@ -19,11 +19,11 @@ export interface IRegistry { } export class RegistryImplementation implements IRegistry { - public getKeys(key: string, hive: Hive, arch?: Architecture) { - return getRegistryKeys({ hive: translateHive(hive), arch: translateArchitecture(arch), key }); + public async getKeys(key: string, hive: Hive, arch?: Architecture) { + return getRegistryKeys({ hive: translateHive(hive)!, arch: translateArchitecture(arch), key }); } - public getValue(key: string, hive: Hive, arch?: Architecture, name: string = '') { - return getRegistryValue({ hive: translateHive(hive), arch: translateArchitecture(arch), key }, name); + public async getValue(key: string, hive: Hive, arch?: Architecture, name: string = '') { + return getRegistryValue({ hive: translateHive(hive)!, arch: translateArchitecture(arch), key }, name); } } @@ -38,28 +38,28 @@ export function getArchitectureDislayName(arch?: Architecture) { } } -function getRegistryValue(options: Registry.Options, name: string = '') { +async function getRegistryValue(options: Registry.Options, name: string = '') { return new Promise((resolve, reject) => { new Registry(options).get(name, (error, result) => { - if (error) { + if (error || !result || typeof result.value !== 'string') { return resolve(undefined); } resolve(result.value); }); }); } -function getRegistryKeys(options: Registry.Options): Promise { +async function getRegistryKeys(options: Registry.Options): Promise { // https://github.com/python/peps/blob/master/pep-0514.txt#L85 return new Promise((resolve, reject) => { new Registry(options).keys((error, result) => { - if (error) { + if (error || !Array.isArray(result)) { return resolve([]); } - resolve(result.map(item => item.key)); + resolve(result.filter(item => typeof item.key === 'string').map(item => item.key)); }); }); } -function translateArchitecture(arch?: Architecture): RegistryArchitectures | null | undefined { +function translateArchitecture(arch?: Architecture): RegistryArchitectures | undefined { switch (arch) { case Architecture.x86: return RegistryArchitectures.x86; @@ -69,7 +69,7 @@ function translateArchitecture(arch?: Architecture): RegistryArchitectures | nul return; } } -function translateHive(hive: Hive): string | null | undefined { +function translateHive(hive: Hive): string | undefined { switch (hive) { case Hive.HKCU: return Registry.HKCU; diff --git a/src/client/common/systemVariables.ts b/src/client/common/systemVariables.ts index 444d782d6eea..50962cc77b64 100644 --- a/src/client/common/systemVariables.ts +++ b/src/client/common/systemVariables.ts @@ -131,27 +131,35 @@ export abstract class AbstractSystemVariables implements ISystemVariables { export class SystemVariables extends AbstractSystemVariables { - private _workspaceRoot: string; - private _workspaceRootFolderName: string; + private _workspaceFolder: string; + private _workspaceFolderName: string; - constructor(workspaceRoot?: string) { + constructor(workspaceFolder?: string) { super(); - this._workspaceRoot = typeof workspaceRoot === 'string' ? workspaceRoot : __dirname; - this._workspaceRootFolderName = Path.basename(this._workspaceRoot); + this._workspaceFolder = typeof workspaceFolder === 'string' ? workspaceFolder : __dirname; + this._workspaceFolderName = Path.basename(this._workspaceFolder); Object.keys(process.env).forEach(key => { this[`env:${key}`] = this[`env.${key}`] = process.env[key]; }); } public get cwd(): string { - return this.workspaceRoot; + return this.workspaceFolder; } public get workspaceRoot(): string { - return this._workspaceRoot; + return this._workspaceFolder; + } + + public get workspaceFolder(): string { + return this._workspaceFolder; } public get workspaceRootFolderName(): string { - return this._workspaceRootFolderName; + return this._workspaceFolderName; + } + + public get workspaceFolderBasename(): string { + return this._workspaceFolderName; } } diff --git a/src/client/debugger/Common/Contracts.ts b/src/client/debugger/Common/Contracts.ts index 41222ddf0c79..e8c5635a064e 100644 --- a/src/client/debugger/Common/Contracts.ts +++ b/src/client/debugger/Common/Contracts.ts @@ -1,5 +1,6 @@ "use strict"; import * as net from "net"; +import { ChildProcess } from 'child_process'; import { DebugProtocol } from "vscode-debugprotocol"; import { OutputEvent } from "vscode-debugadapter"; @@ -49,7 +50,6 @@ export interface LaunchRequestArguments extends DebugProtocol.LaunchRequestArgum stopOnEntry?: boolean; args: string[]; applicationType?: string; - externalConsole?: boolean; cwd?: string; debugOptions?: string[]; env?: Object; @@ -103,8 +103,9 @@ export enum PythonEvaluationResultFlags { } export interface IPythonProcess extends NodeJS.EventEmitter { - Connect(buffer: Buffer, socket: net.Socket, isRemoteProcess: boolean); + Connect(buffer: Buffer, socket: net.Socket, isRemoteProcess: boolean): boolean; HandleIncomingData(buffer: Buffer); + attach(proc: ChildProcess): void; Detach(); Kill(); SendStepInto(threadId: number); diff --git a/src/client/debugger/Common/Utils.ts b/src/client/debugger/Common/Utils.ts index a380451f2877..ff3947c48d2c 100644 --- a/src/client/debugger/Common/Utils.ts +++ b/src/client/debugger/Common/Utils.ts @@ -1,11 +1,11 @@ -"use strict"; +'use strict'; -import { IPythonProcess, IPythonThread, IPythonModule, IPythonEvaluationResult } from "./Contracts"; -import * as path from "path"; -import * as fs from 'fs'; import * as child_process from 'child_process'; -import { mergeEnvVariables, parseEnvFile } from '../../common/envFileParser'; +import * as fs from 'fs'; +import * as path from 'path'; import * as untildify from 'untildify'; +import { mergeEnvVariables, mergePythonPath, parseEnvFile } from '../../common/envFileParser'; +import { IPythonEvaluationResult, IPythonModule, IPythonProcess, IPythonThread } from './Contracts'; export const IS_WINDOWS = /^win/.test(process.platform); export const PATH_VARIABLE_NAME = IS_WINDOWS ? 'Path' : 'PATH'; @@ -38,7 +38,7 @@ export function validatePathSync(filePath: string): boolean { return exists; } -export function CreatePythonThread(id: number, isWorker: boolean, process: IPythonProcess, name: string = ""): IPythonThread { +export function CreatePythonThread(id: number, isWorker: boolean, process: IPythonProcess, name: string = ''): IPythonThread { return { IsWorkerThread: isWorker, Process: process, @@ -50,15 +50,13 @@ export function CreatePythonThread(id: number, isWorker: boolean, process: IPyth export function CreatePythonModule(id: number, fileName: string): IPythonModule { let name = fileName; - if (typeof fileName === "string") { + if (typeof fileName === 'string') { try { name = path.basename(fileName); - } - catch (ex) { - } - } - else { - name = ""; + // tslint:disable-next-line:no-empty + } catch { } + } else { + name = ''; } return { @@ -74,7 +72,7 @@ export function FixupEscapedUnicodeChars(value: string): string { export function getPythonExecutable(pythonPath: string): string { pythonPath = untildify(pythonPath); - // If only 'python' + // If only 'python'. if (pythonPath === 'python' || pythonPath.indexOf(path.sep) === -1 || path.basename(pythonPath) === path.dirname(pythonPath)) { @@ -84,21 +82,20 @@ export function getPythonExecutable(pythonPath: string): string { if (isValidPythonPath(pythonPath)) { return pythonPath; } - // Keep python right on top, for backwards compatibility + // Keep python right on top, for backwards compatibility. const KnownPythonExecutables = ['python', 'python4', 'python3.6', 'python3.5', 'python3', 'python2.7', 'python2']; for (let executableName of KnownPythonExecutables) { - // Suffix with 'python' for linux and 'osx', and 'python.exe' for 'windows' + // Suffix with 'python' for linux and 'osx', and 'python.exe' for 'windows'. if (IS_WINDOWS) { - executableName = executableName + '.exe'; + executableName = `${executableName}.exe`; if (isValidPythonPath(path.join(pythonPath, executableName))) { return path.join(pythonPath, executableName); } if (isValidPythonPath(path.join(pythonPath, 'scripts', executableName))) { return path.join(pythonPath, 'scripts', executableName); } - } - else { + } else { if (isValidPythonPath(path.join(pythonPath, executableName))) { return path.join(pythonPath, executableName); } @@ -113,39 +110,36 @@ export function getPythonExecutable(pythonPath: string): string { function isValidPythonPath(pythonPath): boolean { try { - let output = child_process.execFileSync(pythonPath, ['-c', 'print(1234)'], { encoding: 'utf8' }); + const output = child_process.execFileSync(pythonPath, ['-c', 'print(1234)'], { encoding: 'utf8' }); return output.startsWith('1234'); - } - catch (ex) { + } catch { return false; } } +type EnvVars = Object & { [key: string]: string }; -export function getCustomEnvVars(envVars: any, envFile: string): any { - let envFileVars = null; +export function getCustomEnvVars(envVars: Object, envFile: string, mergeWithProcessEnvVars: boolean = true): EnvVars { + let envFileVars: EnvVars = null; if (typeof envFile === 'string' && envFile.length > 0 && fs.existsSync(envFile)) { try { - envFileVars = parseEnvFile(envFile); - } - catch (ex) { + envFileVars = parseEnvFile(envFile, mergeWithProcessEnvVars); + } catch (ex) { console.error('Failed to load env file'); console.error(ex); } } - let configVars = null; - if (envVars && Object.keys(envVars).length > 0 && envFileVars) { - configVars = mergeEnvVariables(envVars, envFileVars); - } - if (envVars && Object.keys(envVars).length > 0) { - configVars = envVars; - } - if (envFileVars) { - configVars = envFileVars; + if (envFileVars && Object.keys(envFileVars).length > 0) { + if (!envVars || Object.keys(envVars).length === 0) { + return envFileVars; + } else { + envVars = envVars || {}; + return mergeEnvVariables(envVars as EnvVars, envFileVars); + } } - if (configVars && typeof configVars === 'object' && Object.keys(configVars).length > 0) { - return configVars; + if (!envVars || Object.keys(envVars).length === 0) { + return null; } - return null; -} \ No newline at end of file + return mergePythonPath(envVars as EnvVars, process.env.PYTHONPATH); +} diff --git a/src/client/debugger/DebugClients/DebugClient.ts b/src/client/debugger/DebugClients/DebugClient.ts index 537b7fe16935..c96eca5e34a6 100644 --- a/src/client/debugger/DebugClients/DebugClient.ts +++ b/src/client/debugger/DebugClients/DebugClient.ts @@ -14,7 +14,7 @@ export enum DebugType { } export abstract class DebugClient extends EventEmitter { protected debugSession: DebugSession; - constructor(args: any, debugSession: DebugSession) { + constructor(protected args: any, debugSession: DebugSession) { super(); this.debugSession = debugSession; } diff --git a/src/client/debugger/DebugClients/DebugFactory.ts b/src/client/debugger/DebugClients/DebugFactory.ts index e6c06d678f20..fd17f4151a97 100644 --- a/src/client/debugger/DebugClients/DebugFactory.ts +++ b/src/client/debugger/DebugClients/DebugFactory.ts @@ -1,21 +1,21 @@ -import {BaseDebugServer} from "../DebugServers/BaseDebugServer"; -import {LocalDebugServer} from "../DebugServers/LocalDebugServer"; -import {IPythonProcess, IPythonThread, IDebugServer} from "../Common/Contracts"; -import {DebugSession, OutputEvent} from "vscode-debugadapter"; -import * as path from "path"; -import * as child_process from "child_process"; -import {DjangoApp, LaunchRequestArguments, AttachRequestArguments} from "../Common/Contracts"; -import {LocalDebugClient} from "./LocalDebugClient"; -import {NonDebugClient} from "./NonDebugClient"; -import {RemoteDebugClient} from "./RemoteDebugClient"; -import {DebugClient} from "./DebugClient"; +import * as child_process from 'child_process'; +import * as path from 'path'; +import { DebugSession, OutputEvent } from 'vscode-debugadapter'; +import { IDebugServer, IPythonProcess, IPythonThread } from '../Common/Contracts'; +import { AttachRequestArguments, DjangoApp, LaunchRequestArguments } from '../Common/Contracts'; +import { BaseDebugServer } from '../DebugServers/BaseDebugServer'; +import { LocalDebugServer } from '../DebugServers/LocalDebugServer'; +import { DebugClient } from './DebugClient'; +import { LocalDebugClient } from './LocalDebugClient'; +import { NonDebugClient } from './NonDebugClient'; +import { RemoteDebugClient } from './RemoteDebugClient'; -export function CreateLaunchDebugClient(launchRequestOptions: LaunchRequestArguments, debugSession: DebugSession): DebugClient { +export function CreateLaunchDebugClient(launchRequestOptions: LaunchRequestArguments, debugSession: DebugSession, canLaunchTerminal: boolean): DebugClient { if (launchRequestOptions.noDebug === true) { - return new NonDebugClient(launchRequestOptions, debugSession); + return new NonDebugClient(launchRequestOptions, debugSession, canLaunchTerminal); } - return new LocalDebugClient(launchRequestOptions, debugSession); + return new LocalDebugClient(launchRequestOptions, debugSession, canLaunchTerminal); } export function CreateAttachDebugClient(attachRequestOptions: AttachRequestArguments, debugSession: DebugSession): DebugClient { return new RemoteDebugClient(attachRequestOptions, debugSession); -} \ No newline at end of file +} diff --git a/src/client/debugger/DebugClients/LocalDebugClient.ts b/src/client/debugger/DebugClients/LocalDebugClient.ts index 2cbc911d5b80..8b0032ad760a 100644 --- a/src/client/debugger/DebugClients/LocalDebugClient.ts +++ b/src/client/debugger/DebugClients/LocalDebugClient.ts @@ -1,32 +1,31 @@ -import { BaseDebugServer } from '../DebugServers/BaseDebugServer'; -import { LocalDebugServer } from '../DebugServers/LocalDebugServer'; -import { IPythonProcess, IDebugServer } from '../Common/Contracts'; +import * as child_process from 'child_process'; +import { ChildProcess } from 'child_process'; +import * as path from 'path'; import { DebugSession, OutputEvent } from 'vscode-debugadapter'; import { DebugProtocol } from 'vscode-debugprotocol'; -import * as path from 'path'; -import * as child_process from 'child_process'; -import { LaunchRequestArguments } from '../Common/Contracts'; -import { DebugClient, DebugType } from './DebugClient'; import { open } from '../../common/open'; +import { IDebugServer, IPythonProcess } from '../Common/Contracts'; +import { LaunchRequestArguments } from '../Common/Contracts'; import { getCustomEnvVars } from '../Common/Utils'; +import { BaseDebugServer } from '../DebugServers/BaseDebugServer'; +import { LocalDebugServer } from '../DebugServers/LocalDebugServer'; +import { DebugClient, DebugType } from './DebugClient'; -const VALID_DEBUG_OPTIONS = ['WaitOnAbnormalExit', - 'WaitOnNormalExit', +const VALID_DEBUG_OPTIONS = [ 'RedirectOutput', 'DebugStdLib', 'BreakOnSystemExitZero', 'DjangoDebugging']; export class LocalDebugClient extends DebugClient { - protected args: LaunchRequestArguments; - constructor(args: any, debugSession: DebugSession) { + protected pyProc: child_process.ChildProcess; + protected pythonProcess: IPythonProcess; + protected debugServer: BaseDebugServer; + // tslint:disable-next-line:no-any + constructor(args: any, debugSession: DebugSession, private canLaunchTerminal: boolean) { super(args, debugSession); - this.args = args; } - private pyProc: child_process.ChildProcess; - private pythonProcess: IPythonProcess; - private debugServer: BaseDebugServer; public CreateDebugServer(pythonProcess: IPythonProcess): BaseDebugServer { this.pythonProcess = pythonProcess; this.debugServer = new LocalDebugServer(this.debugSession, this.pythonProcess); @@ -44,29 +43,38 @@ export class LocalDebugClient extends DebugClient { } if (this.pyProc) { - try { this.pyProc.send('EXIT'); } - catch (ex) { } - try { this.pyProc.stdin.write('EXIT'); } - catch (ex) { } - try { this.pyProc.disconnect(); } - catch (ex) { } + try { + this.pyProc.send('EXIT'); + // tslint:disable-next-line:no-empty + } catch { } + try { + this.pyProc.stdin.write('EXIT'); + // tslint:disable-next-line:no-empty + } catch { } + try { + this.pyProc.disconnect(); + // tslint:disable-next-line:no-empty + } catch { } this.pyProc = null; } } - private getPTVSToolsFilePath(): string { - let currentFileName = module.filename; - let ptVSToolsPath = path.join(path.dirname(currentFileName), '..', '..', '..', '..', 'pythonFiles', 'PythonTools'); + protected getLauncherFilePath(): string { + const currentFileName = module.filename; + const ptVSToolsPath = path.join(path.dirname(currentFileName), '..', '..', '..', '..', 'pythonFiles', 'PythonTools'); return path.join(ptVSToolsPath, 'visualstudio_py_launcher.py'); } + // tslint:disable-next-line:no-any private displayError(error: any) { - let errorMsg = typeof error === 'string' ? error : ((error.message && error.message.length > 0) ? error.message : ''); + const errorMsg = typeof error === 'string' ? error : ((error.message && error.message.length > 0) ? error.message : ''); if (errorMsg.length > 0) { this.debugSession.sendEvent(new OutputEvent(errorMsg, 'stderr')); } } + // tslint:disable-next-line:max-func-body-length member-ordering no-any public LaunchApplicationToDebug(dbgServer: IDebugServer, processErrored: (error: any) => void): Promise { + // tslint:disable-next-line:max-func-body-length cyclomatic-complexity no-any return new Promise((resolve, reject) => { - let fileDir = this.args && this.args.program ? path.dirname(this.args.program) : ''; + const fileDir = this.args && this.args.program ? path.dirname(this.args.program) : ''; let processCwd = fileDir; if (typeof this.args.cwd === 'string' && this.args.cwd.length > 0 && this.args.cwd !== 'null') { processCwd = this.args.cwd; @@ -75,126 +83,120 @@ export class LocalDebugClient extends DebugClient { if (typeof this.args.pythonPath === 'string' && this.args.pythonPath.trim().length > 0) { pythonPath = this.args.pythonPath; } - let environmentVariables = getCustomEnvVars(this.args.env, this.args.envFile); + let environmentVariables = getCustomEnvVars(this.args.env, this.args.envFile, false); environmentVariables = environmentVariables ? environmentVariables : {}; - let newEnvVars = {}; - if (environmentVariables) { - for (let setting in environmentVariables) { - if (!newEnvVars[setting]) { - newEnvVars[setting] = environmentVariables[setting]; - process.env[setting] = environmentVariables[setting]; - } - } - for (let setting in process.env) { - if (!environmentVariables[setting]) { - environmentVariables[setting] = process.env[setting]; - } - } - } if (!environmentVariables.hasOwnProperty('PYTHONIOENCODING')) { - environmentVariables['PYTHONIOENCODING'] = 'UTF-8'; - newEnvVars['PYTHONIOENCODING'] = 'UTF-8'; - process.env['PYTHONIOENCODING'] = 'UTF-8'; + environmentVariables.PYTHONIOENCODING = 'UTF-8'; } if (!environmentVariables.hasOwnProperty('PYTHONUNBUFFERED')) { - environmentVariables['PYTHONUNBUFFERED'] = '1'; - newEnvVars['PYTHONUNBUFFERED'] = '1'; - process.env['PYTHONUNBUFFERED'] = '1'; + environmentVariables.PYTHONUNBUFFERED = '1'; } - let ptVSToolsFilePath = this.getPTVSToolsFilePath(); - let launcherArgs = this.buildLauncherArguments(); + const ptVSToolsFilePath = this.getLauncherFilePath(); + const launcherArgs = this.buildLauncherArguments(); - let args = [ptVSToolsFilePath, processCwd, dbgServer.port.toString(), '34806ad9-833a-4524-8cd6-18ca4aa74f14'].concat(launcherArgs); + const args = [ptVSToolsFilePath, processCwd, dbgServer.port.toString(), '34806ad9-833a-4524-8cd6-18ca4aa74f14'].concat(launcherArgs); switch (this.args.console) { - case 'externalTerminal': { - const isSudo = Array.isArray(this.args.debugOptions) && this.args.debugOptions.some(opt => opt === 'Sudo'); - open({ wait: false, app: [pythonPath].concat(args), cwd: processCwd, env: environmentVariables, sudo: isSudo }).then(proc => { - this.pyProc = proc; - resolve(); - }, error => { - // TODO: This condition makes no sense (refactor) - if (!this.debugServer && this.debugServer.IsRunning) { - return; - } - reject(error); - }); - break; - } + case 'externalTerminal': case 'integratedTerminal': { const isSudo = Array.isArray(this.args.debugOptions) && this.args.debugOptions.some(opt => opt === 'Sudo'); - const command = isSudo ? 'sudo' : pythonPath; - const commandArgs = isSudo ? [pythonPath].concat(args) : args; - const termArgs: DebugProtocol.RunInTerminalRequestArguments = { - kind: 'integrated', - title: 'Python Debug Console', - cwd: processCwd, - args: [command].concat(commandArgs), - env: newEnvVars as { [key: string]: string } - }; - this.debugSession.runInTerminalRequest(termArgs, 5000, (response) => { - if (response.success) { - resolve(); - } else { - reject(response); - } - }); + this.launchExternalTerminal(isSudo, processCwd, pythonPath, args, environmentVariables).then(resolve).catch(reject); break; } default: { this.pyProc = child_process.spawn(pythonPath, args, { cwd: processCwd, env: environmentVariables }); - this.pyProc.on('error', error => { - // TODO: This condition makes no sense (refactor) - if (!this.debugServer && this.debugServer.IsRunning) { - return; - } - if (!this.debugServer.IsRunning && typeof (error) === 'object' && error !== null) { - // return processErrored(error); - return reject(error); - } - this.displayError(error); - }); - this.pyProc.stderr.setEncoding('utf8'); - this.pyProc.stderr.on('data', error => { - // We generally don't need to display the errors as stderr output is being captured by debugger - // and it gets sent out to the debug client - - // Either way, we need some code in here so we read the stdout of the python process - // Else it just keep building up (related to issue #203 and #52) - if (this.debugServer && !this.debugServer.IsRunning) { - return reject(error); - } - }); - this.pyProc.stdout.on('data', d => { - // This is necessary so we read the stdout of the python process - // Else it just keep building up (related to issue #203 and #52) - let x = 0; - }); + this.handleProcessOutput(this.pyProc, reject); - // Here we wait for the application to connect to the socket server - // Only once connected do we know that the application has successfully launched - // resolve(); + // Here we wait for the application to connect to the socket server. + // Only once connected do we know that the application has successfully launched. this.debugServer.DebugClientConnected.then(resolve); } } }); } + // tslint:disable-next-line:member-ordering + protected handleProcessOutput(proc: ChildProcess, failedToLaunch: (error: Error | string | Buffer) => void) { + proc.on('error', error => { + // If debug server has started, then don't display errors. + // The debug adapter will get this info from the debugger (e.g. ptvsd lib). + if (!this.debugServer && this.debugServer.IsRunning) { + return; + } + if (!this.debugServer.IsRunning && typeof (error) === 'object' && error !== null) { + return failedToLaunch(error); + } + // This could happen when the debugger didn't launch at all, e.g. python doesn't exist. + this.displayError(error); + }); + proc.stderr.setEncoding('utf8'); + proc.stderr.on('data', error => { + // We generally don't need to display the errors as stderr output is being captured by debugger + // and it gets sent out to the debug client. + + // Either way, we need some code in here so we read the stdout of the python process, + // Else it just keep building up (related to issue #203 and #52). + if (this.debugServer && !this.debugServer.IsRunning) { + return failedToLaunch(error); + } + }); + proc.stdout.on('data', d => { + // This is necessary so we read the stdout of the python process, + // Else it just keep building up (related to issue #203 and #52). + let x = 0; + }); + } + // tslint:disable-next-line:member-ordering protected buildLauncherArguments(): string[] { - let vsDebugOptions = 'WaitOnAbnormalExit,WaitOnNormalExit,RedirectOutput'; + let vsDebugOptions = ['RedirectOutput']; if (Array.isArray(this.args.debugOptions)) { - vsDebugOptions = this.args.debugOptions.filter(opt => VALID_DEBUG_OPTIONS.indexOf(opt) >= 0).join(','); + vsDebugOptions = this.args.debugOptions.filter(opt => VALID_DEBUG_OPTIONS.indexOf(opt) >= 0); } - // If internal or external console, then don't re-direct the output - if (this.args.externalConsole === true || this.args.console === 'integratedTerminal' || this.args.console === 'externalTerminal') { - vsDebugOptions = vsDebugOptions.split(',').filter(opt => opt !== 'RedirectOutput').join(','); + // If internal or external console, then don't re-direct the output. + if (this.args.console === 'integratedTerminal' || this.args.console === 'externalTerminal') { + vsDebugOptions = vsDebugOptions.filter(opt => opt !== 'RedirectOutput'); } - let programArgs = Array.isArray(this.args.args) && this.args.args.length > 0 ? this.args.args : []; + // Include a dummy value, to ensure something gets sent. + // Else, argument positions get messed up due to an empty string. + vsDebugOptions = vsDebugOptions.length === 0 ? ['DUMMYVALUE'] : vsDebugOptions; + + const programArgs = Array.isArray(this.args.args) && this.args.args.length > 0 ? this.args.args : []; if (typeof this.args.module === 'string' && this.args.module.length > 0) { - return [vsDebugOptions, '-m', this.args.module].concat(programArgs); + return [vsDebugOptions.join(','), '-m', this.args.module].concat(programArgs); } - return [vsDebugOptions, this.args.program].concat(programArgs); - // Use this ability to debug unit tests or modules - // Adding breakpoints programatically to the first executable line of the test program - // return [vsDebugOptions, '-c', "import pytest;pytest.main(['/Users/donjayamanne/Desktop/Development/Python/Temp/MyEnvs/tests/test_another.py::Test_CheckMyApp::test_complex_check'])"].concat(programArgs); + return [vsDebugOptions.join(','), this.args.program].concat(programArgs); + } + private launchExternalTerminal(sudo: boolean, cwd: string, pythonPath: string, args: string[], env: {}) { + return new Promise((resolve, reject) => { + if (this.canLaunchTerminal) { + const command = sudo ? 'sudo' : pythonPath; + const commandArgs = sudo ? [pythonPath].concat(args) : args; + const isExternalTerminal = this.args.console === 'externalTerminal'; + const consoleKind = isExternalTerminal ? 'external' : 'integrated'; + const termArgs: DebugProtocol.RunInTerminalRequestArguments = { + kind: consoleKind, + title: 'Python Debug Console', + cwd, + args: [command].concat(commandArgs), + env + }; + this.debugSession.runInTerminalRequest(termArgs, 5000, (response) => { + if (response.success) { + resolve(); + } else { + reject(response); + } + }); + } else { + open({ wait: false, app: [pythonPath].concat(args), cwd, env, sudo: sudo }).then(proc => { + this.pyProc = proc; + resolve(); + }, error => { + if (!this.debugServer && this.debugServer.IsRunning) { + return; + } + reject(error); + }); + } + }); } } diff --git a/src/client/debugger/DebugClients/NonDebugClient.ts b/src/client/debugger/DebugClients/NonDebugClient.ts index c2619d185fd5..8f5abc15ae4d 100644 --- a/src/client/debugger/DebugClients/NonDebugClient.ts +++ b/src/client/debugger/DebugClients/NonDebugClient.ts @@ -1,25 +1,17 @@ -import { BaseDebugServer } from "../DebugServers/BaseDebugServer"; -import { NonDebugServer } from "../DebugServers/NonDebugServer"; -import { IPythonProcess, IDebugServer } from "../Common/Contracts"; -import { DebugSession, OutputEvent } from "vscode-debugadapter"; -import { DebugProtocol } from "vscode-debugprotocol"; -import * as path from "path"; -import * as child_process from "child_process"; -import { LaunchRequestArguments } from "../Common/Contracts"; -import { DebugClient, DebugType } from "./DebugClient"; -import { open } from "../../common/open"; - -export class NonDebugClient extends DebugClient { - protected args: LaunchRequestArguments; - constructor(args: any, debugSession: DebugSession) { - super(args, debugSession); - this.args = args; - } - - private pyProc: child_process.ChildProcess; - private debugServer: BaseDebugServer; - public CreateDebugServer(pythonProcess: IPythonProcess): BaseDebugServer { - return new NonDebugServer(this.debugSession, pythonProcess); +import { ChildProcess } from 'child_process'; +import * as path from 'path'; +import { DebugSession, OutputEvent } from 'vscode-debugadapter'; +import { IPythonProcess } from '../Common/Contracts'; +import { LaunchRequestArguments } from '../Common/Contracts'; +import { BaseDebugServer } from '../DebugServers/BaseDebugServer'; +import { LocalDebugServer } from '../DebugServers/LocalDebugServer'; +import { DebugType } from './DebugClient'; +import { LocalDebugClient } from './LocalDebugClient'; + +export class NonDebugClient extends LocalDebugClient { + // tslint:disable-next-line:no-any + constructor(args: LaunchRequestArguments, debugSession: DebugSession, canLaunchTerminal: boolean) { + super(args, debugSession, canLaunchTerminal); } public get DebugType(): DebugType { @@ -27,108 +19,21 @@ export class NonDebugClient extends DebugClient { } public Stop() { - if (this.debugServer) { - this.debugServer.Stop(); - this.debugServer = null; - } - + super.Stop(); if (this.pyProc) { - try { this.pyProc.kill(); } - catch (ex) { } + try { + this.pyProc.kill(); + // tslint:disable-next-line:no-empty + } catch { } this.pyProc = null; } } - public LaunchApplicationToDebug(dbgServer: IDebugServer, processErrored: (error: any) => void): Promise { - return new Promise((resolve, reject) => { - let fileDir = path.dirname(this.args.program); - let processCwd = fileDir; - if (typeof this.args.cwd === "string" && this.args.cwd.length > 0 && this.args.cwd !== 'null') { - processCwd = this.args.cwd; - } - let pythonPath = "python"; - if (typeof this.args.pythonPath === "string" && this.args.pythonPath.trim().length > 0) { - pythonPath = this.args.pythonPath; - } - let environmentVariables = this.args.env ? this.args.env : {}; - let newEnvVars = {}; - if (environmentVariables) { - for (let setting in environmentVariables) { - if (!newEnvVars[setting]) { - newEnvVars[setting] = environmentVariables[setting]; - } - } - for (let setting in process.env) { - if (!environmentVariables[setting]) { - environmentVariables[setting] = process.env[setting]; - } - } - } - if (!environmentVariables.hasOwnProperty("PYTHONIOENCODING")) { - environmentVariables["PYTHONIOENCODING"] = "UTF-8"; - newEnvVars["PYTHONIOENCODING"] = "UTF-8"; - } - let launcherArgs = this.buildLauncherArguments(); - - let args = launcherArgs; - if (this.args.console === 'externalTerminal') { - open({ wait: false, app: [pythonPath].concat(args), cwd: processCwd, env: environmentVariables }).then(proc => { - this.pyProc = proc; - this.pyProc.on('exit', () => { - this.pyProc = null; - this.emit('exit'); - }); - resolve(); - }, error => { - if (reject) { - reject(error); - reject = null; - } - }); - return; - } - - if (this.args.console === 'integratedTerminal') { - const isSudo = Array.isArray(this.args.debugOptions) && this.args.debugOptions.some(opt => opt === 'Sudo'); - const command = isSudo ? 'sudo' : pythonPath; - const commandArgs = isSudo ? [pythonPath].concat(args) : args; - const termArgs: DebugProtocol.RunInTerminalRequestArguments = { - kind: 'integrated', - title: "Python Debug Console", - cwd: processCwd, - args: [command].concat(commandArgs), - env: newEnvVars as { [key: string]: string } - }; - this.debugSession.runInTerminalRequest(termArgs, 5000, (response) => { - if (response.success) { - resolve(); - } else { - reject(response); - } - }); - return; - } - - this.pyProc = child_process.spawn(pythonPath, args, { cwd: processCwd, env: environmentVariables }); - this.pyProc.on("error", error => { - this.debugSession.sendEvent(new OutputEvent(error + '', "stderr")); - }); - this.pyProc.stderr.setEncoding("utf8"); - this.pyProc.stdout.setEncoding("utf8"); - this.pyProc.stderr.on("data", (error: string) => { - this.debugSession.sendEvent(new OutputEvent(error, "stderr")); - }); - this.pyProc.stdout.on("data", (d: string) => { - this.debugSession.sendEvent(new OutputEvent(d, "stdout")); - }); - this.pyProc.on('exit', () => { - this.pyProc = null; - this.emit('exit'); - }); - resolve(); - }); + protected handleProcessOutput(proc: ChildProcess, _failedToLaunch: (error: Error | string | Buffer) => void) { + this.pythonProcess.attach(proc); } - protected buildLauncherArguments(): string[] { - let programArgs = Array.isArray(this.args.args) && this.args.args.length > 0 ? this.args.args : []; - return [this.args.program].concat(programArgs); + protected getLauncherFilePath(): string { + const currentFileName = module.filename; + const ptVSToolsPath = path.join(path.dirname(currentFileName), '..', '..', '..', '..', 'pythonFiles', 'PythonTools'); + return path.join(ptVSToolsPath, 'visualstudio_py_launcher_nodebug.py'); } } diff --git a/src/client/debugger/DebugClients/RemoteDebugClient.ts b/src/client/debugger/DebugClients/RemoteDebugClient.ts index 79deb35c54c1..88cb9480fb9f 100644 --- a/src/client/debugger/DebugClients/RemoteDebugClient.ts +++ b/src/client/debugger/DebugClients/RemoteDebugClient.ts @@ -1,19 +1,18 @@ -import { BaseDebugServer } from "../DebugServers/BaseDebugServer"; -import { RemoteDebugServer } from "../DebugServers/RemoteDebugServer"; -import { IPythonProcess } from "../Common/Contracts"; -import { DebugSession } from "vscode-debugadapter"; -import { AttachRequestArguments } from "../Common/Contracts"; -import { DebugClient, DebugType } from "./DebugClient"; +import { DebugSession } from 'vscode-debugadapter'; +import { IPythonProcess } from '../Common/Contracts'; +import { AttachRequestArguments } from '../Common/Contracts'; +import { BaseDebugServer } from '../DebugServers/BaseDebugServer'; +import { RemoteDebugServer } from '../DebugServers/RemoteDebugServer'; +import { DebugClient, DebugType } from './DebugClient'; export class RemoteDebugClient extends DebugClient { - private args: AttachRequestArguments; + private pythonProcess: IPythonProcess; + private debugServer: BaseDebugServer; + // tslint:disable-next-line:no-any constructor(args: any, debugSession: DebugSession) { super(args, debugSession); - this.args = args; } - private pythonProcess: IPythonProcess; - private debugServer: BaseDebugServer; public CreateDebugServer(pythonProcess: IPythonProcess): BaseDebugServer { this.pythonProcess = pythonProcess; this.debugServer = new RemoteDebugServer(this.debugSession, this.pythonProcess, this.args); diff --git a/src/client/debugger/DebugServers/LocalDebugServer.ts b/src/client/debugger/DebugServers/LocalDebugServer.ts index 0987323b7966..f2eccd65e593 100644 --- a/src/client/debugger/DebugServers/LocalDebugServer.ts +++ b/src/client/debugger/DebugServers/LocalDebugServer.ts @@ -1,9 +1,10 @@ -"use strict"; +'use strict'; -import { DebugSession, OutputEvent } from "vscode-debugadapter"; -import { IPythonProcess, IDebugServer } from "../Common/Contracts"; -import * as net from "net"; -import { BaseDebugServer } from "./BaseDebugServer"; +import * as net from 'net'; +import { EOL } from 'os'; +import { DebugSession, OutputEvent } from 'vscode-debugadapter'; +import { IDebugServer, IPythonProcess } from '../Common/Contracts'; +import { BaseDebugServer } from './BaseDebugServer'; export class LocalDebugServer extends BaseDebugServer { private debugSocketServer: net.Server = null; @@ -16,62 +17,70 @@ export class LocalDebugServer extends BaseDebugServer { if (this.debugSocketServer === null) { return; } try { this.debugSocketServer.close(); - } - catch (ex) { } + // tslint:disable-next-line:no-empty + } catch { } this.debugSocketServer = null; } - public Start(): Promise { + public async Start(): Promise { return new Promise((resolve, reject) => { - let that = this; let connectedResolve = this.debugClientConnected.resolve.bind(this.debugClientConnected); let connected = false; + let disconnected = false; this.debugSocketServer = net.createServer(c => { // "connection" listener - c.on("data", (buffer: Buffer) => { + c.on('data', (buffer: Buffer) => { if (connectedResolve) { // The debug client has connected to the debug server connectedResolve(true); connectedResolve = null; } if (!connected) { - connected = that.pythonProcess.Connect(buffer, c, false); - } - else { - that.pythonProcess.HandleIncomingData(buffer); - that.isRunning = true; + connected = this.pythonProcess.Connect(buffer, c, false); + } else { + this.pythonProcess.HandleIncomingData(buffer); + this.isRunning = true; } }); - c.on("close", d => { - that.emit("detach", d); + c.on('close', d => { + disconnected = true; + this.emit('detach', d); }); - c.on("timeout", d => { - let msg = "Debugger client timedout, " + d; - that.debugSession.sendEvent(new OutputEvent(msg + "\n", "stderr")); + c.on('timeout', d => { + const msg = `Debugger client timedout, ${d}`; + this.debugSession.sendEvent(new OutputEvent(`${msg}${EOL}`, 'stderr')); + }); + c.on('error', ex => { + // Errors can be raised, when program has termined, but the adapter has + // sent an acknowledgement of the last message (LAST), however the socket client has ended. + if (connected || disconnected) { + return; + } + const msg = `There was an error in starting the debug server. Error = ${JSON.stringify(ex)}`; + this.debugSession.sendEvent(new OutputEvent(`${msg}${EOL}`, 'stderr')); + reject(msg); }); }); - this.debugSocketServer.on("error", ex => { - let exMessage = JSON.stringify(ex); - let msg = ""; - if ((ex as any).code === "EADDRINUSE") { + this.debugSocketServer.on('error', ex => { + const exMessage = JSON.stringify(ex); + let msg = ''; + // tslint:disable-next-line:no-any + if ((ex as any).code === 'EADDRINUSE') { msg = `The port used for debugging is in use, please try again or try restarting Visual Studio Code, Error = ${exMessage}`; - } - else { + } else { if (connected) { - // Under what circumstance does this happen? - // Needs to be documented return; } msg = `There was an error in starting the debug server. Error = ${exMessage}`; } - that.debugSession.sendEvent(new OutputEvent(msg + "\n", "stderr")); + this.debugSession.sendEvent(new OutputEvent(`${msg}${EOL}`, 'stderr')); reject(msg); }); this.debugSocketServer.listen(0, () => { - let server = that.debugSocketServer.address(); + const server = this.debugSocketServer.address(); resolve({ port: server.port }); }); }); } -} \ No newline at end of file +} diff --git a/src/client/debugger/DebugServers/NonDebugServer.ts b/src/client/debugger/DebugServers/NonDebugServer.ts deleted file mode 100644 index 1d938661ba24..000000000000 --- a/src/client/debugger/DebugServers/NonDebugServer.ts +++ /dev/null @@ -1,18 +0,0 @@ -'use strict'; - -import {DebugSession} from 'vscode-debugadapter'; -import {IPythonProcess, IDebugServer} from '../Common/Contracts'; -import {BaseDebugServer} from './BaseDebugServer'; - -export class NonDebugServer extends BaseDebugServer { - constructor(debugSession: DebugSession, pythonProcess: IPythonProcess) { - super(debugSession, pythonProcess); - } - - public Stop() { - } - - public Start(): Promise { - return Promise.resolve({ port: NaN, host: null }); - } -} \ No newline at end of file diff --git a/src/client/debugger/Main.ts b/src/client/debugger/Main.ts index 5b6b89f849a8..d8c07b821468 100644 --- a/src/client/debugger/Main.ts +++ b/src/client/debugger/Main.ts @@ -1,21 +1,21 @@ "use strict"; -import { DebugSession, InitializedEvent, TerminatedEvent, StoppedEvent, OutputEvent, Thread, StackFrame, Scope, Source, Handles } from "vscode-debugadapter"; +import * as fs from "fs"; +import * as path from "path"; +import { DebugSession, Handles, InitializedEvent, OutputEvent, Scope, Source, StackFrame, StoppedEvent, TerminatedEvent, Thread } from "vscode-debugadapter"; import { ThreadEvent } from "vscode-debugadapter"; import { DebugProtocol } from "vscode-debugprotocol"; -import * as path from "path"; -import * as fs from "fs"; -import { PythonProcess } from "./PythonProcess"; -import { IPythonThread, IPythonModule, IPythonEvaluationResult, IPythonStackFrame, IDebugServer } from "./Common/Contracts"; -import { IPythonBreakpoint, PythonBreakpointConditionKind, PythonBreakpointPassCountKind, IPythonException, PythonEvaluationResultReprKind, enum_EXCEPTION_STATE } from "./Common/Contracts"; -import { BaseDebugServer } from "./DebugServers/BaseDebugServer"; -import { DebugClient } from "./DebugClients/DebugClient"; -import { CreateAttachDebugClient, CreateLaunchDebugClient } from "./DebugClients/DebugFactory"; -import { LaunchRequestArguments, AttachRequestArguments, DebugOptions, TelemetryEvent, PythonEvaluationResultFlags } from "./Common/Contracts"; -import { validatePath, getPythonExecutable } from './Common/Utils'; -import { isNotInstalledError } from '../common/helpers'; import { DEBUGGER } from '../../client/telemetry/constants'; import { DebuggerTelemetry } from '../../client/telemetry/types'; +import { isNotInstalledError } from '../common/helpers'; +import { enum_EXCEPTION_STATE, IPythonBreakpoint, IPythonException, PythonBreakpointConditionKind, PythonBreakpointPassCountKind, PythonEvaluationResultReprKind } from "./Common/Contracts"; +import { IDebugServer, IPythonEvaluationResult, IPythonModule, IPythonStackFrame, IPythonThread } from "./Common/Contracts"; +import { AttachRequestArguments, DebugOptions, LaunchRequestArguments, PythonEvaluationResultFlags, TelemetryEvent } from "./Common/Contracts"; +import { getPythonExecutable, validatePath } from './Common/Utils'; +import { DebugClient } from "./DebugClients/DebugClient"; +import { CreateAttachDebugClient, CreateLaunchDebugClient } from "./DebugClients/DebugFactory"; +import { BaseDebugServer } from "./DebugServers/BaseDebugServer"; +import { PythonProcess } from "./PythonProcess"; const CHILD_ENUMEARATION_TIMEOUT = 5000; @@ -37,6 +37,7 @@ export class PythonDebugger extends DebugSession { private configurationDonePromiseResolve: () => void; private lastException: IPythonException; private _supportsRunInTerminalRequest: boolean; + private terminateEventSent: boolean; public constructor(debuggerLinesStartAt1: boolean, isServer: boolean) { super(debuggerLinesStartAt1, isServer === true); this._variableHandles = new Handles(); @@ -97,6 +98,8 @@ export class PythonDebugger extends DebugSession { this.pythonProcess.Kill(); this.pythonProcess = null; } + this.terminateEventSent = true; + this.sendEvent(new TerminatedEvent()); } private InitializeEventHandlers() { this.pythonProcess.on("last", arg => this.onLastCommand()); @@ -104,31 +107,26 @@ export class PythonDebugger extends DebugSession { 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("output", (pyThread, output) => this.onDebuggerOutput(pyThread, output, 'stdout')); this.pythonProcess.on("exceptionRaised", (pyThread, ex) => this.onPythonException(pyThread, ex)); this.pythonProcess.on("breakpointHit", (pyThread, breakpointId) => this.onBreakpointHit(pyThread, breakpointId)); this.pythonProcess.on("stepCompleted", (pyThread) => this.onStepCompleted(pyThread)); this.pythonProcess.on("detach", () => this.onDetachDebugger()); - this.pythonProcess.on("error", ex => this.sendEvent(new OutputEvent(ex, "stderr"))); + this.pythonProcess.on("error", ex => this.onDebuggerOutput(undefined, ex, 'stderr')); this.pythonProcess.on("asyncBreakCompleted", arg => this.onPythonProcessPaused(arg)); this.debugServer.on("detach", () => this.onDetachDebugger()); } private onLastCommand() { - // If we're running in terminal (integrated or external) - // Then don't stop the debug server - if (this.launchArgs && (this.launchArgs.console === "externalTerminal" || - this.launchArgs.console === "integratedTerminal")) { - return; - } - - // Else default behaviour as previous, which was to perform the same as onDetachDebugger - this.onDetachDebugger(); + this.terminateEventSent = true; + // When running in terminals, and if there are any errors, the PTVSD library + // first sends the LAST command (meaning everything has ended) and then sends the stderr and stdout messages. + // I.e. to us, it looks as though everything is done and completed, when it isn't. + // A simple solution is to tell vscode that it has ended 500ms later (giving us time to receive any messages from stderr/stdout from ptvsd). + setTimeout(() => this.sendEvent(new TerminatedEvent()), 500); } private onDetachDebugger() { this.stopDebugServer(); - this.sendEvent(new TerminatedEvent()); - this.shutdown(); } private onPythonThreadCreated(pyThread: IPythonThread) { this.sendEvent(new ThreadEvent("started", pyThread.Id)); @@ -150,39 +148,47 @@ export class PythonDebugger extends DebugSession { private onPythonModuleLoaded(module: IPythonModule) { } private debuggerHasLoaded: boolean; - private onPythonProcessLoaded(pyThread: IPythonThread) { + private onPythonProcessLoaded(pyThread?: IPythonThread) { this.debuggerHasLoaded = true; - this.sendResponse(this.entryResponse); + if (this.entryResponse) { + this.sendResponse(this.entryResponse); + } this.debuggerLoadedPromiseResolve(); if (this.launchArgs && !this.launchArgs.console) { - this.launchArgs.console = this.launchArgs.externalConsole === true ? 'externalTerminal' : 'none'; + this.launchArgs.console = 'none'; } - // If launching the integrated terminal is not supported, then defer to external terminal - // that will be displayed by our own code + // If launching the integrated terminal is not supported, then defer to external terminal + // that will be displayed by our own code. if (!this._supportsRunInTerminalRequest && this.launchArgs && this.launchArgs.console === 'integratedTerminal') { this.launchArgs.console = 'externalTerminal'; } - if (this.launchArgs && this.launchArgs.stopOnEntry === true) { - this.sendEvent(new StoppedEvent("entry", pyThread.Id)); - } - else if (this.launchArgs && this.launchArgs.stopOnEntry === false) { - this.configurationDone.then(() => { - this.pythonProcess.SendResumeThread(pyThread.Id); - }); - } - else { - this.pythonProcess.SendResumeThread(pyThread.Id); + if (!this.launchArgs || this.launchArgs.noDebug !== true) { + // tslint:disable-next-line:no-non-null-assertion + const thread = pyThread!; + if (this.launchArgs && this.launchArgs.stopOnEntry === true) { + this.sendEvent(new StoppedEvent("entry", thread.Id)); + } else if (this.launchArgs && this.launchArgs.stopOnEntry === false) { + this.configurationDone.then(() => { + this.pythonProcess.SendResumeThread(thread.Id); + }); + } else { + this.pythonProcess.SendResumeThread(thread.Id); + } } } - private onDebuggerOutput(pyThread: IPythonThread, output: string) { - if (!this.debuggerHasLoaded) { + private onDebuggerOutput(pyThread: IPythonThread | undefined, output: string, outputChannel: 'stdout' | 'stderr') { + if (this.entryResponse) { + // Sometimes we can get output from PTVSD even before things load. + // E.g. if the program didn't even run (e.g. simple one liner with invalid syntax). + // But we need to tell vscode that the debugging has started, so we can send error messages. this.sendResponse(this.entryResponse); this.debuggerLoadedPromiseResolve(); + this.entryResponse = undefined; } - this.sendEvent(new OutputEvent(output, "stdout")); + this.sendEvent(new OutputEvent(output, outputChannel)); } - private entryResponse: DebugProtocol.LaunchResponse; + private entryResponse?: DebugProtocol.LaunchResponse; private launchArgs: LaunchRequestArguments; private attachArgs: AttachRequestArguments; private canStartDebugger(): Promise { @@ -236,14 +242,14 @@ export class PythonDebugger extends DebugSession { const telemetryProps: DebuggerTelemetry = { trigger: 'launch', console: args.console, - debugOptions: args.debugOptions.join(","), + debugOptions: (Array.isArray(args.debugOptions) ? args.debugOptions : []).join(","), pyspark: typeof args.pythonPath === 'string' && args.pythonPath.indexOf('spark-submit') > 0, hasEnvVars: args.env && typeof args.env === "object" && Object.keys(args.env).length > 0 }; this.sendEvent(new TelemetryEvent(DEBUGGER, telemetryProps)); this.launchArgs = args; - this.debugClient = CreateLaunchDebugClient(args, this); + this.debugClient = CreateLaunchDebugClient(args, this, this._supportsRunInTerminalRequest); //this.debugClient.on('exit', () => this.sendEvent(new TerminatedEvent())); this.configurationDone = new Promise(resolve => { this.configurationDonePromiseResolve = resolve; @@ -273,6 +279,7 @@ export class PythonDebugger extends DebugSession { if (errorMsg.length > 0) { this.sendEvent(new OutputEvent(errorMsg + "\n", "stderr")); } + this.terminateEventSent = true; this.sendEvent(new TerminatedEvent()); } protected attachRequest(response: DebugProtocol.AttachResponse, args: AttachRequestArguments) { @@ -339,6 +346,12 @@ export class PythonDebugger extends DebugSession { } protected setBreakPointsRequest(response: DebugProtocol.SetBreakpointsResponse, args: DebugProtocol.SetBreakpointsArguments): void { this.debuggerLoaded.then(() => { + if (this.terminateEventSent) { + response.body = { + breakpoints: [] + }; + return this.sendResponse(response); + } if (!this.registeredBreakpointsByFileName.has(args.source.path)) { this.registeredBreakpointsByFileName.set(args.source.path, []); } @@ -352,7 +365,7 @@ export class PythonDebugger extends DebugSession { // Always add new breakpoints, don't re-enable previous breakpoints // Cuz sometimes some breakpoints get added too early (e.g. in django) and don't get registeredBks // and the response comes back indicating it wasn't set properly - // However, at a later point in time, the program breaks at that point!!! + // However, at a later point in time, the program breaks at that point!!! let linesToAddPromises = args.breakpoints.map(bk => { return new Promise(resolve => { let breakpoint: IPythonBreakpoint; @@ -411,9 +424,11 @@ export class PythonDebugger extends DebugSession { protected threadsRequest(response: DebugProtocol.ThreadsResponse): void { let threads = []; - this.pythonProcess.Threads.forEach(t => { - threads.push(new Thread(t.Id, t.Name)); - }); + if (this.pythonProcess) { + this.pythonProcess.Threads.forEach(t => { + threads.push(new Thread(t.Id, t.Name)); + }); + } response.body = { threads: threads @@ -452,11 +467,11 @@ export class PythonDebugger extends DebugSession { } protected stackTraceRequest(response: DebugProtocol.StackTraceResponse, args: DebugProtocol.StackTraceArguments): void { this.debuggerLoaded.then(() => { - if (!this.pythonProcess.Threads.has(args.threadId)) { + if (this.terminateEventSent || !this.pythonProcess || !this.pythonProcess.Threads.has(args.threadId)) { response.body = { stackFrames: [] }; - this.sendResponse(response); + return this.sendResponse(response); } let pyThread = this.pythonProcess.Threads.get(args.threadId); @@ -506,7 +521,7 @@ export class PythonDebugger extends DebugSession { protected evaluateRequest(response: DebugProtocol.EvaluateResponse, args: DebugProtocol.EvaluateArguments): void { this.debuggerLoaded.then(() => { let frame = this._pythonStackFrames.get(args.frameId); - if (!frame) { + if (this.terminateEventSent || !frame || !this.pythonProcess) { response.body = { result: null, variablesReference: 0 @@ -536,7 +551,7 @@ export class PythonDebugger extends DebugSession { protected scopesRequest(response: DebugProtocol.ScopesResponse, args: DebugProtocol.ScopesArguments): void { this.debuggerLoaded.then(() => { let frame = this._pythonStackFrames.get(args.frameId); - if (!frame) { + if (this.terminateEventSent || !frame || !this.pythonProcess) { response.body = { scopes: [] }; @@ -645,6 +660,9 @@ export class PythonDebugger extends DebugSession { } protected setExceptionBreakPointsRequest(response: DebugProtocol.SetExceptionBreakpointsResponse, args: DebugProtocol.SetExceptionBreakpointsArguments): void { this.debuggerLoaded.then(() => { + if (this.terminateEventSent) { + return this.sendResponse(response); + } let mode = enum_EXCEPTION_STATE.BREAK_MODE_NEVER; if (args.filters.indexOf("uncaught") >= 0) { mode = enum_EXCEPTION_STATE.BREAK_MODE_UNHANDLED; @@ -683,7 +701,9 @@ export class PythonDebugger extends DebugSession { if (!exToIgnore.has('GeneratorExit')) { exToIgnore.set('GeneratorExit', enum_EXCEPTION_STATE.BREAK_MODE_NEVER); } - this.pythonProcess.SendExceptionInfo(mode, exToIgnore); + if (this.pythonProcess) { + this.pythonProcess.SendExceptionInfo(mode, exToIgnore); + } this.sendResponse(response); }); } diff --git a/src/client/debugger/PythonProcess.ts b/src/client/debugger/PythonProcess.ts index 1464842a3af2..0a643630c94c 100644 --- a/src/client/debugger/PythonProcess.ts +++ b/src/client/debugger/PythonProcess.ts @@ -1,14 +1,15 @@ "use strict"; import * as net from "net"; -import {EventEmitter} from "events"; -import {FrameKind, IPythonProcess, IPythonThread, IPythonModule, IPythonEvaluationResult, IPythonStackFrame, IStepCommand} from "./Common/Contracts"; -import {IPythonBreakpoint, PythonBreakpointConditionKind, PythonBreakpointPassCountKind, IBreakpointCommand, IChildEnumCommand} from "./Common/Contracts"; -import {PythonEvaluationResultReprKind, IExecutionCommand, enum_EXCEPTION_STATE} from "./Common/Contracts"; -import {Commands} from "./ProxyCommands"; -import {IdDispenser} from "../common/idDispenser"; -import {PythonProcessCallbackHandler} from "./PythonProcessCallbackHandler"; -import {SocketStream} from "../common/comms/SocketStream"; +import { ChildProcess } from 'child_process'; +import { EventEmitter } from "events"; +import { FrameKind, IPythonProcess, IPythonThread, IPythonModule, IPythonEvaluationResult, IPythonStackFrame, IStepCommand } from "./Common/Contracts"; +import { IPythonBreakpoint, PythonBreakpointConditionKind, PythonBreakpointPassCountKind, IBreakpointCommand, IChildEnumCommand } from "./Common/Contracts"; +import { PythonEvaluationResultReprKind, IExecutionCommand, enum_EXCEPTION_STATE } from "./Common/Contracts"; +import { Commands } from "./ProxyCommands"; +import { IdDispenser } from "../common/idDispenser"; +import { PythonProcessCallbackHandler } from "./PythonProcessCallbackHandler"; +import { SocketStream } from "../common/comms/SocketStream"; export class PythonProcess extends EventEmitter implements IPythonProcess { private id: number; @@ -202,6 +203,9 @@ export class PythonProcess extends EventEmitter implements IPythonProcess { } public SendExceptionInfo(defaultBreakOnMode: enum_EXCEPTION_STATE, breakOn: Map) { + if (!this.stream) { + return; + } this.stream.Write(Commands.SetExceptionInfoCommandBytes); this.stream.WriteInt32(defaultBreakOnMode); if (breakOn === null || breakOn === undefined) { @@ -225,7 +229,7 @@ export class PythonProcess extends EventEmitter implements IPythonProcess { public SendStepInto(threadId: number) { return this.sendStepCommand(threadId, Commands.StepIntoCommandBytes); } - // #endregion + // #endregion private onBreakpointHit(pyThread: IPythonThread, breakpointId: number) { this._lastExecutedThread = pyThread; this.emit("breakpointHit", pyThread, breakpointId); @@ -385,4 +389,20 @@ export class PythonProcess extends EventEmitter implements IPythonProcess { public SetLineNumber(pythonStackFrame: IPythonStackFrame, lineNo: number) { } + public attach(proc: ChildProcess): void { + proc.on('error', error => { + this.emit("error", undefined, error.toString()); + }); + proc.stderr.setEncoding('utf8'); + proc.stdout.setEncoding('utf8'); + proc.stderr.on('data', (error: string) => { + this.emit("error", error.toString()); + }); + proc.stdout.on('data', (d: string) => { + this.emit("output", undefined, d); + }); + proc.on('close', () => { + this.emit('detach'); + }); + } } diff --git a/src/client/debugger/configProviders/simpleProvider.ts b/src/client/debugger/configProviders/simpleProvider.ts index 391edd782d99..bde83ba66b30 100644 --- a/src/client/debugger/configProviders/simpleProvider.ts +++ b/src/client/debugger/configProviders/simpleProvider.ts @@ -38,10 +38,11 @@ export class SimpleConfigurationProvider implements DebugConfigurationProvider { return undefined; } resolveDebugConfiguration(folder: WorkspaceFolder | undefined, debugConfiguration: DebugConfiguration, token?: CancellationToken): ProviderResult { - if (Object.keys(debugConfiguration).length > 0) { + const keys = Object.keys(debugConfiguration); + const provideConfig = (debugConfiguration.noDebug === true && keys.length === 1) || keys.length === 0; + if (!provideConfig) { return debugConfiguration; } - const config = debugConfiguration as PythonDebugConfiguration; const defaultProgram = this.getProgram(config); const workspaceFolder = this.getWorkspaceFolder(config); @@ -57,10 +58,9 @@ export class SimpleConfigurationProvider implements DebugConfigurationProvider { envFile, env: {}, debugOptions: [ - 'WaitOnAbnormalExit', - 'WaitOnNormalExit', 'RedirectOutput' - ] + ], + noDebug: debugConfiguration.noDebug }; } } diff --git a/src/client/interpreter/configuration/services/workspaceFolderUpdaterService.ts b/src/client/interpreter/configuration/services/workspaceFolderUpdaterService.ts index 37f45d5cde14..38224d7c4e50 100644 --- a/src/client/interpreter/configuration/services/workspaceFolderUpdaterService.ts +++ b/src/client/interpreter/configuration/services/workspaceFolderUpdaterService.ts @@ -14,7 +14,7 @@ export class WorkspaceFolderPythonPathUpdaterService implements IPythonPathUpdat } if (pythonPath.startsWith(this.workspaceFolder.fsPath)) { // tslint:disable-next-line:no-invalid-template-strings - pythonPath = path.join('${workspaceRoot}', path.relative(this.workspaceFolder.fsPath, pythonPath)); + pythonPath = path.join('${workspaceFolder}', path.relative(this.workspaceFolder.fsPath, pythonPath)); } await pythonConfig.update('pythonPath', pythonPath, ConfigurationTarget.WorkspaceFolder); } diff --git a/src/client/interpreter/configuration/services/workspaceUpdaterService.ts b/src/client/interpreter/configuration/services/workspaceUpdaterService.ts index a5a35c3483e5..51c0644c503e 100644 --- a/src/client/interpreter/configuration/services/workspaceUpdaterService.ts +++ b/src/client/interpreter/configuration/services/workspaceUpdaterService.ts @@ -14,7 +14,7 @@ export class WorkspacePythonPathUpdaterService implements IPythonPathUpdaterServ } if (pythonPath.startsWith(this.wkspace.fsPath)) { // tslint:disable-next-line:no-invalid-template-strings - pythonPath = path.join('${workspaceRoot}', path.relative(this.wkspace.fsPath, pythonPath)); + pythonPath = path.join('${workspaceFolder}', path.relative(this.wkspace.fsPath, pythonPath)); } await pythonConfig.update('pythonPath', pythonPath, false); } diff --git a/src/client/interpreter/contracts.ts b/src/client/interpreter/contracts.ts index 24c11f906597..1bee73bb62b8 100644 --- a/src/client/interpreter/contracts.ts +++ b/src/client/interpreter/contracts.ts @@ -5,6 +5,10 @@ export interface IInterpreterLocatorService extends Disposable { getInterpreters(resource?: Uri): Promise; } +export interface ICondaLocatorService { + getCondaFile(): Promise; +} + export type PythonInterpreter = { path: string; companyDisplayName?: string; diff --git a/src/client/interpreter/helpers.ts b/src/client/interpreter/helpers.ts index f78df63a31f8..69103313a909 100644 --- a/src/client/interpreter/helpers.ts +++ b/src/client/interpreter/helpers.ts @@ -3,7 +3,7 @@ import { ConfigurationTarget, window, workspace } from 'vscode'; import { RegistryImplementation } from '../common/registry'; import { Is_64Bit, IS_WINDOWS } from '../common/utils'; import { WorkspacePythonPath } from './contracts'; -import { CondaEnvService } from './locators/services/condaEnvService'; +import { CondaLocatorService } from './locators/services/condaLocator'; import { WindowsRegistryService } from './locators/services/windowsRegistryService'; export function getFirstNonEmptyLineFromMultilineString(stdout: string) { @@ -29,14 +29,10 @@ export function getActiveWorkspaceUri(): WorkspacePythonPath | undefined { return undefined; } export async function getCondaVersion() { - let condaService: CondaEnvService; - if (IS_WINDOWS) { - const windowsRegistryProvider = new WindowsRegistryService(new RegistryImplementation(), Is_64Bit); - condaService = new CondaEnvService(windowsRegistryProvider); - } else { - condaService = new CondaEnvService(); - } - return condaService.getCondaFile() + const windowsRegistryProvider = IS_WINDOWS ? new WindowsRegistryService(new RegistryImplementation(), Is_64Bit) : undefined; + const condaLocator = new CondaLocatorService(IS_WINDOWS, windowsRegistryProvider); + + return condaLocator.getCondaFile() .then(async condaFile => { return new Promise((resolve, reject) => { child_process.execFile(condaFile, ['--version'], (_, stdout) => { diff --git a/src/client/interpreter/locators/index.ts b/src/client/interpreter/locators/index.ts index 198c37f25869..67f470e7e970 100644 --- a/src/client/interpreter/locators/index.ts +++ b/src/client/interpreter/locators/index.ts @@ -9,6 +9,7 @@ import { VirtualEnvironmentManager } from '../virtualEnvs'; import { fixInterpreterDisplayName, fixInterpreterPath } from './helpers'; import { CondaEnvFileService, getEnvironmentsFile as getCondaEnvFile } from './services/condaEnvFileService'; import { CondaEnvService } from './services/condaEnvService'; +import { CondaLocatorService } from './services/condaLocator'; import { CurrentPathService } from './services/currentPathService'; import { getKnownSearchPathsForInterpreters, KnownPathsService } from './services/KnownPathsService'; import { getKnownSearchPathsForVirtualEnvs, VirtualEnvService } from './services/virtualEnvService'; @@ -66,10 +67,12 @@ export class PythonInterpreterLocatorService implements IInterpreterLocatorServi // The order of the services is important. if (IS_WINDOWS) { const windowsRegistryProvider = new WindowsRegistryService(new RegistryImplementation(), Is_64Bit); + const condaLocator = new CondaLocatorService(IS_WINDOWS, windowsRegistryProvider); locators.push(windowsRegistryProvider); - locators.push(new CondaEnvService(windowsRegistryProvider)); + locators.push(new CondaEnvService(condaLocator)); } else { - locators.push(new CondaEnvService()); + const condaLocator = new CondaLocatorService(IS_WINDOWS); + locators.push(new CondaEnvService(condaLocator)); } // Supplements the above list of conda environments. locators.push(new CondaEnvFileService(getCondaEnvFile(), versionService)); diff --git a/src/client/interpreter/locators/services/condaEnvService.ts b/src/client/interpreter/locators/services/condaEnvService.ts index c6afce8abe0a..f6bab291c59e 100644 --- a/src/client/interpreter/locators/services/condaEnvService.ts +++ b/src/client/interpreter/locators/services/condaEnvService.ts @@ -4,33 +4,19 @@ import * as fs from 'fs-extra'; import * as path from 'path'; import { Uri } from 'vscode'; import { VersionUtils } from '../../../common/versionUtils'; -import { IInterpreterLocatorService, PythonInterpreter } from '../../contracts'; +import { ICondaLocatorService, IInterpreterLocatorService, PythonInterpreter } from '../../contracts'; import { AnacondaCompanyName, CONDA_RELATIVE_PY_PATH, CondaInfo } from './conda'; import { CondaHelper } from './condaHelper'; export class CondaEnvService implements IInterpreterLocatorService { private readonly condaHelper = new CondaHelper(); - constructor(private registryLookupForConda?: IInterpreterLocatorService) { + constructor(private condaLocator: ICondaLocatorService) { } public async getInterpreters(resource?: Uri) { return this.getSuggestionsFromConda(); } // tslint:disable-next-line:no-empty public dispose() { } - public async getCondaFile() { - if (this.registryLookupForConda) { - return this.registryLookupForConda.getInterpreters() - .then(interpreters => interpreters.filter(this.isCondaEnvironment)) - .then(condaInterpreters => this.getLatestVersion(condaInterpreters)) - .then(condaInterpreter => { - return condaInterpreter ? path.join(path.dirname(condaInterpreter.path), 'conda.exe') : 'conda'; - }) - .then(async condaPath => { - return fs.pathExists(condaPath).then(exists => exists ? condaPath : 'conda'); - }); - } - return Promise.resolve('conda'); - } public isCondaEnvironment(interpreter: PythonInterpreter) { return (interpreter.displayName ? interpreter.displayName : '').toUpperCase().indexOf('ANACONDA') >= 0 || (interpreter.companyDisplayName ? interpreter.companyDisplayName : '').toUpperCase().indexOf('CONTINUUM') >= 0; @@ -73,7 +59,7 @@ export class CondaEnvService implements IInterpreterLocatorService { .then(interpreters => interpreters.map(interpreter => interpreter!)); } private async getSuggestionsFromConda(): Promise { - return this.getCondaFile() + return this.condaLocator.getCondaFile() .then(async condaFile => { return new Promise((resolve, reject) => { // interrogate conda (if it's on the path) to find all environments. diff --git a/src/client/interpreter/locators/services/condaLocator.ts b/src/client/interpreter/locators/services/condaLocator.ts new file mode 100644 index 000000000000..ff1db7269c50 --- /dev/null +++ b/src/client/interpreter/locators/services/condaLocator.ts @@ -0,0 +1,69 @@ +'use strict'; +import * as child_process from 'child_process'; +import * as fs from 'fs-extra'; +import * as path from 'path'; +import { IS_WINDOWS } from '../../../common/utils'; +import { VersionUtils } from '../../../common/versionUtils'; +import { ICondaLocatorService, IInterpreterLocatorService, PythonInterpreter } from '../../contracts'; +// tslint:disable-next-line:no-require-imports no-var-requires +const untildify: (value: string) => string = require('untildify'); + +const KNOWN_CONDA_LOCATIONS = ['~/anaconda/bin/conda', '~/miniconda/bin/conda', + '~/anaconda2/bin/conda', '~/miniconda2/bin/conda', + '~/anaconda3/bin/conda', '~/miniconda3/bin/conda']; + +export class CondaLocatorService implements ICondaLocatorService { + constructor(private isWindows: boolean, private registryLookupForConda?: IInterpreterLocatorService) { + } + // tslint:disable-next-line:no-empty + public dispose() { } + public async getCondaFile(): Promise { + const isAvailable = await this.isCondaInCurrentPath(); + if (isAvailable) { + return 'conda'; + } + if (this.isWindows && this.registryLookupForConda) { + return this.registryLookupForConda.getInterpreters() + .then(interpreters => interpreters.filter(this.isCondaEnvironment)) + .then(condaInterpreters => this.getLatestVersion(condaInterpreters)) + .then(condaInterpreter => { + return condaInterpreter ? path.join(path.dirname(condaInterpreter.path), 'conda.exe') : 'conda'; + }) + .then(async condaPath => { + return fs.pathExists(condaPath).then(exists => exists ? condaPath : 'conda'); + }); + } + return this.getCondaFileFromKnownLocations(); + } + public isCondaEnvironment(interpreter: PythonInterpreter) { + return (interpreter.displayName ? interpreter.displayName : '').toUpperCase().indexOf('ANACONDA') >= 0 || + (interpreter.companyDisplayName ? interpreter.companyDisplayName : '').toUpperCase().indexOf('CONTINUUM') >= 0; + } + public getLatestVersion(interpreters: PythonInterpreter[]) { + const sortedInterpreters = interpreters.filter(interpreter => interpreter.version && interpreter.version.length > 0); + // tslint:disable-next-line:no-non-null-assertion + sortedInterpreters.sort((a, b) => VersionUtils.compareVersion(a.version!, b.version!)); + if (sortedInterpreters.length > 0) { + return sortedInterpreters[sortedInterpreters.length - 1]; + } + } + public async isCondaInCurrentPath() { + return new Promise((resolve, reject) => { + child_process.execFile('conda', ['--version'], (_, stdout) => { + if (stdout && stdout.length > 0) { + resolve(true); + } else { + resolve(false); + } + }); + }); + } + private async getCondaFileFromKnownLocations(): Promise { + const condaFiles = await Promise.all(KNOWN_CONDA_LOCATIONS + .map(untildify) + .map(async (condaPath: string) => fs.pathExists(condaPath).then(exists => exists ? condaPath : ''))); + + const validCondaFiles = condaFiles.filter(condaPath => condaPath.length > 0); + return validCondaFiles.length === 0 ? 'conda' : validCondaFiles[0]; + } +} diff --git a/src/client/providers/jediProxy.ts b/src/client/providers/jediProxy.ts index ae1a03b9858c..97f5c3edc2c1 100644 --- a/src/client/providers/jediProxy.ts +++ b/src/client/providers/jediProxy.ts @@ -188,7 +188,7 @@ export class JediProxy implements vscode.Disposable { private spawnRetryAttempts = 0; private spawnProcess(dir: string) { try { - let environmentVariables = { 'PYTHONUNBUFFERED': '1' }; + let environmentVariables: Object & { [key: string]: string } = { 'PYTHONUNBUFFERED': '1' }; let customEnvironmentVars = getCustomEnvVarsSync(vscode.Uri.file(dir)); if (customEnvironmentVars) { environmentVariables = mergeEnvVariables(environmentVariables, customEnvironmentVars); diff --git a/src/client/refactor/proxy.ts b/src/client/refactor/proxy.ts index 2511c99450d9..d434f522c0c7 100644 --- a/src/client/refactor/proxy.ts +++ b/src/client/refactor/proxy.ts @@ -104,7 +104,7 @@ export class RefactorProxy extends vscode.Disposable { private initialize(pythonPath: string): Promise { return new Promise((resolve, reject) => { this._initializeReject = reject; - let environmentVariables = { 'PYTHONUNBUFFERED': '1' }; + let environmentVariables: Object & { [key: string]: string } = { 'PYTHONUNBUFFERED': '1' }; let customEnvironmentVars = getCustomEnvVarsSync(vscode.Uri.file(this.workspaceRoot)); if (customEnvironmentVars) { environmentVariables = mergeEnvVariables(environmentVariables, customEnvironmentVars); diff --git a/src/test/common/configSettings.multiroot.test.ts b/src/test/common/configSettings.multiroot.test.ts index dc7ef9590885..1f3190995858 100644 --- a/src/test/common/configSettings.multiroot.test.ts +++ b/src/test/common/configSettings.multiroot.test.ts @@ -162,7 +162,7 @@ suite('Multiroot Config Settings', () => { }); // tslint:disable-next-line:no-invalid-template-strings - test('${workspaceRoot} variable in settings should be replaced with the right value', async () => { + test('${workspaceFolder} variable in settings should be replaced with the right value', async () => { const workspace2Uri = Uri.file(path.join(multirootPath, 'workspace2')); let fileToOpen = path.join(workspace2Uri.fsPath, 'file.py'); diff --git a/src/test/interpreters/condaEnvService.test.ts b/src/test/interpreters/condaEnvService.test.ts index 8ba4fcf5d43c..ae3dfba4c4ab 100644 --- a/src/test/interpreters/condaEnvService.test.ts +++ b/src/test/interpreters/condaEnvService.test.ts @@ -1,13 +1,13 @@ import * as assert from 'assert'; import * as path from 'path'; import { Uri } from 'vscode'; -import { PythonSettings } from '../../client/common/configSettings'; -import { IS_WINDOWS } from '../../client/common/utils'; +import { IS_WINDOWS, PythonSettings } from '../../client/common/configSettings'; import { PythonInterpreter } from '../../client/interpreter/contracts'; import { AnacondaCompanyName, AnacondaDisplayName } from '../../client/interpreter/locators/services/conda'; import { CondaEnvService } from '../../client/interpreter/locators/services/condaEnvService'; +import { CondaLocatorService } from '../../client/interpreter/locators/services/condaLocator'; import { initialize, initializeTest } from '../initialize'; -import { MockProvider } from './mocks'; +import { MockCondaLocatorService, MockProvider } from './mocks'; const environmentsPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'environments'); const fileInNonRootWorkspace = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'dummy.py'); @@ -17,13 +17,13 @@ suite('Interpreters from Conda Environments', () => { suiteSetup(initialize); setup(initializeTest); test('Must return an empty list for empty json', async () => { - const condaProvider = new CondaEnvService(); + const condaProvider = new CondaEnvService(new CondaLocatorService(IS_WINDOWS)); // tslint:disable-next-line:no-any prefer-type-cast const interpreters = await condaProvider.parseCondaInfo({} as any); assert.equal(interpreters.length, 0, 'Incorrect number of entries'); }); test('Must extract display name from version info', async () => { - const condaProvider = new CondaEnvService(); + const condaProvider = new CondaEnvService(new CondaLocatorService(IS_WINDOWS)); const info = { envs: [path.join(environmentsPath, 'conda', 'envs', 'numpy'), path.join(environmentsPath, 'conda', 'envs', 'scipy')], @@ -44,7 +44,7 @@ suite('Interpreters from Conda Environments', () => { assert.equal(interpreters[1].companyDisplayName, AnacondaCompanyName, 'Incorrect company display name for first env'); }); test('Must use the default display name if sys.version is invalid', async () => { - const condaProvider = new CondaEnvService(); + const condaProvider = new CondaEnvService(new CondaLocatorService(IS_WINDOWS)); const info = { envs: [path.join(environmentsPath, 'conda', 'envs', 'numpy')], default_prefix: '', @@ -59,7 +59,7 @@ suite('Interpreters from Conda Environments', () => { assert.equal(interpreters[0].companyDisplayName, AnacondaCompanyName, 'Incorrect company display name for first env'); }); test('Must use the default display name if sys.version is empty', async () => { - const condaProvider = new CondaEnvService(); + const condaProvider = new CondaEnvService(new CondaLocatorService(IS_WINDOWS)); const info = { envs: [path.join(environmentsPath, 'conda', 'envs', 'numpy')] }; @@ -72,7 +72,7 @@ suite('Interpreters from Conda Environments', () => { assert.equal(interpreters[0].companyDisplayName, AnacondaCompanyName, 'Incorrect company display name for first env'); }); test('Must include the default_prefix into the list of interpreters', async () => { - const condaProvider = new CondaEnvService(); + const condaProvider = new CondaEnvService(new CondaLocatorService(IS_WINDOWS)); const info = { default_prefix: path.join(environmentsPath, 'conda', 'envs', 'numpy') }; @@ -85,7 +85,7 @@ suite('Interpreters from Conda Environments', () => { assert.equal(interpreters[0].companyDisplayName, AnacondaCompanyName, 'Incorrect company display name for first env'); }); test('Must exclude interpreters that do not exist on disc', async () => { - const condaProvider = new CondaEnvService(); + const condaProvider = new CondaEnvService(new CondaLocatorService(IS_WINDOWS)); const info = { envs: [path.join(environmentsPath, 'conda', 'envs', 'numpy'), path.join(environmentsPath, 'path0', 'one.exe'), @@ -117,7 +117,7 @@ suite('Interpreters from Conda Environments', () => { { displayName: 'xnaconda', path: path.join(environmentsPath, 'path2', 'one.exe'), companyDisplayName: 'Continuum Analytics, Inc.' } ]; const mockRegistryProvider = new MockProvider(registryInterpreters); - const condaProvider = new CondaEnvService(mockRegistryProvider); + const condaProvider = new CondaEnvService(new CondaLocatorService(true, mockRegistryProvider)); assert.equal(condaProvider.isCondaEnvironment(registryInterpreters[0]), false, '1. Identified environment incorrectly'); assert.equal(condaProvider.isCondaEnvironment(registryInterpreters[1]), false, '2. Identified environment incorrectly'); @@ -140,7 +140,7 @@ suite('Interpreters from Conda Environments', () => { { displayName: 'Seven', path: path.join(environmentsPath, 'conda', 'envs', 'numpy'), companyDisplayName: 'Continuum Analytics, Inc.' } ]; const mockRegistryProvider = new MockProvider(registryInterpreters); - const condaProvider = new CondaEnvService(mockRegistryProvider); + const condaProvider = new CondaEnvService(new CondaLocatorService(true, mockRegistryProvider)); // tslint:disable-next-line:no-non-null-assertion assert.equal(condaProvider.getLatestVersion(registryInterpreters)!.displayName, 'Two', 'Failed to identify latest version'); @@ -158,7 +158,7 @@ suite('Interpreters from Conda Environments', () => { { displayName: 'Seven', path: path.join(environmentsPath, 'conda', 'envs', 'numpy'), companyDisplayName: 'Continuum Analytics, Inc.' } ]; const mockRegistryProvider = new MockProvider(registryInterpreters); - const condaProvider = new CondaEnvService(mockRegistryProvider); + const condaProvider = new CondaEnvService(new CondaLocatorService(true, mockRegistryProvider)); // tslint:disable-next-line:no-non-null-assertion assert.equal(condaProvider.getLatestVersion(registryInterpreters)!.displayName, 'Two', 'Failed to identify latest version'); @@ -172,7 +172,7 @@ suite('Interpreters from Conda Environments', () => { { displayName: 'Seven', path: path.join(environmentsPath, 'conda', 'envs', 'numpy'), companyDisplayName: 'Continuum Analytics, Inc.' } ]; const mockRegistryProvider = new MockProvider(registryInterpreters); - const condaProvider = new CondaEnvService(mockRegistryProvider); + const condaProvider = new MockCondaLocatorService(true, mockRegistryProvider, false); const condaExe = await condaProvider.getCondaFile(); assert.equal(condaExe, path.join(path.dirname(condaPythonExePath), 'conda.exe'), 'Failed to identify conda.exe'); diff --git a/src/test/interpreters/mocks.ts b/src/test/interpreters/mocks.ts index db4cfed0c474..be61eb9c52c9 100644 --- a/src/test/interpreters/mocks.ts +++ b/src/test/interpreters/mocks.ts @@ -1,12 +1,13 @@ import { Architecture, Hive, IRegistry } from '../../client/common/registry'; import { IInterpreterLocatorService, PythonInterpreter } from '../../client/interpreter/contracts'; import { IInterpreterVersionService } from '../../client/interpreter/interpreterVersion'; +import { CondaLocatorService } from '../../client/interpreter/locators/services/condaLocator'; import { IVirtualEnvironment } from '../../client/interpreter/virtualEnvs/contracts'; export class MockProvider implements IInterpreterLocatorService { constructor(private suggestions: PythonInterpreter[]) { } - public getInterpreters(): Promise { + public async getInterpreters(): Promise { return Promise.resolve(this.suggestions); } // tslint:disable-next-line:no-empty @@ -17,9 +18,9 @@ export class MockRegistry implements IRegistry { constructor(private keys: { key: string, hive: Hive, arch?: Architecture, values: string[] }[], private values: { key: string, hive: Hive, arch?: Architecture, value: string, name?: string }[]) { } - public getKeys(key: string, hive: Hive, arch?: Architecture): Promise { + public async getKeys(key: string, hive: Hive, arch?: Architecture): Promise { const items = this.keys.find(item => { - if (item.arch) { + if (typeof item.arch === 'number') { return item.key === key && item.hive === hive && item.arch === arch; } return item.key === key && item.hive === hive; @@ -27,12 +28,12 @@ export class MockRegistry implements IRegistry { return items ? Promise.resolve(items.values) : Promise.resolve([]); } - public getValue(key: string, hive: Hive, arch?: Architecture, name?: string): Promise { + public async getValue(key: string, hive: Hive, arch?: Architecture, name?: string): Promise { const items = this.values.find(item => { if (item.key !== key || item.hive !== hive) { return false; } - if (item.arch && item.arch !== arch) { + if (typeof item.arch === 'number' && item.arch !== arch) { return false; } if (name && item.name !== name) { @@ -48,7 +49,7 @@ export class MockRegistry implements IRegistry { export class MockVirtualEnv implements IVirtualEnvironment { constructor(private isDetected: boolean, public name: string) { } - public detect(pythonPath: string): Promise { + public async detect(pythonPath: string): Promise { return Promise.resolve(this.isDetected); } } @@ -57,12 +58,26 @@ export class MockVirtualEnv implements IVirtualEnvironment { export class MockInterpreterVersionProvider implements IInterpreterVersionService { constructor(private displayName: string, private useDefaultDisplayName: boolean = false, private pipVersionPromise?: Promise) { } - public getVersion(pythonPath: string, defaultDisplayName: string): Promise { + public async getVersion(pythonPath: string, defaultDisplayName: string): Promise { return this.useDefaultDisplayName ? Promise.resolve(defaultDisplayName) : Promise.resolve(this.displayName); } - public getPipVersion(pythonPath: string): Promise { - return this.pipVersionPromise; + public async getPipVersion(pythonPath: string): Promise { + // tslint:disable-next-line:no-non-null-assertion + return this.pipVersionPromise!; } // tslint:disable-next-line:no-empty public dispose() { } } + +// tslint:disable-next-line:max-classes-per-file +export class MockCondaLocatorService extends CondaLocatorService { + constructor(isWindows: boolean, registryLookupForConda?: IInterpreterLocatorService, private isCondaInEnv?: boolean) { + super(isWindows, registryLookupForConda); + } + public async isCondaInCurrentPath() { + if (typeof this.isCondaInEnv === 'boolean') { + return this.isCondaInEnv; + } + return super.isCondaInCurrentPath(); + } +} diff --git a/src/test/interpreters/pythonPathUpdater.multiroot.test.ts b/src/test/interpreters/pythonPathUpdater.multiroot.test.ts index 05025376fe3d..f110790224d2 100644 --- a/src/test/interpreters/pythonPathUpdater.multiroot.test.ts +++ b/src/test/interpreters/pythonPathUpdater.multiroot.test.ts @@ -1,6 +1,7 @@ import * as assert from 'assert'; import * as path from 'path'; import { ConfigurationTarget, Uri, workspace } from 'vscode'; +import { PythonSettings } from '../../client/common/configSettings'; import { PythonPathUpdaterService } from '../../client/interpreter/configuration/pythonPathUpdaterService'; import { PythonPathUpdaterServiceFactory } from '../../client/interpreter/configuration/pythonPathUpdaterServiceFactory'; import { WorkspaceFolderPythonPathUpdaterService } from '../../client/interpreter/configuration/services/workspaceFolderUpdaterService'; @@ -32,20 +33,43 @@ suite('Multiroot Python Path Settings Updater', () => { test('Updating Workspace Folder Python Path should work', async () => { const workspaceUri = workspace3Uri; - const workspaceUpdater = new WorkspaceFolderPythonPathUpdaterService(workspace.getWorkspaceFolder(workspaceUri).uri); + const workspaceUpdater = new WorkspaceFolderPythonPathUpdaterService(workspace.getWorkspaceFolder(workspaceUri)!.uri); const pythonPath = `xWorkspacePythonPath${new Date().getMilliseconds()}`; await workspaceUpdater.updatePythonPath(pythonPath); - const folderValue = workspace.getConfiguration('python', workspace3Uri).inspect('pythonPath').workspaceFolderValue; + const folderValue = workspace.getConfiguration('python', workspace3Uri).inspect('pythonPath')!.workspaceFolderValue!; assert.equal(folderValue, pythonPath, 'Workspace Python Path not updated'); }); + test('Updating Workspace Folder Python Path when Python Path is in a sub directory of Workspace Folder', async () => { + const workspaceUri = workspace3Uri; + const workspaceFolder = workspace.getWorkspaceFolder(workspaceUri)!.uri; + const workspaceUpdater = new WorkspaceFolderPythonPathUpdaterService(workspaceFolder); + + // Python path within a sub directory of the workspace folder. + const pythonExec = path.join('virtual Envs', 'env1', `xWorkspacePythonPath${new Date().getMilliseconds()}`); + + // Expected path to python executable where ${workspaceFolder} token represents the fully qualified path to executable. + // tslint:disable-next-line:no-invalid-template-strings + const rawPythonPath = path.join('${workspaceFolder}', pythonExec); + + // Fully qualified path to the python executable. + const pythonPath = path.join(workspaceFolder.fsPath, pythonExec); + await workspaceUpdater.updatePythonPath(pythonPath); + PythonSettings.dispose(); + + const folderValue = workspace.getConfiguration('python', workspace3Uri).inspect('pythonPath')!.workspaceFolderValue!; + assert.equal(folderValue, rawPythonPath, 'Raw Workspace Python Path not updated with a path relative to workspace folder'); + const resolvedPythonPath = PythonSettings.getInstance(workspaceUri).pythonPath; + assert.equal(resolvedPythonPath, pythonPath, 'Resolved Workspace Python Path is incorrect'); + }); + test('Updating Workspace Folder Python Path using the factor service should work', async () => { const workspaceUri = workspace3Uri; const factory = new PythonPathUpdaterServiceFactory(); - const workspaceUpdater = factory.getWorkspaceFolderPythonPathConfigurationService(workspace.getWorkspaceFolder(workspaceUri).uri); + const workspaceUpdater = factory.getWorkspaceFolderPythonPathConfigurationService(workspace.getWorkspaceFolder(workspaceUri)!.uri); const pythonPath = `xWorkspacePythonPathFromFactory${new Date().getMilliseconds()}`; await workspaceUpdater.updatePythonPath(pythonPath); - const folderValue = workspace.getConfiguration('python', workspace3Uri).inspect('pythonPath').workspaceFolderValue; + const folderValue = workspace.getConfiguration('python', workspace3Uri).inspect('pythonPath')!.workspaceFolderValue!; assert.equal(folderValue, pythonPath, 'Workspace Python Path not updated'); }); @@ -53,19 +77,38 @@ suite('Multiroot Python Path Settings Updater', () => { const workspaceUri = workspace3Uri; const updaterService = new PythonPathUpdaterService(new PythonPathUpdaterServiceFactory(), new InterpreterVersionService()); const pythonPath = `xWorkspacePythonPathFromUpdater${new Date().getMilliseconds()}`; - await updaterService.updatePythonPath(pythonPath, ConfigurationTarget.WorkspaceFolder, 'ui', workspace.getWorkspaceFolder(workspaceUri).uri); - const folderValue = workspace.getConfiguration('python', workspace3Uri).inspect('pythonPath').workspaceFolderValue; + await updaterService.updatePythonPath(pythonPath, ConfigurationTarget.WorkspaceFolder, 'ui', workspace.getWorkspaceFolder(workspaceUri)!.uri); + const folderValue = workspace.getConfiguration('python', workspace3Uri).inspect('pythonPath')!.workspaceFolderValue!; assert.equal(folderValue, pythonPath, 'Workspace Python Path not updated'); }); - test('Python Path should be relative to workspace', async () => { - const workspaceUri = workspace.getWorkspaceFolder(workspace3Uri).uri; + // tslint:disable-next-line:no-invalid-template-strings + test('Python Paths containing ${workspaceRoot} should be resolved as ${workspaceFolder}', async () => { + const workspaceUri = workspace.getWorkspaceFolder(workspace3Uri)!.uri; + const pythonInterpreter = `xWorkspacePythonPath${new Date().getMilliseconds()}`; + // tslint:disable-next-line:no-invalid-template-strings + const pythonPath = path.join('${workspaceRoot}', 'x', 'y', 'z', pythonInterpreter); + const workspaceUpdater = new WorkspacePythonPathUpdaterService(workspaceUri); + await workspaceUpdater.updatePythonPath(pythonPath); + const workspaceValue = workspace.getConfiguration('python').inspect('pythonPath')!.workspaceValue!; + const resolvedPythonPath = path.join(workspaceUri.fsPath, 'x', 'y', 'z', pythonInterpreter); + // tslint:disable-next-line:no-invalid-template-strings + assert.equal(workspaceValue, pythonPath, 'Workspace Python Path not updated'); + PythonSettings.dispose(); + assert.equal(PythonSettings.getInstance(workspace3Uri).pythonPath, resolvedPythonPath, 'Resolved Workspace Python Path is incorrect'); + }); + + // tslint:disable-next-line:no-invalid-template-strings + test('Python Path should be relative to workspace when using ${workspaceFolder}', async () => { + const workspaceUri = workspace.getWorkspaceFolder(workspace3Uri)!.uri; const pythonInterpreter = `xWorkspacePythonPath${new Date().getMilliseconds()}`; const pythonPath = path.join(workspaceUri.fsPath, 'x', 'y', 'z', pythonInterpreter); const workspaceUpdater = new WorkspacePythonPathUpdaterService(workspaceUri); await workspaceUpdater.updatePythonPath(pythonPath); - const workspaceValue = workspace.getConfiguration('python').inspect('pythonPath').workspaceValue; + const workspaceValue = workspace.getConfiguration('python').inspect('pythonPath')!.workspaceValue!; // tslint:disable-next-line:no-invalid-template-strings - assert.equal(workspaceValue, path.join('${workspaceRoot}', 'x', 'y', 'z', pythonInterpreter), 'Workspace Python Path not updated'); + assert.equal(workspaceValue, path.join('${workspaceFolder}', 'x', 'y', 'z', pythonInterpreter), 'Workspace Python Path not updated'); + PythonSettings.dispose(); + assert.equal(PythonSettings.getInstance(workspace3Uri).pythonPath, pythonPath, 'Resolved Workspace Python Path is incorrect'); }); }); diff --git a/src/test/interpreters/pythonPathUpdater.test.ts b/src/test/interpreters/pythonPathUpdater.test.ts index c74aba47ec07..33099dc8c701 100644 --- a/src/test/interpreters/pythonPathUpdater.test.ts +++ b/src/test/interpreters/pythonPathUpdater.test.ts @@ -1,8 +1,10 @@ import * as assert from 'assert'; import * as path from 'path'; import { ConfigurationTarget, Uri, workspace } from 'vscode'; +import { PythonSettings } from '../../client/common/configSettings'; import { PythonPathUpdaterService } from '../../client/interpreter/configuration/pythonPathUpdaterService'; import { PythonPathUpdaterServiceFactory } from '../../client/interpreter/configuration/pythonPathUpdaterServiceFactory'; +import { GlobalPythonPathUpdaterService } from '../../client/interpreter/configuration/services/globalUpdaterService'; import { WorkspacePythonPathUpdaterService } from '../../client/interpreter/configuration/services/workspaceUpdaterService'; import { InterpreterVersionService } from '../../client/interpreter/interpreterVersion'; import { closeActiveWindows, initialize, initializeTest } from '../initialize'; @@ -76,7 +78,7 @@ suite('Python Path Settings Updater', () => { assert.equal(workspaceValue, pythonPath, 'Workspace Python Path not updated'); }); - test('Python Path should be relative to workspace', async () => { + test('Python Path should be relative to workspaceFolder', async () => { const workspaceUri = workspace.getWorkspaceFolder(Uri.file(workspaceRoot)).uri; const pythonInterpreter = `xWorkspacePythonPath${new Date().getMilliseconds()}`; const pythonPath = path.join(workspaceUri.fsPath, 'x', 'y', 'z', pythonInterpreter); @@ -84,6 +86,9 @@ suite('Python Path Settings Updater', () => { await workspaceUpdater.updatePythonPath(pythonPath); const workspaceValue = workspace.getConfiguration('python').inspect('pythonPath').workspaceValue; // tslint:disable-next-line:no-invalid-template-strings - assert.equal(workspaceValue, path.join('${workspaceRoot}', 'x', 'y', 'z', pythonInterpreter), 'Workspace Python Path not updated'); + assert.equal(workspaceValue, path.join('${workspaceFolder}', 'x', 'y', 'z', pythonInterpreter), 'Workspace Python Path not updated'); + const resolvedPath = PythonSettings.getInstance(Uri.file(workspaceRoot)).pythonPath; + assert.equal(resolvedPath, pythonPath, 'Resolved Workspace Python Path not updated'); }); + }); diff --git a/src/testMultiRootWkspc/workspace2/.vscode/settings.json b/src/testMultiRootWkspc/workspace2/.vscode/settings.json index 750d19764931..3705457b09a7 100644 --- a/src/testMultiRootWkspc/workspace2/.vscode/settings.json +++ b/src/testMultiRootWkspc/workspace2/.vscode/settings.json @@ -1,4 +1,4 @@ { - "python.workspaceSymbols.tagFilePath": "${workspaceRoot}/workspace2.tags.file", + "python.workspaceSymbols.tagFilePath": "${workspaceFolder}/workspace2.tags.file", "python.workspaceSymbols.enabled": false } diff --git a/tslint.json b/tslint.json index 3462d05dbb2a..51c6b1a37c01 100644 --- a/tslint.json +++ b/tslint.json @@ -43,10 +43,13 @@ "PromiseLike" ], "completed-docs": false, - "no-backbone-get-set-outside-model": false, "no-unsafe-any": false, + "no-backbone-get-set-outside-model": false, "underscore-consistent-invocation": false, "no-void-expression": false, - "no-non-null-assertion": false + "no-non-null-assertion": false, + "prefer-type-cast": false, + "function-name": false, + "variable-name": false } }