* Add support for multi roots for formatting and linting (#1281) * update to use latest api * config changes for multiroot workspace * linting support with multi roots * multi root support for formatters * determine workspace root path * revert change * support multiple configs per workspace folder * modify formatters to use resource specific settings * modified to settings are resolved using document uri * 1228 multi root master (#1) * fix #1280 handle env in shebang (#1290) * handle shebangs that resolve paths from env * oops * make test more specific * handle promise * fix #1282 use PYTHONIOENCODING variable (#1291) * fix #1270 debugger contribution changes (#1288) * add onDebug activation * use debug config provider for non workspace debugging * forgot to save file before commiting a merge * ability to opt out of telemetry using vscode settings (#1297) * Fix #1284 debugging parameterized tests (#1299) * fix #1298 remove vscode.startDebug command (#1300) * fix #1298 remove vscode.startDebug command * fix code review comments * added period as per code review comments * #1288 installer config and tests (#1302) * update to use latest api * config changes for multiroot workspace * linting support with multi roots * multi root support for formatters * determine workspace root path * revert change * support multiple configs per workspace folder * modify formatters to use resource specific settings * modified installer to pass resource for workspace resolution * null test in installer * canges to config settings to support multiroot workspace * changes to code refactoring to support workspace symbols * oops * modified to settings are resolved using document uri * unit tests for multi root support * fix unittests for multiroot * exclude files * add new line * config changes for multiroot workspace * new lines and enabled multi root linter tests * fix sys variables * added unit test to resolve ${workspaceRoot} in settings.json * fixed code review comments * fixed code review comments * fix #1276 Pre-commit hooks to ensure code complies with standards (#1277) * sanitize code * gulp file to check code hygiene * fix preLaunchTask in launch.json * added missing packages * enabled pre-commit using husky * enabled a few checks for precommit hook * fix tslint warnings when running tslint via gulp * exclude webpack building, else tries to pull in tests as well * improved checks for commits (strict) * added new lines * 1228 multi root workspace symbols (#1307) * update to use latest api * config changes for multiroot workspace * linting support with multi roots * multi root support for formatters * determine workspace root path * revert change * support multiple configs per workspace folder * modify formatters to use resource specific settings * modified installer to pass resource for workspace resolution * null test in installer * canges to config settings to support multiroot workspace * changes to code refactoring to support workspace symbols * oops * modified to settings are resolved using document uri * unit tests for multi root support * fix unittests for multiroot * exclude files * add new line * config changes for multiroot workspace * new lines and enabled multi root linter tests * fix sys variables * added unit test to resolve ${workspaceRoot} in settings.json * #1228 workspace symbols with multiroot support * fix test * added some data for workspace symbol tests * data for unit tests * fixed to add support for multit roots with unit tests * account for mutiroot files in sub directory * disable all but multiroot tests * fixed tests * include files for tests * Fixed travis tests for multi root workspace symbols (#1306) * added logging * more logging * yay * fixed * more fixes * fix tests * removed logging * enable all tests * uncommented * Added brackets around print statements (for p3) * Fixed travis unit tests (#1308) * update to use latest api * config changes for multiroot workspace * linting support with multi roots * multi root support for formatters * determine workspace root path * revert change * support multiple configs per workspace folder * modify formatters to use resource specific settings * modified installer to pass resource for workspace resolution * null test in installer * canges to config settings to support multiroot workspace * changes to code refactoring to support workspace symbols * oops * modified to settings are resolved using document uri * unit tests for multi root support * fix unittests for multiroot * exclude files * add new line * config changes for multiroot workspace * new lines and enabled multi root linter tests * fix sys variables * added unit test to resolve ${workspaceRoot} in settings.json * #1228 workspace symbols with multiroot support * fix test * added some data for workspace symbol tests * data for unit tests * fixed to add support for multit roots with unit tests * account for mutiroot files in sub directory * disable all but multiroot tests * fixed tests * fix tests * test where failing * properly determine root workspace * fix pytest unit test * delete files * add awaiter * use a path that works on multiple os * fixes * uncomment * invert * debug statements * use default workspace * reverted unwanted changes * oops * test unittests only * more logging * partial fixes to unit tests * run all tests * changes not to set paths for shebang tests * remove comments * update settings only if necessary * fix test * include files for tests * Fixed travis tests for multi root workspace symbols (#1306) * added logging * more logging * yay * fixed * more fixes * fix tests * removed logging * enable all tests * uncommented * Added brackets around print statements (for p3) * use resource when getting settings * fix #1315 unit tests need to wait for extension to activate (#1316) * fix #1314 allow for simultaneous language features (#1317) * #1228 support multi roots in language service (#1309) * update to use latest api * config changes for multiroot workspace * linting support with multi roots * multi root support for formatters * determine workspace root path * revert change * support multiple configs per workspace folder * modify formatters to use resource specific settings * modified installer to pass resource for workspace resolution * null test in installer * canges to config settings to support multiroot workspace * changes to code refactoring to support workspace symbols * oops * modified to settings are resolved using document uri * unit tests for multi root support * fix unittests for multiroot * exclude files * add new line * config changes for multiroot workspace * new lines and enabled multi root linter tests * fix sys variables * added unit test to resolve ${workspaceRoot} in settings.json * #1228 workspace symbols with multiroot support * fix test * added some data for workspace symbol tests * data for unit tests * fixed to add support for multit roots with unit tests * account for mutiroot files in sub directory * disable all but multiroot tests * fixed tests * fix tests * test where failing * properly determine root workspace * fix pytest unit test * delete files * add awaiter * use a path that works on multiple os * fixes * uncomment * invert * debug statements * use default workspace * reverted unwanted changes * oops * test unittests only * more logging * partial fixes to unit tests * run all tests * changes not to set paths for shebang tests * remove comments * update settings only if necessary * fix test * include files for tests * Fixed travis tests for multi root workspace symbols (#1306) * added logging * more logging * yay * fixed * more fixes * fix tests * removed logging * enable all tests * uncommented * Added brackets around print statements (for p3) * use resource when getting settings * support multiroot in language services * add additional tests for #1314 (#1318) * #1228 run all tests under multiroot (#1322) * modifications to fix tests to run under multi root setup * log errors * fix return type * fix linter messages * fix linter errors * changes to ensure code is formatted correctly * fixed comments * delete unwanted file * hide unwanted folders * fixes to linters to run on multiroot setup * udpate settings sequentially * log the output * show errors in deleting dir * removed prospector test, to be completed in #1319 * fixes to tests and sorting provider * fixed test for interpreter display * undo commenting of code * add new line * fix code review issues * ensure else is properly formatted * fix code review comments * fix #1304 preserve empty lines (#1329) * #1228 multiroot interpreter display (#1339) * modifications to fix tests to run under multi root setup * log errors * fix return type * fix linter messages * fix linter errors * changes to ensure code is formatted correctly * fixed comments * delete unwanted file * hide unwanted folders * fixes to linters to run on multiroot setup * udpate settings sequentially * log the output * show errors in deleting dir * removed prospector test, to be completed in #1319 * fixes to tests and sorting provider * fixed test for interpreter display * undo commenting of code * add new line * support multi root in interpreter display * fix linter * changed package version * disabled multiroot test * backwards compatible change * fix nose tests * revert change * enable test but disable it * multi root support in utils.ts * fixed #1328 * retries for flaky unit tests * retry beforeEach * common retry decorator * enable telemetry for extension loads * disable jupyter tests in multiroot tests * clean up python Path before and after testsclean up python Path before and after tests * rename test env variable * dispose cfg settings * dispose cfg settings * update comment * clean up * rearrange to ensurfe launching ext is first debug option * bug fix for display name * resolved code review comment * Fixed typp * 1228 multiroot interpreter ui changes (#1345) * fixes to unit tests and forgotten multiroot * globally retry all tests 3 times * refactor changing interpreters * added tests * fixed linter * removed redundant files * removed unwanted grep * remove blank line * fix 948 remove hardcoding of port number (#1353) * fix #1041 when debugging a test do not cancel it when re-discovering tests (#1354) * fix 1041 when debugging a test do not cancel it when re-discovering tests * create enum for creation of cancellation token * dispose correct cancellationToken * bug fix - in unit tests * bug fix - in unit tests * #1228 multiroot unit test runner (#1357) * fixes to unit tests and forgotten multiroot * globally retry all tests 3 times * refactor changing interpreters * added tests * fixed linter * removed redundant files * temp changes * more changes * lots of refactoring * adding support for multiroot workspaces * removed grep * copy changes for #948 and #1353 into multroot * replicate solution for #1041 and #1354 * #1041 create enum for creation of cancellation token * multiroot support for unit tests * remove empty line (linter warning) * delete pyc before making changes to py file * delete pyc file in teardown * merged multiroot master * pass uri of workspace when displaing prompt for configuration * pass uri to commands * fixed typos based on code review * prefix path with forward slash, as is done in the extension unit tests
This commit is contained in:
Родитель
45a456bf90
Коммит
2bc6b2ec78
|
@ -8,6 +8,7 @@ root = true
|
|||
indent_style = space
|
||||
indent_size = 4
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
# The indent size used in the `package.json` file cannot be changed
|
||||
# https://github.com/npm/npm/pull/3180#issuecomment-16336516
|
||||
|
|
|
@ -2,7 +2,10 @@
|
|||
out
|
||||
node_modules
|
||||
*.pyc
|
||||
.vscode/.ropeproject/**
|
||||
src/test/.vscode/**
|
||||
**/.vscode/.ropeproject/**
|
||||
**/testFiles/**/.cache/**
|
||||
*.noseids
|
||||
.vscode-test
|
||||
__pycache__
|
||||
npm-debug.log
|
||||
**/.mypy_cache/**
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
node_modules
|
10
.jshintrc
10
.jshintrc
|
@ -1,10 +0,0 @@
|
|||
{
|
||||
"node": true,
|
||||
|
||||
"curly": true,
|
||||
"latedef": true,
|
||||
"quotmark": true,
|
||||
"undef": true,
|
||||
"unused": true,
|
||||
"trailing": true
|
||||
}
|
|
@ -48,6 +48,7 @@ before_install: |
|
|||
pyenv install $PYTHON
|
||||
pyenv global $PYTHON
|
||||
fi
|
||||
export TRAVIS_PYTHON_PATH=`which python`
|
||||
install:
|
||||
- pip install --upgrade -r requirements.txt
|
||||
- npm install
|
||||
|
|
|
@ -1,25 +1,7 @@
|
|||
// A launch configuration that compiles the extension and then opens it inside a new window
|
||||
{
|
||||
"version": "0.1.0",
|
||||
"configurations": [{
|
||||
"name": "CompletionServer.ppy",
|
||||
"type": "python",
|
||||
"request": "launch",
|
||||
"stopOnEntry": true,
|
||||
"pythonPath": "python",
|
||||
"program": "${workspaceRoot}/pythonFiles/completionServer.py",
|
||||
"cwd": "${workspaceRoot}",
|
||||
"env": {},
|
||||
"args": [
|
||||
"123"
|
||||
],
|
||||
"envFile": "${workspaceRoot}/.env",
|
||||
"debugOptions": [
|
||||
"WaitOnAbnormalExit",
|
||||
"WaitOnNormalExit",
|
||||
"RedirectOutput"
|
||||
]
|
||||
},
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Launch Extension",
|
||||
"type": "extensionHost",
|
||||
|
@ -30,7 +12,9 @@
|
|||
],
|
||||
"stopOnEntry": false,
|
||||
"sourceMaps": true,
|
||||
"outDir": "${workspaceRoot}/out",
|
||||
"outFiles": [
|
||||
"${workspaceRoot}/out/**/*.js"
|
||||
],
|
||||
"preLaunchTask": "compile"
|
||||
},
|
||||
{
|
||||
|
@ -43,7 +27,9 @@
|
|||
"--server=4711"
|
||||
],
|
||||
"sourceMaps": true,
|
||||
"outDir": "${workspaceRoot}/out/client",
|
||||
"outFiles": [
|
||||
"${workspaceRoot}/out/client/**/*.js"
|
||||
],
|
||||
"cwd": "${workspaceRoot}"
|
||||
},
|
||||
{
|
||||
|
@ -58,33 +44,36 @@
|
|||
],
|
||||
"stopOnEntry": false,
|
||||
"sourceMaps": true,
|
||||
"xxoutDir": "${workspaceRoot}/out/test",
|
||||
"outFiles": [
|
||||
"${workspaceRoot}/out/**/*.js"
|
||||
],
|
||||
"preLaunchTask": "compile"
|
||||
},
|
||||
{
|
||||
"name": "Python",
|
||||
"type": "python",
|
||||
"name": "Launch Multiroot Tests",
|
||||
"type": "extensionHost",
|
||||
"request": "launch",
|
||||
"stopOnEntry": true,
|
||||
"pythonPath": "python",
|
||||
"program": "${file}",
|
||||
"console": "integratedTerminal",
|
||||
"args": [],
|
||||
"debugOptions": [
|
||||
"WaitOnAbnormalExit",
|
||||
"WaitOnNormalExit",
|
||||
"RedirectOutput"
|
||||
"runtimeExecutable": "${execPath}",
|
||||
"args": [
|
||||
"${workspaceRoot}/src/testMultiRootWkspc/multi.code-workspace",
|
||||
"--extensionDevelopmentPath=${workspaceRoot}",
|
||||
"--extensionTestsPath=${workspaceRoot}/out/test"
|
||||
],
|
||||
"cwd": "${workspaceRoot}"
|
||||
"stopOnEntry": false,
|
||||
"sourceMaps": true,
|
||||
"outFiles": [
|
||||
"${workspaceRoot}/out/**/*.js"
|
||||
],
|
||||
"preLaunchTask": "compile"
|
||||
}
|
||||
],
|
||||
"compounds": [{
|
||||
"name": "Extension + Debugger",
|
||||
"configurations": [
|
||||
"Launch Extension", "Launch Extension as debugServer"
|
||||
]
|
||||
}]
|
||||
"compounds": [
|
||||
{
|
||||
"name": "Extension + Debugger",
|
||||
"configurations": [
|
||||
"Launch Extension",
|
||||
"Launch Extension as debugServer"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -4,7 +4,10 @@
|
|||
"out": true, // set this to true to hide the "out" folder with the compiled JS files
|
||||
"**/*.pyc": true,
|
||||
"**/__pycache__": true,
|
||||
"node_modules": true
|
||||
"node_modules": true,
|
||||
".vscode-test": true,
|
||||
"**/.mypy_cache/**": true,
|
||||
"**/.ropeproject/**": true
|
||||
},
|
||||
"search.exclude": {
|
||||
"out": true // set this to false to include "out" folder in search results
|
||||
|
|
|
@ -53,6 +53,18 @@
|
|||
"fileLocation": "relative"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "lint-staged",
|
||||
"type": "npm",
|
||||
"script": "lint-staged",
|
||||
"problemMatcher": [
|
||||
"$tsc",
|
||||
{
|
||||
"base": "$tslint5",
|
||||
"fileLocation": "relative"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
17
gulpfile.js
17
gulpfile.js
|
@ -80,7 +80,7 @@ function reportFailures(failures) {
|
|||
const line = position.lineAndCharacter ? position.lineAndCharacter.line : position.line;
|
||||
const character = position.lineAndCharacter ? position.lineAndCharacter.character : position.character;
|
||||
|
||||
// Output in format similar to tslint for the linter to pickup
|
||||
// Output in format similar to tslint for the linter to pickup.
|
||||
console.error(`ERROR: (${failure.ruleName}) ${relative(__dirname, name)}[${line + 1}, ${character + 1}]: ${failure.failure}`);
|
||||
});
|
||||
}
|
||||
|
@ -103,9 +103,9 @@ const hygiene = exports.hygiene = (some, options) => {
|
|||
.split(/\r\n|\r|\n/)
|
||||
.forEach((line, i) => {
|
||||
if (/^\s*$/.test(line)) {
|
||||
// empty or whitespace lines are OK
|
||||
// Empty or whitespace lines are OK.
|
||||
} else if (/^(\s\s\s\s)+.*/.test(line)) {
|
||||
// good indent
|
||||
// Good indent.
|
||||
} else if (/^[\t]+.*/.test(line)) {
|
||||
console.error(file.relative + '(' + (i + 1) + ',1): Bad whitespace indentation');
|
||||
errorCount++;
|
||||
|
@ -118,9 +118,10 @@ const hygiene = exports.hygiene = (some, options) => {
|
|||
const formatting = es.map(function (file, cb) {
|
||||
tsfmt.processString(file.path, file.contents.toString('utf8'), {
|
||||
verify: true,
|
||||
tsfmt: true,
|
||||
editorconfig: true
|
||||
// verbose: true
|
||||
tsconfig: true,
|
||||
tslint: true,
|
||||
editorconfig: true,
|
||||
tsfmt: true
|
||||
}).then(result => {
|
||||
if (result.error) {
|
||||
console.error(result.message);
|
||||
|
@ -176,7 +177,7 @@ const hygiene = exports.hygiene = (some, options) => {
|
|||
console.error(error.message);
|
||||
},
|
||||
finish: function () {
|
||||
// forget the summary
|
||||
// forget the summary.
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -213,7 +214,7 @@ const hygiene = exports.hygiene = (some, options) => {
|
|||
|
||||
gulp.task('hygiene', () => hygiene());
|
||||
|
||||
// this allows us to run hygiene as a git pre-commit hook
|
||||
// this allows us to run hygiene as a git pre-commit hook.
|
||||
if (require.main === module) {
|
||||
const cp = require('child_process');
|
||||
|
||||
|
|
268
package.json
268
package.json
|
@ -33,7 +33,8 @@
|
|||
"jupyter",
|
||||
"django",
|
||||
"debugger",
|
||||
"unittest"
|
||||
"unittest",
|
||||
"multi-root ready"
|
||||
],
|
||||
"categories": [
|
||||
"Languages",
|
||||
|
@ -921,32 +922,38 @@
|
|||
"python.promptToInstallJupyter": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Display prompt to install Jupyter Extension."
|
||||
"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."
|
||||
"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)."
|
||||
"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"
|
||||
"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)."
|
||||
"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": ""
|
||||
"default": "",
|
||||
"scope": "resource"
|
||||
},
|
||||
"python.sortImports.args": {
|
||||
"type": "array",
|
||||
|
@ -954,7 +961,8 @@
|
|||
"default": [],
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"scope": "resource"
|
||||
},
|
||||
"python.disablePromptForFeatures": {
|
||||
"type": "array",
|
||||
|
@ -973,62 +981,74 @@
|
|||
"pydocstyle",
|
||||
"pylint"
|
||||
]
|
||||
}
|
||||
},
|
||||
"scope": "resource"
|
||||
},
|
||||
"python.linting.enabled": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Whether to lint Python files."
|
||||
"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."
|
||||
"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."
|
||||
"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."
|
||||
"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"
|
||||
"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"
|
||||
"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"
|
||||
"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."
|
||||
"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."
|
||||
"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."
|
||||
"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."
|
||||
"description": "Controls the maximum number of problems produced by the server.",
|
||||
"scope": "resource"
|
||||
},
|
||||
"python.linting.pylintCategorySeverity.convention": {
|
||||
"type": "string",
|
||||
|
@ -1039,7 +1059,8 @@
|
|||
"Error",
|
||||
"Information",
|
||||
"Warning"
|
||||
]
|
||||
],
|
||||
"scope": "resource"
|
||||
},
|
||||
"python.linting.pylintCategorySeverity.refactor": {
|
||||
"type": "string",
|
||||
|
@ -1050,7 +1071,8 @@
|
|||
"Error",
|
||||
"Information",
|
||||
"Warning"
|
||||
]
|
||||
],
|
||||
"scope": "resource"
|
||||
},
|
||||
"python.linting.pylintCategorySeverity.warning": {
|
||||
"type": "string",
|
||||
|
@ -1061,7 +1083,8 @@
|
|||
"Error",
|
||||
"Information",
|
||||
"Warning"
|
||||
]
|
||||
],
|
||||
"scope": "resource"
|
||||
},
|
||||
"python.linting.pylintCategorySeverity.error": {
|
||||
"type": "string",
|
||||
|
@ -1072,7 +1095,8 @@
|
|||
"Error",
|
||||
"Information",
|
||||
"Warning"
|
||||
]
|
||||
],
|
||||
"scope": "resource"
|
||||
},
|
||||
"python.linting.pylintCategorySeverity.fatal": {
|
||||
"type": "string",
|
||||
|
@ -1083,7 +1107,8 @@
|
|||
"Error",
|
||||
"Information",
|
||||
"Warning"
|
||||
]
|
||||
],
|
||||
"scope": "resource"
|
||||
},
|
||||
"python.linting.pep8CategorySeverity.W": {
|
||||
"type": "string",
|
||||
|
@ -1094,7 +1119,8 @@
|
|||
"Error",
|
||||
"Information",
|
||||
"Warning"
|
||||
]
|
||||
],
|
||||
"scope": "resource"
|
||||
},
|
||||
"python.linting.pep8CategorySeverity.E": {
|
||||
"type": "string",
|
||||
|
@ -1105,7 +1131,8 @@
|
|||
"Error",
|
||||
"Information",
|
||||
"Warning"
|
||||
]
|
||||
],
|
||||
"scope": "resource"
|
||||
},
|
||||
"python.linting.flake8CategorySeverity.F": {
|
||||
"type": "string",
|
||||
|
@ -1116,7 +1143,8 @@
|
|||
"Error",
|
||||
"Information",
|
||||
"Warning"
|
||||
]
|
||||
],
|
||||
"scope": "resource"
|
||||
},
|
||||
"python.linting.flake8CategorySeverity.E": {
|
||||
"type": "string",
|
||||
|
@ -1127,7 +1155,8 @@
|
|||
"Error",
|
||||
"Information",
|
||||
"Warning"
|
||||
]
|
||||
],
|
||||
"scope": "resource"
|
||||
},
|
||||
"python.linting.flake8CategorySeverity.W": {
|
||||
"type": "string",
|
||||
|
@ -1138,7 +1167,8 @@
|
|||
"Error",
|
||||
"Information",
|
||||
"Warning"
|
||||
]
|
||||
],
|
||||
"scope": "resource"
|
||||
},
|
||||
"python.linting.mypyCategorySeverity.error": {
|
||||
"type": "string",
|
||||
|
@ -1149,7 +1179,8 @@
|
|||
"Error",
|
||||
"Information",
|
||||
"Warning"
|
||||
]
|
||||
],
|
||||
"scope": "resource"
|
||||
},
|
||||
"python.linting.mypyCategorySeverity.note": {
|
||||
"type": "string",
|
||||
|
@ -1160,37 +1191,44 @@
|
|||
"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."
|
||||
"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."
|
||||
"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."
|
||||
"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."
|
||||
"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."
|
||||
"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."
|
||||
"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",
|
||||
|
@ -1198,7 +1236,8 @@
|
|||
"default": [],
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"scope": "resource"
|
||||
},
|
||||
"python.linting.pylintArgs": {
|
||||
"type": "array",
|
||||
|
@ -1206,7 +1245,8 @@
|
|||
"default": [],
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"scope": "resource"
|
||||
},
|
||||
"python.linting.pep8Args": {
|
||||
"type": "array",
|
||||
|
@ -1214,7 +1254,8 @@
|
|||
"default": [],
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"scope": "resource"
|
||||
},
|
||||
"python.linting.flake8Args": {
|
||||
"type": "array",
|
||||
|
@ -1222,7 +1263,8 @@
|
|||
"default": [],
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"scope": "resource"
|
||||
},
|
||||
"python.linting.pydocstyleArgs": {
|
||||
"type": "array",
|
||||
|
@ -1230,7 +1272,8 @@
|
|||
"default": [],
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"scope": "resource"
|
||||
},
|
||||
"python.linting.mypyArgs": {
|
||||
"type": "array",
|
||||
|
@ -1241,12 +1284,14 @@
|
|||
],
|
||||
"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."
|
||||
"description": "The output window name for the linting messages, defaults to Python output window.",
|
||||
"scope": "resource"
|
||||
},
|
||||
"python.formatting.provider": {
|
||||
"type": "string",
|
||||
|
@ -1256,17 +1301,20 @@
|
|||
"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."
|
||||
"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."
|
||||
"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",
|
||||
|
@ -1274,7 +1322,8 @@
|
|||
"default": [],
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"scope": "resource"
|
||||
},
|
||||
"python.formatting.yapfArgs": {
|
||||
"type": "array",
|
||||
|
@ -1282,17 +1331,20 @@
|
|||
"default": [],
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"scope": "resource"
|
||||
},
|
||||
"python.formatting.formatOnSave": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Format the document upon saving."
|
||||
"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."
|
||||
"description": "The output window name for the formatting messages, defaults to Python output window.",
|
||||
"scope": "resource"
|
||||
},
|
||||
"python.autoComplete.preloadModules": {
|
||||
"type": "array",
|
||||
|
@ -1300,42 +1352,50 @@
|
|||
"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)."
|
||||
"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."
|
||||
"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."
|
||||
"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."
|
||||
"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."
|
||||
"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)."
|
||||
"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."
|
||||
"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)."
|
||||
"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",
|
||||
|
@ -1345,42 +1405,50 @@
|
|||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Pattern used to exclude files and folders from ctags See http://ctags.sourceforge.net/ctags.html."
|
||||
"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."
|
||||
"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."
|
||||
"description": "Port number used for debugging of unittests.",
|
||||
"scope": "resource"
|
||||
},
|
||||
"python.unitTest.cwd": {
|
||||
"type": "string",
|
||||
"default": null,
|
||||
"description": "Optional working directory for unit tests."
|
||||
"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."
|
||||
"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."
|
||||
"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."
|
||||
"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."
|
||||
"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",
|
||||
|
@ -1388,7 +1456,8 @@
|
|||
"default": [],
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"scope": "resource"
|
||||
},
|
||||
"python.unitTest.pyTestArgs": {
|
||||
"type": "array",
|
||||
|
@ -1396,12 +1465,14 @@
|
|||
"default": [],
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"scope": "resource"
|
||||
},
|
||||
"python.unitTest.unittestEnabled": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Whether to enable or disable unit testing using unittest."
|
||||
"description": "Whether to enable or disable unit testing using unittest.",
|
||||
"scope": "resource"
|
||||
},
|
||||
"python.unitTest.unittestArgs": {
|
||||
"type": "array",
|
||||
|
@ -1415,7 +1486,8 @@
|
|||
],
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"scope": "resource"
|
||||
},
|
||||
"python.linting.ignorePatterns": {
|
||||
"type": "array",
|
||||
|
@ -1426,17 +1498,20 @@
|
|||
],
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"scope": "resource"
|
||||
},
|
||||
"python.linting.pylamaEnabled": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Whether to lint Python files using pylama."
|
||||
"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."
|
||||
"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",
|
||||
|
@ -1444,32 +1519,38 @@
|
|||
"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."
|
||||
"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."
|
||||
"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."
|
||||
"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."
|
||||
"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."
|
||||
"description": "Default kernel to be used. By default the first available kernel is used.",
|
||||
"scope": "resource"
|
||||
},
|
||||
"python.jupyter.startupCode": {
|
||||
"type": "array",
|
||||
|
@ -1479,7 +1560,8 @@
|
|||
"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."
|
||||
"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"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -1512,8 +1594,9 @@
|
|||
"vscode:prepublish": "tsc -p ./ && webpack",
|
||||
"compile": "webpack && tsc -watch -p ./",
|
||||
"postinstall": "node ./node_modules/vscode/bin/install",
|
||||
"test": "node ./node_modules/vscode/bin/test",
|
||||
"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": {
|
||||
|
@ -1539,17 +1622,20 @@
|
|||
"untildify": "^3.0.2",
|
||||
"vscode-debugadapter": "^1.0.1",
|
||||
"vscode-debugprotocol": "^1.0.1",
|
||||
"vscode-extension-telemetry": "0.0.5",
|
||||
"vscode-languageclient": "^1.1.0",
|
||||
"vscode-languageserver": "^1.1.0",
|
||||
"vscode-extension-telemetry": "^0.0.5",
|
||||
"vscode-languageclient": "^3.1.0",
|
||||
"vscode-languageserver": "^3.1.0",
|
||||
"winreg": "^1.2.4",
|
||||
"xml2js": "^0.4.17"
|
||||
"xml2js": "^0.4.17",
|
||||
"vscode": "^1.1.5"
|
||||
},
|
||||
"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.32",
|
||||
"@types/mocha": "^2.2.43",
|
||||
"@types/node": "^6.0.40",
|
||||
"@types/rx": "^2.5.33",
|
||||
"@types/semver": "^5.4.0",
|
||||
|
@ -1558,9 +1644,12 @@
|
|||
"@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",
|
||||
|
@ -1577,7 +1666,6 @@
|
|||
"tslint-microsoft-contrib": "^5.0.1",
|
||||
"typescript": "^2.5.2",
|
||||
"typescript-formatter": "^6.0.0",
|
||||
"vscode": "^1.1.5",
|
||||
"webpack": "^1.13.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,7 +55,7 @@ except ImportError:
|
|||
from queue import Empty, Queue # Python 3
|
||||
|
||||
DEBUG = os.environ.get('DEBUG_DJAYAMANNE_IPYTHON', '0') == '1'
|
||||
TEST = os.environ.get('PYTHON_DONJAYAMANNE_TEST', '0') == '1'
|
||||
TEST = os.environ.get('VSC_PYTHON_CI_TEST', '0') == '1'
|
||||
|
||||
# The great "support IPython 2, 3, 4" strat begins
|
||||
if not TEST:
|
||||
|
|
|
@ -55,7 +55,7 @@ except ImportError:
|
|||
from queue import Empty, Queue # Python 3
|
||||
|
||||
DEBUG = os.environ.get('DEBUG_DJAYAMANNE_IPYTHON', '0') == '1'
|
||||
TEST = os.environ.get('PYTHON_DONJAYAMANNE_TEST', '0') == '1'
|
||||
TEST = os.environ.get('VSC_PYTHON_CI_TEST', '0') == '1'
|
||||
|
||||
def _debug_write(out):
|
||||
if DEBUG:
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
'use strict';
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import * as path from 'path';
|
||||
import * as child_process from 'child_process';
|
||||
import { SystemVariables } from './systemVariables';
|
||||
import { EventEmitter } from 'events';
|
||||
import * as path from 'path';
|
||||
import * as vscode from 'vscode';
|
||||
import { Uri } from 'vscode';
|
||||
import { InterpreterInfoCache } from './interpreterInfoCache';
|
||||
import { SystemVariables } from './systemVariables';
|
||||
|
||||
// tslint:disable-next-line:no-require-imports no-var-requires
|
||||
const untildify = require('untildify');
|
||||
|
||||
export const IS_WINDOWS = /^win/.test(process.platform);
|
||||
|
@ -55,6 +59,7 @@ export interface IPep8CategorySeverity {
|
|||
W: vscode.DiagnosticSeverity;
|
||||
E: vscode.DiagnosticSeverity;
|
||||
}
|
||||
// tslint:disable-next-line:interface-name
|
||||
export interface Flake8CategorySeverity {
|
||||
F: vscode.DiagnosticSeverity;
|
||||
E: vscode.DiagnosticSeverity;
|
||||
|
@ -124,67 +129,123 @@ export interface ITerminalSettings {
|
|||
executeInFileDir: boolean;
|
||||
launchArgs: string[];
|
||||
}
|
||||
// tslint:disable-next-line:interface-name
|
||||
export interface JupyterSettings {
|
||||
appendResults: boolean;
|
||||
defaultKernel: string;
|
||||
startupCode: string[];
|
||||
}
|
||||
|
||||
const IS_TEST_EXECUTION = process.env['PYTHON_DONJAYAMANNE_TEST'] === '1';
|
||||
// tslint:disable-next-line:no-string-literal
|
||||
const IS_TEST_EXECUTION = process.env['VSC_PYTHON_CI_TEST'] === '1';
|
||||
|
||||
// tslint:disable-next-line:completed-docs
|
||||
export class PythonSettings extends EventEmitter implements IPythonSettings {
|
||||
private static pythonSettings: PythonSettings = new PythonSettings();
|
||||
private static pythonSettings: Map<string, PythonSettings> = new Map<string, PythonSettings>();
|
||||
|
||||
public jediPath: string;
|
||||
public envFile: string;
|
||||
public disablePromptForFeatures: string[];
|
||||
public venvPath: string;
|
||||
public devOptions: string[];
|
||||
public linting: ILintingSettings;
|
||||
public formatting: IFormattingSettings;
|
||||
public autoComplete: IAutoCompeteSettings;
|
||||
public unitTest: IUnitTestSettings;
|
||||
public terminal: ITerminalSettings;
|
||||
public jupyter: JupyterSettings;
|
||||
public sortImports: ISortImportSettings;
|
||||
public workspaceSymbols: IWorkspaceSymbolSettings;
|
||||
|
||||
private workspaceRoot: vscode.Uri;
|
||||
private disposables: vscode.Disposable[] = [];
|
||||
constructor() {
|
||||
// tslint:disable-next-line:variable-name
|
||||
private _pythonPath: string;
|
||||
|
||||
constructor(workspaceFolder?: Uri) {
|
||||
super();
|
||||
if (PythonSettings.pythonSettings) {
|
||||
throw new Error('Singleton class, Use getInstance method');
|
||||
}
|
||||
this.workspaceRoot = workspaceFolder ? workspaceFolder : vscode.Uri.file(__dirname);
|
||||
this.disposables.push(vscode.workspace.onDidChangeConfiguration(() => {
|
||||
this.initializeSettings();
|
||||
}));
|
||||
|
||||
this.initializeSettings();
|
||||
}
|
||||
public static getInstance(): PythonSettings {
|
||||
return PythonSettings.pythonSettings;
|
||||
// tslint:disable-next-line:function-name
|
||||
public static getInstance(resource?: Uri): PythonSettings {
|
||||
const workspaceFolder = resource ? vscode.workspace.getWorkspaceFolder(resource) : undefined;
|
||||
let workspaceFolderUri: Uri | undefined = workspaceFolder ? workspaceFolder.uri : undefined;
|
||||
if (!workspaceFolderUri && Array.isArray(vscode.workspace.workspaceFolders) && vscode.workspace.workspaceFolders.length > 0) {
|
||||
workspaceFolderUri = vscode.workspace.workspaceFolders[0].uri;
|
||||
}
|
||||
const workspaceFolderKey = workspaceFolderUri ? workspaceFolderUri.fsPath : '';
|
||||
if (!PythonSettings.pythonSettings.has(workspaceFolderKey)) {
|
||||
const settings = new PythonSettings(workspaceFolderUri);
|
||||
PythonSettings.pythonSettings.set(workspaceFolderKey, settings);
|
||||
}
|
||||
// tslint:disable-next-line:no-non-null-assertion
|
||||
return PythonSettings.pythonSettings.get(workspaceFolderKey)!;
|
||||
}
|
||||
// tslint:disable-next-line:function-name
|
||||
public static dispose() {
|
||||
if (!IS_TEST_EXECUTION) {
|
||||
throw new Error('Dispose can only be called from unit tests');
|
||||
}
|
||||
// tslint:disable-next-line:no-void-expression
|
||||
PythonSettings.pythonSettings.forEach(item => item.dispose());
|
||||
PythonSettings.pythonSettings.clear();
|
||||
}
|
||||
public dispose() {
|
||||
// tslint:disable-next-line:no-unsafe-any
|
||||
this.disposables.forEach(disposable => disposable.dispose());
|
||||
this.disposables = [];
|
||||
InterpreterInfoCache.clear();
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:cyclomatic-complexity max-func-body-length
|
||||
private initializeSettings() {
|
||||
const systemVariables: SystemVariables = new SystemVariables();
|
||||
const workspaceRoot = (IS_TEST_EXECUTION || typeof vscode.workspace.rootPath !== 'string') ? __dirname : vscode.workspace.rootPath;
|
||||
let pythonSettings = vscode.workspace.getConfiguration('python');
|
||||
InterpreterInfoCache.clear();
|
||||
const workspaceRoot = this.workspaceRoot.fsPath;
|
||||
const systemVariables: SystemVariables = new SystemVariables(this.workspaceRoot ? this.workspaceRoot.fsPath : undefined);
|
||||
const pythonSettings = vscode.workspace.getConfiguration('python', this.workspaceRoot);
|
||||
// tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion
|
||||
this.pythonPath = systemVariables.resolveAny(pythonSettings.get<string>('pythonPath'))!;
|
||||
this.pythonPath = getAbsolutePath(this.pythonPath, IS_TEST_EXECUTION ? __dirname : workspaceRoot);
|
||||
this.pythonPath = getAbsolutePath(this.pythonPath, workspaceRoot);
|
||||
// tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion
|
||||
this.venvPath = systemVariables.resolveAny(pythonSettings.get<string>('venvPath'))!;
|
||||
// tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion
|
||||
this.jediPath = systemVariables.resolveAny(pythonSettings.get<string>('jediPath'))!;
|
||||
if (typeof this.jediPath === 'string' && this.jediPath.length > 0) {
|
||||
this.jediPath = getAbsolutePath(systemVariables.resolveAny(this.jediPath), IS_TEST_EXECUTION ? __dirname : workspaceRoot);
|
||||
}
|
||||
else {
|
||||
this.jediPath = getAbsolutePath(systemVariables.resolveAny(this.jediPath), workspaceRoot);
|
||||
} else {
|
||||
this.jediPath = '';
|
||||
}
|
||||
// tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion
|
||||
this.envFile = systemVariables.resolveAny(pythonSettings.get<string>('envFile'))!;
|
||||
// tslint:disable-next-line:no-any
|
||||
// tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion no-any
|
||||
this.devOptions = systemVariables.resolveAny(pythonSettings.get<any[]>('devOptions'))!;
|
||||
this.devOptions = Array.isArray(this.devOptions) ? this.devOptions : [];
|
||||
let lintingSettings = systemVariables.resolveAny(pythonSettings.get<ILintingSettings>('linting'))!;
|
||||
// tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion
|
||||
const lintingSettings = systemVariables.resolveAny(pythonSettings.get<ILintingSettings>('linting'))!;
|
||||
// tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion
|
||||
this.disablePromptForFeatures = pythonSettings.get<string[]>('disablePromptForFeatures')!;
|
||||
this.disablePromptForFeatures = Array.isArray(this.disablePromptForFeatures) ? this.disablePromptForFeatures : [];
|
||||
if (this.linting) {
|
||||
Object.assign<ILintingSettings, ILintingSettings>(this.linting, lintingSettings);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
this.linting = lintingSettings;
|
||||
}
|
||||
let sortImportSettings = systemVariables.resolveAny(pythonSettings.get<ISortImportSettings>('sortImports'))!;
|
||||
// tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion
|
||||
const sortImportSettings = systemVariables.resolveAny(pythonSettings.get<ISortImportSettings>('sortImports'))!;
|
||||
if (this.sortImports) {
|
||||
Object.assign<ISortImportSettings, ISortImportSettings>(this.sortImports, sortImportSettings);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
this.sortImports = sortImportSettings;
|
||||
}
|
||||
// Support for travis
|
||||
// Support for travis.
|
||||
this.sortImports = this.sortImports ? this.sortImports : { path: '', args: [] };
|
||||
// Support for travis
|
||||
// Support for travis.
|
||||
this.linting = this.linting ? this.linting : {
|
||||
enabled: false,
|
||||
enabledWithoutWorkspace: false,
|
||||
|
@ -226,14 +287,14 @@ export class PythonSettings extends EventEmitter implements IPythonSettings {
|
|||
this.linting.pydocstylePath = getAbsolutePath(systemVariables.resolveAny(this.linting.pydocstylePath), workspaceRoot);
|
||||
this.linting.mypyPath = getAbsolutePath(systemVariables.resolveAny(this.linting.mypyPath), workspaceRoot);
|
||||
|
||||
let formattingSettings = systemVariables.resolveAny(pythonSettings.get<IFormattingSettings>('formatting'))!;
|
||||
// tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion
|
||||
const formattingSettings = systemVariables.resolveAny(pythonSettings.get<IFormattingSettings>('formatting'))!;
|
||||
if (this.formatting) {
|
||||
Object.assign<IFormattingSettings, IFormattingSettings>(this.formatting, formattingSettings);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
this.formatting = formattingSettings;
|
||||
}
|
||||
// Support for travis
|
||||
// Support for travis.
|
||||
this.formatting = this.formatting ? this.formatting : {
|
||||
autopep8Args: [], autopep8Path: 'autopep8',
|
||||
outputWindow: 'python',
|
||||
|
@ -244,45 +305,46 @@ export class PythonSettings extends EventEmitter implements IPythonSettings {
|
|||
this.formatting.autopep8Path = getAbsolutePath(systemVariables.resolveAny(this.formatting.autopep8Path), workspaceRoot);
|
||||
this.formatting.yapfPath = getAbsolutePath(systemVariables.resolveAny(this.formatting.yapfPath), workspaceRoot);
|
||||
|
||||
let autoCompleteSettings = systemVariables.resolveAny(pythonSettings.get<IAutoCompeteSettings>('autoComplete'))!;
|
||||
// tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion
|
||||
const autoCompleteSettings = systemVariables.resolveAny(pythonSettings.get<IAutoCompeteSettings>('autoComplete'))!;
|
||||
if (this.autoComplete) {
|
||||
Object.assign<IAutoCompeteSettings, IAutoCompeteSettings>(this.autoComplete, autoCompleteSettings);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
this.autoComplete = autoCompleteSettings;
|
||||
}
|
||||
// Support for travis
|
||||
// Support for travis.
|
||||
this.autoComplete = this.autoComplete ? this.autoComplete : {
|
||||
extraPaths: [],
|
||||
addBrackets: false,
|
||||
preloadModules: []
|
||||
};
|
||||
|
||||
let workspaceSymbolsSettings = systemVariables.resolveAny(pythonSettings.get<IWorkspaceSymbolSettings>('workspaceSymbols'))!;
|
||||
// tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion
|
||||
const workspaceSymbolsSettings = systemVariables.resolveAny(pythonSettings.get<IWorkspaceSymbolSettings>('workspaceSymbols'))!;
|
||||
if (this.workspaceSymbols) {
|
||||
Object.assign<IWorkspaceSymbolSettings, IWorkspaceSymbolSettings>(this.workspaceSymbols, workspaceSymbolsSettings);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
this.workspaceSymbols = workspaceSymbolsSettings;
|
||||
}
|
||||
// Support for travis
|
||||
// Support for travis.
|
||||
this.workspaceSymbols = this.workspaceSymbols ? this.workspaceSymbols : {
|
||||
ctagsPath: 'ctags',
|
||||
enabled: true,
|
||||
exclusionPatterns: [],
|
||||
rebuildOnFileSave: true,
|
||||
rebuildOnStart: true,
|
||||
tagFilePath: path.join(workspaceRoot, "tags")
|
||||
tagFilePath: path.join(workspaceRoot, 'tags')
|
||||
};
|
||||
this.workspaceSymbols.tagFilePath = getAbsolutePath(systemVariables.resolveAny(this.workspaceSymbols.tagFilePath), workspaceRoot);
|
||||
|
||||
let unitTestSettings = systemVariables.resolveAny(pythonSettings.get<IUnitTestSettings>('unitTest'))!;
|
||||
// tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion
|
||||
const unitTestSettings = systemVariables.resolveAny(pythonSettings.get<IUnitTestSettings>('unitTest'))!;
|
||||
if (this.unitTest) {
|
||||
Object.assign<IUnitTestSettings, IUnitTestSettings>(this.unitTest, unitTestSettings);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
this.unitTest = unitTestSettings;
|
||||
if (IS_TEST_EXECUTION && !this.unitTest) {
|
||||
// tslint:disable-next-line:prefer-type-cast
|
||||
this.unitTest = {
|
||||
nosetestArgs: [], pyTestArgs: [], unittestArgs: [],
|
||||
promptToConfigure: true, debugPort: 3000,
|
||||
|
@ -292,7 +354,7 @@ export class PythonSettings extends EventEmitter implements IPythonSettings {
|
|||
}
|
||||
}
|
||||
|
||||
// Support for travis
|
||||
// Support for travis.
|
||||
this.unitTest = this.unitTest ? this.unitTest : {
|
||||
promptToConfigure: true,
|
||||
debugPort: 3000,
|
||||
|
@ -307,18 +369,19 @@ export class PythonSettings extends EventEmitter implements IPythonSettings {
|
|||
this.unitTest.cwd = getAbsolutePath(systemVariables.resolveAny(this.unitTest.cwd), workspaceRoot);
|
||||
}
|
||||
|
||||
// Resolve any variables found in the test arguments
|
||||
// Resolve any variables found in the test arguments.
|
||||
this.unitTest.nosetestArgs = this.unitTest.nosetestArgs.map(arg => systemVariables.resolveAny(arg));
|
||||
this.unitTest.pyTestArgs = this.unitTest.pyTestArgs.map(arg => systemVariables.resolveAny(arg));
|
||||
this.unitTest.unittestArgs = this.unitTest.unittestArgs.map(arg => systemVariables.resolveAny(arg));
|
||||
|
||||
let terminalSettings = systemVariables.resolveAny(pythonSettings.get<ITerminalSettings>('terminal'))!;
|
||||
// tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion
|
||||
const terminalSettings = systemVariables.resolveAny(pythonSettings.get<ITerminalSettings>('terminal'))!;
|
||||
if (this.terminal) {
|
||||
Object.assign<ITerminalSettings, ITerminalSettings>(this.terminal, terminalSettings);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
this.terminal = terminalSettings;
|
||||
if (IS_TEST_EXECUTION && !this.terminal) {
|
||||
// tslint:disable-next-line:prefer-type-cast
|
||||
this.terminal = {} as ITerminalSettings;
|
||||
}
|
||||
}
|
||||
|
@ -328,16 +391,18 @@ export class PythonSettings extends EventEmitter implements IPythonSettings {
|
|||
launchArgs: []
|
||||
};
|
||||
|
||||
// tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion
|
||||
this.jupyter = pythonSettings.get<JupyterSettings>('jupyter')!;
|
||||
// Support for travis
|
||||
// Support for travis.
|
||||
this.jupyter = this.jupyter ? this.jupyter : {
|
||||
appendResults: true, defaultKernel: '', startupCode: []
|
||||
};
|
||||
|
||||
this.emit('change');
|
||||
// If workspace config changes, then we could have a cascading effect of on change events.
|
||||
// Let's defer the change notification.
|
||||
setTimeout(() => this.emit('change'), 1);
|
||||
}
|
||||
|
||||
private _pythonPath: string;
|
||||
public get pythonPath(): string {
|
||||
return this._pythonPath;
|
||||
}
|
||||
|
@ -345,32 +410,19 @@ export class PythonSettings extends EventEmitter implements IPythonSettings {
|
|||
if (this._pythonPath === value) {
|
||||
return;
|
||||
}
|
||||
// Add support for specifying just the directory where the python executable will be located
|
||||
// E.g. virtual directory name
|
||||
// Add support for specifying just the directory where the python executable will be located.
|
||||
// E.g. virtual directory name.
|
||||
try {
|
||||
this._pythonPath = getPythonExecutable(value);
|
||||
}
|
||||
catch (ex) {
|
||||
} catch (ex) {
|
||||
this._pythonPath = value;
|
||||
}
|
||||
}
|
||||
public jediPath: string;
|
||||
public envFile: string;
|
||||
public disablePromptForFeatures: string[];
|
||||
public venvPath: string;
|
||||
public devOptions: string[];
|
||||
public linting: ILintingSettings;
|
||||
public formatting: IFormattingSettings;
|
||||
public autoComplete: IAutoCompeteSettings;
|
||||
public unitTest: IUnitTestSettings;
|
||||
public terminal: ITerminalSettings;
|
||||
public jupyter: JupyterSettings;
|
||||
public sortImports: ISortImportSettings;
|
||||
public workspaceSymbols: IWorkspaceSymbolSettings;
|
||||
}
|
||||
|
||||
function getAbsolutePath(pathToCheck: string, rootDir: string): string {
|
||||
pathToCheck = untildify(pathToCheck);
|
||||
// tslint:disable-next-line:prefer-type-cast no-unsafe-any
|
||||
pathToCheck = untildify(pathToCheck) as string;
|
||||
if (IS_TEST_EXECUTION && !pathToCheck) { return rootDir; }
|
||||
if (pathToCheck.indexOf(path.sep) === -1) {
|
||||
return pathToCheck;
|
||||
|
@ -379,9 +431,10 @@ function getAbsolutePath(pathToCheck: string, rootDir: string): string {
|
|||
}
|
||||
|
||||
function getPythonExecutable(pythonPath: string): string {
|
||||
pythonPath = untildify(pythonPath);
|
||||
// tslint:disable-next-line:prefer-type-cast no-unsafe-any
|
||||
pythonPath = untildify(pythonPath) as string;
|
||||
|
||||
// If only 'python'
|
||||
// If only 'python'.
|
||||
if (pythonPath === 'python' ||
|
||||
pythonPath.indexOf(path.sep) === -1 ||
|
||||
path.basename(pythonPath) === path.dirname(pythonPath)) {
|
||||
|
@ -391,21 +444,21 @@ 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.
|
||||
// tslint:disable-next-line:variable-name
|
||||
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);
|
||||
}
|
||||
|
@ -420,10 +473,9 @@ function getPythonExecutable(pythonPath: string): string {
|
|||
|
||||
function isValidPythonPath(pythonPath: string): 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 (ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -96,4 +96,4 @@ export namespace Documentation {
|
|||
export const Home = '/docs/workspaceSymbols/';
|
||||
export const InstallOnWindows = '/docs/workspaceSymbols/#Install-Windows';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
import { commands } from 'vscode';
|
||||
|
||||
export class ContextKey {
|
||||
private lastValue: boolean;
|
||||
|
||||
constructor(private name: string) { }
|
||||
|
||||
public async set(value: boolean): Promise<void> {
|
||||
if (this.lastValue === value) {
|
||||
return;
|
||||
}
|
||||
this.lastValue = value;
|
||||
await commands.executeCommand('setContext', this.name, this.lastValue);
|
||||
}
|
||||
}
|
|
@ -75,7 +75,7 @@ export function getTextEditsFromPatch(before: string, patch: string): TextEdit[]
|
|||
|
||||
return textEdits;
|
||||
}
|
||||
export function getWorkspaceEditsFromPatch(filePatches: string[]): WorkspaceEdit {
|
||||
export function getWorkspaceEditsFromPatch(filePatches: string[], workspaceRoot?:string): WorkspaceEdit {
|
||||
const workspaceEdit = new WorkspaceEdit();
|
||||
filePatches.forEach(patch => {
|
||||
const indexOfAtAt = patch.indexOf('@@');
|
||||
|
@ -101,7 +101,7 @@ export function getWorkspaceEditsFromPatch(filePatches: string[]): WorkspaceEdit
|
|||
}
|
||||
|
||||
let fileName = fileNameLines[0].substring(fileNameLines[0].indexOf(' a') + 3).trim();
|
||||
fileName = path.isAbsolute(fileName) ? fileName : path.resolve(vscode.workspace.rootPath, fileName);
|
||||
fileName = workspaceRoot && !path.isAbsolute(fileName) ? path.resolve(workspaceRoot, fileName) : fileName;
|
||||
if (!fs.existsSync(fileName)) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -1,27 +1,29 @@
|
|||
import * as vscode from 'vscode';
|
||||
import * as settings from './configSettings';
|
||||
import * as os from 'os';
|
||||
import * as vscode from 'vscode';
|
||||
import { commands, ConfigurationTarget, Disposable, OutputChannel, Terminal, Uri, window, workspace } from 'vscode';
|
||||
import * as settings from './configSettings';
|
||||
import { isNotInstalledError } from './helpers';
|
||||
import { error } from './logger';
|
||||
import { execPythonFile, getFullyQualifiedPythonInterpreterPath } from './utils';
|
||||
import { Documentation } from './constants';
|
||||
|
||||
export enum Product {
|
||||
pytest,
|
||||
nosetest,
|
||||
pylint,
|
||||
flake8,
|
||||
pep8,
|
||||
pylama,
|
||||
prospector,
|
||||
pydocstyle,
|
||||
yapf,
|
||||
autopep8,
|
||||
mypy,
|
||||
unittest,
|
||||
ctags,
|
||||
rope
|
||||
pytest = 1,
|
||||
nosetest = 2,
|
||||
pylint = 3,
|
||||
flake8 = 4,
|
||||
pep8 = 5,
|
||||
pylama = 6,
|
||||
prospector = 7,
|
||||
pydocstyle = 8,
|
||||
yapf = 9,
|
||||
autopep8 = 10,
|
||||
mypy = 11,
|
||||
unittest = 12,
|
||||
ctags = 13,
|
||||
rope = 14
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:variable-name
|
||||
const ProductInstallScripts = new Map<Product, string[]>();
|
||||
ProductInstallScripts.set(Product.autopep8, ['-m', 'pip', 'install', 'autopep8']);
|
||||
ProductInstallScripts.set(Product.flake8, ['-m', 'pip', 'install', 'flake8']);
|
||||
|
@ -36,6 +38,7 @@ ProductInstallScripts.set(Product.pytest, ['-m', 'pip', 'install', '-U', 'pytest
|
|||
ProductInstallScripts.set(Product.yapf, ['-m', 'pip', 'install', 'yapf']);
|
||||
ProductInstallScripts.set(Product.rope, ['-m', 'pip', 'install', 'rope']);
|
||||
|
||||
// tslint:disable-next-line:variable-name
|
||||
const ProductUninstallScripts = new Map<Product, string[]>();
|
||||
ProductUninstallScripts.set(Product.autopep8, ['-m', 'pip', 'uninstall', 'autopep8', '--yes']);
|
||||
ProductUninstallScripts.set(Product.flake8, ['-m', 'pip', 'uninstall', 'flake8', '--yes']);
|
||||
|
@ -50,6 +53,7 @@ ProductUninstallScripts.set(Product.pytest, ['-m', 'pip', 'uninstall', 'pytest',
|
|||
ProductUninstallScripts.set(Product.yapf, ['-m', 'pip', 'uninstall', 'yapf', '--yes']);
|
||||
ProductUninstallScripts.set(Product.rope, ['-m', 'pip', 'uninstall', 'rope', '--yes']);
|
||||
|
||||
// tslint:disable-next-line:variable-name
|
||||
export const ProductExecutableAndArgs = new Map<Product, { executable: string, args: string[] }>();
|
||||
ProductExecutableAndArgs.set(Product.mypy, { executable: 'python', args: ['-m', 'mypy'] });
|
||||
ProductExecutableAndArgs.set(Product.nosetest, { executable: 'python', args: ['-m', 'nose'] });
|
||||
|
@ -76,6 +80,7 @@ switch (os.platform()) {
|
|||
}
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:variable-name
|
||||
export const Linters: Product[] = [
|
||||
Product.flake8,
|
||||
Product.pep8,
|
||||
|
@ -86,6 +91,7 @@ export const Linters: Product[] = [
|
|||
Product.pydocstyle
|
||||
];
|
||||
|
||||
// tslint:disable-next-line:variable-name
|
||||
const ProductNames = new Map<Product, string>();
|
||||
ProductNames.set(Product.autopep8, 'autopep8');
|
||||
ProductNames.set(Product.flake8, 'flake8');
|
||||
|
@ -100,6 +106,7 @@ ProductNames.set(Product.pytest, 'py.test');
|
|||
ProductNames.set(Product.yapf, 'yapf');
|
||||
ProductNames.set(Product.rope, 'rope');
|
||||
|
||||
// tslint:disable-next-line:variable-name
|
||||
export const SettingToDisableProduct = new Map<Product, string>();
|
||||
SettingToDisableProduct.set(Product.flake8, 'linting.flake8Enabled');
|
||||
SettingToDisableProduct.set(Product.mypy, 'linting.mypyEnabled');
|
||||
|
@ -111,6 +118,10 @@ SettingToDisableProduct.set(Product.pydocstyle, 'linting.pydocstyleEnabled');
|
|||
SettingToDisableProduct.set(Product.pylint, 'linting.pylintEnabled');
|
||||
SettingToDisableProduct.set(Product.pytest, 'unitTest.pyTestEnabled');
|
||||
|
||||
// tslint:disable-next-line:variable-name
|
||||
const ProductInstallationPrompt = new Map<Product, string>();
|
||||
ProductInstallationPrompt.set(Product.ctags, 'Install CTags to enable Python workspace symbols');
|
||||
|
||||
enum ProductType {
|
||||
Linter,
|
||||
Formatter,
|
||||
|
@ -119,6 +130,7 @@ enum ProductType {
|
|||
WorkspaceSymbols
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:variable-name
|
||||
const ProductTypeNames = new Map<ProductType, string>();
|
||||
ProductTypeNames.set(ProductType.Formatter, 'Formatter');
|
||||
ProductTypeNames.set(ProductType.Linter, 'Linter');
|
||||
|
@ -126,6 +138,7 @@ ProductTypeNames.set(ProductType.RefactoringLibrary, 'Refactoring library');
|
|||
ProductTypeNames.set(ProductType.TestFramework, 'Test Framework');
|
||||
ProductTypeNames.set(ProductType.WorkspaceSymbols, 'Workspace Symbols');
|
||||
|
||||
// tslint:disable-next-line:variable-name
|
||||
const ProductTypes = new Map<Product, ProductType>();
|
||||
ProductTypes.set(Product.flake8, ProductType.Linter);
|
||||
ProductTypes.set(Product.mypy, ProductType.Linter);
|
||||
|
@ -142,6 +155,11 @@ ProductTypes.set(Product.autopep8, ProductType.Formatter);
|
|||
ProductTypes.set(Product.yapf, ProductType.Formatter);
|
||||
ProductTypes.set(Product.rope, ProductType.RefactoringLibrary);
|
||||
|
||||
export enum InstallerResponse {
|
||||
Installed,
|
||||
Disabled,
|
||||
Ignore
|
||||
}
|
||||
export class Installer implements vscode.Disposable {
|
||||
private static terminal: vscode.Terminal | undefined | null;
|
||||
private disposables: vscode.Disposable[] = [];
|
||||
|
@ -155,30 +173,37 @@ export class Installer implements vscode.Disposable {
|
|||
public dispose() {
|
||||
this.disposables.forEach(d => d.dispose());
|
||||
}
|
||||
public shouldDisplayPrompt(product: Product) {
|
||||
private shouldDisplayPrompt(product: Product) {
|
||||
// tslint:disable-next-line:no-non-null-assertion
|
||||
const productName = ProductNames.get(product)!;
|
||||
return settings.PythonSettings.getInstance().disablePromptForFeatures.indexOf(productName) === -1;
|
||||
const pythonConfig = workspace.getConfiguration('python');
|
||||
// tslint:disable-next-line:prefer-type-cast
|
||||
const disablePromptForFeatures = pythonConfig.get('disablePromptForFeatures', [] as string[]);
|
||||
return disablePromptForFeatures.indexOf(productName) === -1;
|
||||
}
|
||||
|
||||
async promptToInstall(product: Product) {
|
||||
// tslint:disable-next-line:member-ordering
|
||||
public async promptToInstall(product: Product, resource?: Uri): Promise<InstallerResponse> {
|
||||
// tslint:disable-next-line:no-non-null-assertion
|
||||
const productType = ProductTypes.get(product)!;
|
||||
const productTypeName = ProductTypeNames.get(productType);
|
||||
// tslint:disable-next-line:no-non-null-assertion
|
||||
const productTypeName = ProductTypeNames.get(productType)!;
|
||||
// tslint:disable-next-line:no-non-null-assertion
|
||||
const productName = ProductNames.get(product)!;
|
||||
|
||||
if (!this.shouldDisplayPrompt(product)) {
|
||||
const message = `${productTypeName} '${productName}' not installed.`;
|
||||
if (this.outputChannel) {
|
||||
this.outputChannel.appendLine(message);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
console.warn(message);
|
||||
}
|
||||
return;
|
||||
return InstallerResponse.Ignore;
|
||||
}
|
||||
|
||||
const installOption = 'Install ' + productName;
|
||||
const disableOption = 'Disable ' + productTypeName;
|
||||
const dontShowAgain = `Don't show this prompt again`;
|
||||
const installOption = ProductInstallationPrompt.has(product) ? ProductInstallationPrompt.get(product) : `Install ${productName}`;
|
||||
const disableOption = `Disable ${productTypeName}`;
|
||||
const dontShowAgain = 'Don\'t show this prompt again';
|
||||
const alternateFormatter = product === Product.autopep8 ? 'yapf' : 'autopep8';
|
||||
const useOtherFormatter = `Use '${alternateFormatter}' formatter`;
|
||||
const options = [];
|
||||
|
@ -189,122 +214,165 @@ export class Installer implements vscode.Disposable {
|
|||
if (SettingToDisableProduct.has(product)) {
|
||||
options.push(...[disableOption, dontShowAgain]);
|
||||
}
|
||||
return vscode.window.showErrorMessage(`${productTypeName} ${productName} is not installed`, ...options).then(item => {
|
||||
switch (item) {
|
||||
case installOption: {
|
||||
return this.install(product);
|
||||
}
|
||||
case disableOption: {
|
||||
if (Linters.indexOf(product) >= 0) {
|
||||
return disableLinter(product);
|
||||
}
|
||||
else {
|
||||
const pythonConfig = vscode.workspace.getConfiguration('python');
|
||||
const settingToDisable = SettingToDisableProduct.get(product)!;
|
||||
return pythonConfig.update(settingToDisable, false);
|
||||
}
|
||||
}
|
||||
case useOtherFormatter: {
|
||||
const pythonConfig = vscode.workspace.getConfiguration('python');
|
||||
return pythonConfig.update('formatting.provider', alternateFormatter);
|
||||
}
|
||||
case dontShowAgain: {
|
||||
const pythonConfig = vscode.workspace.getConfiguration('python');
|
||||
const features = pythonConfig.get('disablePromptForFeatures', [] as string[]);
|
||||
features.push(productName);
|
||||
return pythonConfig.update('disablePromptForFeatures', features, true);
|
||||
}
|
||||
case 'Help': {
|
||||
return Promise.resolve();
|
||||
const item = await window.showErrorMessage(`${productTypeName} ${productName} is not installed`, ...options);
|
||||
switch (item) {
|
||||
case installOption: {
|
||||
return this.install(product, resource);
|
||||
}
|
||||
case disableOption: {
|
||||
if (Linters.indexOf(product) >= 0) {
|
||||
return this.disableLinter(product, resource).then(() => InstallerResponse.Disabled);
|
||||
} else {
|
||||
// tslint:disable-next-line:no-non-null-assertion
|
||||
const settingToDisable = SettingToDisableProduct.get(product)!;
|
||||
return this.updateSetting(settingToDisable, false, resource).then(() => InstallerResponse.Disabled);
|
||||
}
|
||||
}
|
||||
});
|
||||
case useOtherFormatter: {
|
||||
return this.updateSetting('formatting.provider', alternateFormatter, resource)
|
||||
.then(() => InstallerResponse.Installed);
|
||||
}
|
||||
case dontShowAgain: {
|
||||
const pythonConfig = workspace.getConfiguration('python');
|
||||
// tslint:disable-next-line:prefer-type-cast
|
||||
const features = pythonConfig.get('disablePromptForFeatures', [] as string[]);
|
||||
features.push(productName);
|
||||
return pythonConfig.update('disablePromptForFeatures', features, true).then(() => InstallerResponse.Ignore);
|
||||
}
|
||||
default: {
|
||||
throw new Error('Invalid selection');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
install(product: Product): Promise<any> {
|
||||
// tslint:disable-next-line:member-ordering
|
||||
public async install(product: Product, resource?: Uri): Promise<InstallerResponse> {
|
||||
if (!this.outputChannel && !Installer.terminal) {
|
||||
Installer.terminal = vscode.window.createTerminal('Python Installer');
|
||||
Installer.terminal = window.createTerminal('Python Installer');
|
||||
}
|
||||
|
||||
if (product === Product.ctags && os.platform() === 'win32') {
|
||||
vscode.commands.executeCommand('python.displayHelp', Documentation.Workspace.InstallOnWindows);
|
||||
return Promise.resolve();
|
||||
if (product === Product.ctags && settings.IS_WINDOWS) {
|
||||
if (this.outputChannel) {
|
||||
this.outputChannel.appendLine('Install Universal Ctags Win32 to enable support for Workspace Symbols');
|
||||
this.outputChannel.appendLine('Download the CTags binary from the Universal CTags site.');
|
||||
this.outputChannel.appendLine('Option 1: Extract ctags.exe from the downloaded zip to any folder within your PATH so that Visual Studio Code can run it.');
|
||||
this.outputChannel.appendLine('Option 2: Extract to any folder and add the path to this folder to the command setting.');
|
||||
this.outputChannel.appendLine('Option 3: Extract to any folder and define that path in the python.workspaceSymbols.ctagsPath setting of your user settings file (settings.json).');
|
||||
this.outputChannel.show();
|
||||
} else {
|
||||
window.showInformationMessage('Install Universal Ctags and set it in your path or define the path in your python.workspaceSymbols.ctagsPath settings');
|
||||
}
|
||||
return InstallerResponse.Ignore;
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:no-non-null-assertion
|
||||
let installArgs = ProductInstallScripts.get(product)!;
|
||||
let pipIndex = installArgs.indexOf('pip');
|
||||
const pipIndex = installArgs.indexOf('pip');
|
||||
if (pipIndex > 0) {
|
||||
installArgs = installArgs.slice();
|
||||
let proxy = vscode.workspace.getConfiguration('http').get('proxy', '');
|
||||
const proxy = vscode.workspace.getConfiguration('http').get('proxy', '');
|
||||
if (proxy.length > 0) {
|
||||
installArgs.splice(2, 0, proxy);
|
||||
installArgs.splice(2, 0, '--proxy');
|
||||
}
|
||||
}
|
||||
// tslint:disable-next-line:no-any
|
||||
let installationPromise: Promise<any>;
|
||||
if (this.outputChannel && installArgs[0] === '-m') {
|
||||
// Errors are just displayed to the user
|
||||
this.outputChannel.show();
|
||||
return execPythonFile(settings.PythonSettings.getInstance().pythonPath, installArgs, vscode.workspace.rootPath!, true, (data) => {
|
||||
this.outputChannel!.append(data);
|
||||
});
|
||||
}
|
||||
else {
|
||||
installationPromise = execPythonFile(resource, settings.PythonSettings.getInstance(resource).pythonPath,
|
||||
// tslint:disable-next-line:no-non-null-assertion
|
||||
installArgs, getCwdForInstallScript(resource), true, (data) => { this.outputChannel!.append(data); });
|
||||
} else {
|
||||
// When using terminal get the fully qualitified path
|
||||
// Cuz people may launch vs code from terminal when they have activated the appropriate virtual env
|
||||
// Problem is terminal doesn't use the currently activated virtual env
|
||||
// Must have something to do with the process being launched in the terminal
|
||||
return getFullyQualifiedPythonInterpreterPath()
|
||||
installationPromise = getFullyQualifiedPythonInterpreterPath(resource)
|
||||
.then(pythonPath => {
|
||||
let installScript = installArgs.join(' ');
|
||||
|
||||
if (installArgs[0] === '-m') {
|
||||
if (pythonPath.indexOf(' ') >= 0) {
|
||||
installScript = `"${pythonPath}" ${installScript}`;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
installScript = `${pythonPath} ${installScript}`;
|
||||
}
|
||||
}
|
||||
// tslint:disable-next-line:no-non-null-assertion
|
||||
Installer.terminal!.sendText(installScript);
|
||||
// tslint:disable-next-line:no-non-null-assertion
|
||||
Installer.terminal!.show(false);
|
||||
});
|
||||
}
|
||||
|
||||
return installationPromise
|
||||
.then(() => this.isInstalled(product))
|
||||
.then(isInstalled => isInstalled ? InstallerResponse.Installed : InstallerResponse.Ignore);
|
||||
}
|
||||
|
||||
isInstalled(product: Product): Promise<boolean | undefined> {
|
||||
return isProductInstalled(product);
|
||||
// tslint:disable-next-line:member-ordering
|
||||
public isInstalled(product: Product, resource?: Uri): Promise<boolean | undefined> {
|
||||
return isProductInstalled(product, resource);
|
||||
}
|
||||
|
||||
uninstall(product: Product): Promise<any> {
|
||||
return uninstallproduct(product);
|
||||
// tslint:disable-next-line:member-ordering no-any
|
||||
public uninstall(product: Product, resource?: Uri): Promise<any> {
|
||||
return uninstallproduct(product, resource);
|
||||
}
|
||||
// tslint:disable-next-line:member-ordering
|
||||
public disableLinter(product: Product, resource: Uri) {
|
||||
if (resource && !workspace.getWorkspaceFolder(resource)) {
|
||||
// tslint:disable-next-line:no-non-null-assertion
|
||||
const settingToDisable = SettingToDisableProduct.get(product)!;
|
||||
const pythonConfig = workspace.getConfiguration('python', resource);
|
||||
return pythonConfig.update(settingToDisable, false, ConfigurationTarget.Workspace);
|
||||
} else {
|
||||
const pythonConfig = workspace.getConfiguration('python');
|
||||
return pythonConfig.update('linting.enabledWithoutWorkspace', false, true);
|
||||
}
|
||||
}
|
||||
// tslint:disable-next-line:no-any
|
||||
private updateSetting(setting: string, value: any, resource?: Uri) {
|
||||
if (resource && !workspace.getWorkspaceFolder(resource)) {
|
||||
const pythonConfig = workspace.getConfiguration('python', resource);
|
||||
return pythonConfig.update(setting, value, ConfigurationTarget.Workspace);
|
||||
} else {
|
||||
const pythonConfig = workspace.getConfiguration('python');
|
||||
return pythonConfig.update(setting, value, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function disableLinter(product: Product, global?: boolean) {
|
||||
const pythonConfig = vscode.workspace.getConfiguration('python');
|
||||
const settingToDisable = SettingToDisableProduct.get(product)!;
|
||||
if (vscode.workspace.rootPath) {
|
||||
return pythonConfig.update(settingToDisable, false, global);
|
||||
function getCwdForInstallScript(resource?: Uri) {
|
||||
const workspaceFolder = resource ? workspace.getWorkspaceFolder(resource) : undefined;
|
||||
if (workspaceFolder) {
|
||||
return workspaceFolder.uri.fsPath;
|
||||
}
|
||||
else {
|
||||
return pythonConfig.update('linting.enabledWithoutWorkspace', false, true);
|
||||
if (Array.isArray(workspace.workspaceFolders) && workspace.workspaceFolders.length > 0) {
|
||||
return workspace.workspaceFolders[0].uri.fsPath;
|
||||
}
|
||||
return __dirname;
|
||||
}
|
||||
|
||||
async function isProductInstalled(product: Product): Promise<boolean | undefined> {
|
||||
async function isProductInstalled(product: Product, resource?: Uri): Promise<boolean | undefined> {
|
||||
if (!ProductExecutableAndArgs.has(product)) {
|
||||
return;
|
||||
}
|
||||
// tslint:disable-next-line:no-non-null-assertion
|
||||
const prodExec = ProductExecutableAndArgs.get(product)!;
|
||||
return execPythonFile(prodExec.executable, prodExec.args.concat(['--version']), vscode.workspace.rootPath!, false)
|
||||
.then(() => {
|
||||
return true;
|
||||
}).catch(reason => {
|
||||
return !isNotInstalledError(reason);
|
||||
});
|
||||
const cwd = getCwdForInstallScript(resource);
|
||||
return execPythonFile(resource, prodExec.executable, prodExec.args.concat(['--version']), cwd, false)
|
||||
.then(() => true)
|
||||
.catch(reason => !isNotInstalledError(reason));
|
||||
}
|
||||
|
||||
function uninstallproduct(product: Product): Promise<any> {
|
||||
// tslint:disable-next-line:no-any
|
||||
function uninstallproduct(product: Product, resource?: Uri): Promise<any> {
|
||||
if (!ProductUninstallScripts.has(product)) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
// tslint:disable-next-line:no-non-null-assertion
|
||||
const uninstallArgs = ProductUninstallScripts.get(product)!;
|
||||
return execPythonFile('python', uninstallArgs, vscode.workspace.rootPath!, false);
|
||||
return execPythonFile(resource, 'python', uninstallArgs, getCwdForInstallScript(resource), false);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
import { Uri, workspace } from 'vscode';
|
||||
|
||||
type InterpreterCache = {
|
||||
pythonInterpreterDirectory?: string;
|
||||
pythonInterpreterPath?: string;
|
||||
pythonSettingsPath?: string;
|
||||
// tslint:disable-next-line:no-any
|
||||
customEnvVariables?: any;
|
||||
};
|
||||
|
||||
const cache = new Map<string, InterpreterCache>();
|
||||
|
||||
// tslint:disable-next-line:no-stateless-class
|
||||
export class InterpreterInfoCache {
|
||||
// tslint:disable-next-line:function-name
|
||||
public static clear(): void {
|
||||
cache.clear();
|
||||
}
|
||||
// tslint:disable-next-line:function-name
|
||||
public static get(resource?: Uri) {
|
||||
const cacheKey = InterpreterInfoCache.getCacheKey(resource) || '';
|
||||
return cache.has(cacheKey) ? cache.get(cacheKey) : {};
|
||||
}
|
||||
// tslint:disable-next-line:function-name
|
||||
public static setPaths(resource?: Uri, pythonSettingsPath?: string, pythonInterpreterPath?: string, pythonInterpreterDirectory?: string) {
|
||||
InterpreterInfoCache.setCacheData('pythonInterpreterDirectory', resource, pythonInterpreterDirectory);
|
||||
InterpreterInfoCache.setCacheData('pythonInterpreterPath', resource, pythonInterpreterPath);
|
||||
InterpreterInfoCache.setCacheData('pythonSettingsPath', resource, pythonSettingsPath);
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:no-any function-name
|
||||
public static setCustomEnvVariables(resource?: Uri, envVars?: any) {
|
||||
// tslint:disable-next-line:no-any
|
||||
InterpreterInfoCache.setCacheData('customEnvVariables', resource, envVars);
|
||||
}
|
||||
// tslint:disable-next-line:no-any function-name
|
||||
private static setCacheData(property: keyof InterpreterCache, resource?: Uri, value?: any) {
|
||||
const cacheKey = InterpreterInfoCache.getCacheKey(resource) || '';
|
||||
// tslint:disable-next-line:prefer-type-cast
|
||||
const data = cache.has(cacheKey) ? cache.get(cacheKey) : {} as InterpreterCache;
|
||||
data[property] = value;
|
||||
cache.set(cacheKey, data);
|
||||
}
|
||||
private static getCacheKey(resource?: Uri): string {
|
||||
if (!Array.isArray(workspace.workspaceFolders) || workspace.workspaceFolders.length === 0) {
|
||||
return '';
|
||||
}
|
||||
if (!resource || workspace.workspaceFolders.length === 1) {
|
||||
return workspace.workspaceFolders[0].uri.fsPath;
|
||||
}
|
||||
const folder = workspace.getWorkspaceFolder(resource);
|
||||
return folder ? folder.uri.fsPath : '';
|
||||
}
|
||||
}
|
|
@ -22,7 +22,9 @@ class Logger {
|
|||
Logger.writeLine(category, message);
|
||||
}
|
||||
static writeLine(category: string = "log", line: any) {
|
||||
console[category](line);
|
||||
if (process.env['VSC_PYTHON_CI_TEST'] !== '1') {
|
||||
console[category](line);
|
||||
}
|
||||
if (outChannel) {
|
||||
outChannel.appendLine(line);
|
||||
}
|
||||
|
|
|
@ -133,11 +133,10 @@ export abstract class AbstractSystemVariables implements ISystemVariables {
|
|||
export class SystemVariables extends AbstractSystemVariables {
|
||||
private _workspaceRoot: string;
|
||||
private _workspaceRootFolderName: string;
|
||||
private _execPath: string;
|
||||
|
||||
constructor() {
|
||||
constructor(workspaceRoot?: string) {
|
||||
super();
|
||||
this._workspaceRoot = typeof vscode.workspace.rootPath === 'string' ? vscode.workspace.rootPath : __dirname;
|
||||
this._workspaceRoot = typeof workspaceRoot === 'string' ? workspaceRoot : __dirname;
|
||||
this._workspaceRootFolderName = Path.basename(this._workspaceRoot);
|
||||
Object.keys(process.env).forEach(key => {
|
||||
this[`env:${key}`] = this[`env.${key}`] = process.env[key];
|
||||
|
|
|
@ -1,17 +1,16 @@
|
|||
/// <reference path="../../../node_modules/@types/node/index.d.ts" />
|
||||
/// <reference path="../../../node_modules/vscode/vscode.d.ts" />
|
||||
|
||||
'use strict';
|
||||
// TODO: Cleanup this place
|
||||
// Add options for execPythonFile
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as child_process from 'child_process';
|
||||
import * as fs from 'fs';
|
||||
import * as fsExtra from 'fs-extra';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import { CancellationToken, Range, TextDocument, Uri } from 'vscode';
|
||||
import * as settings from './configSettings';
|
||||
import { CancellationToken, TextDocument, Range } from 'vscode';
|
||||
import { isNotInstalledError } from './helpers';
|
||||
import { mergeEnvVariables, parseEnvFile } from './envFileParser';
|
||||
import { isNotInstalledError } from './helpers';
|
||||
import { InterpreterInfoCache } from './interpreterInfoCache';
|
||||
|
||||
export const IS_WINDOWS = /^win/.test(process.platform);
|
||||
export const Is_64Bit = os.arch() === 'x64';
|
||||
|
@ -52,32 +51,29 @@ export function fsReaddirAsync(root: string): Promise<string[]> {
|
|||
});
|
||||
}
|
||||
|
||||
let pythonInterpretterDirectory: string = null;
|
||||
let previouslyIdentifiedPythonPath: string = null;
|
||||
let customEnvVariables: any = null;
|
||||
async function getPythonInterpreterDirectory(resource?: Uri): Promise<string> {
|
||||
const cache = InterpreterInfoCache.get(resource);
|
||||
const pythonFileName = settings.PythonSettings.getInstance(resource).pythonPath;
|
||||
|
||||
// If config settings change then clear env variables that we have cached
|
||||
// Remember, the path to the python interpreter can change, hence we need to re-set the paths
|
||||
settings.PythonSettings.getInstance().on('change', function () {
|
||||
pythonInterpretterDirectory = null;
|
||||
previouslyIdentifiedPythonPath = null;
|
||||
customEnvVariables = null;
|
||||
});
|
||||
|
||||
export function getPythonInterpreterDirectory(): Promise<string> {
|
||||
// If we already have it and the python path hasn't changed, yay
|
||||
if (pythonInterpretterDirectory && previouslyIdentifiedPythonPath === settings.PythonSettings.getInstance().pythonPath) {
|
||||
return Promise.resolve(pythonInterpretterDirectory);
|
||||
if (cache.pythonInterpreterDirectory && cache.pythonInterpreterDirectory.length > 0
|
||||
&& cache.pythonSettingsPath === pythonFileName) {
|
||||
return cache.pythonInterpreterDirectory;
|
||||
}
|
||||
|
||||
let pythonFileName = settings.PythonSettings.getInstance().pythonPath;
|
||||
|
||||
// Check if we have the path
|
||||
if (path.basename(pythonFileName) === pythonFileName) {
|
||||
// No path provided, however we can get it by using sys.executableFile
|
||||
return getPathFromPythonCommand(["-c", "import sys;print(sys.executable)"])
|
||||
.then(pythonExecutablePath => pythonInterpretterDirectory = path.dirname(pythonExecutablePath))
|
||||
.catch(() => pythonInterpretterDirectory = '');
|
||||
try {
|
||||
const pythonInterpreterPath = await getPathFromPythonCommand(pythonFileName);
|
||||
const pythonInterpreterDirectory = path.dirname(pythonInterpreterPath);
|
||||
InterpreterInfoCache.setPaths(resource, pythonFileName, pythonInterpreterPath, pythonInterpreterDirectory);
|
||||
return pythonInterpreterDirectory;
|
||||
// tslint:disable-next-line:variable-name
|
||||
} catch (_ex) {
|
||||
InterpreterInfoCache.setPaths(resource, pythonFileName, pythonFileName, '');
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
return new Promise<string>(resolve => {
|
||||
|
@ -85,88 +81,78 @@ export function getPythonInterpreterDirectory(): Promise<string> {
|
|||
child_process.execFile(pythonFileName, ['-c', 'print(1234)'], (error, stdout, stderr) => {
|
||||
// Yes this is a valid python path
|
||||
if (stdout.startsWith('1234')) {
|
||||
previouslyIdentifiedPythonPath = path.dirname(pythonFileName);
|
||||
const pythonInterpreterDirectory = path.dirname(pythonFileName);
|
||||
InterpreterInfoCache.setPaths(resource, pythonFileName, pythonFileName, pythonInterpreterDirectory);
|
||||
resolve(pythonInterpreterDirectory);
|
||||
} else {
|
||||
// No idea, didn't work, hence don't reject, but return empty path
|
||||
InterpreterInfoCache.setPaths(resource, pythonFileName, pythonFileName, '');
|
||||
resolve('');
|
||||
}
|
||||
else {
|
||||
previouslyIdentifiedPythonPath = '';
|
||||
}
|
||||
// No idea, didn't work, hence don't reject, but return empty path
|
||||
resolve(previouslyIdentifiedPythonPath);
|
||||
});
|
||||
});
|
||||
}
|
||||
export function getFullyQualifiedPythonInterpreterPath(): Promise<string> {
|
||||
return getPythonInterpreterDirectory()
|
||||
.then(pyPath => path.join(pyPath, path.basename(settings.PythonSettings.getInstance().pythonPath)));
|
||||
export async function getFullyQualifiedPythonInterpreterPath(resource?: Uri): Promise<string> {
|
||||
const pyDir = await getPythonInterpreterDirectory(resource);
|
||||
const cache = InterpreterInfoCache.get(resource);
|
||||
return cache.pythonInterpreterPath;
|
||||
}
|
||||
export function getPathFromPythonCommand(args: string[]): Promise<string> {
|
||||
return execPythonFile(settings.PythonSettings.getInstance().pythonPath, args, __dirname).then(stdout => {
|
||||
if (stdout.length === 0) {
|
||||
return "";
|
||||
}
|
||||
let lines = stdout.split(/\r?\n/g).filter(line => line.length > 0);
|
||||
return validatePath(lines[0]);
|
||||
}).catch(() => {
|
||||
return "";
|
||||
export async function getPathFromPythonCommand(pythonPath: string): Promise<string> {
|
||||
return await new Promise<string>((resolve, reject) => {
|
||||
child_process.execFile(pythonPath, ['-c', 'import sys;print(sys.executable)'], (_, stdout) => {
|
||||
if (stdout) {
|
||||
const lines = stdout.split(/\r?\n/g).map(line => line.trim()).filter(line => line.length > 0);
|
||||
resolve(lines.length > 0 ? lines[0] : '');
|
||||
} else {
|
||||
reject();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
export function execPythonFile(file: string, args: string[], cwd: string, includeErrorAsResponse: boolean = false, stdOut: (line: string) => void = null, token?: CancellationToken): Promise<string> {
|
||||
const execAsModule = file.toUpperCase() === 'PYTHON' && args.length > 0 && args[0] === '-m';
|
||||
|
||||
// If running the python file, then always revert to execFileInternal
|
||||
// Cuz python interpreter is always a file and we can and will always run it using child_process.execFile()
|
||||
if (file === settings.PythonSettings.getInstance().pythonPath) {
|
||||
if (stdOut) {
|
||||
return spawnFileInternal(file, args, { cwd, env: customEnvVariables }, includeErrorAsResponse, stdOut, token);
|
||||
}
|
||||
if (execAsModule) {
|
||||
return getFullyQualifiedPythonInterpreterPath()
|
||||
.then(p => execPythonModule(p, args, { cwd: cwd }, includeErrorAsResponse, token));
|
||||
}
|
||||
return execFileInternal(file, args, { cwd: cwd }, includeErrorAsResponse, token);
|
||||
async function getEnvVariables(resource?: Uri): Promise<{}> {
|
||||
const cache = InterpreterInfoCache.get(resource);
|
||||
if (cache.customEnvVariables) {
|
||||
return cache.customEnvVariables;
|
||||
}
|
||||
|
||||
return getPythonInterpreterDirectory().then(pyPath => {
|
||||
// We don't have a path
|
||||
if (pyPath.length === 0) {
|
||||
let options: child_process.ExecFileOptions = { cwd };
|
||||
const envVars = customEnvVariables || getCustomEnvVars();
|
||||
if (envVars) {
|
||||
options.env = envVars;
|
||||
}
|
||||
if (stdOut) {
|
||||
return spawnFileInternal(file, args, options, includeErrorAsResponse, stdOut, token);
|
||||
}
|
||||
return execFileInternal(file, args, options, includeErrorAsResponse, token);
|
||||
}
|
||||
const pyPath = await getPythonInterpreterDirectory(resource);
|
||||
let customEnvVariables = await getCustomEnvVars(resource) || {};
|
||||
|
||||
if (customEnvVariables === null) {
|
||||
customEnvVariables = getCustomEnvVars();
|
||||
customEnvVariables = customEnvVariables ? customEnvVariables : {};
|
||||
// Ensure to include the path of the current python
|
||||
let newPath = '';
|
||||
let currentPath = typeof customEnvVariables[PATH_VARIABLE_NAME] === 'string' ? customEnvVariables[PATH_VARIABLE_NAME] : process.env[PATH_VARIABLE_NAME];
|
||||
if (IS_WINDOWS) {
|
||||
newPath = pyPath + '\\' + path.delimiter + path.join(pyPath, 'Scripts\\') + path.delimiter + currentPath;
|
||||
// This needs to be done for windows
|
||||
process.env[PATH_VARIABLE_NAME] = newPath;
|
||||
}
|
||||
else {
|
||||
newPath = pyPath + path.delimiter + currentPath;
|
||||
}
|
||||
customEnvVariables = mergeEnvVariables(customEnvVariables, process.env);
|
||||
customEnvVariables[PATH_VARIABLE_NAME] = newPath;
|
||||
if (pyPath.length > 0) {
|
||||
// Ensure to include the path of the current python.
|
||||
let newPath = '';
|
||||
const currentPath = typeof customEnvVariables[PATH_VARIABLE_NAME] === 'string' ? customEnvVariables[PATH_VARIABLE_NAME] : process.env[PATH_VARIABLE_NAME];
|
||||
if (IS_WINDOWS) {
|
||||
newPath = `${pyPath}\\${path.delimiter}${path.join(pyPath, 'Scripts\\')}${path.delimiter}${currentPath}`;
|
||||
// This needs to be done for windows.
|
||||
process.env[PATH_VARIABLE_NAME] = newPath;
|
||||
} else {
|
||||
newPath = `${pyPath}${path.delimiter}${currentPath}`;
|
||||
}
|
||||
customEnvVariables = mergeEnvVariables(customEnvVariables, process.env);
|
||||
customEnvVariables[PATH_VARIABLE_NAME] = newPath;
|
||||
}
|
||||
|
||||
if (stdOut) {
|
||||
return spawnFileInternal(file, args, { cwd, env: customEnvVariables }, includeErrorAsResponse, stdOut, token);
|
||||
}
|
||||
if (execAsModule) {
|
||||
return getFullyQualifiedPythonInterpreterPath()
|
||||
.then(p => execPythonModule(p, args, { cwd: cwd, env: customEnvVariables }, includeErrorAsResponse, token));
|
||||
}
|
||||
return execFileInternal(file, args, { cwd, env: customEnvVariables }, includeErrorAsResponse, token);
|
||||
});
|
||||
InterpreterInfoCache.setCustomEnvVariables(resource, customEnvVariables);
|
||||
return customEnvVariables;
|
||||
}
|
||||
export async function execPythonFile(resource: string | Uri | undefined, file: string, args: string[], cwd: string, includeErrorAsResponse: boolean = false, stdOut: (line: string) => void = null, token?: CancellationToken): Promise<string> {
|
||||
const resourceUri = typeof resource === 'string' ? Uri.file(resource) : resource;
|
||||
const env = await getEnvVariables(resourceUri);
|
||||
const options = { cwd, env };
|
||||
|
||||
if (stdOut) {
|
||||
return spawnFileInternal(file, args, options, includeErrorAsResponse, stdOut, token);
|
||||
}
|
||||
|
||||
const fileIsPythonInterpreter = (file.toUpperCase() === 'PYTHON' || file === settings.PythonSettings.getInstance(resourceUri).pythonPath);
|
||||
const execAsModule = fileIsPythonInterpreter && args.length > 0 && args[0] === '-m';
|
||||
|
||||
if (execAsModule) {
|
||||
return getFullyQualifiedPythonInterpreterPath(resourceUri)
|
||||
.then(p => execPythonModule(p, args, options, includeErrorAsResponse, token));
|
||||
}
|
||||
return execFileInternal(file, args, options, includeErrorAsResponse, token);
|
||||
}
|
||||
|
||||
function handleResponse(file: string, includeErrorAsResponse: boolean, error: Error, stdout: string, stderr: string, token?: CancellationToken): Promise<string> {
|
||||
|
@ -179,7 +165,7 @@ function handleResponse(file: string, includeErrorAsResponse: boolean, error: Er
|
|||
|
||||
// pylint:
|
||||
// In the case of pylint we have some messages (such as config file not found and using default etc...) being returned in stderr
|
||||
// These error messages are useless when using pylint
|
||||
// These error messages are useless when using pylint
|
||||
if (includeErrorAsResponse && (stdout.length > 0 || stderr.length > 0)) {
|
||||
return Promise.resolve(stdout + '\n' + stderr);
|
||||
}
|
||||
|
@ -203,7 +189,7 @@ function handlePythonModuleResponse(includeErrorAsResponse: boolean, error: Erro
|
|||
|
||||
// pylint:
|
||||
// In the case of pylint we have some messages (such as config file not found and using default etc...) being returned in stderr
|
||||
// These error messages are useless when using pylint
|
||||
// These error messages are useless when using pylint
|
||||
if (includeErrorAsResponse && (stdout.length > 0 || stderr.length > 0)) {
|
||||
return Promise.resolve(stdout + '\n' + stderr);
|
||||
}
|
||||
|
@ -361,21 +347,41 @@ export function getSubDirectories(rootDir: string): Promise<string[]> {
|
|||
});
|
||||
}
|
||||
|
||||
export function getCustomEnvVars(): any {
|
||||
const envFile = settings.PythonSettings.getInstance().envFile;
|
||||
if (typeof envFile === 'string' &&
|
||||
envFile.length > 0 &&
|
||||
fs.existsSync(envFile)) {
|
||||
|
||||
try {
|
||||
let vars = parseEnvFile(envFile);
|
||||
if (vars && typeof vars === 'object' && Object.keys(vars).length > 0) {
|
||||
return vars;
|
||||
}
|
||||
export async function getCustomEnvVars(resource?: Uri): Promise<{} | undefined | null> {
|
||||
const envFile = settings.PythonSettings.getInstance(resource).envFile;
|
||||
if (typeof envFile !== 'string' || envFile.length === 0) {
|
||||
return null;
|
||||
}
|
||||
const exists = await fsExtra.pathExists(envFile);
|
||||
if (!exists) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
const vars = parseEnvFile(envFile);
|
||||
if (vars && typeof vars === 'object' && Object.keys(vars).length > 0) {
|
||||
return vars;
|
||||
}
|
||||
catch (ex) {
|
||||
console.error('Failed to load env file', ex);
|
||||
} catch (ex) {
|
||||
console.error('Failed to parse env file', ex);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
export function getCustomEnvVarsSync(resource?: Uri): {} | undefined | null {
|
||||
const envFile = settings.PythonSettings.getInstance(resource).envFile;
|
||||
if (typeof envFile !== 'string' || envFile.length === 0) {
|
||||
return null;
|
||||
}
|
||||
const exists = fsExtra.pathExistsSync(envFile);
|
||||
if (!exists) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
const vars = parseEnvFile(envFile);
|
||||
if (vars && typeof vars === 'object' && Object.keys(vars).length > 0) {
|
||||
return vars;
|
||||
}
|
||||
} catch (ex) {
|
||||
console.error('Failed to parse env file', ex);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@ -417,10 +423,12 @@ export function areBasePathsSame(path1: string, path2: string) {
|
|||
path2 = IS_WINDOWS ? path2.replace(/\//g, "\\") : path2;
|
||||
return path.dirname(path1).toUpperCase() === path.dirname(path2).toUpperCase();
|
||||
}
|
||||
export function getInterpreterDisplayName(pythonPath: string) {
|
||||
return execPythonFile(pythonPath, ['--version'], __dirname, true)
|
||||
.then(version => {
|
||||
version = version.split(/\r?\n/g).map(line => line.trim()).filter(line => line.length > 0).join('');
|
||||
return version;
|
||||
export async function getInterpreterDisplayName(pythonPath: string) {
|
||||
return await new Promise<string>((resolve, reject) => {
|
||||
child_process.execFile(pythonPath, ['--version'], (error, stdout, stdErr) => {
|
||||
const out = (typeof stdErr === 'string' ? stdErr : '') + os.EOL + (typeof stdout === 'string' ? stdout : '');
|
||||
const lines = out.split(/\r?\n/g).map(line => line.trim()).filter(line => line.length > 0);
|
||||
resolve(lines.length > 0 ? lines[0] : '');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -51,7 +51,7 @@ export class SimpleConfigurationProvider implements DebugConfigurationProvider {
|
|||
type: 'python',
|
||||
request: 'launch',
|
||||
stopOnEntry: true,
|
||||
pythonPath: PythonSettings.getInstance().pythonPath,
|
||||
pythonPath: PythonSettings.getInstance(workspaceFolder ? Uri.file(workspaceFolder) : undefined).pythonPath,
|
||||
program: defaultProgram,
|
||||
cwd: workspaceFolder,
|
||||
envFile,
|
||||
|
|
|
@ -1,39 +1,41 @@
|
|||
'use strict';
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import { workspace } from 'vscode';
|
||||
import * as vscode from 'vscode';
|
||||
import { createDeferred } from './common/helpers';
|
||||
import { PythonCompletionItemProvider } from './providers/completionProvider';
|
||||
import { PythonHoverProvider } from './providers/hoverProvider';
|
||||
import { PythonDefinitionProvider } from './providers/definitionProvider';
|
||||
import { PythonReferenceProvider } from './providers/referenceProvider';
|
||||
import { PythonRenameProvider } from './providers/renameProvider';
|
||||
import { PythonFormattingEditProvider } from './providers/formatProvider';
|
||||
import { ShebangCodeLensProvider } from './providers/shebangCodeLensProvider'
|
||||
import * as sortImports from './sortImports';
|
||||
import { LintProvider } from './providers/lintProvider';
|
||||
import { PythonSymbolProvider } from './providers/symbolProvider';
|
||||
import { PythonSignatureProvider } from './providers/signatureProvider';
|
||||
import * as settings from './common/configSettings';
|
||||
import { Commands } from './common/constants';
|
||||
import { createDeferred } from './common/helpers';
|
||||
import * as telemetryHelper from './common/telemetry';
|
||||
import * as telemetryContracts from './common/telemetryContracts';
|
||||
import { activateSimplePythonRefactorProvider } from './providers/simpleRefactorProvider';
|
||||
import { SetInterpreterProvider } from './providers/setInterpreterProvider';
|
||||
import { activateExecInTerminalProvider } from './providers/execInTerminalProvider';
|
||||
import { Commands } from './common/constants';
|
||||
import * as tests from './unittests/main';
|
||||
import * as jup from './jupyter/main';
|
||||
import { HelpProvider } from './helpProvider';
|
||||
import { activateUpdateSparkLibraryProvider } from './providers/updateSparkLibraryProvider';
|
||||
import { activateFormatOnSaveProvider } from './providers/formatOnSaveProvider';
|
||||
import { WorkspaceSymbols } from './workspaceSymbols/main';
|
||||
import { BlockFormatProviders } from './typeFormatters/blockFormatProvider';
|
||||
import * as os from 'os';
|
||||
import * as fs from 'fs';
|
||||
import { getPathFromPythonCommand } from './common/utils';
|
||||
import { JupyterProvider } from './jupyter/provider';
|
||||
import { activateGoToObjectDefinitionProvider } from './providers/objectDefinitionProvider';
|
||||
import { InterpreterManager } from './interpreter';
|
||||
import { SimpleConfigurationProvider } from './debugger';
|
||||
import { HelpProvider } from './helpProvider';
|
||||
import { InterpreterManager } from './interpreter';
|
||||
import { SetInterpreterProvider } from './interpreter/configuration/setInterpreterProvider';
|
||||
import { ShebangCodeLensProvider } from './interpreter/display/shebangCodeLensProvider';
|
||||
import * as jup from './jupyter/main';
|
||||
import { JupyterProvider } from './jupyter/provider';
|
||||
import { JediFactory } from './languageServices/jediProxyFactory';
|
||||
import { PythonCompletionItemProvider } from './providers/completionProvider';
|
||||
import { PythonDefinitionProvider } from './providers/definitionProvider';
|
||||
import { activateExecInTerminalProvider } from './providers/execInTerminalProvider';
|
||||
import { activateFormatOnSaveProvider } from './providers/formatOnSaveProvider';
|
||||
import { PythonFormattingEditProvider } from './providers/formatProvider';
|
||||
import { PythonHoverProvider } from './providers/hoverProvider';
|
||||
import { LintProvider } from './providers/lintProvider';
|
||||
import { activateGoToObjectDefinitionProvider } from './providers/objectDefinitionProvider';
|
||||
import { PythonReferenceProvider } from './providers/referenceProvider';
|
||||
import { PythonRenameProvider } from './providers/renameProvider';
|
||||
import { ReplProvider } from './providers/replProvider';
|
||||
import { PythonSignatureProvider } from './providers/signatureProvider';
|
||||
import { activateSimplePythonRefactorProvider } from './providers/simpleRefactorProvider';
|
||||
import { PythonSymbolProvider } from './providers/symbolProvider';
|
||||
import { activateUpdateSparkLibraryProvider } from './providers/updateSparkLibraryProvider';
|
||||
import * as sortImports from './sortImports';
|
||||
import { BlockFormatProviders } from './typeFormatters/blockFormatProvider';
|
||||
import * as tests from './unittests/main';
|
||||
import { WorkspaceSymbols } from './workspaceSymbols/main';
|
||||
|
||||
const PYTHON: vscode.DocumentFilter = { language: 'python' };
|
||||
let unitTestOutChannel: vscode.OutputChannel;
|
||||
|
@ -42,15 +44,10 @@ let lintingOutChannel: vscode.OutputChannel;
|
|||
let jupMain: jup.Jupyter;
|
||||
const activationDeferred = createDeferred<void>();
|
||||
export const activated = activationDeferred.promise;
|
||||
// tslint:disable-next-line:max-func-body-length
|
||||
export async function activate(context: vscode.ExtensionContext) {
|
||||
const pythonSettings = settings.PythonSettings.getInstance();
|
||||
const pythonExt = new PythonExt();
|
||||
context.subscriptions.push(pythonExt);
|
||||
// telemetryHelper.sendTelemetryEvent(telemetryContracts.EVENT_LOAD, {
|
||||
// CodeComplete_Has_ExtraPaths: pythonSettings.autoComplete.extraPaths.length > 0 ? 'true' : 'false',
|
||||
// Format_Has_Custom_Python_Path: pythonSettings.pythonPath.length !== 'python'.length ? 'true' : 'false',
|
||||
// Has_PySpark_Path: hasPySparkInCompletionPath ? 'true' : 'false'
|
||||
// });
|
||||
sendStartupTelemetry();
|
||||
lintingOutChannel = vscode.window.createOutputChannel(pythonSettings.linting.outputWindow);
|
||||
formatOutChannel = lintingOutChannel;
|
||||
if (pythonSettings.linting.outputWindow !== pythonSettings.formatting.outputWindow) {
|
||||
|
@ -65,23 +62,17 @@ export async function activate(context: vscode.ExtensionContext) {
|
|||
sortImports.activate(context, formatOutChannel);
|
||||
const interpreterManager = new InterpreterManager();
|
||||
await interpreterManager.autoSetInterpreter();
|
||||
await interpreterManager.refresh();
|
||||
context.subscriptions.push(interpreterManager);
|
||||
context.subscriptions.push(new SetInterpreterProvider(interpreterManager));
|
||||
context.subscriptions.push(...activateExecInTerminalProvider());
|
||||
context.subscriptions.push(activateUpdateSparkLibraryProvider());
|
||||
activateSimplePythonRefactorProvider(context, formatOutChannel);
|
||||
context.subscriptions.push(activateFormatOnSaveProvider(PYTHON, settings.PythonSettings.getInstance(), formatOutChannel));
|
||||
context.subscriptions.push(activateGoToObjectDefinitionProvider(context));
|
||||
context.subscriptions.push(activateFormatOnSaveProvider(PYTHON, formatOutChannel));
|
||||
const jediFactory = new JediFactory(context.asAbsolutePath('.'));
|
||||
context.subscriptions.push(...activateGoToObjectDefinitionProvider(jediFactory));
|
||||
|
||||
context.subscriptions.push(vscode.commands.registerCommand(Commands.Start_REPL, () => {
|
||||
getPathFromPythonCommand(["-c", "import sys;print(sys.executable)"]).catch(() => {
|
||||
return pythonSettings.pythonPath;
|
||||
}).then(pythonExecutablePath => {
|
||||
let term = vscode.window.createTerminal('Python', pythonExecutablePath);
|
||||
term.show();
|
||||
context.subscriptions.push(term);
|
||||
});
|
||||
}));
|
||||
context.subscriptions.push(new ReplProvider());
|
||||
|
||||
// Enable indentAction
|
||||
vscode.languages.setLanguageConfiguration(PYTHON.language, {
|
||||
|
@ -93,37 +84,37 @@ export async function activate(context: vscode.ExtensionContext) {
|
|||
{
|
||||
beforeText: /^ *#.*$/,
|
||||
afterText: /.+$/,
|
||||
action: { indentAction: vscode.IndentAction.None, appendText: '# ' },
|
||||
action: { indentAction: vscode.IndentAction.None, appendText: '# ' }
|
||||
},
|
||||
{
|
||||
beforeText: /^\s+(continue|break|return)\b.*$/,
|
||||
action: { indentAction: vscode.IndentAction.Outdent },
|
||||
action: { indentAction: vscode.IndentAction.Outdent }
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
context.subscriptions.push(jediFactory);
|
||||
context.subscriptions.push(vscode.languages.registerRenameProvider(PYTHON, new PythonRenameProvider(formatOutChannel)));
|
||||
const definitionProvider = new PythonDefinitionProvider(context);
|
||||
const jediProx = definitionProvider.JediProxy;
|
||||
const definitionProvider = new PythonDefinitionProvider(jediFactory);
|
||||
context.subscriptions.push(vscode.languages.registerDefinitionProvider(PYTHON, definitionProvider));
|
||||
context.subscriptions.push(vscode.languages.registerHoverProvider(PYTHON, new PythonHoverProvider(context, jediProx)));
|
||||
context.subscriptions.push(vscode.languages.registerReferenceProvider(PYTHON, new PythonReferenceProvider(context, jediProx)));
|
||||
context.subscriptions.push(vscode.languages.registerCompletionItemProvider(PYTHON, new PythonCompletionItemProvider(context, jediProx), '.'));
|
||||
context.subscriptions.push(vscode.languages.registerCodeLensProvider(PYTHON, new ShebangCodeLensProvider()))
|
||||
context.subscriptions.push(vscode.languages.registerHoverProvider(PYTHON, new PythonHoverProvider(jediFactory)));
|
||||
context.subscriptions.push(vscode.languages.registerReferenceProvider(PYTHON, new PythonReferenceProvider(jediFactory)));
|
||||
context.subscriptions.push(vscode.languages.registerCompletionItemProvider(PYTHON, new PythonCompletionItemProvider(jediFactory), '.'));
|
||||
context.subscriptions.push(vscode.languages.registerCodeLensProvider(PYTHON, new ShebangCodeLensProvider()));
|
||||
|
||||
const symbolProvider = new PythonSymbolProvider(context, jediProx);
|
||||
const symbolProvider = new PythonSymbolProvider(jediFactory);
|
||||
context.subscriptions.push(vscode.languages.registerDocumentSymbolProvider(PYTHON, symbolProvider));
|
||||
if (pythonSettings.devOptions.indexOf('DISABLE_SIGNATURE') === -1) {
|
||||
context.subscriptions.push(vscode.languages.registerSignatureHelpProvider(PYTHON, new PythonSignatureProvider(context, jediProx), '(', ','));
|
||||
context.subscriptions.push(vscode.languages.registerSignatureHelpProvider(PYTHON, new PythonSignatureProvider(jediFactory), '(', ','));
|
||||
}
|
||||
if (pythonSettings.formatting.provider !== 'none') {
|
||||
const formatProvider = new PythonFormattingEditProvider(context, formatOutChannel, pythonSettings);
|
||||
const formatProvider = new PythonFormattingEditProvider(context, formatOutChannel);
|
||||
context.subscriptions.push(vscode.languages.registerDocumentFormattingEditProvider(PYTHON, formatProvider));
|
||||
context.subscriptions.push(vscode.languages.registerDocumentRangeFormattingEditProvider(PYTHON, formatProvider));
|
||||
}
|
||||
|
||||
const jupyterExtInstalled = vscode.extensions.getExtension('donjayamanne.jupyter');
|
||||
let linterProvider = new LintProvider(context, lintingOutChannel, (a, b) => Promise.resolve(false));
|
||||
const linterProvider = new LintProvider(context, lintingOutChannel, (a, b) => Promise.resolve(false));
|
||||
context.subscriptions.push();
|
||||
if (jupyterExtInstalled) {
|
||||
if (jupyterExtInstalled.isActive) {
|
||||
|
@ -135,8 +126,7 @@ export async function activate(context: vscode.ExtensionContext) {
|
|||
jupyterExtInstalled.exports.registerLanguageProvider(PYTHON.language, new JupyterProvider());
|
||||
linterProvider.documentHasJupyterCodeCells = jupyterExtInstalled.exports.hasCodeCells;
|
||||
});
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
jupMain = new jup.Jupyter(lintingOutChannel);
|
||||
const documentHasJupyterCodeCells = jupMain.hasCodeCells.bind(jupMain);
|
||||
jupMain.activate();
|
||||
|
@ -159,39 +149,6 @@ export async function activate(context: vscode.ExtensionContext) {
|
|||
activationDeferred.resolve();
|
||||
}
|
||||
|
||||
class PythonExt implements vscode.Disposable {
|
||||
|
||||
private isDjangoProject: ContextKey;
|
||||
|
||||
constructor() {
|
||||
this.isDjangoProject = new ContextKey('python.isDjangoProject');
|
||||
this.ensureState();
|
||||
}
|
||||
public dispose() {
|
||||
this.isDjangoProject = null;
|
||||
}
|
||||
private ensureState(): void {
|
||||
// context: python.isDjangoProject
|
||||
if (typeof vscode.workspace.rootPath === 'string') {
|
||||
this.isDjangoProject.set(fs.existsSync(vscode.workspace.rootPath.concat("/manage.py")));
|
||||
}
|
||||
else {
|
||||
this.isDjangoProject.set(false);
|
||||
}
|
||||
}
|
||||
function sendStartupTelemetry() {
|
||||
telemetryHelper.sendTelemetryEvent(telemetryContracts.EVENT_LOAD);
|
||||
}
|
||||
|
||||
class ContextKey {
|
||||
private lastValue: boolean;
|
||||
|
||||
constructor(private name: string) {
|
||||
}
|
||||
|
||||
public set(value: boolean): void {
|
||||
if (this.lastValue === value) {
|
||||
return;
|
||||
}
|
||||
this.lastValue = value;
|
||||
vscode.commands.executeCommand('setContext', this.name, this.lastValue);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,21 +2,22 @@
|
|||
|
||||
import * as vscode from 'vscode';
|
||||
import { BaseFormatter } from './baseFormatter';
|
||||
import * as settings from '../common/configSettings';
|
||||
import { PythonSettings } from '../common/configSettings';
|
||||
import { Product } from '../common/installer';
|
||||
|
||||
export class AutoPep8Formatter extends BaseFormatter {
|
||||
constructor(outputChannel: vscode.OutputChannel, pythonSettings: settings.IPythonSettings, workspaceRootPath?: string) {
|
||||
super('autopep8', Product.autopep8, outputChannel, pythonSettings, workspaceRootPath);
|
||||
constructor(outputChannel: vscode.OutputChannel) {
|
||||
super('autopep8', Product.autopep8, outputChannel);
|
||||
}
|
||||
|
||||
public formatDocument(document: vscode.TextDocument, options: vscode.FormattingOptions, token: vscode.CancellationToken, range?: vscode.Range): Thenable<vscode.TextEdit[]> {
|
||||
let autopep8Path = this.pythonSettings.formatting.autopep8Path;
|
||||
let autoPep8Args = Array.isArray(this.pythonSettings.formatting.autopep8Args) ? this.pythonSettings.formatting.autopep8Args : [];
|
||||
const settings = PythonSettings.getInstance(document.uri);
|
||||
const autopep8Path = settings.formatting.autopep8Path;
|
||||
let autoPep8Args = Array.isArray(settings.formatting.autopep8Args) ? settings.formatting.autopep8Args : [];
|
||||
autoPep8Args = autoPep8Args.concat(['--diff']);
|
||||
if (range && !range.isEmpty) {
|
||||
autoPep8Args = autoPep8Args.concat(['--line-range', (range.start.line + 1).toString(), (range.end.line + 1).toString()]);
|
||||
}
|
||||
return super.provideDocumentFormattingEdits(document, options, token, autopep8Path, autoPep8Args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,23 +2,43 @@
|
|||
|
||||
import * as vscode from 'vscode';
|
||||
import * as fs from 'fs';
|
||||
import { execPythonFile } from './../common/utils';
|
||||
import * as path from 'path';
|
||||
import * as settings from './../common/configSettings';
|
||||
import { Uri } from 'vscode';
|
||||
import { execPythonFile } from './../common/utils';
|
||||
import { getTextEditsFromPatch, getTempFileWithDocumentContents } from './../common/editor';
|
||||
import { isNotInstalledError } from '../common/helpers';
|
||||
import { Installer, Product } from '../common/installer';
|
||||
|
||||
|
||||
export abstract class BaseFormatter {
|
||||
private installer: Installer;
|
||||
constructor(public Id: string, private product: Product, protected outputChannel: vscode.OutputChannel, protected pythonSettings: settings.IPythonSettings, protected workspaceRootPath?: string) {
|
||||
constructor(public Id: string, private product: Product, protected outputChannel: vscode.OutputChannel) {
|
||||
this.installer = new Installer();
|
||||
}
|
||||
|
||||
public abstract formatDocument(document: vscode.TextDocument, options: vscode.FormattingOptions, token: vscode.CancellationToken, range?: vscode.Range): Thenable<vscode.TextEdit[]>;
|
||||
|
||||
protected getDocumentPath(document: vscode.TextDocument, fallbackPath: string) {
|
||||
if (path.basename(document.uri.fsPath) === document.uri.fsPath) {
|
||||
return fallbackPath;
|
||||
}
|
||||
return path.dirname(document.fileName);
|
||||
}
|
||||
protected getWorkspaceUri(document: vscode.TextDocument) {
|
||||
const workspaceFolder = vscode.workspace.getWorkspaceFolder(document.uri);
|
||||
if (workspaceFolder) {
|
||||
return workspaceFolder.uri;
|
||||
}
|
||||
if (Array.isArray(vscode.workspace.workspaceFolders) && vscode.workspace.workspaceFolders.length > 0) {
|
||||
return vscode.workspace.workspaceFolders[0].uri;
|
||||
}
|
||||
return vscode.Uri.file(__dirname);
|
||||
}
|
||||
protected provideDocumentFormattingEdits(document: vscode.TextDocument, options: vscode.FormattingOptions, token: vscode.CancellationToken, command: string, args: string[], cwd: string = null): Thenable<vscode.TextEdit[]> {
|
||||
this.outputChannel.clear();
|
||||
cwd = typeof cwd === 'string' && cwd.length > 0 ? cwd : (this.workspaceRootPath ? this.workspaceRootPath : vscode.workspace.rootPath);
|
||||
if (typeof cwd !== 'string' || cwd.length === 0) {
|
||||
cwd = this.getWorkspaceUri(document).fsPath;
|
||||
}
|
||||
|
||||
// autopep8 and yapf have the ability to read from the process input stream and return the formatted code out of the output stream
|
||||
// However they don't support returning the diff of the formatted text when reading data from the input stream
|
||||
|
@ -30,7 +50,7 @@ export abstract class BaseFormatter {
|
|||
if (token && token.isCancellationRequested) {
|
||||
return [filePath, ''];
|
||||
}
|
||||
return Promise.all<string>([Promise.resolve(filePath), execPythonFile(command, args.concat([filePath]), cwd)]);
|
||||
return Promise.all<string>([Promise.resolve(filePath), execPythonFile(document.uri, command, args.concat([filePath]), cwd)]);
|
||||
}).then(data => {
|
||||
// Delete the temporary file created
|
||||
if (tmpFileCreated) {
|
||||
|
@ -41,14 +61,14 @@ export abstract class BaseFormatter {
|
|||
}
|
||||
return getTextEditsFromPatch(document.getText(), data[1]);
|
||||
}).catch(error => {
|
||||
this.handleError(this.Id, command, error);
|
||||
this.handleError(this.Id, command, error, document.uri);
|
||||
return [];
|
||||
});
|
||||
vscode.window.setStatusBarMessage(`Formatting with ${this.Id}`, promise);
|
||||
return promise;
|
||||
}
|
||||
|
||||
protected handleError(expectedFileName: string, fileName: string, error: Error) {
|
||||
protected handleError(expectedFileName: string, fileName: string, error: Error, resource?: Uri) {
|
||||
let customError = `Formatting with ${this.Id} failed.`;
|
||||
|
||||
if (isNotInstalledError(error)) {
|
||||
|
@ -64,7 +84,7 @@ export abstract class BaseFormatter {
|
|||
}
|
||||
else {
|
||||
customError += `\nYou could either install the '${this.Id}' formatter, turn it off or use another formatter.`;
|
||||
this.installer.promptToInstall(this.product);
|
||||
this.installer.promptToInstall(this.product, resource);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,15 +2,14 @@
|
|||
|
||||
import * as vscode from 'vscode';
|
||||
import { BaseFormatter } from './baseFormatter';
|
||||
import * as settings from './../common/configSettings';
|
||||
import { Product } from '../common/installer';
|
||||
|
||||
export class DummyFormatter extends BaseFormatter {
|
||||
constructor(outputChannel: vscode.OutputChannel, pythonSettings: settings.IPythonSettings, workspaceRootPath?: string) {
|
||||
super('none', Product.yapf, outputChannel, pythonSettings, workspaceRootPath);
|
||||
constructor(outputChannel: vscode.OutputChannel) {
|
||||
super('none', Product.yapf, outputChannel);
|
||||
}
|
||||
|
||||
public formatDocument(document: vscode.TextDocument, options: vscode.FormattingOptions, token: vscode.CancellationToken, range?: vscode.Range): Thenable<vscode.TextEdit[]> {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,25 +1,27 @@
|
|||
'use strict';
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import { BaseFormatter } from './baseFormatter';
|
||||
import * as settings from './../common/configSettings';
|
||||
import { Product } from '../common/installer';
|
||||
import * as path from 'path';
|
||||
import { BaseFormatter } from './baseFormatter';
|
||||
import { PythonSettings } from '../common/configSettings';
|
||||
import { Product } from '../common/installer';
|
||||
|
||||
export class YapfFormatter extends BaseFormatter {
|
||||
constructor(outputChannel: vscode.OutputChannel, pythonSettings: settings.IPythonSettings, workspaceRootPath?: string) {
|
||||
super('yapf', Product.yapf, outputChannel, pythonSettings, workspaceRootPath);
|
||||
constructor(outputChannel: vscode.OutputChannel) {
|
||||
super('yapf', Product.yapf, outputChannel);
|
||||
}
|
||||
|
||||
public formatDocument(document: vscode.TextDocument, options: vscode.FormattingOptions, token: vscode.CancellationToken, range?: vscode.Range): Thenable<vscode.TextEdit[]> {
|
||||
let yapfPath = this.pythonSettings.formatting.yapfPath;
|
||||
let yapfArgs = Array.isArray(this.pythonSettings.formatting.yapfArgs) ? this.pythonSettings.formatting.yapfArgs : [];
|
||||
const settings = PythonSettings.getInstance(document.uri);
|
||||
const yapfPath = settings.formatting.yapfPath;
|
||||
let yapfArgs = Array.isArray(settings.formatting.yapfArgs) ? settings.formatting.yapfArgs : [];
|
||||
yapfArgs = yapfArgs.concat(['--diff']);
|
||||
if (range && !range.isEmpty) {
|
||||
yapfArgs = yapfArgs.concat(['--lines', `${range.start.line + 1}-${range.end.line + 1}`]);
|
||||
}
|
||||
// Yapf starts looking for config file starting from the file path
|
||||
let cwd = path.dirname(document.fileName);
|
||||
const fallbarFolder = this.getWorkspaceUri(document).fsPath;
|
||||
const cwd = this.getDocumentPath(document, fallbarFolder);
|
||||
return super.provideDocumentFormattingEdits(document, options, token, yapfPath, yapfArgs, cwd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
import * as path from 'path';
|
||||
import { ConfigurationTarget, Uri, window } from 'vscode';
|
||||
import { WorkspacePythonPath } from '../contracts';
|
||||
import { IPythonPathUpdaterService, IPythonPathUpdaterServiceFactory } from './types';
|
||||
|
||||
export class PythonPathUpdaterService {
|
||||
constructor(private pythonPathSettingsUpdaterFactory: IPythonPathUpdaterServiceFactory) { }
|
||||
public async updatePythonPath(pythonPath: string, configTarget: ConfigurationTarget, wkspace?: Uri): Promise<void> {
|
||||
const pythonPathUpdater = this.getPythonUpdaterService(configTarget, wkspace);
|
||||
|
||||
try {
|
||||
await pythonPathUpdater.updatePythonPath(path.normalize(pythonPath));
|
||||
} catch (reason) {
|
||||
// tslint:disable-next-line:no-unsafe-any prefer-type-cast
|
||||
const message = reason && typeof reason.message === 'string' ? reason.message as string : '';
|
||||
window.showErrorMessage(`Failed to set 'pythonPath'. Error: ${message}`);
|
||||
console.error(reason);
|
||||
}
|
||||
}
|
||||
private getPythonUpdaterService(configTarget: ConfigurationTarget, wkspace?: Uri) {
|
||||
switch (configTarget) {
|
||||
case ConfigurationTarget.Global: {
|
||||
return this.pythonPathSettingsUpdaterFactory.getGlobalPythonPathConfigurationService();
|
||||
}
|
||||
case ConfigurationTarget.Workspace: {
|
||||
if (!wkspace) {
|
||||
throw new Error('Workspace Uri not defined');
|
||||
}
|
||||
// tslint:disable-next-line:no-non-null-assertion
|
||||
return this.pythonPathSettingsUpdaterFactory.getWorkspacePythonPathConfigurationService(wkspace!);
|
||||
}
|
||||
default: {
|
||||
if (!wkspace) {
|
||||
throw new Error('Workspace Uri not defined');
|
||||
}
|
||||
// tslint:disable-next-line:no-non-null-assertion
|
||||
return this.pythonPathSettingsUpdaterFactory.getWorkspaceFolderPythonPathConfigurationService(wkspace!);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
import { Uri } from 'vscode';
|
||||
import { InterpreterManager } from '../index';
|
||||
import { GlobalPythonPathUpdaterService } from './services/globalUpdaterService';
|
||||
import { WorkspaceFolderPythonPathUpdaterService } from './services/workspaceFolderUpdaterService';
|
||||
import { WorkspacePythonPathUpdaterService } from './services/workspaceUpdaterService';
|
||||
import { IPythonPathUpdaterService, IPythonPathUpdaterServiceFactory } from './types';
|
||||
|
||||
export class PythonPathUpdaterServiceFactory implements IPythonPathUpdaterServiceFactory {
|
||||
public getGlobalPythonPathConfigurationService(): IPythonPathUpdaterService {
|
||||
return new GlobalPythonPathUpdaterService();
|
||||
}
|
||||
public getWorkspacePythonPathConfigurationService(wkspace: Uri): IPythonPathUpdaterService {
|
||||
return new WorkspacePythonPathUpdaterService(wkspace);
|
||||
}
|
||||
public getWorkspaceFolderPythonPathConfigurationService(workspaceFolder: Uri): IPythonPathUpdaterService {
|
||||
return new WorkspaceFolderPythonPathUpdaterService(workspaceFolder);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
import { ConfigurationTarget, Uri, workspace } from 'vscode';
|
||||
import { InterpreterManager } from '../..';
|
||||
import { WorkspacePythonPath } from '../../contracts';
|
||||
import { IPythonPathUpdaterService } from '../types';
|
||||
|
||||
export class GlobalPythonPathUpdaterService implements IPythonPathUpdaterService {
|
||||
public async updatePythonPath(pythonPath: string): Promise<void> {
|
||||
const pythonConfig = workspace.getConfiguration('python');
|
||||
const pythonPathValue = pythonConfig.inspect<string>('pythonPath');
|
||||
|
||||
if (pythonPathValue && pythonPathValue.globalValue === pythonPath) {
|
||||
return;
|
||||
}
|
||||
await pythonConfig.update('pythonPath', pythonPath, true);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
import * as path from 'path';
|
||||
import { ConfigurationTarget, Uri, workspace } from 'vscode';
|
||||
import { InterpreterManager } from '../..';
|
||||
import { WorkspacePythonPath } from '../../contracts';
|
||||
import { IPythonPathUpdaterService } from '../types';
|
||||
|
||||
export class WorkspaceFolderPythonPathUpdaterService implements IPythonPathUpdaterService {
|
||||
constructor(private workspaceFolder: Uri) {
|
||||
}
|
||||
public async updatePythonPath(pythonPath: string): Promise<void> {
|
||||
const pythonConfig = workspace.getConfiguration('python', this.workspaceFolder);
|
||||
const pythonPathValue = pythonConfig.inspect<string>('pythonPath');
|
||||
|
||||
if (pythonPathValue && pythonPathValue.workspaceFolderValue === pythonPath) {
|
||||
return;
|
||||
}
|
||||
if (pythonPath.startsWith(this.workspaceFolder.fsPath)) {
|
||||
// tslint:disable-next-line:no-invalid-template-strings
|
||||
pythonPath = path.join('${workspaceRoot}', path.relative(this.workspaceFolder.fsPath, pythonPath));
|
||||
}
|
||||
await pythonConfig.update('pythonPath', pythonPath, ConfigurationTarget.WorkspaceFolder);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
import * as path from 'path';
|
||||
import { ConfigurationTarget, Uri, workspace } from 'vscode';
|
||||
import { InterpreterManager } from '../..';
|
||||
import { WorkspacePythonPath } from '../../contracts';
|
||||
import { IPythonPathUpdaterService } from '../types';
|
||||
|
||||
export class WorkspacePythonPathUpdaterService implements IPythonPathUpdaterService {
|
||||
constructor(private wkspace: Uri) {
|
||||
}
|
||||
public async updatePythonPath(pythonPath: string): Promise<void> {
|
||||
const pythonConfig = workspace.getConfiguration('python', this.wkspace);
|
||||
const pythonPathValue = pythonConfig.inspect<string>('pythonPath');
|
||||
|
||||
if (pythonPathValue && pythonPathValue.workspaceValue === pythonPath) {
|
||||
return;
|
||||
}
|
||||
if (pythonPath.startsWith(this.wkspace.fsPath)) {
|
||||
// tslint:disable-next-line:no-invalid-template-strings
|
||||
pythonPath = path.join('${workspaceRoot}', path.relative(this.wkspace.fsPath, pythonPath));
|
||||
}
|
||||
await pythonConfig.update('pythonPath', pythonPath, false);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,114 @@
|
|||
'use strict';
|
||||
import * as path from 'path';
|
||||
import { commands, ConfigurationTarget, Disposable, QuickPickItem, QuickPickOptions, Uri, window, workspace } from 'vscode';
|
||||
import { InterpreterManager } from '../';
|
||||
import * as settings from '../../common/configSettings';
|
||||
import { PythonInterpreter, WorkspacePythonPath } from '../contracts';
|
||||
import { ShebangCodeLensProvider } from '../display/shebangCodeLensProvider';
|
||||
import { PythonPathUpdaterService } from './pythonPathUpdaterService';
|
||||
import { PythonPathUpdaterServiceFactory } from './pythonPathUpdaterServiceFactory';
|
||||
import { IPythonPathUpdaterServiceFactory } from './types';
|
||||
|
||||
// tslint:disable-next-line:interface-name
|
||||
interface PythonPathQuickPickItem extends QuickPickItem {
|
||||
path: string;
|
||||
}
|
||||
|
||||
export class SetInterpreterProvider implements Disposable {
|
||||
private disposables: Disposable[] = [];
|
||||
private pythonPathUpdaterService: PythonPathUpdaterService;
|
||||
constructor(private interpreterManager: InterpreterManager) {
|
||||
this.disposables.push(commands.registerCommand('python.setInterpreter', this.setInterpreter.bind(this)));
|
||||
this.disposables.push(commands.registerCommand('python.setShebangInterpreter', this.setShebangInterpreter.bind(this)));
|
||||
this.pythonPathUpdaterService = new PythonPathUpdaterService(new PythonPathUpdaterServiceFactory());
|
||||
}
|
||||
public dispose() {
|
||||
this.disposables.forEach(disposable => disposable.dispose());
|
||||
}
|
||||
private async getWorkspaceToSetPythonPath(): Promise<WorkspacePythonPath | undefined> {
|
||||
if (!Array.isArray(workspace.workspaceFolders) || workspace.workspaceFolders.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
if (workspace.workspaceFolders.length === 1) {
|
||||
return { folderUri: workspace.workspaceFolders[0].uri, configTarget: ConfigurationTarget.Workspace };
|
||||
}
|
||||
|
||||
// Ok we have multiple interpreters, get the user to pick a folder.
|
||||
// tslint:disable-next-line:no-any prefer-type-cast
|
||||
const workspaceFolder = await (window as any).showWorkspaceFolderPick({ placeHolder: 'Select a workspace' });
|
||||
return workspaceFolder ? { folderUri: workspaceFolder.uri, configTarget: ConfigurationTarget.WorkspaceFolder } : undefined;
|
||||
}
|
||||
private async suggestionToQuickPickItem(suggestion: PythonInterpreter, workspaceUri?: Uri): Promise<PythonPathQuickPickItem> {
|
||||
let detail = suggestion.path;
|
||||
if (workspaceUri && suggestion.path.startsWith(workspaceUri.fsPath)) {
|
||||
detail = `.${path.sep}${path.relative(workspaceUri.fsPath, suggestion.path)}`;
|
||||
}
|
||||
return {
|
||||
// tslint:disable-next-line:no-non-null-assertion
|
||||
label: suggestion.displayName!,
|
||||
description: suggestion.companyDisplayName || '',
|
||||
detail: detail,
|
||||
path: suggestion.path
|
||||
};
|
||||
}
|
||||
|
||||
private async getSuggestions(resourceUri?: Uri) {
|
||||
const interpreters = await this.interpreterManager.getInterpreters(resourceUri);
|
||||
// tslint:disable-next-line:no-non-null-assertion
|
||||
interpreters.sort((a, b) => a.displayName! > b.displayName! ? 1 : -1);
|
||||
return Promise.all(interpreters.map(item => this.suggestionToQuickPickItem(item, resourceUri)));
|
||||
}
|
||||
|
||||
private async setInterpreter() {
|
||||
const setInterpreterGlobally = !Array.isArray(workspace.workspaceFolders) || workspace.workspaceFolders.length === 0;
|
||||
let configTarget = ConfigurationTarget.Global;
|
||||
let wkspace: Uri;
|
||||
if (!setInterpreterGlobally) {
|
||||
const targetConfig = await this.getWorkspaceToSetPythonPath();
|
||||
if (!targetConfig) {
|
||||
return;
|
||||
}
|
||||
configTarget = targetConfig.configTarget;
|
||||
wkspace = targetConfig.folderUri;
|
||||
}
|
||||
|
||||
const suggestions = await this.getSuggestions(wkspace);
|
||||
let currentPythonPath = settings.PythonSettings.getInstance().pythonPath;
|
||||
if (wkspace && currentPythonPath.startsWith(wkspace.fsPath)) {
|
||||
currentPythonPath = `.${path.sep}${path.relative(wkspace.fsPath, currentPythonPath)}`;
|
||||
}
|
||||
const quickPickOptions: QuickPickOptions = {
|
||||
matchOnDetail: true,
|
||||
matchOnDescription: true,
|
||||
placeHolder: `current: ${currentPythonPath}`
|
||||
};
|
||||
|
||||
const selection = await window.showQuickPick(suggestions, quickPickOptions);
|
||||
if (selection !== undefined) {
|
||||
await this.pythonPathUpdaterService.updatePythonPath(selection.path, configTarget, wkspace);
|
||||
}
|
||||
}
|
||||
|
||||
private async setShebangInterpreter(): Promise<void> {
|
||||
const shebang = await ShebangCodeLensProvider.detectShebang(window.activeTextEditor.document);
|
||||
if (!shebang) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isGlobalChange = !Array.isArray(workspace.workspaceFolders) || workspace.workspaceFolders.length === 0;
|
||||
const workspaceFolder = workspace.getWorkspaceFolder(window.activeTextEditor.document.uri);
|
||||
const isWorkspaceChange = Array.isArray(workspace.workspaceFolders) && workspace.workspaceFolders.length === 1;
|
||||
|
||||
if (isGlobalChange) {
|
||||
await this.pythonPathUpdaterService.updatePythonPath(shebang, ConfigurationTarget.Global);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isWorkspaceChange || !workspaceFolder) {
|
||||
await this.pythonPathUpdaterService.updatePythonPath(shebang, ConfigurationTarget.Workspace, workspace.workspaceFolders[0].uri);
|
||||
return;
|
||||
}
|
||||
|
||||
await this.pythonPathUpdaterService.updatePythonPath(shebang, ConfigurationTarget.WorkspaceFolder, workspaceFolder.uri);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
import { Uri } from 'vscode';
|
||||
import { IPythonPathUpdaterService } from './types';
|
||||
|
||||
export interface IPythonPathUpdaterService {
|
||||
updatePythonPath(pythonPath: string): Promise<void>;
|
||||
}
|
||||
|
||||
export interface IPythonPathUpdaterServiceFactory {
|
||||
getGlobalPythonPathConfigurationService(): IPythonPathUpdaterService;
|
||||
getWorkspacePythonPathConfigurationService(wkspace: Uri): IPythonPathUpdaterService;
|
||||
getWorkspaceFolderPythonPathConfigurationService(workspaceFolder: Uri): IPythonPathUpdaterService;
|
||||
}
|
|
@ -1,12 +1,20 @@
|
|||
import { Architecture } from "../common/registry";
|
||||
import { ConfigurationTarget, Disposable, Uri } from 'vscode';
|
||||
import { Architecture } from '../common/registry';
|
||||
|
||||
export interface IInterpreterLocatorService {
|
||||
getInterpreters(): Promise<PythonInterpreter[]>;
|
||||
export interface IInterpreterLocatorService extends Disposable {
|
||||
getInterpreters(resource?: Uri): Promise<PythonInterpreter[]>;
|
||||
}
|
||||
export interface PythonInterpreter {
|
||||
|
||||
export type PythonInterpreter = {
|
||||
path: string;
|
||||
companyDisplayName?: string;
|
||||
displayName?: string;
|
||||
version?: string;
|
||||
architecture?: Architecture;
|
||||
}
|
||||
};
|
||||
|
||||
export type WorkspacePythonPath = {
|
||||
folderUri: Uri;
|
||||
pytonPath?: string;
|
||||
configTarget: ConfigurationTarget.Workspace | ConfigurationTarget.WorkspaceFolder;
|
||||
};
|
||||
|
|
|
@ -1,30 +1,36 @@
|
|||
'use strict';
|
||||
import * as path from 'path';
|
||||
import * as utils from '../../common/utils';
|
||||
import * as child_process from 'child_process';
|
||||
import { StatusBarItem, Disposable } from 'vscode';
|
||||
import { PythonSettings } from '../../common/configSettings';
|
||||
import { EOL } from 'os';
|
||||
import * as path from 'path';
|
||||
import { Disposable, StatusBarItem } from 'vscode';
|
||||
import { PythonSettings } from '../../common/configSettings';
|
||||
import * as utils from '../../common/utils';
|
||||
import { IInterpreterLocatorService } from '../contracts';
|
||||
import { getActiveWorkspaceUri, getFirstNonEmptyLineFromMultilineString } from '../helpers';
|
||||
import { IInterpreterVersionService } from '../interpreterVersion';
|
||||
import { VirtualEnvironmentManager } from '../virtualEnvs/index';
|
||||
import { getFirstNonEmptyLineFromMultilineString } from '../helpers';
|
||||
|
||||
const settings = PythonSettings.getInstance();
|
||||
// tslint:disable-next-line:completed-docs
|
||||
export class InterpreterDisplay implements Disposable {
|
||||
constructor(private statusBar: StatusBarItem,
|
||||
private interpreterLocator: IInterpreterLocatorService,
|
||||
private virtualEnvMgr: VirtualEnvironmentManager,
|
||||
private versionProvider: IInterpreterVersionService) {
|
||||
|
||||
this.statusBar.command = 'python.setInterpreter';
|
||||
}
|
||||
public dispose() {
|
||||
//
|
||||
}
|
||||
public async refresh() {
|
||||
const pythonPath = await this.getFullyQualifiedPathToInterpreter(settings.pythonPath);
|
||||
const wkspc = getActiveWorkspaceUri();
|
||||
if (!wkspc) {
|
||||
return;
|
||||
}
|
||||
const pythonPath = await this.getFullyQualifiedPathToInterpreter(PythonSettings.getInstance(wkspc.folderUri).pythonPath);
|
||||
await this.updateDisplay(pythonPath);
|
||||
}
|
||||
private getInterpreters() {
|
||||
private async getInterpreters() {
|
||||
return this.interpreterLocator.getInterpreters();
|
||||
}
|
||||
private async updateDisplay(pythonPath: string) {
|
||||
|
@ -34,18 +40,19 @@ export class InterpreterDisplay implements Disposable {
|
|||
this.statusBar.color = '';
|
||||
this.statusBar.tooltip = pythonPath;
|
||||
if (interpreter) {
|
||||
// tslint:disable-next-line:no-non-null-assertion
|
||||
this.statusBar.text = interpreter.displayName!;
|
||||
if (interpreter.companyDisplayName) {
|
||||
const toolTipSuffix = `${EOL}${interpreter.companyDisplayName}`;
|
||||
this.statusBar.tooltip += toolTipSuffix;
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
const defaultDisplayName = `${path.basename(pythonPath)} [Environment]`;
|
||||
const interpreterExists = utils.fsExistsAsync(pythonPath);
|
||||
const displayName = this.versionProvider.getVersion(pythonPath, defaultDisplayName);
|
||||
const virtualEnvName = this.getVirtualEnvironmentName(pythonPath);
|
||||
await Promise.all([interpreterExists, displayName, virtualEnvName])
|
||||
await Promise.all([
|
||||
utils.fsExistsAsync(pythonPath),
|
||||
this.versionProvider.getVersion(pythonPath, defaultDisplayName),
|
||||
this.getVirtualEnvironmentName(pythonPath)
|
||||
])
|
||||
.then(([interpreterExists, displayName, virtualEnvName]) => {
|
||||
const dislayNameSuffix = virtualEnvName.length > 0 ? ` (${virtualEnvName})` : '';
|
||||
this.statusBar.text = `${displayName}${dislayNameSuffix}`;
|
||||
|
@ -63,13 +70,13 @@ export class InterpreterDisplay implements Disposable {
|
|||
.detect(pythonPath)
|
||||
.then(env => env ? env.name : '');
|
||||
}
|
||||
private getFullyQualifiedPathToInterpreter(pythonPath: string) {
|
||||
private async getFullyQualifiedPathToInterpreter(pythonPath: string) {
|
||||
return new Promise<string>(resolve => {
|
||||
child_process.execFile(pythonPath, ["-c", "import sys;print(sys.executable)"], (_, stdout) => {
|
||||
child_process.execFile(pythonPath, ['-c', 'import sys;print(sys.executable)'], (_, stdout) => {
|
||||
resolve(getFirstNonEmptyLineFromMultilineString(stdout));
|
||||
});
|
||||
})
|
||||
.then(value => value.length === 0 ? pythonPath : value);
|
||||
.then(value => value.length === 0 ? pythonPath : value)
|
||||
.catch(() => pythonPath);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,40 +1,16 @@
|
|||
"use strict";
|
||||
import { IS_WINDOWS } from '../common/utils';
|
||||
import * as vscode from 'vscode';
|
||||
'use strict';
|
||||
import * as child_process from 'child_process';
|
||||
import * as settings from '../common/configSettings';
|
||||
import { TextDocument, CodeLens, CancellationToken } from 'vscode';
|
||||
import { getFirstNonEmptyLineFromMultilineString } from '../interpreter/helpers';
|
||||
import * as vscode from 'vscode';
|
||||
import { CancellationToken, CodeLens, TextDocument } from 'vscode';
|
||||
import * as settings from '../../common/configSettings';
|
||||
import { IS_WINDOWS } from '../../common/utils';
|
||||
import { getFirstNonEmptyLineFromMultilineString } from '../../interpreter/helpers';
|
||||
|
||||
export class ShebangCodeLensProvider implements vscode.CodeLensProvider {
|
||||
onDidChangeCodeLenses: vscode.Event<void> = vscode.workspace.onDidChangeConfiguration;
|
||||
|
||||
public async provideCodeLenses(document: TextDocument, token: CancellationToken): Promise<CodeLens[]> {
|
||||
const codeLenses = await this.createShebangCodeLens(document);
|
||||
return Promise.resolve(codeLenses);
|
||||
}
|
||||
|
||||
private async createShebangCodeLens(document: TextDocument) {
|
||||
const shebang = await ShebangCodeLensProvider.detectShebang(document);
|
||||
if (!shebang || shebang === settings.PythonSettings.getInstance().pythonPath) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const firstLine = document.lineAt(0);
|
||||
const startOfShebang = new vscode.Position(0, 0);
|
||||
const endOfShebang = new vscode.Position(0, firstLine.text.length - 1);
|
||||
const shebangRange = new vscode.Range(startOfShebang, endOfShebang);
|
||||
|
||||
const cmd: vscode.Command = {
|
||||
command: 'python.setShebangInterpreter',
|
||||
title: 'Set as interpreter'
|
||||
};
|
||||
|
||||
const codeLenses = [(new CodeLens(shebangRange, cmd))];
|
||||
return codeLenses;
|
||||
}
|
||||
|
||||
public onDidChangeCodeLenses: vscode.Event<void> = vscode.workspace.onDidChangeConfiguration;
|
||||
// tslint:disable-next-line:function-name
|
||||
public static async detectShebang(document: TextDocument): Promise<string | undefined> {
|
||||
let firstLine = document.lineAt(0);
|
||||
const firstLine = document.lineAt(0);
|
||||
if (firstLine.isEmptyOrWhitespace) {
|
||||
return;
|
||||
}
|
||||
|
@ -49,7 +25,7 @@ export class ShebangCodeLensProvider implements vscode.CodeLensProvider {
|
|||
}
|
||||
private static async getFullyQualifiedPathToInterpreter(pythonPath: string) {
|
||||
if (pythonPath.indexOf('bin/env ') >= 0 && !IS_WINDOWS) {
|
||||
// In case we have pythonPath as '/usr/bin/env python'
|
||||
// In case we have pythonPath as '/usr/bin/env python'
|
||||
return new Promise<string>(resolve => {
|
||||
const command = child_process.exec(`${pythonPath} -c 'import sys;print(sys.executable)'`);
|
||||
let result = '';
|
||||
|
@ -60,13 +36,36 @@ export class ShebangCodeLensProvider implements vscode.CodeLensProvider {
|
|||
resolve(getFirstNonEmptyLineFromMultilineString(result));
|
||||
});
|
||||
});
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
return new Promise<string>(resolve => {
|
||||
child_process.execFile(pythonPath, ["-c", "import sys;print(sys.executable)"], (_, stdout) => {
|
||||
child_process.execFile(pythonPath, ['-c', 'import sys;print(sys.executable)'], (_, stdout) => {
|
||||
resolve(getFirstNonEmptyLineFromMultilineString(stdout));
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public async provideCodeLenses(document: TextDocument, token: CancellationToken): Promise<CodeLens[]> {
|
||||
const codeLenses = await this.createShebangCodeLens(document);
|
||||
return Promise.resolve(codeLenses);
|
||||
}
|
||||
|
||||
private async createShebangCodeLens(document: TextDocument) {
|
||||
const shebang = await ShebangCodeLensProvider.detectShebang(document);
|
||||
if (!shebang || shebang === settings.PythonSettings.getInstance(document.uri).pythonPath) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const firstLine = document.lineAt(0);
|
||||
const startOfShebang = new vscode.Position(0, 0);
|
||||
const endOfShebang = new vscode.Position(0, firstLine.text.length - 1);
|
||||
const shebangRange = new vscode.Range(startOfShebang, endOfShebang);
|
||||
|
||||
const cmd: vscode.Command = {
|
||||
command: 'python.setShebangInterpreter',
|
||||
title: 'Set as interpreter'
|
||||
};
|
||||
|
||||
return [(new CodeLens(shebangRange, cmd))];
|
||||
}
|
||||
}
|
|
@ -1,3 +1,6 @@
|
|||
import { ConfigurationTarget, window, workspace } from 'vscode';
|
||||
import { WorkspacePythonPath } from './contracts';
|
||||
|
||||
export function getFirstNonEmptyLineFromMultilineString(stdout: string) {
|
||||
if (!stdout) {
|
||||
return '';
|
||||
|
@ -5,3 +8,18 @@ export function getFirstNonEmptyLineFromMultilineString(stdout: string) {
|
|||
const lines = stdout.split(/\r?\n/g).map(line => line.trim()).filter(line => line.length > 0);
|
||||
return lines.length > 0 ? lines[0] : '';
|
||||
}
|
||||
export function getActiveWorkspaceUri(): WorkspacePythonPath | undefined {
|
||||
if (!Array.isArray(workspace.workspaceFolders) || workspace.workspaceFolders.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
if (workspace.workspaceFolders.length === 1) {
|
||||
return { folderUri: workspace.workspaceFolders[0].uri, configTarget: ConfigurationTarget.Workspace };
|
||||
}
|
||||
if (window.activeTextEditor) {
|
||||
const workspaceFolder = workspace.getWorkspaceFolder(window.activeTextEditor.document.uri);
|
||||
if (workspaceFolder) {
|
||||
return { configTarget: ConfigurationTarget.WorkspaceFolder, folderUri: workspaceFolder.uri };
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
|
|
@ -1,43 +1,53 @@
|
|||
'use strict';
|
||||
import { InterpreterVersionService } from './interpreterVersion';
|
||||
import { VirtualEnv } from './virtualEnvs/virtualEnv';
|
||||
import { VEnv } from './virtualEnvs/venv';
|
||||
import { Disposable, window, StatusBarAlignment, workspace } from 'vscode';
|
||||
import * as path from 'path';
|
||||
import { ConfigurationTarget, Disposable, StatusBarAlignment, Uri, window, workspace } from 'vscode';
|
||||
import { PythonSettings } from '../common/configSettings';
|
||||
import { IS_WINDOWS } from '../common/utils';
|
||||
import { PythonPathUpdaterService } from './configuration/pythonPathUpdaterService';
|
||||
import { PythonPathUpdaterServiceFactory } from './configuration/pythonPathUpdaterServiceFactory';
|
||||
import { WorkspacePythonPath } from './contracts';
|
||||
import { InterpreterDisplay } from './display';
|
||||
import { getActiveWorkspaceUri } from './helpers';
|
||||
import { InterpreterVersionService } from './interpreterVersion';
|
||||
import { PythonInterpreterLocatorService } from './locators';
|
||||
import { VirtualEnvironmentManager } from './virtualEnvs/index';
|
||||
import { IS_WINDOWS } from '../common/utils';
|
||||
import * as path from 'path';
|
||||
|
||||
const settings = PythonSettings.getInstance();
|
||||
import { VEnv } from './virtualEnvs/venv';
|
||||
import { VirtualEnv } from './virtualEnvs/virtualEnv';
|
||||
|
||||
export class InterpreterManager implements Disposable {
|
||||
private disposables: Disposable[] = [];
|
||||
private display: InterpreterDisplay | null | undefined;
|
||||
private interpreterProvider: PythonInterpreterLocatorService;
|
||||
private pythonPathUpdaterService: PythonPathUpdaterService;
|
||||
constructor() {
|
||||
const virtualEnvMgr = new VirtualEnvironmentManager([new VEnv(), new VirtualEnv()]);
|
||||
const statusBar = window.createStatusBarItem(StatusBarAlignment.Left);
|
||||
this.interpreterProvider = new PythonInterpreterLocatorService(virtualEnvMgr);
|
||||
const versionService = new InterpreterVersionService();
|
||||
this.display = new InterpreterDisplay(statusBar, this.interpreterProvider, virtualEnvMgr, versionService);
|
||||
settings.addListener('change', this.onConfigChanged.bind(this));
|
||||
this.display.refresh();
|
||||
|
||||
this.pythonPathUpdaterService = new PythonPathUpdaterService(new PythonPathUpdaterServiceFactory());
|
||||
PythonSettings.getInstance().addListener('change', () => this.onConfigChanged());
|
||||
this.disposables.push(window.onDidChangeActiveTextEditor(() => this.refresh()));
|
||||
this.disposables.push(statusBar);
|
||||
this.disposables.push(this.display);
|
||||
}
|
||||
public getInterpreters() {
|
||||
return this.interpreterProvider.getInterpreters();
|
||||
public async refresh() {
|
||||
return this.display.refresh();
|
||||
}
|
||||
public getInterpreters(resource?: Uri) {
|
||||
return this.interpreterProvider.getInterpreters(resource);
|
||||
}
|
||||
public async autoSetInterpreter() {
|
||||
if (!this.shouldAutoSetInterpreter()) {
|
||||
return;
|
||||
}
|
||||
const interpreters = await this.interpreterProvider.getInterpreters();
|
||||
const rootPath = workspace.rootPath!.toUpperCase();
|
||||
const interpretersInWorkspace = interpreters.filter(interpreter => interpreter.path.toUpperCase().startsWith(rootPath));
|
||||
const activeWorkspace = getActiveWorkspaceUri();
|
||||
if (!activeWorkspace) {
|
||||
return;
|
||||
}
|
||||
const interpreters = await this.interpreterProvider.getInterpreters(activeWorkspace.folderUri);
|
||||
const workspacePathUpper = activeWorkspace.folderUri.fsPath.toUpperCase();
|
||||
const interpretersInWorkspace = interpreters.filter(interpreter => interpreter.path.toUpperCase().startsWith(workspacePathUpper));
|
||||
if (interpretersInWorkspace.length !== 1) {
|
||||
return;
|
||||
}
|
||||
|
@ -46,45 +56,30 @@ export class InterpreterManager implements Disposable {
|
|||
// In windows the interpreter is under scripts/python.exe on linux it is under bin/python.
|
||||
// Meaning the sub directory must be either scripts, bin or other (but only one level deep).
|
||||
const pythonPath = interpretersInWorkspace[0].path;
|
||||
const relativePath = path.dirname(pythonPath).substring(workspace.rootPath!.length);
|
||||
const relativePath = path.dirname(pythonPath).substring(activeWorkspace.folderUri.fsPath.length);
|
||||
if (relativePath.split(path.sep).filter(l => l.length > 0).length === 2) {
|
||||
this.setPythonPath(pythonPath);
|
||||
await this.pythonPathUpdaterService.updatePythonPath(pythonPath, activeWorkspace.configTarget, activeWorkspace.folderUri);
|
||||
}
|
||||
}
|
||||
|
||||
public setPythonPath(pythonPath: string) {
|
||||
pythonPath = IS_WINDOWS ? pythonPath.replace(/\\/g, "/") : pythonPath;
|
||||
if (pythonPath.startsWith(workspace.rootPath!)) {
|
||||
pythonPath = path.join('${workspaceRoot}', path.relative(workspace.rootPath!, pythonPath));
|
||||
}
|
||||
const pythonConfig = workspace.getConfiguration('python');
|
||||
let global = null;
|
||||
if (typeof workspace.rootPath !== 'string') {
|
||||
global = true;
|
||||
}
|
||||
pythonConfig.update('pythonPath', pythonPath, global).then(() => {
|
||||
//Done
|
||||
}, reason => {
|
||||
window.showErrorMessage(`Failed to set 'pythonPath'. Error: ${reason.message}`);
|
||||
console.error(reason);
|
||||
});
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
this.disposables.forEach(disposable => disposable.dispose());
|
||||
public dispose(): void {
|
||||
// tslint:disable-next-line:prefer-type-cast
|
||||
this.disposables.forEach(disposable => disposable.dispose() as void);
|
||||
this.display = null;
|
||||
this.interpreterProvider.dispose();
|
||||
}
|
||||
private shouldAutoSetInterpreter() {
|
||||
if (!workspace.rootPath) {
|
||||
const activeWorkspace = getActiveWorkspaceUri();
|
||||
if (!activeWorkspace) {
|
||||
return false;
|
||||
}
|
||||
const pythonConfig = workspace.getConfiguration('python');
|
||||
const pythonPathInConfig = pythonConfig.get('pythonPath', 'python');
|
||||
return pythonPathInConfig.toUpperCase() === 'PYTHON';
|
||||
const pythonPathInConfig = pythonConfig.get<string>('pythonPath', 'python');
|
||||
return path.basename(pythonPathInConfig) === pythonPathInConfig;
|
||||
}
|
||||
private onConfigChanged() {
|
||||
if (this.display) {
|
||||
// tslint:disable-next-line:no-floating-promises
|
||||
this.display.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,61 +1,90 @@
|
|||
"use strict";
|
||||
'use strict';
|
||||
import * as _ from 'lodash';
|
||||
import { fixInterpreterPath, fixInterpreterDisplayName } from './helpers';
|
||||
import { IInterpreterLocatorService, PythonInterpreter } from '../contracts';
|
||||
import { InterpreterVersionService } from '../interpreterVersion';
|
||||
import { IS_WINDOWS, Is_64Bit, arePathsSame, areBasePathsSame } from '../../common/utils';
|
||||
import { Disposable, Uri, workspace } from 'vscode';
|
||||
import { RegistryImplementation } from '../../common/registry';
|
||||
import { CondaEnvService } from './services/condaEnvService';
|
||||
import { VirtualEnvService, getKnownSearchPathsForVirtualEnvs } from './services/virtualEnvService';
|
||||
import { KnownPathsService, getKnownSearchPathsForInterpreters } from './services/KnownPathsService';
|
||||
import { CurrentPathService } from './services/currentPathService';
|
||||
import { WindowsRegistryService } from './services/windowsRegistryService';
|
||||
import { areBasePathsSame, arePathsSame, Is_64Bit, IS_WINDOWS } from '../../common/utils';
|
||||
import { IInterpreterLocatorService, PythonInterpreter } from '../contracts';
|
||||
import { IInterpreterVersionService, InterpreterVersionService } from '../interpreterVersion';
|
||||
import { VirtualEnvironmentManager } from '../virtualEnvs';
|
||||
import { fixInterpreterDisplayName, fixInterpreterPath } from './helpers';
|
||||
import { CondaEnvFileService, getEnvironmentsFile as getCondaEnvFile } from './services/condaEnvFileService';
|
||||
import { CondaEnvService } from './services/condaEnvService';
|
||||
import { CurrentPathService } from './services/currentPathService';
|
||||
import { getKnownSearchPathsForInterpreters, KnownPathsService } from './services/KnownPathsService';
|
||||
import { getKnownSearchPathsForVirtualEnvs, VirtualEnvService } from './services/virtualEnvService';
|
||||
import { WindowsRegistryService } from './services/windowsRegistryService';
|
||||
|
||||
export class PythonInterpreterLocatorService implements IInterpreterLocatorService {
|
||||
private interpreters: PythonInterpreter[] = [];
|
||||
private locators: IInterpreterLocatorService[] = [];
|
||||
private interpretersPerResource: Map<string, PythonInterpreter[]>;
|
||||
private disposables: Disposable[] = [];
|
||||
constructor(private virtualEnvMgr: VirtualEnvironmentManager) {
|
||||
const versionService = new InterpreterVersionService();
|
||||
// The order of the services is important.
|
||||
if (IS_WINDOWS) {
|
||||
const windowsRegistryProvider = new WindowsRegistryService(new RegistryImplementation(), Is_64Bit);
|
||||
this.locators.push(windowsRegistryProvider);
|
||||
this.locators.push(new CondaEnvService(windowsRegistryProvider));
|
||||
}
|
||||
else {
|
||||
this.locators.push(new CondaEnvService());
|
||||
}
|
||||
// Supplements the above list of conda environments.
|
||||
this.locators.push(new CondaEnvFileService(getCondaEnvFile(), versionService));
|
||||
this.locators.push(new VirtualEnvService(getKnownSearchPathsForVirtualEnvs(), this.virtualEnvMgr, versionService));
|
||||
|
||||
if (!IS_WINDOWS) {
|
||||
// This must be last, it is possible we have paths returned here that are already returned
|
||||
// in one of the above lists.
|
||||
this.locators.push(new KnownPathsService(getKnownSearchPathsForInterpreters(), versionService));
|
||||
}
|
||||
// This must be last, it is possible we have paths returned here that are already returned
|
||||
// in one of the above lists.
|
||||
this.locators.push(new CurrentPathService(this.virtualEnvMgr, versionService));
|
||||
this.interpretersPerResource = new Map<string, PythonInterpreter[]>();
|
||||
this.disposables.push(workspace.onDidChangeConfiguration(this.onConfigChanged, this));
|
||||
}
|
||||
public async getInterpreters() {
|
||||
if (this.interpreters.length > 0) {
|
||||
return this.interpreters;
|
||||
public async getInterpreters(resource?: Uri) {
|
||||
const resourceKey = this.getResourceKey(resource);
|
||||
if (!this.interpretersPerResource.has(resourceKey)) {
|
||||
const interpreters = await this.getInterpretersPerResource(resource);
|
||||
this.interpretersPerResource.set(resourceKey, interpreters);
|
||||
}
|
||||
const promises = this.locators.map(provider => provider.getInterpreters());
|
||||
return Promise.all(promises)
|
||||
.then(interpreters => _.flatten(interpreters))
|
||||
.then(items => items.map(fixInterpreterDisplayName))
|
||||
.then(items => items.map(fixInterpreterPath))
|
||||
.then(items => items.reduce<PythonInterpreter[]>((accumulator, current) => {
|
||||
|
||||
// tslint:disable-next-line:no-non-null-assertion
|
||||
return this.interpretersPerResource.get(resourceKey)!;
|
||||
}
|
||||
public dispose() {
|
||||
this.disposables.forEach(disposable => disposable.dispose());
|
||||
}
|
||||
private onConfigChanged() {
|
||||
this.interpretersPerResource.clear();
|
||||
}
|
||||
private getResourceKey(resource?: Uri) {
|
||||
if (!resource) {
|
||||
return '';
|
||||
}
|
||||
const workspaceFolder = workspace.getWorkspaceFolder(resource);
|
||||
return workspaceFolder ? workspaceFolder.uri.fsPath : '';
|
||||
}
|
||||
private async getInterpretersPerResource(resource?: Uri) {
|
||||
const locators = this.getLocators(resource);
|
||||
const promises = locators.map(provider => provider.getInterpreters(resource));
|
||||
const listOfInterpreters = await Promise.all(promises);
|
||||
|
||||
// tslint:disable-next-line:underscore-consistent-invocation
|
||||
return _.flatten(listOfInterpreters)
|
||||
.map(fixInterpreterDisplayName)
|
||||
.map(fixInterpreterPath)
|
||||
.reduce<PythonInterpreter[]>((accumulator, current) => {
|
||||
if (accumulator.findIndex(item => arePathsSame(item.path, current.path)) === -1 &&
|
||||
accumulator.findIndex(item => areBasePathsSame(item.path, current.path)) === -1) {
|
||||
accumulator.push(current);
|
||||
}
|
||||
return accumulator;
|
||||
}, []))
|
||||
.then(interpreters => this.interpreters = interpreters);
|
||||
}, []);
|
||||
}
|
||||
private getLocators(resource?: Uri) {
|
||||
const locators: IInterpreterLocatorService[] = [];
|
||||
const versionService = new InterpreterVersionService();
|
||||
// The order of the services is important.
|
||||
if (IS_WINDOWS) {
|
||||
const windowsRegistryProvider = new WindowsRegistryService(new RegistryImplementation(), Is_64Bit);
|
||||
locators.push(windowsRegistryProvider);
|
||||
locators.push(new CondaEnvService(windowsRegistryProvider));
|
||||
} else {
|
||||
locators.push(new CondaEnvService());
|
||||
}
|
||||
// Supplements the above list of conda environments.
|
||||
locators.push(new CondaEnvFileService(getCondaEnvFile(), versionService));
|
||||
locators.push(new VirtualEnvService(getKnownSearchPathsForVirtualEnvs(resource), this.virtualEnvMgr, versionService));
|
||||
|
||||
if (!IS_WINDOWS) {
|
||||
// This must be last, it is possible we have paths returned here that are already returned
|
||||
// in one of the above lists.
|
||||
locators.push(new KnownPathsService(getKnownSearchPathsForInterpreters(), versionService));
|
||||
}
|
||||
// This must be last, it is possible we have paths returned here that are already returned
|
||||
// in one of the above lists.
|
||||
locators.push(new CurrentPathService(this.virtualEnvMgr, versionService));
|
||||
|
||||
return locators;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,22 +1,27 @@
|
|||
"use strict";
|
||||
import * as path from 'path';
|
||||
'use strict';
|
||||
import * as _ from 'lodash';
|
||||
import * as path from 'path';
|
||||
import { Uri } from 'vscode';
|
||||
import { fsExistsAsync, IS_WINDOWS } from '../../../common/utils';
|
||||
import { IInterpreterLocatorService } from '../../contracts';
|
||||
import { IInterpreterVersionService } from '../../interpreterVersion';
|
||||
import { fsExistsAsync, IS_WINDOWS } from '../../../common/utils';
|
||||
import { lookForInterpretersInDirectory } from '../helpers';
|
||||
// tslint:disable-next-line:no-require-imports no-var-requires
|
||||
const untildify = require('untildify');
|
||||
|
||||
export class KnownPathsService implements IInterpreterLocatorService {
|
||||
public constructor(private knownSearchPaths: string[],
|
||||
private versionProvider: IInterpreterVersionService) { }
|
||||
public getInterpreters() {
|
||||
// tslint:disable-next-line:no-shadowed-variable
|
||||
public getInterpreters(_?: Uri) {
|
||||
return this.suggestionsFromKnownPaths();
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:no-empty
|
||||
public dispose() { }
|
||||
private suggestionsFromKnownPaths() {
|
||||
const promises = this.knownSearchPaths.map(dir => this.getInterpretersInDirectory(dir));
|
||||
return Promise.all<string[]>(promises)
|
||||
// tslint:disable-next-line:underscore-consistent-invocation
|
||||
.then(listOfInterpreters => _.flatten(listOfInterpreters))
|
||||
.then(interpreters => interpreters.filter(item => item.length > 0))
|
||||
.then(interpreters => Promise.all(interpreters.map(interpreter => this.getInterpreterDetails(interpreter))));
|
||||
|
@ -40,14 +45,14 @@ export function getKnownSearchPathsForInterpreters(): string[] {
|
|||
if (IS_WINDOWS) {
|
||||
return [];
|
||||
} else {
|
||||
let paths = ['/usr/local/bin', '/usr/bin', '/bin', '/usr/sbin', '/sbin', '/usr/local/sbin'];
|
||||
const paths = ['/usr/local/bin', '/usr/bin', '/bin', '/usr/sbin', '/sbin', '/usr/local/sbin'];
|
||||
paths.forEach(p => {
|
||||
paths.push(untildify('~' + p));
|
||||
paths.push(untildify(`~${p}`));
|
||||
});
|
||||
// Add support for paths such as /Users/xxx/anaconda/bin.
|
||||
if (process.env['HOME']) {
|
||||
paths.push(path.join(process.env['HOME'], 'anaconda', 'bin'));
|
||||
paths.push(path.join(process.env['HOME'], 'python', 'bin'));
|
||||
if (process.env.HOME) {
|
||||
paths.push(path.join(process.env.HOME, 'anaconda', 'bin'));
|
||||
paths.push(path.join(process.env.HOME, 'python', 'bin'));
|
||||
}
|
||||
return paths;
|
||||
}
|
||||
|
|
|
@ -1,24 +1,26 @@
|
|||
"use strict";
|
||||
'use strict';
|
||||
import * as fs from 'fs-extra';
|
||||
import * as path from 'path';
|
||||
import { Uri } from 'vscode';
|
||||
import { IS_WINDOWS } from '../../../common/configSettings';
|
||||
import { IInterpreterVersionService } from '../../interpreterVersion';
|
||||
import { IInterpreterLocatorService, PythonInterpreter } from '../../contracts';
|
||||
import { AnacondaDisplayName, AnacondaCompanyName, AnacondaCompanyNames, CONDA_RELATIVE_PY_PATH } from './conda';
|
||||
import { IInterpreterVersionService } from '../../interpreterVersion';
|
||||
import { AnacondaCompanyName, AnacondaCompanyNames, AnacondaDisplayName, CONDA_RELATIVE_PY_PATH } from './conda';
|
||||
|
||||
export class CondaEnvFileService implements IInterpreterLocatorService {
|
||||
constructor(private condaEnvironmentFile: string,
|
||||
private versionService: IInterpreterVersionService) {
|
||||
}
|
||||
public getInterpreters() {
|
||||
public async getInterpreters(_?: Uri) {
|
||||
return this.getSuggestionsFromConda();
|
||||
}
|
||||
|
||||
private getSuggestionsFromConda(): Promise<PythonInterpreter[]> {
|
||||
// tslint:disable-next-line:no-empty
|
||||
public dispose() { }
|
||||
private async getSuggestionsFromConda(): Promise<PythonInterpreter[]> {
|
||||
return fs.pathExists(this.condaEnvironmentFile)
|
||||
.then(exists => exists ? this.getEnvironmentsFromFile(this.condaEnvironmentFile) : Promise.resolve([]));
|
||||
}
|
||||
private getEnvironmentsFromFile(envFile: string) {
|
||||
private async getEnvironmentsFromFile(envFile: string) {
|
||||
return fs.readFile(envFile)
|
||||
.then(buffer => buffer.toString().split(/\r?\n/g))
|
||||
.then(lines => lines.map(line => line.trim()))
|
||||
|
@ -29,11 +31,12 @@ export class CondaEnvFileService implements IInterpreterLocatorService {
|
|||
.then(interpreterPaths => interpreterPaths.map(item => this.getInterpreterDetails(item)))
|
||||
.then(promises => Promise.all(promises));
|
||||
}
|
||||
private getInterpreterDetails(interpreter: string) {
|
||||
private async getInterpreterDetails(interpreter: string) {
|
||||
return this.versionService.getVersion(interpreter, path.basename(interpreter))
|
||||
.then(version => {
|
||||
version = this.stripCompanyName(version);
|
||||
const envName = this.getEnvironmentRootDirectory(interpreter);
|
||||
// tslint:disable-next-line:no-unnecessary-local-variable
|
||||
const info: PythonInterpreter = {
|
||||
displayName: `${AnacondaDisplayName} ${version} (${envName})`,
|
||||
path: interpreter,
|
||||
|
|
|
@ -1,23 +1,81 @@
|
|||
"use strict";
|
||||
'use strict';
|
||||
import * as child_process from 'child_process';
|
||||
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 { VersionUtils } from "../../../common/versionUtils";
|
||||
import { AnacondaCompanyName, AnacondaDisplayName, CONDA_RELATIVE_PY_PATH } from './conda';
|
||||
|
||||
type CondaInfo = {
|
||||
envs?: string[];
|
||||
"sys.version"?: string;
|
||||
'sys.version'?: string;
|
||||
default_prefix?: string;
|
||||
}
|
||||
};
|
||||
export class CondaEnvService implements IInterpreterLocatorService {
|
||||
constructor(private registryLookupForConda?: IInterpreterLocatorService) {
|
||||
}
|
||||
public getInterpreters() {
|
||||
public getInterpreters(resource?: Uri) {
|
||||
return this.getSuggestionsFromConda();
|
||||
}
|
||||
// tslint:disable-next-line:no-empty
|
||||
public dispose() { }
|
||||
public 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(condaPath => {
|
||||
return fs.pathExists(condaPath).then(exists => exists ? condaPath : 'conda');
|
||||
});
|
||||
}
|
||||
return Promise.resolve('conda');
|
||||
}
|
||||
public isCondaEnvironment(interpreter: PythonInterpreter) {
|
||||
return (interpreter.displayName || '').toUpperCase().indexOf('ANACONDA') >= 0 ||
|
||||
(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 parseCondaInfo(info: CondaInfo) {
|
||||
// "sys.version": "3.6.1 |Anaconda 4.4.0 (64-bit)| (default, May 11 2017, 13:25:24) [MSC v.1900 64 bit (AMD64)]".
|
||||
const displayName = this.getDisplayNameFromVersionInfo(info['sys.version'] || '');
|
||||
|
||||
// The root of the conda environment is itself a Python interpreter
|
||||
// envs reported as e.g.: /Users/bob/miniconda3/envs/someEnv.
|
||||
const envs = Array.isArray(info.envs) ? info.envs : [];
|
||||
if (info.default_prefix && info.default_prefix.length > 0) {
|
||||
envs.push(info.default_prefix);
|
||||
}
|
||||
|
||||
const promises = envs
|
||||
.map(env => {
|
||||
// If it is an environment, hence suffix with env name.
|
||||
const interpreterDisplayName = env === info.default_prefix ? displayName : `${displayName} (${path.basename(env)})`;
|
||||
// tslint:disable-next-line:no-unnecessary-local-variable
|
||||
const interpreter: PythonInterpreter = {
|
||||
path: path.join(env, ...CONDA_RELATIVE_PY_PATH),
|
||||
displayName: interpreterDisplayName,
|
||||
companyDisplayName: AnacondaCompanyName
|
||||
};
|
||||
return interpreter;
|
||||
})
|
||||
.map(env => fs.pathExists(env.path).then(exists => exists ? env : null));
|
||||
|
||||
return Promise.all(promises)
|
||||
.then(interpreters => interpreters.filter(interpreter => interpreter !== null && interpreter !== undefined))
|
||||
// tslint:disable-next-line:no-non-null-assertion
|
||||
.then(interpreters => interpreters.map(interpreter => interpreter!));
|
||||
}
|
||||
private getSuggestionsFromConda(): Promise<PythonInterpreter[]> {
|
||||
return this.getCondaFile()
|
||||
.then(condaFile => {
|
||||
|
@ -43,59 +101,6 @@ export class CondaEnvService implements IInterpreterLocatorService {
|
|||
});
|
||||
});
|
||||
}
|
||||
public 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(condaPath => {
|
||||
return fs.pathExists(condaPath).then(exists => exists ? condaPath : 'conda');
|
||||
});
|
||||
}
|
||||
return Promise.resolve('conda');
|
||||
}
|
||||
public isCondaEnvironment(interpreter: PythonInterpreter) {
|
||||
return (interpreter.displayName || '').toUpperCase().indexOf('ANACONDA') >= 0 ||
|
||||
(interpreter.companyDisplayName || '').toUpperCase().indexOf('CONTINUUM') >= 0;
|
||||
}
|
||||
public getLatestVersion(interpreters: PythonInterpreter[]) {
|
||||
const sortedInterpreters = interpreters.filter(interpreter => interpreter.version && interpreter.version.length > 0);
|
||||
sortedInterpreters.sort((a, b) => VersionUtils.compareVersion(a.version!, b.version!));
|
||||
if (sortedInterpreters.length > 0) {
|
||||
return sortedInterpreters[sortedInterpreters.length - 1];
|
||||
}
|
||||
}
|
||||
public async parseCondaInfo(info: CondaInfo) {
|
||||
// "sys.version": "3.6.1 |Anaconda 4.4.0 (64-bit)| (default, May 11 2017, 13:25:24) [MSC v.1900 64 bit (AMD64)]".
|
||||
const displayName = this.getDisplayNameFromVersionInfo(info['sys.version'] || '');
|
||||
|
||||
// The root of the conda environment is itself a Python interpreter
|
||||
// envs reported as e.g.: /Users/bob/miniconda3/envs/someEnv.
|
||||
const envs = Array.isArray(info.envs) ? info.envs : [];
|
||||
if (info.default_prefix && info.default_prefix.length > 0) {
|
||||
envs.push(info.default_prefix);
|
||||
}
|
||||
|
||||
const promises = envs
|
||||
.map(env => {
|
||||
// If it is an environment, hence suffix with env name.
|
||||
const interpreterDisplayName = env === info.default_prefix ? displayName : `${displayName} (${path.basename(env)})`;
|
||||
const interpreter: PythonInterpreter = {
|
||||
path: path.join(env, ...CONDA_RELATIVE_PY_PATH),
|
||||
displayName: interpreterDisplayName,
|
||||
companyDisplayName: AnacondaCompanyName
|
||||
};
|
||||
return interpreter;
|
||||
})
|
||||
.map(env => fs.pathExists(env.path).then(exists => exists ? env : null));
|
||||
|
||||
return Promise.all(promises)
|
||||
.then(interpreters => interpreters.filter(interpreter => interpreter !== null && interpreter !== undefined))
|
||||
.then(interpreters => interpreters.map(interpreter => interpreter!));
|
||||
}
|
||||
private getDisplayNameFromVersionInfo(versionInfo: string = '') {
|
||||
if (!versionInfo) {
|
||||
return AnacondaDisplayName;
|
||||
|
|
|
@ -1,36 +1,39 @@
|
|||
"use strict";
|
||||
'use strict';
|
||||
import * as child_process from 'child_process';
|
||||
import * as _ from 'lodash';
|
||||
import * as path from 'path';
|
||||
import * as child_process from 'child_process';
|
||||
import { IInterpreterLocatorService } from '../../contracts';
|
||||
import { IInterpreterVersionService } from '../../interpreterVersion';
|
||||
import { getFirstNonEmptyLineFromMultilineString } from '../../helpers';
|
||||
import { VirtualEnvironmentManager } from '../../virtualEnvs';
|
||||
import { Uri } from 'vscode';
|
||||
import { PythonSettings } from '../../../common/configSettings';
|
||||
|
||||
const settings = PythonSettings.getInstance();
|
||||
import { IInterpreterLocatorService } from '../../contracts';
|
||||
import { getFirstNonEmptyLineFromMultilineString } from '../../helpers';
|
||||
import { IInterpreterVersionService } from '../../interpreterVersion';
|
||||
import { VirtualEnvironmentManager } from '../../virtualEnvs';
|
||||
|
||||
export class CurrentPathService implements IInterpreterLocatorService {
|
||||
public constructor(private virtualEnvMgr: VirtualEnvironmentManager,
|
||||
private versionProvider: IInterpreterVersionService) { }
|
||||
public getInterpreters() {
|
||||
public async getInterpreters(resource?: Uri) {
|
||||
return this.suggestionsFromKnownPaths();
|
||||
}
|
||||
|
||||
private suggestionsFromKnownPaths() {
|
||||
const currentPythonInterpreter = this.getInterpreter(settings.pythonPath, '').then(interpreter => [interpreter]);
|
||||
// tslint:disable-next-line:no-empty
|
||||
public dispose() { }
|
||||
private async suggestionsFromKnownPaths(resource?: Uri) {
|
||||
const currentPythonInterpreter = this.getInterpreter(PythonSettings.getInstance(resource).pythonPath, '').then(interpreter => [interpreter]);
|
||||
const python = this.getInterpreter('python', '').then(interpreter => [interpreter]);
|
||||
const python2 = this.getInterpreter('python2', '').then(interpreter => [interpreter]);
|
||||
const python3 = this.getInterpreter('python3', '').then(interpreter => [interpreter]);
|
||||
return Promise.all<string[]>([currentPythonInterpreter, python, python2, python3])
|
||||
// tslint:disable-next-line:underscore-consistent-invocation
|
||||
.then(listOfInterpreters => _.flatten(listOfInterpreters))
|
||||
.then(interpreters => interpreters.filter(item => item.length > 0))
|
||||
// tslint:disable-next-line:promise-function-async
|
||||
.then(interpreters => Promise.all(interpreters.map(interpreter => this.getInterpreterDetails(interpreter))));
|
||||
}
|
||||
private getInterpreterDetails(interpreter: string) {
|
||||
const virtualEnv = this.virtualEnvMgr.detect(interpreter);
|
||||
const displayName = this.versionProvider.getVersion(interpreter, path.basename(interpreter));
|
||||
return Promise.all([displayName, virtualEnv])
|
||||
private async getInterpreterDetails(interpreter: string) {
|
||||
return Promise.all([
|
||||
this.versionProvider.getVersion(interpreter, path.basename(interpreter)),
|
||||
this.virtualEnvMgr.detect(interpreter)
|
||||
])
|
||||
.then(([displayName, virtualEnv]) => {
|
||||
displayName += virtualEnv ? ` (${virtualEnv.name})` : '';
|
||||
return {
|
||||
|
@ -39,9 +42,10 @@ export class CurrentPathService implements IInterpreterLocatorService {
|
|||
};
|
||||
});
|
||||
}
|
||||
private getInterpreter(pythonPath: string, defaultValue: string) {
|
||||
private async getInterpreter(pythonPath: string, defaultValue: string) {
|
||||
return new Promise<string>(resolve => {
|
||||
child_process.execFile(pythonPath, ["-c", "import sys;print(sys.executable)"], (_, stdout) => {
|
||||
// tslint:disable-next-line:variable-name
|
||||
child_process.execFile(pythonPath, ['-c', 'import sys;print(sys.executable)'], (_err, stdout) => {
|
||||
resolve(getFirstNonEmptyLineFromMultilineString(stdout));
|
||||
});
|
||||
})
|
||||
|
|
|
@ -1,32 +1,36 @@
|
|||
"use strict";
|
||||
'use strict';
|
||||
import * as _ from 'lodash';
|
||||
import * as path from 'path';
|
||||
import * as settings from './../../../common/configSettings';
|
||||
import { VirtualEnvironmentManager } from '../../virtualEnvs';
|
||||
import { Uri, workspace } from 'vscode';
|
||||
import { fsReaddirAsync, IS_WINDOWS } from '../../../common/utils';
|
||||
import { IInterpreterLocatorService, PythonInterpreter } from '../../contracts';
|
||||
import { IInterpreterVersionService } from '../../interpreterVersion';
|
||||
import { IS_WINDOWS, fsReaddirAsync } from "../../../common/utils";
|
||||
import { VirtualEnvironmentManager } from '../../virtualEnvs';
|
||||
import { lookForInterpretersInDirectory } from '../helpers';
|
||||
import { workspace } from 'vscode';
|
||||
import * as settings from './../../../common/configSettings';
|
||||
// tslint:disable-next-line:no-require-imports no-var-requires
|
||||
const untildify = require('untildify');
|
||||
|
||||
export class VirtualEnvService implements IInterpreterLocatorService {
|
||||
public constructor(private knownSearchPaths: string[],
|
||||
private virtualEnvMgr: VirtualEnvironmentManager,
|
||||
private versionProvider: IInterpreterVersionService) { }
|
||||
public getInterpreters() {
|
||||
public async getInterpreters(resource?: Uri) {
|
||||
return this.suggestionsFromKnownVenvs();
|
||||
}
|
||||
|
||||
private suggestionsFromKnownVenvs() {
|
||||
// tslint:disable-next-line:no-empty
|
||||
public dispose() { }
|
||||
private async suggestionsFromKnownVenvs() {
|
||||
return Promise.all(this.knownSearchPaths.map(dir => this.lookForInterpretersInVenvs(dir)))
|
||||
// tslint:disable-next-line:underscore-consistent-invocation
|
||||
.then(listOfInterpreters => _.flatten(listOfInterpreters));
|
||||
}
|
||||
private lookForInterpretersInVenvs(pathToCheck: string) {
|
||||
private async lookForInterpretersInVenvs(pathToCheck: string) {
|
||||
return fsReaddirAsync(pathToCheck)
|
||||
.then(subDirs => Promise.all(this.getProspectiveDirectoriesForLookup(subDirs)))
|
||||
.then(dirs => dirs.filter(dir => dir.length > 0))
|
||||
.then(dirs => Promise.all(dirs.map(dir => lookForInterpretersInDirectory(dir))))
|
||||
.then(dirs => Promise.all(dirs.map(lookForInterpretersInDirectory)))
|
||||
// tslint:disable-next-line:underscore-consistent-invocation
|
||||
.then(pathsWithInterpreters => _.flatten(pathsWithInterpreters))
|
||||
.then(interpreters => Promise.all(interpreters.map(interpreter => this.getVirtualEnvDetails(interpreter))));
|
||||
}
|
||||
|
@ -41,9 +45,10 @@ export class VirtualEnvService implements IInterpreterLocatorService {
|
|||
}));
|
||||
}
|
||||
private async getVirtualEnvDetails(interpreter: string): Promise<PythonInterpreter> {
|
||||
const displayName = this.versionProvider.getVersion(interpreter, path.basename(interpreter));
|
||||
const virtualEnv = this.virtualEnvMgr.detect(interpreter);
|
||||
return Promise.all([displayName, virtualEnv])
|
||||
return Promise.all([
|
||||
this.versionProvider.getVersion(interpreter, path.basename(interpreter)),
|
||||
this.virtualEnvMgr.detect(interpreter)
|
||||
])
|
||||
.then(([displayName, virtualEnv]) => {
|
||||
const virtualEnvSuffix = virtualEnv ? virtualEnv.name : this.getVirtualEnvironmentRootDirectory(interpreter);
|
||||
return {
|
||||
|
@ -57,20 +62,20 @@ export class VirtualEnvService implements IInterpreterLocatorService {
|
|||
}
|
||||
}
|
||||
|
||||
export function getKnownSearchPathsForVirtualEnvs(): string[] {
|
||||
export function getKnownSearchPathsForVirtualEnvs(resource?: Uri): string[] {
|
||||
const paths: string[] = [];
|
||||
if (!IS_WINDOWS) {
|
||||
const defaultPaths = ['/Envs', '/.virtualenvs', '/.pyenv', '/.pyenv/versions'];
|
||||
defaultPaths.forEach(p => {
|
||||
paths.push(untildify('~' + p));
|
||||
paths.push(untildify(`~${p}`));
|
||||
});
|
||||
}
|
||||
const venvPath = settings.PythonSettings.getInstance().venvPath;
|
||||
const venvPath = settings.PythonSettings.getInstance(resource).venvPath;
|
||||
if (venvPath) {
|
||||
paths.push(untildify(venvPath));
|
||||
}
|
||||
if (workspace.rootPath) {
|
||||
paths.push(workspace.rootPath);
|
||||
if (Array.isArray(workspace.workspaceFolders) && workspace.workspaceFolders.length === 0) {
|
||||
paths.push(workspace.workspaceFolders[0].uri.fsPath);
|
||||
}
|
||||
return paths;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,18 @@
|
|||
import * as path from 'path';
|
||||
import * as _ from 'lodash';
|
||||
import * as fs from 'fs-extra';
|
||||
import * as _ from 'lodash';
|
||||
import * as path from 'path';
|
||||
import { Uri } from 'vscode';
|
||||
import { Architecture, Hive, IRegistry } from '../../../common/registry';
|
||||
import { IInterpreterLocatorService, PythonInterpreter } from '../../contracts';
|
||||
|
||||
// tslint:disable-next-line:variable-name
|
||||
const DefaultPythonExecutable = 'python.exe';
|
||||
// tslint:disable-next-line:variable-name
|
||||
const CompaniesToIgnore = ['PYLAUNCHER'];
|
||||
const PythonCoreCompanyDisplayName = "Python Software Foundation";
|
||||
const PythonCoreComany = "PYTHONCORE";
|
||||
// tslint:disable-next-line:variable-name
|
||||
const PythonCoreCompanyDisplayName = 'Python Software Foundation';
|
||||
// tslint:disable-next-line:variable-name
|
||||
const PythonCoreComany = 'PYTHONCORE';
|
||||
|
||||
type CompanyInterpreter = {
|
||||
companyKey: string,
|
||||
|
@ -19,9 +24,12 @@ export class WindowsRegistryService implements IInterpreterLocatorService {
|
|||
constructor(private registry: IRegistry, private is64Bit: boolean) {
|
||||
|
||||
}
|
||||
public getInterpreters() {
|
||||
// tslint:disable-next-line:variable-name
|
||||
public getInterpreters(_resource?: Uri) {
|
||||
return this.getInterpretersFromRegistry();
|
||||
}
|
||||
// tslint:disable-next-line:no-empty
|
||||
public dispose() { }
|
||||
private async getInterpretersFromRegistry() {
|
||||
// https://github.com/python/peps/blob/master/pep-0514.txt#L357
|
||||
const hkcuArch = this.is64Bit ? undefined : Architecture.x86;
|
||||
|
@ -35,14 +43,17 @@ export class WindowsRegistryService implements IInterpreterLocatorService {
|
|||
}
|
||||
|
||||
const companies = await Promise.all<CompanyInterpreter[]>(promises);
|
||||
// tslint:disable-next-line:underscore-consistent-invocation
|
||||
const companyInterpreters = await Promise.all(_.flatten(companies)
|
||||
.filter(item => item !== undefined && item !== null)
|
||||
.map(company => {
|
||||
return this.getInterpretersForCompany(company.companyKey, company.hive, company.arch);
|
||||
}));
|
||||
|
||||
// tslint:disable-next-line:underscore-consistent-invocation
|
||||
return _.flatten(companyInterpreters)
|
||||
.filter(item => item !== undefined && item !== null)
|
||||
// tslint:disable-next-line:no-non-null-assertion
|
||||
.map(item => item!)
|
||||
.reduce<PythonInterpreter[]>((prev, current) => {
|
||||
if (prev.findIndex(item => item.path.toUpperCase() === current.path.toUpperCase()) === -1) {
|
||||
|
@ -52,7 +63,7 @@ export class WindowsRegistryService implements IInterpreterLocatorService {
|
|||
}, []);
|
||||
}
|
||||
private async getCompanies(hive: Hive, arch?: Architecture): Promise<CompanyInterpreter[]> {
|
||||
return this.registry.getKeys(`\\Software\\Python`, hive, arch)
|
||||
return this.registry.getKeys('\\Software\\Python', hive, arch)
|
||||
.then(companyKeys => companyKeys
|
||||
.filter(companyKey => CompaniesToIgnore.indexOf(path.basename(companyKey).toUpperCase()) === -1)
|
||||
.map(companyKey => {
|
||||
|
@ -84,12 +95,14 @@ export class WindowsRegistryService implements IInterpreterLocatorService {
|
|||
return Promise.all([
|
||||
Promise.resolve(installPath),
|
||||
this.registry.getValue(key, hive, arch, 'ExecutablePath'),
|
||||
this.getInterpreterDisplayName(tagKey, companyKey, hive, arch)!,
|
||||
this.registry.getValue(tagKey, hive, arch, 'Version')!,
|
||||
this.getCompanyDisplayName(companyKey, hive, arch)!
|
||||
// tslint:disable-next-line:no-non-null-assertion
|
||||
this.getInterpreterDisplayName(tagKey, companyKey, hive, arch),
|
||||
this.registry.getValue(tagKey, hive, arch, 'Version'),
|
||||
this.getCompanyDisplayName(companyKey, hive, arch)
|
||||
])
|
||||
.then(([installPath, executablePath, displayName, version, companyDisplayName]) => {
|
||||
return { installPath, executablePath, displayName, version, companyDisplayName } as InterpreterInformation;
|
||||
.then(([installedPath, executablePath, displayName, version, companyDisplayName]) => {
|
||||
// tslint:disable-next-line:prefer-type-cast
|
||||
return { installPath: installedPath, executablePath, displayName, version, companyDisplayName } as InterpreterInformation;
|
||||
});
|
||||
})
|
||||
.then((interpreterInfo?: InterpreterInformation) => {
|
||||
|
@ -100,6 +113,7 @@ export class WindowsRegistryService implements IInterpreterLocatorService {
|
|||
const executablePath = interpreterInfo.executablePath && interpreterInfo.executablePath.length > 0 ? interpreterInfo.executablePath : path.join(interpreterInfo.installPath, DefaultPythonExecutable);
|
||||
const displayName = interpreterInfo.displayName;
|
||||
const version = interpreterInfo.version ? path.basename(interpreterInfo.version) : path.basename(tagKey);
|
||||
// tslint:disable-next-line:prefer-type-cast
|
||||
return {
|
||||
architecture: arch,
|
||||
displayName,
|
||||
|
@ -129,4 +143,4 @@ export class WindowsRegistryService implements IInterpreterLocatorService {
|
|||
const company = path.basename(companyKey);
|
||||
return company.toUpperCase() === PythonCoreComany ? PythonCoreCompanyDisplayName : company;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
"use strict";
|
||||
|
||||
import { SocketClient } from './socketClient';
|
||||
import { SocketServer } from '../common/comms/socketServer';
|
||||
import * as child_process from 'child_process';
|
||||
import * as path from 'path';
|
||||
import * as vscode from 'vscode';
|
||||
import { SocketClient } from './socketClient';
|
||||
import { SocketServer } from '../common/comms/socketServer';
|
||||
import { createDeferred, Deferred } from '../common/helpers';
|
||||
import { PythonSettings } from '../common/configSettings';
|
||||
import { EventEmitter } from 'events';
|
||||
|
@ -80,7 +80,8 @@ export class ClientAdapter extends EventEmitter {
|
|||
this.startSocketServer().then(port => {
|
||||
const def = createDeferred<any>();
|
||||
const options = { env: newEnv, cwd: this.rootDir };
|
||||
this.process = child_process.spawn(PythonSettings.getInstance().pythonPath, [pyFile, port.toString()], options);
|
||||
const rootDirUri = this.rootDir ? vscode.Uri.file(this.rootDir) : undefined;
|
||||
this.process = child_process.spawn(PythonSettings.getInstance(rootDirUri).pythonPath, [pyFile, port.toString()], options);
|
||||
this.process.stdout.setEncoding('utf8');
|
||||
this.process.stderr.setEncoding('utf8');
|
||||
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
import { CompletionItem, SymbolKind, SnippetString } from 'vscode';
|
||||
import * as proxy from '../../providers/jediProxy';
|
||||
import { extractSignatureAndDocumentation } from '../../providers/jediHelpers';
|
||||
import { PythonSettings } from '../../common/configSettings';
|
||||
|
||||
const pythonSettings = PythonSettings.getInstance();
|
||||
import { CompletionItem, SymbolKind, SnippetString, Uri } from 'vscode';
|
||||
|
||||
export class CompletionParser {
|
||||
public static parse(data: proxy.ICompletionResult): CompletionItem[] {
|
||||
public static parse(data: proxy.ICompletionResult, resource: Uri): CompletionItem[] {
|
||||
if (!data || data.items.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
@ -16,7 +14,7 @@ export class CompletionParser {
|
|||
completionItem.kind = item.type;
|
||||
completionItem.documentation = sigAndDocs[1].length === 0 ? item.description : sigAndDocs[1];
|
||||
completionItem.detail = sigAndDocs[0].split(/\r?\n/).join('');
|
||||
if (pythonSettings.autoComplete.addBrackets === true &&
|
||||
if (PythonSettings.getInstance(resource).autoComplete.addBrackets === true &&
|
||||
(item.kind === SymbolKind.Function || item.kind === SymbolKind.Method)) {
|
||||
completionItem.insertText = new SnippetString(item.text).appendText("(").appendTabstop().appendText(")");
|
||||
}
|
||||
|
|
|
@ -70,7 +70,7 @@ export class JupyterClientAdapter extends EventEmitter implements IJupyterClient
|
|||
|
||||
let processStarted = false;
|
||||
let handshakeDone = false;
|
||||
let isInTestRun = newEnv['PYTHON_DONJAYAMANNE_TEST'] === "1";
|
||||
let isInTestRun = newEnv['VSC_PYTHON_CI_TEST'] === "1";
|
||||
const testDef = createDeferred<any>();
|
||||
const promiseToResolve = isInTestRun ? testDef.resolve.bind(testDef) : def.resolve.bind(def);
|
||||
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
import { Disposable, Uri, workspace } from 'vscode';
|
||||
import { JediProxy, JediProxyHandler, ICommandResult } from '../providers/jediProxy';
|
||||
|
||||
export class JediFactory implements Disposable {
|
||||
private disposables: Disposable[];
|
||||
private jediProxyHandlers: Map<string, JediProxyHandler<ICommandResult>>;
|
||||
|
||||
constructor(private extensionRootPath: string) {
|
||||
this.disposables = [];
|
||||
this.jediProxyHandlers = new Map<string, JediProxyHandler<ICommandResult>>();
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
this.disposables.forEach(disposable => disposable.dispose());
|
||||
this.disposables = [];
|
||||
}
|
||||
public getJediProxyHandler<T extends ICommandResult>(resource: Uri): JediProxyHandler<T> {
|
||||
const workspaceFolder = workspace.getWorkspaceFolder(resource);
|
||||
let workspacePath = workspaceFolder ? workspaceFolder.uri.fsPath : undefined;
|
||||
if (!workspacePath) {
|
||||
if (Array.isArray(workspace.workspaceFolders) && workspace.workspaceFolders.length > 0) {
|
||||
workspacePath = workspace.workspaceFolders[0].uri.fsPath;
|
||||
}
|
||||
else {
|
||||
workspacePath = __dirname;
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.jediProxyHandlers.has(workspacePath)) {
|
||||
const jediProxy = new JediProxy(this.extensionRootPath, workspacePath);
|
||||
const jediProxyHandler = new JediProxyHandler(jediProxy);
|
||||
this.disposables.push(jediProxy, jediProxyHandler);
|
||||
this.jediProxyHandlers.set(workspacePath, jediProxyHandler);
|
||||
}
|
||||
// tslint:disable-next-line:no-non-null-assertion
|
||||
return this.jediProxyHandlers.get(workspacePath)! as JediProxyHandler<T>;
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
'use strict';
|
||||
import { IPythonSettings, PythonSettings } from '../common/configSettings';
|
||||
import { execPythonFile } from './../common/utils';
|
||||
import * as settings from './../common/configSettings';
|
||||
import { OutputChannel } from 'vscode';
|
||||
import { OutputChannel, Uri } from 'vscode';
|
||||
import { Installer, Product } from '../common/installer';
|
||||
import * as vscode from 'vscode';
|
||||
import { ErrorHandler } from './errorHandlers/main';
|
||||
|
@ -49,22 +49,26 @@ export function matchNamedRegEx(data, regex): IRegexGroup {
|
|||
|
||||
export abstract class BaseLinter {
|
||||
public Id: string;
|
||||
protected pythonSettings: settings.IPythonSettings;
|
||||
private _workspaceRootPath: string;
|
||||
protected _columnOffset = 0;
|
||||
private _errorHandler: ErrorHandler;
|
||||
protected get workspaceRootPath(): string {
|
||||
return typeof this._workspaceRootPath === 'string' ? this._workspaceRootPath : vscode.workspace.rootPath;
|
||||
private _pythonSettings: IPythonSettings;
|
||||
protected get pythonSettings(): IPythonSettings {
|
||||
return this._pythonSettings;
|
||||
}
|
||||
constructor(id: string, public product: Product, protected outputChannel: OutputChannel, workspaceRootPath: string) {
|
||||
protected getWorkspaceRootPath(document: vscode.TextDocument): string {
|
||||
const workspaceFolder = vscode.workspace.getWorkspaceFolder(document.uri);
|
||||
const workspaceRootPath = (workspaceFolder && typeof workspaceFolder.uri.fsPath === 'string') ? workspaceFolder.uri.fsPath : undefined;
|
||||
return typeof workspaceRootPath === 'string' ? workspaceRootPath : __dirname;
|
||||
}
|
||||
constructor(id: string, public product: Product, protected outputChannel: OutputChannel) {
|
||||
this.Id = id;
|
||||
this._workspaceRootPath = workspaceRootPath;
|
||||
this.pythonSettings = settings.PythonSettings.getInstance();
|
||||
this._errorHandler = new ErrorHandler(this.Id, product, new Installer(), this.outputChannel);
|
||||
}
|
||||
public abstract isEnabled(): Boolean;
|
||||
public abstract runLinter(document: vscode.TextDocument, cancellation: vscode.CancellationToken): Promise<ILintMessage[]>;
|
||||
|
||||
public lint(document: vscode.TextDocument, cancellation: vscode.CancellationToken): Promise<ILintMessage[]> {
|
||||
this._pythonSettings = PythonSettings.getInstance(document.uri);
|
||||
return this.runLinter(document, cancellation);
|
||||
}
|
||||
protected abstract runLinter(document: vscode.TextDocument, cancellation: vscode.CancellationToken): Promise<ILintMessage[]>;
|
||||
protected parseMessagesSeverity(error: string, categorySeverity: any): LintMessageSeverity {
|
||||
if (categorySeverity[error]) {
|
||||
let severityName = categorySeverity[error];
|
||||
|
@ -127,7 +131,7 @@ export abstract class BaseLinter {
|
|||
this.outputChannel.append(data);
|
||||
}
|
||||
protected run(command: string, args: string[], document: vscode.TextDocument, cwd: string, cancellation: vscode.CancellationToken, regEx: string = REGEX): Promise<ILintMessage[]> {
|
||||
return execPythonFile(command, args, cwd, true, null, cancellation).then(data => {
|
||||
return execPythonFile(document.uri, command, args, cwd, true, null, cancellation).then(data => {
|
||||
if (!data) {
|
||||
data = '';
|
||||
}
|
||||
|
@ -135,12 +139,12 @@ export abstract class BaseLinter {
|
|||
let outputLines = data.split(/\r?\n/g);
|
||||
return this.parseLines(outputLines, regEx);
|
||||
}).catch(error => {
|
||||
this.handleError(this.Id, command, error);
|
||||
this.handleError(this.Id, command, error, document.uri);
|
||||
return [];
|
||||
});
|
||||
}
|
||||
|
||||
protected handleError(expectedFileName: string, fileName: string, error: Error) {
|
||||
this._errorHandler.handleError(expectedFileName, fileName, error);
|
||||
protected handleError(expectedFileName: string, fileName: string, error: Error, resource: Uri) {
|
||||
this._errorHandler.handleError(expectedFileName, fileName, error, resource);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
'use strict';
|
||||
import { isNotInstalledError } from '../../common/helpers';
|
||||
import * as vscode from 'vscode';
|
||||
import { Uri, window } from 'vscode';
|
||||
import { StandardErrorHandler } from './standard';
|
||||
|
||||
export class InvalidArgumentsErrorHandler extends StandardErrorHandler {
|
||||
|
@ -13,14 +13,14 @@ export class InvalidArgumentsErrorHandler extends StandardErrorHandler {
|
|||
|
||||
private displayInvalidArgsError() {
|
||||
// Ok if we have a space after the file name, this means we have some arguments defined and this isn't supported
|
||||
vscode.window.showErrorMessage(`Unsupported configuration for '${this.id}'`, 'View Errors').then(item => {
|
||||
window.showErrorMessage(`Unsupported configuration for '${this.id}'`, 'View Errors').then(item => {
|
||||
if (item === 'View Errors') {
|
||||
this.outputChannel.show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public handleError(expectedFileName: string, fileName: string, error: Error): boolean {
|
||||
public handleError(expectedFileName: string, fileName: string, error: Error, resource?: Uri): boolean {
|
||||
if (!isNotInstalledError(error)) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
'use strict';
|
||||
import { OutputChannel } from 'vscode';
|
||||
import { OutputChannel, Uri } from 'vscode';
|
||||
import { Installer, Product } from '../../common/installer';
|
||||
import { InvalidArgumentsErrorHandler } from './invalidArgs';
|
||||
import { StandardErrorHandler } from './standard';
|
||||
import { NotInstalledErrorHandler } from './notInstalled';
|
||||
import { StandardErrorHandler } from './standard';
|
||||
|
||||
export class ErrorHandler {
|
||||
// tslint:disable-next-line:variable-name
|
||||
private _errorHandlers: StandardErrorHandler[] = [];
|
||||
constructor(protected id: string, protected product: Product, protected installer: Installer, protected outputChannel: OutputChannel) {
|
||||
this._errorHandlers = [
|
||||
|
@ -15,7 +16,7 @@ export class ErrorHandler {
|
|||
];
|
||||
}
|
||||
|
||||
public handleError(expectedFileName: string, fileName: string, error: Error) {
|
||||
this._errorHandlers.some(handler => handler.handleError(expectedFileName, fileName, error));
|
||||
public handleError(expectedFileName: string, fileName: string, error: Error, resource: Uri) {
|
||||
this._errorHandlers.some(handler => handler.handleError(expectedFileName, fileName, error, resource));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
'use strict';
|
||||
import { Uri } from 'vscode';
|
||||
import { isNotInstalledError } from '../../common/helpers';
|
||||
import { StandardErrorHandler } from './standard';
|
||||
|
||||
export class NotInstalledErrorHandler extends StandardErrorHandler {
|
||||
public handleError(expectedFileName: string, fileName: string, error: Error): boolean {
|
||||
public handleError(expectedFileName: string, fileName: string, error: Error, resource?: Uri): boolean {
|
||||
if (!isNotInstalledError(error)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.installer.promptToInstall(this.product);
|
||||
this.installer.promptToInstall(this.product, resource);
|
||||
const customError = `Linting with ${this.id} failed.\nYou could either install the '${this.id}' linter or turn it off in setings.json via "python.linting.${this.id}Enabled = false".`;
|
||||
this.outputChannel.appendLine(`\n${customError}\n${error + ''}`);
|
||||
return true;
|
||||
|
|
|
@ -1,18 +1,17 @@
|
|||
'use strict';
|
||||
import { OutputChannel } from 'vscode';
|
||||
import { Installer, Product, disableLinter } from '../../common/installer';
|
||||
import * as vscode from 'vscode';
|
||||
import { OutputChannel, Uri, window } from 'vscode';
|
||||
import { Installer, Product } from '../../common/installer';
|
||||
|
||||
export class StandardErrorHandler {
|
||||
constructor(protected id: string, protected product: Product, protected installer: Installer, protected outputChannel: OutputChannel) {
|
||||
|
||||
}
|
||||
private displayLinterError() {
|
||||
private displayLinterError(resource: Uri) {
|
||||
const message = `There was an error in running the linter '${this.id}'`;
|
||||
vscode.window.showErrorMessage(message, 'Disable linter', 'View Errors').then(item => {
|
||||
window.showErrorMessage(message, 'Disable linter', 'View Errors').then(item => {
|
||||
switch (item) {
|
||||
case 'Disable linter': {
|
||||
disableLinter(this.product);
|
||||
this.installer.disableLinter(this.product, resource);
|
||||
break;
|
||||
}
|
||||
case 'View Errors': {
|
||||
|
@ -23,7 +22,7 @@ export class StandardErrorHandler {
|
|||
});
|
||||
}
|
||||
|
||||
public handleError(expectedFileName: string, fileName: string, error: Error): boolean {
|
||||
public handleError(expectedFileName: string, fileName: string, error: Error, resource: Uri): boolean {
|
||||
if (typeof error === 'string' && (error as string).indexOf("OSError: [Errno 2] No such file or directory: '/") > 0) {
|
||||
return false;
|
||||
}
|
||||
|
@ -31,7 +30,7 @@ export class StandardErrorHandler {
|
|||
console.error(error);
|
||||
|
||||
this.outputChannel.appendLine(`Linting with ${this.id} failed.\n${error + ''}`);
|
||||
this.displayLinterError();
|
||||
this.displayLinterError(resource);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,14 +8,11 @@ import { TextDocument, CancellationToken } from 'vscode';
|
|||
export class Linter extends baseLinter.BaseLinter {
|
||||
_columnOffset = 1;
|
||||
|
||||
constructor(outputChannel: OutputChannel, workspaceRootPath?: string) {
|
||||
super('flake8', Product.flake8, outputChannel, workspaceRootPath);
|
||||
constructor(outputChannel: OutputChannel) {
|
||||
super('flake8', Product.flake8, outputChannel);
|
||||
}
|
||||
|
||||
public isEnabled(): Boolean {
|
||||
return this.pythonSettings.linting.flake8Enabled;
|
||||
}
|
||||
public runLinter(document: TextDocument, cancellation: CancellationToken): Promise<baseLinter.ILintMessage[]> {
|
||||
protected runLinter(document: TextDocument, cancellation: CancellationToken): Promise<baseLinter.ILintMessage[]> {
|
||||
if (!this.pythonSettings.linting.flake8Enabled) {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
|
@ -29,7 +26,7 @@ export class Linter extends baseLinter.BaseLinter {
|
|||
}
|
||||
|
||||
return new Promise<baseLinter.ILintMessage[]>((resolve, reject) => {
|
||||
this.run(flake8Path, flake8Args.concat(['--format=%(row)d,%(col)d,%(code).1s,%(code)s:%(text)s', document.uri.fsPath]), document, this.workspaceRootPath, cancellation).then(messages => {
|
||||
this.run(flake8Path, flake8Args.concat(['--format=%(row)d,%(col)d,%(code).1s,%(code)s:%(text)s', document.uri.fsPath]), document, this.getWorkspaceRootPath(document), cancellation).then(messages => {
|
||||
messages.forEach(msg => {
|
||||
msg.severity = this.parseMessagesSeverity(msg.type, this.pythonSettings.linting.flake8CategorySeverity);
|
||||
});
|
||||
|
|
|
@ -12,32 +12,32 @@ import * as pydocstyle from './../linters/pydocstyle';
|
|||
import * as mypy from './../linters/mypy';
|
||||
|
||||
export class LinterFactor {
|
||||
public static createLinter(product: Product, outputChannel: OutputChannel, workspaceRootPath: string = workspace.rootPath): BaseLinter {
|
||||
public static createLinter(product: Product, outputChannel: OutputChannel): BaseLinter {
|
||||
switch (product) {
|
||||
case Product.flake8: {
|
||||
return new flake8.Linter(outputChannel, workspaceRootPath);
|
||||
return new flake8.Linter(outputChannel);
|
||||
}
|
||||
case Product.mypy: {
|
||||
return new mypy.Linter(outputChannel, workspaceRootPath);
|
||||
return new mypy.Linter(outputChannel);
|
||||
}
|
||||
case Product.pep8: {
|
||||
return new pep8.Linter(outputChannel, workspaceRootPath);
|
||||
return new pep8.Linter(outputChannel);
|
||||
}
|
||||
case Product.prospector: {
|
||||
return new prospector.Linter(outputChannel, workspaceRootPath);
|
||||
return new prospector.Linter(outputChannel);
|
||||
}
|
||||
case Product.pydocstyle: {
|
||||
return new pydocstyle.Linter(outputChannel, workspaceRootPath);
|
||||
return new pydocstyle.Linter(outputChannel);
|
||||
}
|
||||
case Product.pylama: {
|
||||
return new pylama.Linter(outputChannel, workspaceRootPath);
|
||||
return new pylama.Linter(outputChannel);
|
||||
}
|
||||
case Product.pylint: {
|
||||
return new pylint.Linter(outputChannel, workspaceRootPath);
|
||||
return new pylint.Linter(outputChannel);
|
||||
}
|
||||
default: {
|
||||
throw new Error(`Invalid Linter '${Product[product]}''`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,14 +8,11 @@ import { TextDocument, CancellationToken } from 'vscode';
|
|||
const REGEX = '(?<file>.py):(?<line>\\d+): (?<type>\\w+): (?<message>.*)\\r?(\\n|$)';
|
||||
|
||||
export class Linter extends baseLinter.BaseLinter {
|
||||
constructor(outputChannel: OutputChannel, workspaceRootPath?: string) {
|
||||
super('mypy', Product.mypy, outputChannel, workspaceRootPath);
|
||||
constructor(outputChannel: OutputChannel) {
|
||||
super('mypy', Product.mypy, outputChannel);
|
||||
}
|
||||
|
||||
public isEnabled(): Boolean {
|
||||
return this.pythonSettings.linting.mypyEnabled;
|
||||
}
|
||||
public runLinter(document: TextDocument, cancellation: CancellationToken): Promise<baseLinter.ILintMessage[]> {
|
||||
protected runLinter(document: TextDocument, cancellation: CancellationToken): Promise<baseLinter.ILintMessage[]> {
|
||||
if (!this.pythonSettings.linting.mypyEnabled) {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
|
@ -29,7 +26,7 @@ export class Linter extends baseLinter.BaseLinter {
|
|||
}
|
||||
|
||||
return new Promise<baseLinter.ILintMessage[]>((resolve, reject) => {
|
||||
this.run(mypyPath, mypyArgs.concat([document.uri.fsPath]), document, this.workspaceRootPath, cancellation, REGEX).then(messages => {
|
||||
this.run(mypyPath, mypyArgs.concat([document.uri.fsPath]), document, this.getWorkspaceRootPath(document), cancellation, REGEX).then(messages => {
|
||||
messages.forEach(msg => {
|
||||
msg.severity = this.parseMessagesSeverity(msg.type, this.pythonSettings.linting.mypyCategorySeverity);
|
||||
msg.code = msg.type;
|
||||
|
@ -39,4 +36,4 @@ export class Linter extends baseLinter.BaseLinter {
|
|||
}, reject);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,29 +7,26 @@ import { TextDocument, CancellationToken } from 'vscode';
|
|||
|
||||
export class Linter extends baseLinter.BaseLinter {
|
||||
_columnOffset = 1;
|
||||
|
||||
constructor(outputChannel: OutputChannel, workspaceRootPath?: string) {
|
||||
super('pep8', Product.pep8, outputChannel, workspaceRootPath);
|
||||
|
||||
constructor(outputChannel: OutputChannel) {
|
||||
super('pep8', Product.pep8, outputChannel);
|
||||
}
|
||||
|
||||
public isEnabled(): Boolean {
|
||||
return this.pythonSettings.linting.pep8Enabled;
|
||||
}
|
||||
public runLinter(document: TextDocument, cancellation: CancellationToken): Promise<baseLinter.ILintMessage[]> {
|
||||
protected runLinter(document: TextDocument, cancellation: CancellationToken): Promise<baseLinter.ILintMessage[]> {
|
||||
if (!this.pythonSettings.linting.pep8Enabled) {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
|
||||
let pep8Path = this.pythonSettings.linting.pep8Path;
|
||||
let pep8Args = Array.isArray(this.pythonSettings.linting.pep8Args) ? this.pythonSettings.linting.pep8Args : [];
|
||||
|
||||
if (pep8Args.length === 0 && ProductExecutableAndArgs.has(Product.pep8) && pep8Path.toLocaleLowerCase() === 'pep8'){
|
||||
|
||||
if (pep8Args.length === 0 && ProductExecutableAndArgs.has(Product.pep8) && pep8Path.toLocaleLowerCase() === 'pep8') {
|
||||
pep8Path = ProductExecutableAndArgs.get(Product.pep8).executable;
|
||||
pep8Args = ProductExecutableAndArgs.get(Product.pep8).args;
|
||||
}
|
||||
|
||||
return new Promise<baseLinter.ILintMessage[]>(resolve => {
|
||||
this.run(pep8Path, pep8Args.concat(['--format=%(row)d,%(col)d,%(code).1s,%(code)s:%(text)s', document.uri.fsPath]), document, this.workspaceRootPath, cancellation).then(messages => {
|
||||
this.run(pep8Path, pep8Args.concat(['--format=%(row)d,%(col)d,%(code).1s,%(code)s:%(text)s', document.uri.fsPath]), document, this.getWorkspaceRootPath(document), cancellation).then(messages => {
|
||||
messages.forEach(msg => {
|
||||
msg.severity = this.parseMessagesSeverity(msg.type, this.pythonSettings.linting.pep8CategorySeverity);
|
||||
});
|
||||
|
|
|
@ -24,14 +24,11 @@ interface IProspectorLocation {
|
|||
}
|
||||
|
||||
export class Linter extends baseLinter.BaseLinter {
|
||||
constructor(outputChannel: OutputChannel, workspaceRootPath?: string) {
|
||||
super('prospector', Product.prospector, outputChannel, workspaceRootPath);
|
||||
constructor(outputChannel: OutputChannel) {
|
||||
super('prospector', Product.prospector, outputChannel);
|
||||
}
|
||||
|
||||
public isEnabled(): Boolean {
|
||||
return this.pythonSettings.linting.prospectorEnabled;
|
||||
}
|
||||
public runLinter(document: TextDocument, cancellation: CancellationToken): Promise<baseLinter.ILintMessage[]> {
|
||||
protected runLinter(document: TextDocument, cancellation: CancellationToken): Promise<baseLinter.ILintMessage[]> {
|
||||
if (!this.pythonSettings.linting.prospectorEnabled) {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
|
@ -39,14 +36,14 @@ export class Linter extends baseLinter.BaseLinter {
|
|||
let prospectorPath = this.pythonSettings.linting.prospectorPath;
|
||||
let outputChannel = this.outputChannel;
|
||||
let prospectorArgs = Array.isArray(this.pythonSettings.linting.prospectorArgs) ? this.pythonSettings.linting.prospectorArgs : [];
|
||||
|
||||
if (prospectorArgs.length === 0 && ProductExecutableAndArgs.has(Product.prospector) && prospectorPath.toLocaleLowerCase() === 'prospector'){
|
||||
|
||||
if (prospectorArgs.length === 0 && ProductExecutableAndArgs.has(Product.prospector) && prospectorPath.toLocaleLowerCase() === 'prospector') {
|
||||
prospectorPath = ProductExecutableAndArgs.get(Product.prospector).executable;
|
||||
prospectorArgs = ProductExecutableAndArgs.get(Product.prospector).args;
|
||||
}
|
||||
|
||||
return new Promise<baseLinter.ILintMessage[]>((resolve, reject) => {
|
||||
execPythonFile(prospectorPath, prospectorArgs.concat(['--absolute-paths', '--output-format=json', document.uri.fsPath]), this.workspaceRootPath, false, null, cancellation).then(data => {
|
||||
execPythonFile(document.uri, prospectorPath, prospectorArgs.concat(['--absolute-paths', '--output-format=json', document.uri.fsPath]), this.getWorkspaceRootPath(document), false, null, cancellation).then(data => {
|
||||
let parsedData: IProspectorResponse;
|
||||
try {
|
||||
parsedData = JSON.parse(data);
|
||||
|
@ -73,7 +70,7 @@ export class Linter extends baseLinter.BaseLinter {
|
|||
|
||||
resolve(diagnostics);
|
||||
}).catch(error => {
|
||||
this.handleError(this.Id, prospectorPath, error);
|
||||
this.handleError(this.Id, prospectorPath, error, document.uri);
|
||||
resolve([]);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -9,14 +9,11 @@ import { Product, ProductExecutableAndArgs } from '../common/installer';
|
|||
import { TextDocument, CancellationToken } from 'vscode';
|
||||
|
||||
export class Linter extends baseLinter.BaseLinter {
|
||||
constructor(outputChannel: OutputChannel, workspaceRootPath?: string) {
|
||||
super('pydocstyle', Product.pydocstyle, outputChannel, workspaceRootPath);
|
||||
constructor(outputChannel: OutputChannel) {
|
||||
super('pydocstyle', Product.pydocstyle, outputChannel);
|
||||
}
|
||||
|
||||
public isEnabled(): Boolean {
|
||||
return this.pythonSettings.linting.pydocstyleEnabled;
|
||||
}
|
||||
public runLinter(document: TextDocument, cancellation: CancellationToken): Promise<baseLinter.ILintMessage[]> {
|
||||
protected runLinter(document: TextDocument, cancellation: CancellationToken): Promise<baseLinter.ILintMessage[]> {
|
||||
if (!this.pythonSettings.linting.pydocstyleEnabled) {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
|
@ -45,7 +42,7 @@ export class Linter extends baseLinter.BaseLinter {
|
|||
let outputChannel = this.outputChannel;
|
||||
|
||||
return new Promise<ILintMessage[]>((resolve, reject) => {
|
||||
execPythonFile(commandLine, args, this.workspaceRootPath, true, null, cancellation).then(data => {
|
||||
execPythonFile(document.uri, commandLine, args, this.getWorkspaceRootPath(document), true, null, cancellation).then(data => {
|
||||
outputChannel.append('#'.repeat(10) + 'Linting Output - ' + this.Id + '#'.repeat(10) + '\n');
|
||||
outputChannel.append(data);
|
||||
let outputLines = data.split(/\r?\n/g);
|
||||
|
@ -102,7 +99,7 @@ export class Linter extends baseLinter.BaseLinter {
|
|||
});
|
||||
resolve(diagnostics);
|
||||
}, error => {
|
||||
this.handleError(this.Id, commandLine, error);
|
||||
this.handleError(this.Id, commandLine, error, document.uri);
|
||||
resolve([]);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -10,14 +10,11 @@ const REGEX = '(?<file>.py):(?<line>\\d+):(?<column>\\d+): \\[(?<type>\\w+)\\] (
|
|||
export class Linter extends baseLinter.BaseLinter {
|
||||
_columnOffset = 1;
|
||||
|
||||
constructor(outputChannel: OutputChannel, workspaceRootPath?: string) {
|
||||
super('pylama', Product.pylama, outputChannel, workspaceRootPath);
|
||||
constructor(outputChannel: OutputChannel) {
|
||||
super('pylama', Product.pylama, outputChannel);
|
||||
}
|
||||
|
||||
public isEnabled(): Boolean {
|
||||
return this.pythonSettings.linting.pylamaEnabled;
|
||||
}
|
||||
public runLinter(document: TextDocument, cancellation: CancellationToken): Promise<baseLinter.ILintMessage[]> {
|
||||
protected runLinter(document: TextDocument, cancellation: CancellationToken): Promise<baseLinter.ILintMessage[]> {
|
||||
if (!this.pythonSettings.linting.pylamaEnabled) {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
|
@ -31,7 +28,7 @@ export class Linter extends baseLinter.BaseLinter {
|
|||
}
|
||||
|
||||
return new Promise<baseLinter.ILintMessage[]>(resolve => {
|
||||
this.run(pylamaPath, pylamaArgs.concat(['--format=parsable', document.uri.fsPath]), document, this.workspaceRootPath, cancellation, REGEX).then(messages => {
|
||||
this.run(pylamaPath, pylamaArgs.concat(['--format=parsable', document.uri.fsPath]), document, this.getWorkspaceRootPath(document), cancellation, REGEX).then(messages => {
|
||||
// All messages in pylama are treated as warnings for now
|
||||
messages.forEach(msg => {
|
||||
msg.severity = baseLinter.LintMessageSeverity.Information;
|
||||
|
|
|
@ -6,14 +6,11 @@ import { Product, ProductExecutableAndArgs } from '../common/installer';
|
|||
import { TextDocument, CancellationToken } from 'vscode';
|
||||
|
||||
export class Linter extends baseLinter.BaseLinter {
|
||||
constructor(outputChannel: OutputChannel, workspaceRootPath?: string) {
|
||||
super('pylint', Product.pylint, outputChannel, workspaceRootPath);
|
||||
constructor(outputChannel: OutputChannel) {
|
||||
super('pylint', Product.pylint, outputChannel);
|
||||
}
|
||||
|
||||
public isEnabled(): Boolean {
|
||||
return this.pythonSettings.linting.pylintEnabled;
|
||||
}
|
||||
public runLinter(document: TextDocument, cancellation: CancellationToken): Promise<baseLinter.ILintMessage[]> {
|
||||
protected runLinter(document: TextDocument, cancellation: CancellationToken): Promise<baseLinter.ILintMessage[]> {
|
||||
if (!this.pythonSettings.linting.pylintEnabled) {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
|
@ -27,7 +24,7 @@ export class Linter extends baseLinter.BaseLinter {
|
|||
}
|
||||
|
||||
return new Promise<baseLinter.ILintMessage[]>((resolve, reject) => {
|
||||
this.run(pylintPath, pylintArgs.concat(['--msg-template=\'{line},{column},{category},{msg_id}:{msg}\'', '--reports=n', '--output-format=text', document.uri.fsPath]), document, this.workspaceRootPath, cancellation).then(messages => {
|
||||
this.run(pylintPath, pylintArgs.concat(['--msg-template=\'{line},{column},{category},{msg_id}:{msg}\'', '--reports=n', '--output-format=text', document.uri.fsPath]), document, this.getWorkspaceRootPath(document), cancellation).then(messages => {
|
||||
messages.forEach(msg => {
|
||||
msg.severity = this.parseMessagesSeverity(msg.type, this.pythonSettings.linting.pylintCategorySeverity);
|
||||
});
|
||||
|
@ -36,4 +33,4 @@ export class Linter extends baseLinter.BaseLinter {
|
|||
}, reject);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,17 +7,13 @@ import * as telemetryContracts from '../common/telemetryContracts';
|
|||
import { extractSignatureAndDocumentation } from './jediHelpers';
|
||||
import { EOL } from 'os';
|
||||
import { PythonSettings } from '../common/configSettings';
|
||||
import { SnippetString } from 'vscode';
|
||||
|
||||
const pythonSettings = PythonSettings.getInstance();
|
||||
import { SnippetString, Uri } from 'vscode';
|
||||
import { JediFactory } from '../languageServices/jediProxyFactory';
|
||||
|
||||
export class PythonCompletionItemProvider implements vscode.CompletionItemProvider {
|
||||
private jediProxyHandler: proxy.JediProxyHandler<proxy.ICompletionResult>;
|
||||
|
||||
public constructor(context: vscode.ExtensionContext, jediProxy: proxy.JediProxy = null) {
|
||||
this.jediProxyHandler = new proxy.JediProxyHandler(context, jediProxy);
|
||||
}
|
||||
private static parseData(data: proxy.ICompletionResult): vscode.CompletionItem[] {
|
||||
public constructor(private jediFactory: JediFactory) { }
|
||||
private static parseData(data: proxy.ICompletionResult, resource: Uri): vscode.CompletionItem[] {
|
||||
if (data && data.items.length > 0) {
|
||||
return data.items.map(item => {
|
||||
const sigAndDocs = extractSignatureAndDocumentation(item);
|
||||
|
@ -25,7 +21,7 @@ export class PythonCompletionItemProvider implements vscode.CompletionItemProvid
|
|||
completionItem.kind = item.type;
|
||||
completionItem.documentation = sigAndDocs[1].length === 0 ? item.description : sigAndDocs[1];
|
||||
completionItem.detail = sigAndDocs[0].split(/\r?\n/).join('');
|
||||
if (pythonSettings.autoComplete.addBrackets === true &&
|
||||
if (PythonSettings.getInstance(resource).autoComplete.addBrackets === true &&
|
||||
(item.kind === vscode.SymbolKind.Function || item.kind === vscode.SymbolKind.Method)) {
|
||||
completionItem.insertText = new SnippetString(item.text).appendText("(").appendTabstop().appendText(")");
|
||||
}
|
||||
|
@ -67,10 +63,10 @@ export class PythonCompletionItemProvider implements vscode.CompletionItemProvid
|
|||
};
|
||||
|
||||
const timer = new telemetryHelper.Delays();
|
||||
return this.jediProxyHandler.sendCommand(cmd, token).then(data => {
|
||||
return this.jediFactory.getJediProxyHandler<proxy.ICompletionResult>(document.uri).sendCommand(cmd, token).then(data => {
|
||||
timer.stop();
|
||||
telemetryHelper.sendTelemetryEvent(telemetryContracts.IDE.Completion, {}, timer.toMeasures());
|
||||
const completions = PythonCompletionItemProvider.parseData(data);
|
||||
const completions = PythonCompletionItemProvider.parseData(data, document.uri);
|
||||
return completions;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -2,17 +2,11 @@
|
|||
|
||||
import * as vscode from 'vscode';
|
||||
import * as proxy from './jediProxy';
|
||||
import * as telemetryContracts from "../common/telemetryContracts";
|
||||
import * as telemetryContracts from '../common/telemetryContracts';
|
||||
import { JediFactory } from '../languageServices/jediProxyFactory';
|
||||
|
||||
export class PythonDefinitionProvider implements vscode.DefinitionProvider {
|
||||
private jediProxyHandler: proxy.JediProxyHandler<proxy.IDefinitionResult>;
|
||||
public get JediProxy(): proxy.JediProxy {
|
||||
return this.jediProxyHandler.JediProxy;
|
||||
}
|
||||
|
||||
public constructor(context: vscode.ExtensionContext) {
|
||||
this.jediProxyHandler = new proxy.JediProxyHandler(context);
|
||||
}
|
||||
public constructor(private jediFactory: JediFactory) { }
|
||||
private static parseData(data: proxy.IDefinitionResult, possibleWord: string): vscode.Definition {
|
||||
if (data && Array.isArray(data.definitions) && data.definitions.length > 0) {
|
||||
const definitions = data.definitions.filter(d => d.text === possibleWord);
|
||||
|
@ -46,7 +40,7 @@ export class PythonDefinitionProvider implements vscode.DefinitionProvider {
|
|||
cmd.source = document.getText();
|
||||
}
|
||||
let possibleWord = document.getText(range);
|
||||
return this.jediProxyHandler.sendCommand(cmd, token).then(data => {
|
||||
return this.jediFactory.getJediProxyHandler<proxy.IDefinitionResult>(document.uri).sendCommand(cmd, token).then(data => {
|
||||
return PythonDefinitionProvider.parseData(data, possibleWord);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
'use strict';
|
||||
import * as fs from 'fs-extra';
|
||||
import { EOL } from 'os';
|
||||
import * as path from 'path';
|
||||
import * as vscode from 'vscode';
|
||||
import { Disposable, workspace } from 'vscode';
|
||||
import * as settings from '../common/configSettings';
|
||||
import { Commands, PythonLanguage } from '../common/constants';
|
||||
import { EOL } from 'os';
|
||||
let path = require('path');
|
||||
let terminal: vscode.Terminal;
|
||||
import { ContextKey } from '../common/contextKey';
|
||||
import { IS_WINDOWS } from '../common/utils';
|
||||
|
||||
let terminal: vscode.Terminal;
|
||||
export function activateExecInTerminalProvider(): vscode.Disposable[] {
|
||||
const disposables: vscode.Disposable[] = [];
|
||||
disposables.push(vscode.commands.registerCommand(Commands.Exec_In_Terminal, execInTerminal));
|
||||
|
@ -17,13 +20,14 @@ export function activateExecInTerminalProvider(): vscode.Disposable[] {
|
|||
terminal = null;
|
||||
}
|
||||
}));
|
||||
disposables.push(new DjangoContextInitializer());
|
||||
return disposables;
|
||||
}
|
||||
|
||||
function removeBlankLines(code: string): string {
|
||||
let codeLines = code.split(/\r?\n/g);
|
||||
let codeLinesWithoutEmptyLines = codeLines.filter(line => line.trim().length > 0);
|
||||
let lastLineIsEmpty = codeLines.length > 0 && codeLines[codeLines.length - 1].trim().length === 0;
|
||||
const codeLines = code.split(/\r?\n/g);
|
||||
const codeLinesWithoutEmptyLines = codeLines.filter(line => line.trim().length > 0);
|
||||
const lastLineIsEmpty = codeLines.length > 0 && codeLines[codeLines.length - 1].trim().length === 0;
|
||||
if (lastLineIsEmpty) {
|
||||
codeLinesWithoutEmptyLines.unshift('');
|
||||
}
|
||||
|
@ -31,9 +35,10 @@ function removeBlankLines(code: string): string {
|
|||
}
|
||||
function execInTerminal(fileUri?: vscode.Uri) {
|
||||
const terminalShellSettings = vscode.workspace.getConfiguration('terminal.integrated.shell');
|
||||
// tslint:disable-next-line:no-backbone-get-set-outside-model
|
||||
const IS_POWERSHELL = /powershell/.test(terminalShellSettings.get<string>('windows'));
|
||||
|
||||
let pythonSettings = settings.PythonSettings.getInstance();
|
||||
const pythonSettings = settings.PythonSettings.getInstance(fileUri);
|
||||
let filePath: string;
|
||||
|
||||
let currentPythonPath = pythonSettings.pythonPath;
|
||||
|
@ -67,137 +72,191 @@ function execInTerminal(fileUri?: vscode.Uri) {
|
|||
filePath = `"${filePath}"`;
|
||||
}
|
||||
|
||||
terminal = terminal ? terminal : vscode.window.createTerminal(`Python`);
|
||||
terminal = terminal ? terminal : vscode.window.createTerminal('Python');
|
||||
if (pythonSettings.terminal && pythonSettings.terminal.executeInFileDir) {
|
||||
const fileDirPath = path.dirname(filePath);
|
||||
if (fileDirPath !== vscode.workspace.rootPath && fileDirPath.substring(1) !== vscode.workspace.rootPath) {
|
||||
const wkspace = vscode.workspace.getWorkspaceFolder(vscode.Uri.file(filePath));
|
||||
if (wkspace && fileDirPath !== wkspace.uri.fsPath && fileDirPath.substring(1) !== wkspace.uri.fsPath) {
|
||||
terminal.sendText(`cd "${fileDirPath}"`);
|
||||
}
|
||||
}
|
||||
const launchArgs = settings.PythonSettings.getInstance().terminal.launchArgs;
|
||||
const launchArgsString = launchArgs.length > 0 ? " ".concat(launchArgs.join(" ")) : "";
|
||||
const launchArgs = settings.PythonSettings.getInstance(fileUri).terminal.launchArgs;
|
||||
const launchArgsString = launchArgs.length > 0 ? ' '.concat(launchArgs.join(' ')) : '';
|
||||
const command = `${currentPythonPath}${launchArgsString} ${filePath}`;
|
||||
if (IS_WINDOWS) {
|
||||
const commandWin = command.replace(/\\/g, "/");
|
||||
const commandWin = command.replace(/\\/g, '/');
|
||||
if (IS_POWERSHELL) {
|
||||
terminal.sendText(`& ${commandWin}`);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
terminal.sendText(commandWin);
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
terminal.sendText(command);
|
||||
}
|
||||
terminal.show();
|
||||
}
|
||||
|
||||
function execSelectionInTerminal() {
|
||||
const terminalShellSettings = vscode.workspace.getConfiguration('terminal.integrated.shell');
|
||||
const IS_POWERSHELL = /powershell/.test(terminalShellSettings.get<string>('windows'));
|
||||
|
||||
let currentPythonPath = settings.PythonSettings.getInstance().pythonPath;
|
||||
if (currentPythonPath.indexOf(' ') > 0) {
|
||||
currentPythonPath = `"${currentPythonPath}"`;
|
||||
}
|
||||
|
||||
const activeEditor = vscode.window.activeTextEditor;
|
||||
if (!activeEditor) {
|
||||
return;
|
||||
}
|
||||
|
||||
const terminalShellSettings = vscode.workspace.getConfiguration('terminal.integrated.shell');
|
||||
// tslint:disable-next-line:no-backbone-get-set-outside-model
|
||||
const IS_POWERSHELL = /powershell/.test(terminalShellSettings.get<string>('windows'));
|
||||
|
||||
let currentPythonPath = settings.PythonSettings.getInstance(activeEditor.document.uri).pythonPath;
|
||||
if (currentPythonPath.indexOf(' ') > 0) {
|
||||
currentPythonPath = `"${currentPythonPath}"`;
|
||||
}
|
||||
|
||||
const selection = vscode.window.activeTextEditor.selection;
|
||||
let code: string;
|
||||
if (selection.isEmpty) {
|
||||
code = vscode.window.activeTextEditor.document.lineAt(selection.start.line).text;
|
||||
}
|
||||
else {
|
||||
let textRange = new vscode.Range(selection.start, selection.end);
|
||||
} else {
|
||||
const textRange = new vscode.Range(selection.start, selection.end);
|
||||
code = vscode.window.activeTextEditor.document.getText(textRange);
|
||||
}
|
||||
if (code.length === 0) {
|
||||
return;
|
||||
}
|
||||
const launchArgs = settings.PythonSettings.getInstance().terminal.launchArgs;
|
||||
const launchArgsString = launchArgs.length > 0 ? " ".concat(launchArgs.join(" ")) : "";
|
||||
code = removeBlankLines(code);
|
||||
const launchArgs = settings.PythonSettings.getInstance(activeEditor.document.uri).terminal.launchArgs;
|
||||
const launchArgsString = launchArgs.length > 0 ? ' '.concat(launchArgs.join(' ')) : '';
|
||||
const command = `${currentPythonPath}${launchArgsString}`;
|
||||
if (!terminal) {
|
||||
terminal = vscode.window.createTerminal(`Python`);
|
||||
terminal = vscode.window.createTerminal('Python');
|
||||
if (IS_WINDOWS) {
|
||||
const commandWin = command.replace(/\\/g, "/");
|
||||
const commandWin = command.replace(/\\/g, '/');
|
||||
if (IS_POWERSHELL) {
|
||||
terminal.sendText(`& ${commandWin}`);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
terminal.sendText(commandWin);
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
terminal.sendText(command);
|
||||
}
|
||||
}
|
||||
const unix_code = code.replace(/\r\n/g, "\n");
|
||||
// tslint:disable-next-line:variable-name
|
||||
const unix_code = code.replace(/\r\n/g, '\n');
|
||||
if (IS_WINDOWS) {
|
||||
terminal.sendText(unix_code.replace(/\n/g, "\r\n"));
|
||||
}
|
||||
else {
|
||||
terminal.sendText(unix_code.replace(/\n/g, '\r\n'));
|
||||
} else {
|
||||
terminal.sendText(unix_code);
|
||||
}
|
||||
terminal.show();
|
||||
}
|
||||
|
||||
function execSelectionInDjangoShell() {
|
||||
const terminalShellSettings = vscode.workspace.getConfiguration('terminal.integrated.shell');
|
||||
const IS_POWERSHELL = /powershell/.test(terminalShellSettings.get<string>('windows'));
|
||||
|
||||
let currentPythonPath = settings.PythonSettings.getInstance().pythonPath;
|
||||
if (currentPythonPath.indexOf(' ') > 0) {
|
||||
currentPythonPath = `"${currentPythonPath}"`;
|
||||
}
|
||||
|
||||
const activeEditor = vscode.window.activeTextEditor;
|
||||
if (!activeEditor) {
|
||||
return;
|
||||
}
|
||||
|
||||
const workspaceRoot = vscode.workspace.rootPath;
|
||||
const djangoShellCmd = `"${workspaceRoot}/manage.py" shell`;
|
||||
const terminalShellSettings = vscode.workspace.getConfiguration('terminal.integrated.shell');
|
||||
// tslint:disable-next-line:no-backbone-get-set-outside-model
|
||||
const IS_POWERSHELL = /powershell/.test(terminalShellSettings.get<string>('windows'));
|
||||
|
||||
let currentPythonPath = settings.PythonSettings.getInstance(activeEditor.document.uri).pythonPath;
|
||||
if (currentPythonPath.indexOf(' ') > 0) {
|
||||
currentPythonPath = `"${currentPythonPath}"`;
|
||||
}
|
||||
|
||||
const workspaceUri = vscode.workspace.getWorkspaceFolder(activeEditor.document.uri);
|
||||
const defaultWorkspace = Array.isArray(vscode.workspace.workspaceFolders) && vscode.workspace.workspaceFolders.length > 0 ? vscode.workspace.workspaceFolders[0].uri.fsPath : '';
|
||||
const workspaceRoot = workspaceUri ? workspaceUri.uri.fsPath : defaultWorkspace;
|
||||
const djangoShellCmd = `"${path.join(workspaceRoot, 'manage.py')}" shell`;
|
||||
const selection = vscode.window.activeTextEditor.selection;
|
||||
let code: string;
|
||||
if (selection.isEmpty) {
|
||||
code = vscode.window.activeTextEditor.document.lineAt(selection.start.line).text;
|
||||
}
|
||||
else {
|
||||
let textRange = new vscode.Range(selection.start, selection.end);
|
||||
} else {
|
||||
const textRange = new vscode.Range(selection.start, selection.end);
|
||||
code = vscode.window.activeTextEditor.document.getText(textRange);
|
||||
}
|
||||
if (code.length === 0) {
|
||||
return;
|
||||
}
|
||||
const launchArgs = settings.PythonSettings.getInstance().terminal.launchArgs;
|
||||
const launchArgsString = launchArgs.length > 0 ? " ".concat(launchArgs.join(" ")) : "";
|
||||
const launchArgs = settings.PythonSettings.getInstance(activeEditor.document.uri).terminal.launchArgs;
|
||||
const launchArgsString = launchArgs.length > 0 ? ' '.concat(launchArgs.join(' ')) : '';
|
||||
const command = `${currentPythonPath}${launchArgsString} ${djangoShellCmd}`;
|
||||
if (!terminal) {
|
||||
terminal = vscode.window.createTerminal(`Django Shell`);
|
||||
terminal = vscode.window.createTerminal('Django Shell');
|
||||
if (IS_WINDOWS) {
|
||||
const commandWin = command.replace(/\\/g, "/");
|
||||
const commandWin = command.replace(/\\/g, '/');
|
||||
if (IS_POWERSHELL) {
|
||||
terminal.sendText(`& ${commandWin}`);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
terminal.sendText(commandWin);
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
terminal.sendText(command);
|
||||
}
|
||||
}
|
||||
const unix_code = code.replace(/\r\n/g, "\n");
|
||||
// tslint:disable-next-line:variable-name
|
||||
const unix_code = code.replace(/\r\n/g, '\n');
|
||||
if (IS_WINDOWS) {
|
||||
terminal.sendText(unix_code.replace(/\n/g, "\r\n"));
|
||||
}
|
||||
else {
|
||||
terminal.sendText(unix_code.replace(/\n/g, '\r\n'));
|
||||
} else {
|
||||
terminal.sendText(unix_code);
|
||||
}
|
||||
terminal.show();
|
||||
}
|
||||
}
|
||||
|
||||
class DjangoContextInitializer implements vscode.Disposable {
|
||||
private isDjangoProject: ContextKey;
|
||||
private monitoringActiveTextEditor: boolean;
|
||||
private workspaceContextKeyValues = new Map<string, boolean>();
|
||||
private lastCheckedWorkspace: string;
|
||||
private disposables: Disposable[] = [];
|
||||
constructor() {
|
||||
this.isDjangoProject = new ContextKey('python.isDjangoProject');
|
||||
this.ensureState();
|
||||
this.disposables.push(vscode.workspace.onDidChangeWorkspaceFolders(() => this.updateContextKeyBasedOnActiveWorkspace()));
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
this.isDjangoProject = null;
|
||||
this.disposables.forEach(disposable => disposable.dispose());
|
||||
}
|
||||
private updateContextKeyBasedOnActiveWorkspace() {
|
||||
if (this.monitoringActiveTextEditor) {
|
||||
return;
|
||||
}
|
||||
this.monitoringActiveTextEditor = true;
|
||||
this.disposables.push(vscode.window.onDidChangeActiveTextEditor(() => this.ensureState()));
|
||||
}
|
||||
private getActiveWorkspace(): string | undefined {
|
||||
if (!Array.isArray(workspace.workspaceFolders || workspace.workspaceFolders.length === 0)) {
|
||||
return undefined;
|
||||
}
|
||||
if (workspace.workspaceFolders.length === 1) {
|
||||
return workspace.workspaceFolders[0].uri.fsPath;
|
||||
}
|
||||
const activeEditor = vscode.window.activeTextEditor;
|
||||
if (!activeEditor) {
|
||||
return undefined;
|
||||
}
|
||||
const workspaceFolder = vscode.workspace.getWorkspaceFolder(activeEditor.document.uri);
|
||||
return workspaceFolder ? workspaceFolder.uri.fsPath : undefined;
|
||||
}
|
||||
private async ensureState(): Promise<void> {
|
||||
const activeWorkspace = this.getActiveWorkspace();
|
||||
if (!activeWorkspace) {
|
||||
return await this.isDjangoProject.set(false);
|
||||
}
|
||||
if (this.lastCheckedWorkspace === activeWorkspace) {
|
||||
return;
|
||||
}
|
||||
if (this.workspaceContextKeyValues.has(activeWorkspace)) {
|
||||
await this.isDjangoProject.set(this.workspaceContextKeyValues.get(activeWorkspace));
|
||||
} else {
|
||||
const exists = await fs.pathExists(path.join(activeWorkspace, 'manage.py'));
|
||||
await this.isDjangoProject.set(exists);
|
||||
this.workspaceContextKeyValues.set(activeWorkspace, exists);
|
||||
this.lastCheckedWorkspace = activeWorkspace;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,29 +6,31 @@ import * as vscode from "vscode";
|
|||
import { BaseFormatter } from "./../formatters/baseFormatter";
|
||||
import { YapfFormatter } from "./../formatters/yapfFormatter";
|
||||
import { AutoPep8Formatter } from "./../formatters/autoPep8Formatter";
|
||||
import * as settings from "./../common/configSettings";
|
||||
import { DummyFormatter } from "./../formatters/dummyFormatter";
|
||||
import { PythonSettings } from "./../common/configSettings";
|
||||
|
||||
export function activateFormatOnSaveProvider(languageFilter: vscode.DocumentFilter, settings: settings.IPythonSettings, outputChannel: vscode.OutputChannel, workspaceRootPath?: string): vscode.Disposable {
|
||||
let formatters = new Map<string, BaseFormatter>();
|
||||
let pythonSettings = settings;
|
||||
|
||||
let yapfFormatter = new YapfFormatter(outputChannel, settings, workspaceRootPath);
|
||||
let autoPep8 = new AutoPep8Formatter(outputChannel, settings, workspaceRootPath);
|
||||
export function activateFormatOnSaveProvider(languageFilter: vscode.DocumentFilter, outputChannel: vscode.OutputChannel): vscode.Disposable {
|
||||
const formatters = new Map<string, BaseFormatter>();
|
||||
const yapfFormatter = new YapfFormatter(outputChannel);
|
||||
const autoPep8 = new AutoPep8Formatter(outputChannel);
|
||||
const dummyFormatter = new DummyFormatter(outputChannel);
|
||||
|
||||
formatters.set(yapfFormatter.Id, yapfFormatter);
|
||||
formatters.set(autoPep8.Id, autoPep8);
|
||||
formatters.set(dummyFormatter.Id, dummyFormatter);
|
||||
|
||||
return vscode.workspace.onWillSaveTextDocument(e => {
|
||||
const document = e.document;
|
||||
if (document.languageId !== languageFilter.language) {
|
||||
return;
|
||||
}
|
||||
let textEditor = vscode.window.activeTextEditor;
|
||||
let editorConfig = vscode.workspace.getConfiguration('editor');
|
||||
const textEditor = vscode.window.activeTextEditor;
|
||||
const editorConfig = vscode.workspace.getConfiguration('editor');
|
||||
const globalEditorFormatOnSave = editorConfig && editorConfig.has('formatOnSave') && editorConfig.get('formatOnSave') === true;
|
||||
if ((pythonSettings.formatting.formatOnSave || globalEditorFormatOnSave) && textEditor.document === document) {
|
||||
let formatter = formatters.get(pythonSettings.formatting.provider);
|
||||
const settings = PythonSettings.getInstance(document.uri);
|
||||
if ((settings.formatting.formatOnSave || globalEditorFormatOnSave) && textEditor.document === document) {
|
||||
const formatter = formatters.get(settings.formatting.provider);
|
||||
e.waitUntil(formatter.formatDocument(document, null, null));
|
||||
}
|
||||
}, null, null);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,15 +5,15 @@ import { BaseFormatter } from './../formatters/baseFormatter';
|
|||
import { YapfFormatter } from './../formatters/yapfFormatter';
|
||||
import { AutoPep8Formatter } from './../formatters/autoPep8Formatter';
|
||||
import { DummyFormatter } from './../formatters/dummyFormatter';
|
||||
import * as settings from './../common/configSettings';
|
||||
import { PythonSettings } from './../common/configSettings';
|
||||
|
||||
export class PythonFormattingEditProvider implements vscode.DocumentFormattingEditProvider, vscode.DocumentRangeFormattingEditProvider {
|
||||
private formatters = new Map<string, BaseFormatter>();
|
||||
|
||||
public constructor(context: vscode.ExtensionContext, outputChannel: vscode.OutputChannel, private settings: settings.IPythonSettings) {
|
||||
let yapfFormatter = new YapfFormatter(outputChannel, settings);
|
||||
let autoPep8 = new AutoPep8Formatter(outputChannel, settings);
|
||||
let dummy = new DummyFormatter(outputChannel, settings);
|
||||
public constructor(context: vscode.ExtensionContext, outputChannel: vscode.OutputChannel) {
|
||||
const yapfFormatter = new YapfFormatter(outputChannel);
|
||||
const autoPep8 = new AutoPep8Formatter(outputChannel);
|
||||
const dummy = new DummyFormatter(outputChannel);
|
||||
this.formatters.set(yapfFormatter.Id, yapfFormatter);
|
||||
this.formatters.set(autoPep8.Id, autoPep8);
|
||||
this.formatters.set(dummy.Id, dummy);
|
||||
|
@ -24,7 +24,8 @@ export class PythonFormattingEditProvider implements vscode.DocumentFormattingEd
|
|||
}
|
||||
|
||||
public provideDocumentRangeFormattingEdits(document: vscode.TextDocument, range: vscode.Range, options: vscode.FormattingOptions, token: vscode.CancellationToken): Thenable<vscode.TextEdit[]> {
|
||||
let formatter = this.formatters.get(this.settings.formatting.provider);
|
||||
const settings = PythonSettings.getInstance(document.uri);
|
||||
const formatter = this.formatters.get(settings.formatting.provider);
|
||||
return formatter.formatDocument(document, options, token, range);
|
||||
}
|
||||
|
||||
|
|
|
@ -4,13 +4,10 @@ import * as vscode from 'vscode';
|
|||
import * as proxy from './jediProxy';
|
||||
import { highlightCode } from './jediHelpers';
|
||||
import { EOL } from 'os';
|
||||
import { JediFactory } from '../languageServices/jediProxyFactory';
|
||||
|
||||
export class PythonHoverProvider implements vscode.HoverProvider {
|
||||
private jediProxyHandler: proxy.JediProxyHandler<proxy.IHoverResult>;
|
||||
|
||||
public constructor(context: vscode.ExtensionContext, jediProxy: proxy.JediProxy = null) {
|
||||
this.jediProxyHandler = new proxy.JediProxyHandler(context, jediProxy);
|
||||
}
|
||||
public constructor(private jediFactory: JediFactory) { }
|
||||
private static parseData(data: proxy.IHoverResult, currentWord: string): vscode.Hover {
|
||||
let results = [];
|
||||
let capturedInfo: string[] = [];
|
||||
|
@ -96,7 +93,7 @@ export class PythonHoverProvider implements vscode.HoverProvider {
|
|||
cmd.source = document.getText();
|
||||
}
|
||||
|
||||
const data = await this.jediProxyHandler.sendCommand(cmd, token);
|
||||
const data = await this.jediFactory.getJediProxyHandler<proxy.IHoverResult>(document.uri).sendCommand(cmd, token);
|
||||
if (!data || !data.items.length) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -1,57 +1,54 @@
|
|||
"use strict";
|
||||
|
||||
import * as vscode from "vscode";
|
||||
import * as path from "path";
|
||||
import * as fs from "fs";
|
||||
import * as child_process from "child_process";
|
||||
import * as settings from '../common/configSettings';
|
||||
import { getTextEditsFromPatch, getTempFileWithDocumentContents } from "../common/editor";
|
||||
'use strict';
|
||||
import * as child_process from 'child_process';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as vscode from 'vscode';
|
||||
import { PythonSettings } from '../common/configSettings';
|
||||
import { getTempFileWithDocumentContents, getTextEditsFromPatch } from '../common/editor';
|
||||
|
||||
// tslint:disable-next-line:completed-docs
|
||||
export class PythonImportSortProvider {
|
||||
public sortImports(extensionDir: string, document: vscode.TextDocument): Promise<vscode.TextEdit[]> {
|
||||
public async sortImports(extensionDir: string, document: vscode.TextDocument): Promise<vscode.TextEdit[]> {
|
||||
if (document.lineCount === 1) {
|
||||
return Promise.resolve([]);
|
||||
return [];
|
||||
}
|
||||
return new Promise<vscode.TextEdit[]>((resolve, reject) => {
|
||||
// isort does have the ability to read from the process input stream and return the formatted code out of the output stream
|
||||
// However they don't support returning the diff of the formatted text when reading data from the input stream
|
||||
// Yes getting text formatted that way avoids having to create a temporary file, however the diffing will have
|
||||
// to be done here in node (extension), i.e. extension cpu, i.e. les responsive solution
|
||||
let importScript = path.join(extensionDir, "pythonFiles", "sortImports.py");
|
||||
let tmpFileCreated = document.isDirty;
|
||||
let filePromise = tmpFileCreated ? getTempFileWithDocumentContents(document) : Promise.resolve(document.fileName);
|
||||
filePromise.then(filePath => {
|
||||
const pythonPath = settings.PythonSettings.getInstance().pythonPath;
|
||||
const isort = settings.PythonSettings.getInstance().sortImports.path;
|
||||
const args = settings.PythonSettings.getInstance().sortImports.args.join(' ');
|
||||
let isort_cmd = '';
|
||||
if (typeof isort === 'string' && isort.length > 0) {
|
||||
if (isort.indexOf(' ') > 0) {
|
||||
isort_cmd = `"${isort}" "${filePath}" --diff ${args}`;
|
||||
}
|
||||
else {
|
||||
isort_cmd = `${isort} "${filePath}" --diff ${args}`;
|
||||
}
|
||||
} else {
|
||||
if (pythonPath.indexOf(' ') > 0) {
|
||||
isort_cmd = `"${pythonPath}" "${importScript}" "${filePath}" --diff ${args}`;
|
||||
}
|
||||
else {
|
||||
isort_cmd = `${pythonPath} "${importScript}" "${filePath}" --diff ${args}`;
|
||||
}
|
||||
// isort does have the ability to read from the process input stream and return the formatted code out of the output stream.
|
||||
// However they don't support returning the diff of the formatted text when reading data from the input stream.
|
||||
// Yes getting text formatted that way avoids having to create a temporary file, however the diffing will have
|
||||
// to be done here in node (extension), i.e. extension cpu, i.e. less responsive solution.
|
||||
const importScript = path.join(extensionDir, 'pythonFiles', 'sortImports.py');
|
||||
const tmpFileCreated = document.isDirty;
|
||||
const filePath = tmpFileCreated ? await getTempFileWithDocumentContents(document) : document.fileName;
|
||||
const settings = PythonSettings.getInstance(document.uri);
|
||||
const pythonPath = settings.pythonPath;
|
||||
const isort = settings.sortImports.path;
|
||||
const args = settings.sortImports.args.join(' ');
|
||||
let isortCmd = '';
|
||||
if (typeof isort === 'string' && isort.length > 0) {
|
||||
if (isort.indexOf(' ') > 0) {
|
||||
isortCmd = `"${isort}" "${filePath}" --diff ${args}`;
|
||||
} else {
|
||||
isortCmd = `${isort} "${filePath}" --diff ${args}`;
|
||||
}
|
||||
} else {
|
||||
if (pythonPath.indexOf(' ') > 0) {
|
||||
isortCmd = `"${pythonPath}" "${importScript}" "${filePath}" --diff ${args}`;
|
||||
} else {
|
||||
isortCmd = `${pythonPath} "${importScript}" "${filePath}" --diff ${args}`;
|
||||
}
|
||||
}
|
||||
// tslint:disable-next-line:promise-must-complete
|
||||
return await new Promise<vscode.TextEdit[]>((resolve, reject) => {
|
||||
child_process.exec(isortCmd, (error, stdout, stderr) => {
|
||||
if (tmpFileCreated) {
|
||||
fs.unlink(filePath);
|
||||
}
|
||||
child_process.exec(isort_cmd, (error, stdout, stderr) => {
|
||||
if (tmpFileCreated) {
|
||||
fs.unlink(filePath);
|
||||
}
|
||||
if (error || (stderr && stderr.length > 0)) {
|
||||
return reject(error ? error : stderr);
|
||||
}
|
||||
|
||||
let edits = getTextEditsFromPatch(document.getText(), stdout);
|
||||
resolve(edits);
|
||||
});
|
||||
}).catch(reject);
|
||||
if (error || (stderr && stderr.length > 0)) {
|
||||
reject(error ? error : stderr);
|
||||
} else {
|
||||
resolve(getTextEditsFromPatch(document.getText(), stdout));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,15 +5,14 @@ import * as vscode from 'vscode';
|
|||
import * as path from 'path';
|
||||
import * as settings from './../common/configSettings';
|
||||
import * as logger from './../common/logger';
|
||||
import * as telemetryHelper from "../common/telemetry";
|
||||
import { execPythonFile, validatePath } from "../common/utils";
|
||||
import * as telemetryHelper from '../common/telemetry';
|
||||
import { execPythonFile, getCustomEnvVarsSync, validatePath } from '../common/utils';
|
||||
import { createDeferred, Deferred } from '../common/helpers';
|
||||
import { getCustomEnvVars } from '../common/utils';
|
||||
import { mergeEnvVariables } from '../common/envFileParser';
|
||||
import { IPythonSettings, PythonSettings } from '../common/configSettings';
|
||||
|
||||
const IS_WINDOWS = /^win/.test(process.platform);
|
||||
var proc: child_process.ChildProcess;
|
||||
var pythonSettings = settings.PythonSettings.getInstance();
|
||||
|
||||
const pythonVSCodeTypeMappings = new Map<string, vscode.CompletionItemKind>();
|
||||
pythonVSCodeTypeMappings.set('none', vscode.CompletionItemKind.Value);
|
||||
|
@ -122,202 +121,250 @@ commandNames.set(CommandType.Hover, "tooltip");
|
|||
commandNames.set(CommandType.Usages, "usages");
|
||||
commandNames.set(CommandType.Symbols, "names");
|
||||
|
||||
export class JediProxy extends vscode.Disposable {
|
||||
public constructor(context: vscode.ExtensionContext) {
|
||||
super(killProcess);
|
||||
export class JediProxy implements vscode.Disposable {
|
||||
private proc: child_process.ChildProcess;
|
||||
private pythonSettings: PythonSettings;
|
||||
|
||||
context.subscriptions.push(this);
|
||||
initialize(context.asAbsolutePath("."));
|
||||
public constructor(private extensionRootDir: string, private workspacePath: string) {
|
||||
this.pythonSettings = PythonSettings.getInstance(vscode.Uri.file(workspacePath));
|
||||
this.lastKnownPythonInterpreter = this.pythonSettings.pythonPath
|
||||
this.pythonSettings.on('change', this.onPythonSettingsChanged.bind(this));
|
||||
vscode.workspace.onDidChangeConfiguration(this.onConfigChanged.bind(this));
|
||||
this.onConfigChanged();
|
||||
this.initialize(extensionRootDir);
|
||||
}
|
||||
public dispose() {
|
||||
this.killProcess();
|
||||
}
|
||||
|
||||
private cmdId: number = 0;
|
||||
|
||||
public getNextCommandId(): number {
|
||||
return this.cmdId++;
|
||||
}
|
||||
public sendCommand<T extends ICommandResult>(cmd: ICommand<T>): Promise<T> {
|
||||
return sendCommand(cmd);
|
||||
|
||||
// keep track of the directory so we can re-spawn the process
|
||||
private pythonProcessCWD = "";
|
||||
private initialize(dir: string) {
|
||||
this.pythonProcessCWD = dir;
|
||||
this.spawnProcess(path.join(dir, "pythonFiles"));
|
||||
}
|
||||
}
|
||||
|
||||
// keep track of the directory so we can re-spawn the process
|
||||
let pythonProcessCWD = "";
|
||||
function initialize(dir: string) {
|
||||
pythonProcessCWD = dir;
|
||||
spawnProcess(path.join(dir, "pythonFiles"));
|
||||
}
|
||||
|
||||
// Check if settings changes
|
||||
let lastKnownPythonInterpreter = pythonSettings.pythonPath;
|
||||
pythonSettings.on('change', onPythonSettingsChanged);
|
||||
|
||||
function onPythonSettingsChanged() {
|
||||
if (lastKnownPythonInterpreter === pythonSettings.pythonPath) {
|
||||
return;
|
||||
}
|
||||
killProcess();
|
||||
clearPendingRequests();
|
||||
initialize(pythonProcessCWD);
|
||||
}
|
||||
|
||||
function clearPendingRequests() {
|
||||
commandQueue = [];
|
||||
commands.forEach(item => {
|
||||
item.deferred.resolve();
|
||||
});
|
||||
commands.clear();
|
||||
}
|
||||
var previousData = "";
|
||||
var commands = new Map<number, IExecutionCommand<ICommandResult>>();
|
||||
var commandQueue: number[] = [];
|
||||
|
||||
function killProcess() {
|
||||
try {
|
||||
if (proc) {
|
||||
proc.kill();
|
||||
}
|
||||
}
|
||||
catch (ex) { }
|
||||
proc = null;
|
||||
}
|
||||
|
||||
function handleError(source: string, errorMessage: string) {
|
||||
logger.error(source + ' jediProxy', `Error (${source}) ${errorMessage}`);
|
||||
}
|
||||
|
||||
let spawnRetryAttempts = 0;
|
||||
function spawnProcess(dir: string) {
|
||||
try {
|
||||
let environmentVariables = { 'PYTHONUNBUFFERED': '1' };
|
||||
let customEnvironmentVars = getCustomEnvVars();
|
||||
if (customEnvironmentVars) {
|
||||
environmentVariables = mergeEnvVariables(environmentVariables, customEnvironmentVars);
|
||||
}
|
||||
environmentVariables = mergeEnvVariables(environmentVariables);
|
||||
|
||||
logger.log('child_process.spawn in jediProxy', 'Value of pythonSettings.pythonPath is :' + pythonSettings.pythonPath);
|
||||
const args = ["completion.py"];
|
||||
if (typeof pythonSettings.jediPath !== 'string' || pythonSettings.jediPath.length === 0) {
|
||||
if (Array.isArray(pythonSettings.devOptions) &&
|
||||
pythonSettings.devOptions.some(item => item.toUpperCase().trim() === 'USERELEASEAUTOCOMP')) {
|
||||
// Use standard version of jedi library
|
||||
args.push('std');
|
||||
}
|
||||
else {
|
||||
// Use preview version of jedi library
|
||||
args.push('preview');
|
||||
}
|
||||
}
|
||||
else {
|
||||
args.push('custom');
|
||||
args.push(pythonSettings.jediPath);
|
||||
}
|
||||
if (Array.isArray(pythonSettings.autoComplete.preloadModules) &&
|
||||
pythonSettings.autoComplete.preloadModules.length > 0) {
|
||||
var modules = pythonSettings.autoComplete.preloadModules.filter(m => m.trim().length > 0).join(',');
|
||||
args.push(modules);
|
||||
}
|
||||
proc = child_process.spawn(pythonSettings.pythonPath, args, {
|
||||
cwd: dir,
|
||||
env: environmentVariables
|
||||
});
|
||||
}
|
||||
catch (ex) {
|
||||
return handleError("spawnProcess", ex.message);
|
||||
}
|
||||
proc.stderr.setEncoding('utf8');
|
||||
proc.stderr.on("data", (data: string) => {
|
||||
handleError("stderr", data);
|
||||
});
|
||||
proc.on("end", (end) => {
|
||||
logger.error('spawnProcess.end', "End - " + end);
|
||||
});
|
||||
proc.on("error", error => {
|
||||
handleError("error", error + '');
|
||||
spawnRetryAttempts++;
|
||||
if (spawnRetryAttempts < 10 && error && error.message &&
|
||||
error.message.indexOf('This socket has been ended by the other party') >= 0) {
|
||||
spawnProcess(dir);
|
||||
}
|
||||
});
|
||||
proc.stdout.setEncoding('utf8');
|
||||
proc.stdout.on("data", (data: string) => {
|
||||
//Possible there was an exception in parsing the data returned
|
||||
//So append the data then parse it
|
||||
var dataStr = previousData = previousData + data + "";
|
||||
var responses: any[];
|
||||
try {
|
||||
responses = dataStr.split(/\r?\n/g).filter(line => line.length > 0).map(resp => JSON.parse(resp));
|
||||
previousData = "";
|
||||
}
|
||||
catch (ex) {
|
||||
// Possible we've only received part of the data, hence don't clear previousData
|
||||
// Don't log errors when we haven't received the entire response
|
||||
if (ex.message.indexOf('Unexpected end of input') === -1 &&
|
||||
ex.message.indexOf('Unexpected end of JSON input') === -1 &&
|
||||
ex.message.indexOf('Unexpected token') === -1) {
|
||||
handleError("stdout", ex.message);
|
||||
}
|
||||
// Check if settings changes
|
||||
private lastKnownPythonInterpreter: string;
|
||||
private onPythonSettingsChanged() {
|
||||
if (this.lastKnownPythonInterpreter === this.pythonSettings.pythonPath) {
|
||||
return;
|
||||
}
|
||||
this.killProcess();
|
||||
this.clearPendingRequests();
|
||||
this.initialize(this.pythonProcessCWD);
|
||||
}
|
||||
|
||||
responses.forEach((response) => {
|
||||
// What's this, can't remember,
|
||||
// Great example of poorly written code (this whole file is a mess)
|
||||
// I think this needs to be removed, because this is misspelt, it is argments, 'U' is missing
|
||||
// And that case is handled further down
|
||||
// case CommandType.Arguments: {
|
||||
// Rewrite this mess to use stratergy..
|
||||
if (response["argments"]) {
|
||||
var index = commandQueue.indexOf(cmd.id);
|
||||
commandQueue.splice(index, 1);
|
||||
private clearPendingRequests() {
|
||||
this.commandQueue = [];
|
||||
this.commands.forEach(item => {
|
||||
item.deferred.resolve();
|
||||
});
|
||||
this.commands.clear();
|
||||
}
|
||||
private previousData = "";
|
||||
private commands = new Map<number, IExecutionCommand<ICommandResult>>();
|
||||
private commandQueue: number[] = [];
|
||||
|
||||
private killProcess() {
|
||||
try {
|
||||
if (this.proc) {
|
||||
this.proc.kill();
|
||||
}
|
||||
}
|
||||
catch (ex) { }
|
||||
this.proc = null;
|
||||
}
|
||||
|
||||
private handleError(source: string, errorMessage: string) {
|
||||
logger.error(source + ' jediProxy', `Error (${source}) ${errorMessage}`);
|
||||
}
|
||||
|
||||
private spawnRetryAttempts = 0;
|
||||
private spawnProcess(dir: string) {
|
||||
try {
|
||||
let environmentVariables = { 'PYTHONUNBUFFERED': '1' };
|
||||
let customEnvironmentVars = getCustomEnvVarsSync(vscode.Uri.file(dir));
|
||||
if (customEnvironmentVars) {
|
||||
environmentVariables = mergeEnvVariables(environmentVariables, customEnvironmentVars);
|
||||
}
|
||||
environmentVariables = mergeEnvVariables(environmentVariables);
|
||||
|
||||
logger.log('child_process.spawn in jediProxy', 'Value of pythonSettings.pythonPath is :' + this.pythonSettings.pythonPath);
|
||||
const args = ["completion.py"];
|
||||
if (typeof this.pythonSettings.jediPath !== 'string' || this.pythonSettings.jediPath.length === 0) {
|
||||
if (Array.isArray(this.pythonSettings.devOptions) &&
|
||||
this.pythonSettings.devOptions.some(item => item.toUpperCase().trim() === 'USERELEASEAUTOCOMP')) {
|
||||
// Use standard version of jedi library
|
||||
args.push('std');
|
||||
}
|
||||
else {
|
||||
// Use preview version of jedi library
|
||||
args.push('preview');
|
||||
}
|
||||
}
|
||||
else {
|
||||
args.push('custom');
|
||||
args.push(this.pythonSettings.jediPath);
|
||||
}
|
||||
if (Array.isArray(this.pythonSettings.autoComplete.preloadModules) &&
|
||||
this.pythonSettings.autoComplete.preloadModules.length > 0) {
|
||||
var modules = this.pythonSettings.autoComplete.preloadModules.filter(m => m.trim().length > 0).join(',');
|
||||
args.push(modules);
|
||||
}
|
||||
this.proc = child_process.spawn(this.pythonSettings.pythonPath, args, {
|
||||
cwd: dir,
|
||||
env: environmentVariables
|
||||
});
|
||||
}
|
||||
catch (ex) {
|
||||
return this.handleError("spawnProcess", ex.message);
|
||||
}
|
||||
this.proc.stderr.setEncoding('utf8');
|
||||
this.proc.stderr.on("data", (data: string) => {
|
||||
this.handleError("stderr", data);
|
||||
});
|
||||
this.proc.on("end", (end) => {
|
||||
logger.error('spawnProcess.end', "End - " + end);
|
||||
});
|
||||
this.proc.on("error", error => {
|
||||
this.handleError("error", error + '');
|
||||
this.spawnRetryAttempts++;
|
||||
if (this.spawnRetryAttempts < 10 && error && error.message &&
|
||||
error.message.indexOf('This socket has been ended by the other party') >= 0) {
|
||||
this.spawnProcess(dir);
|
||||
}
|
||||
});
|
||||
this.proc.stdout.setEncoding('utf8');
|
||||
this.proc.stdout.on("data", (data: string) => {
|
||||
//Possible there was an exception in parsing the data returned
|
||||
//So append the data then parse it
|
||||
var dataStr = this.previousData = this.previousData + data + "";
|
||||
var responses: any[];
|
||||
try {
|
||||
responses = dataStr.split(/\r?\n/g).filter(line => line.length > 0).map(resp => JSON.parse(resp));
|
||||
this.previousData = "";
|
||||
}
|
||||
catch (ex) {
|
||||
// Possible we've only received part of the data, hence don't clear previousData
|
||||
// Don't log errors when we haven't received the entire response
|
||||
if (ex.message.indexOf('Unexpected end of input') === -1 &&
|
||||
ex.message.indexOf('Unexpected end of JSON input') === -1 &&
|
||||
ex.message.indexOf('Unexpected token') === -1) {
|
||||
this.handleError("stdout", ex.message);
|
||||
}
|
||||
return;
|
||||
}
|
||||
var responseId = <number>response["id"];
|
||||
|
||||
var cmd = <IExecutionCommand<ICommandResult>>commands.get(responseId);
|
||||
if (typeof cmd === "object" && cmd !== null) {
|
||||
commands.delete(responseId);
|
||||
var index = commandQueue.indexOf(cmd.id);
|
||||
commandQueue.splice(index, 1);
|
||||
|
||||
if (cmd.delays && typeof cmd.telemetryEvent === 'string') {
|
||||
// cmd.delays.stop();
|
||||
// telemetryHelper.sendTelemetryEvent(cmd.telemetryEvent, null, cmd.delays.toMeasures());
|
||||
}
|
||||
|
||||
// Check if this command has expired
|
||||
if (cmd.token.isCancellationRequested) {
|
||||
cmd.deferred.resolve();
|
||||
responses.forEach((response) => {
|
||||
// What's this, can't remember,
|
||||
// Great example of poorly written code (this whole file is a mess)
|
||||
// I think this needs to be removed, because this is misspelt, it is argments, 'U' is missing
|
||||
// And that case is handled further down
|
||||
// case CommandType.Arguments: {
|
||||
// Rewrite this mess to use stratergy..
|
||||
if (response["argments"]) {
|
||||
var index = this.commandQueue.indexOf(cmd.id);
|
||||
this.commandQueue.splice(index, 1);
|
||||
return;
|
||||
}
|
||||
var responseId = <number>response["id"];
|
||||
|
||||
switch (cmd.command) {
|
||||
case CommandType.Completions: {
|
||||
let results = <IAutoCompleteItem[]>response['results'];
|
||||
results = Array.isArray(results) ? results : [];
|
||||
results.forEach(item => {
|
||||
const originalType = <string><any>item.type;
|
||||
item.type = getMappedVSCodeType(originalType);
|
||||
item.kind = getMappedVSCodeSymbol(originalType);
|
||||
item.rawType = getMappedVSCodeType(originalType);
|
||||
});
|
||||
var cmd = <IExecutionCommand<ICommandResult>>this.commands.get(responseId);
|
||||
if (typeof cmd === "object" && cmd !== null) {
|
||||
this.commands.delete(responseId);
|
||||
var index = this.commandQueue.indexOf(cmd.id);
|
||||
this.commandQueue.splice(index, 1);
|
||||
|
||||
let completionResult: ICompletionResult = {
|
||||
items: results,
|
||||
requestId: cmd.id
|
||||
};
|
||||
cmd.deferred.resolve(completionResult);
|
||||
break;
|
||||
if (cmd.delays && typeof cmd.telemetryEvent === 'string') {
|
||||
// cmd.delays.stop();
|
||||
// telemetryHelper.sendTelemetryEvent(cmd.telemetryEvent, null, cmd.delays.toMeasures());
|
||||
}
|
||||
case CommandType.Definitions: {
|
||||
let defs = <any[]>response['results'];
|
||||
let defResult: IDefinitionResult = {
|
||||
requestId: cmd.id,
|
||||
definitions: []
|
||||
};
|
||||
if (defs.length > 0) {
|
||||
defResult.definitions = defs.map(def => {
|
||||
|
||||
// Check if this command has expired
|
||||
if (cmd.token.isCancellationRequested) {
|
||||
cmd.deferred.resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
switch (cmd.command) {
|
||||
case CommandType.Completions: {
|
||||
let results = <IAutoCompleteItem[]>response['results'];
|
||||
results = Array.isArray(results) ? results : [];
|
||||
results.forEach(item => {
|
||||
const originalType = <string><any>item.type;
|
||||
item.type = getMappedVSCodeType(originalType);
|
||||
item.kind = getMappedVSCodeSymbol(originalType);
|
||||
item.rawType = getMappedVSCodeType(originalType);
|
||||
});
|
||||
|
||||
let completionResult: ICompletionResult = {
|
||||
items: results,
|
||||
requestId: cmd.id
|
||||
};
|
||||
cmd.deferred.resolve(completionResult);
|
||||
break;
|
||||
}
|
||||
case CommandType.Definitions: {
|
||||
let defs = <any[]>response['results'];
|
||||
let defResult: IDefinitionResult = {
|
||||
requestId: cmd.id,
|
||||
definitions: []
|
||||
};
|
||||
if (defs.length > 0) {
|
||||
defResult.definitions = defs.map(def => {
|
||||
const originalType = def.type as string;
|
||||
return {
|
||||
fileName: def.fileName,
|
||||
text: def.text,
|
||||
rawType: originalType,
|
||||
type: getMappedVSCodeType(originalType),
|
||||
kind: getMappedVSCodeSymbol(originalType),
|
||||
container: def.container,
|
||||
range: {
|
||||
startLine: def.range.start_line,
|
||||
startColumn: def.range.start_column,
|
||||
endLine: def.range.end_line,
|
||||
endColumn: def.range.end_column
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
cmd.deferred.resolve(defResult);
|
||||
break;
|
||||
}
|
||||
case CommandType.Hover: {
|
||||
let defs = <any[]>response['results'];
|
||||
var defResult: IHoverResult = {
|
||||
requestId: cmd.id,
|
||||
items: defs.map(def => {
|
||||
return {
|
||||
kind: getMappedVSCodeSymbol(def.type),
|
||||
description: def.description,
|
||||
signature: def.signature,
|
||||
docstring: def.docstring,
|
||||
text: def.text
|
||||
};
|
||||
})
|
||||
};
|
||||
|
||||
cmd.deferred.resolve(defResult);
|
||||
break;
|
||||
}
|
||||
case CommandType.Symbols: {
|
||||
let defs = <any[]>response['results'];
|
||||
defs = Array.isArray(defs) ? defs : [];
|
||||
var defResults: ISymbolResult = {
|
||||
requestId: cmd.id,
|
||||
definitions: []
|
||||
};
|
||||
defResults.definitions = defs.map<IDefinition>(def => {
|
||||
const originalType = def.type as string;
|
||||
return {
|
||||
fileName: def.fileName,
|
||||
|
@ -334,250 +381,201 @@ function spawnProcess(dir: string) {
|
|||
}
|
||||
};
|
||||
});
|
||||
|
||||
cmd.deferred.resolve(defResults);
|
||||
break;
|
||||
}
|
||||
|
||||
cmd.deferred.resolve(defResult);
|
||||
break;
|
||||
}
|
||||
case CommandType.Hover: {
|
||||
let defs = <any[]>response['results'];
|
||||
var defResult: IHoverResult = {
|
||||
requestId: cmd.id,
|
||||
items: defs.map(def => {
|
||||
return {
|
||||
kind: getMappedVSCodeSymbol(def.type),
|
||||
description: def.description,
|
||||
signature: def.signature,
|
||||
docstring: def.docstring,
|
||||
text: def.text
|
||||
};
|
||||
})
|
||||
};
|
||||
|
||||
cmd.deferred.resolve(defResult);
|
||||
break;
|
||||
}
|
||||
case CommandType.Symbols: {
|
||||
let defs = <any[]>response['results'];
|
||||
defs = Array.isArray(defs) ? defs : [];
|
||||
var defResults: ISymbolResult = {
|
||||
requestId: cmd.id,
|
||||
definitions: []
|
||||
};
|
||||
defResults.definitions = defs.map<IDefinition>(def => {
|
||||
const originalType = def.type as string;
|
||||
return {
|
||||
fileName: def.fileName,
|
||||
text: def.text,
|
||||
rawType: originalType,
|
||||
type: getMappedVSCodeType(originalType),
|
||||
kind: getMappedVSCodeSymbol(originalType),
|
||||
container: def.container,
|
||||
range: {
|
||||
startLine: def.range.start_line,
|
||||
startColumn: def.range.start_column,
|
||||
endLine: def.range.end_line,
|
||||
endColumn: def.range.end_column
|
||||
case CommandType.Usages: {
|
||||
let defs = <any[]>response['results'];
|
||||
defs = Array.isArray(defs) ? defs : [];
|
||||
var refResult: IReferenceResult = {
|
||||
requestId: cmd.id,
|
||||
references: defs.map(item => {
|
||||
return {
|
||||
columnIndex: item.column,
|
||||
fileName: item.fileName,
|
||||
lineIndex: item.line - 1,
|
||||
moduleName: item.moduleName,
|
||||
name: item.name
|
||||
};
|
||||
}
|
||||
)
|
||||
};
|
||||
});
|
||||
|
||||
cmd.deferred.resolve(defResults);
|
||||
break;
|
||||
}
|
||||
case CommandType.Usages: {
|
||||
let defs = <any[]>response['results'];
|
||||
defs = Array.isArray(defs) ? defs : [];
|
||||
var refResult: IReferenceResult = {
|
||||
requestId: cmd.id,
|
||||
references: defs.map(item => {
|
||||
return {
|
||||
columnIndex: item.column,
|
||||
fileName: item.fileName,
|
||||
lineIndex: item.line - 1,
|
||||
moduleName: item.moduleName,
|
||||
name: item.name
|
||||
};
|
||||
}
|
||||
)
|
||||
};
|
||||
|
||||
cmd.deferred.resolve(refResult);
|
||||
break;
|
||||
}
|
||||
case CommandType.Arguments: {
|
||||
let defs = <any[]>response["results"];
|
||||
cmd.deferred.resolve(<IArgumentsResult>{
|
||||
requestId: cmd.id,
|
||||
definitions: defs
|
||||
});
|
||||
break;
|
||||
cmd.deferred.resolve(refResult);
|
||||
break;
|
||||
}
|
||||
case CommandType.Arguments: {
|
||||
let defs = <any[]>response["results"];
|
||||
cmd.deferred.resolve(<IArgumentsResult>{
|
||||
requestId: cmd.id,
|
||||
definitions: defs
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Ok, check if too many pending requets
|
||||
if (commandQueue.length > 10) {
|
||||
var items = commandQueue.splice(0, commandQueue.length - 10);
|
||||
items.forEach(id => {
|
||||
if (commands.has(id)) {
|
||||
const cmd = commands.get(id);
|
||||
try {
|
||||
cmd.deferred.resolve(null);
|
||||
//Ok, check if too many pending requets
|
||||
if (this.commandQueue.length > 10) {
|
||||
var items = this.commandQueue.splice(0, this.commandQueue.length - 10);
|
||||
items.forEach(id => {
|
||||
if (this.commands.has(id)) {
|
||||
const cmd = this.commands.get(id);
|
||||
try {
|
||||
cmd.deferred.resolve(null);
|
||||
}
|
||||
catch (ex) {
|
||||
}
|
||||
this.commands.delete(id);
|
||||
}
|
||||
catch (ex) {
|
||||
}
|
||||
commands.delete(id);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function sendCommand<T extends ICommandResult>(cmd: ICommand<T>): Promise<T> {
|
||||
if (!proc) {
|
||||
return Promise.reject(new Error("Python proc not initialized"));
|
||||
}
|
||||
var executionCmd = <IExecutionCommand<T>>cmd;
|
||||
var payload = createPayload(executionCmd);
|
||||
executionCmd.deferred = createDeferred<T>();
|
||||
// if (typeof executionCmd.telemetryEvent === 'string') {
|
||||
// executionCmd.delays = new telemetryHelper.Delays();
|
||||
// }
|
||||
try {
|
||||
proc.stdin.write(JSON.stringify(payload) + "\n");
|
||||
commands.set(executionCmd.id, executionCmd);
|
||||
commandQueue.push(executionCmd.id);
|
||||
}
|
||||
catch (ex) {
|
||||
console.error(ex);
|
||||
//If 'This socket is closed.' that means process didn't start at all (at least not properly)
|
||||
if (ex.message === "This socket is closed.") {
|
||||
|
||||
killProcess();
|
||||
public sendCommand<T extends ICommandResult>(cmd: ICommand<T>): Promise<T> {
|
||||
if (!this.proc) {
|
||||
return Promise.reject(new Error("Python proc not initialized"));
|
||||
}
|
||||
else {
|
||||
handleError("sendCommand", ex.message);
|
||||
var executionCmd = <IExecutionCommand<T>>cmd;
|
||||
var payload = this.createPayload(executionCmd);
|
||||
executionCmd.deferred = createDeferred<T>();
|
||||
// if (typeof executionCmd.telemetryEvent === 'string') {
|
||||
// executionCmd.delays = new telemetryHelper.Delays();
|
||||
// }
|
||||
try {
|
||||
this.proc.stdin.write(JSON.stringify(payload) + "\n");
|
||||
this.commands.set(executionCmd.id, executionCmd);
|
||||
this.commandQueue.push(executionCmd.id);
|
||||
}
|
||||
return Promise.reject(ex);
|
||||
}
|
||||
return executionCmd.deferred.promise;
|
||||
}
|
||||
catch (ex) {
|
||||
console.error(ex);
|
||||
//If 'This socket is closed.' that means process didn't start at all (at least not properly)
|
||||
if (ex.message === "This socket is closed.") {
|
||||
|
||||
function createPayload<T extends ICommandResult>(cmd: IExecutionCommand<T>): any {
|
||||
var payload = {
|
||||
id: cmd.id,
|
||||
prefix: "",
|
||||
lookup: commandNames.get(cmd.command),
|
||||
path: cmd.fileName,
|
||||
source: cmd.source,
|
||||
line: cmd.lineIndex,
|
||||
column: cmd.columnIndex,
|
||||
config: getConfig()
|
||||
};
|
||||
|
||||
if (cmd.command === CommandType.Symbols) {
|
||||
delete payload.column;
|
||||
delete payload.line;
|
||||
this.killProcess();
|
||||
}
|
||||
else {
|
||||
this.handleError("sendCommand", ex.message);
|
||||
}
|
||||
return Promise.reject(ex);
|
||||
}
|
||||
return executionCmd.deferred.promise;
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
private createPayload<T extends ICommandResult>(cmd: IExecutionCommand<T>): any {
|
||||
var payload = {
|
||||
id: cmd.id,
|
||||
prefix: "",
|
||||
lookup: commandNames.get(cmd.command),
|
||||
path: cmd.fileName,
|
||||
source: cmd.source,
|
||||
line: cmd.lineIndex,
|
||||
column: cmd.columnIndex,
|
||||
config: this.getConfig()
|
||||
};
|
||||
|
||||
let lastKnownPythonPath: string = null;
|
||||
let additionalAutoCopletePaths: string[] = [];
|
||||
function getPathFromPythonCommand(args: string[]): Promise<string> {
|
||||
return execPythonFile(pythonSettings.pythonPath, args, vscode.workspace.rootPath).then(stdout => {
|
||||
if (stdout.length === 0) {
|
||||
if (cmd.command === CommandType.Symbols) {
|
||||
delete payload.column;
|
||||
delete payload.line;
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
private lastKnownPythonPath: string = null;
|
||||
private additionalAutoCopletePaths: string[] = [];
|
||||
private getPathFromPythonCommand(args: string[]): Promise<string> {
|
||||
return execPythonFile(this.workspacePath, this.pythonSettings.pythonPath, args, this.workspacePath).then(stdout => {
|
||||
if (stdout.length === 0) {
|
||||
return "";
|
||||
}
|
||||
let lines = stdout.split(/\r?\n/g).filter(line => line.length > 0);
|
||||
return validatePath(lines[0]);
|
||||
}).catch(() => {
|
||||
return "";
|
||||
});
|
||||
}
|
||||
private onConfigChanged() {
|
||||
// We're only interested in changes to the python path
|
||||
if (this.lastKnownPythonPath === this.pythonSettings.pythonPath) {
|
||||
return;
|
||||
}
|
||||
let lines = stdout.split(/\r?\n/g).filter(line => line.length > 0);
|
||||
return validatePath(lines[0]);
|
||||
}).catch(() => {
|
||||
return "";
|
||||
});
|
||||
|
||||
this.lastKnownPythonPath = this.pythonSettings.pythonPath;
|
||||
let filePaths = [
|
||||
// Sysprefix
|
||||
this.getPathFromPythonCommand(["-c", "import sys;print(sys.prefix)"]),
|
||||
// exeucutable path
|
||||
this.getPathFromPythonCommand(["-c", "import sys;print(sys.executable)"]),
|
||||
// Python specific site packages
|
||||
this.getPathFromPythonCommand(["-c", "from distutils.sysconfig import get_python_lib; print(get_python_lib())"]),
|
||||
// Python global site packages, as a fallback in case user hasn't installed them in custom environment
|
||||
this.getPathFromPythonCommand(["-m", "site", "--user-site"]),
|
||||
];
|
||||
|
||||
let PYTHONPATH: string = process.env['PYTHONPATH'];
|
||||
if (typeof PYTHONPATH !== 'string') {
|
||||
PYTHONPATH = '';
|
||||
}
|
||||
let customEnvironmentVars = getCustomEnvVarsSync(vscode.Uri.file(this.pythonProcessCWD));
|
||||
if (customEnvironmentVars && customEnvironmentVars['PYTHONPATH']) {
|
||||
let PYTHONPATHFromEnvFile = customEnvironmentVars['PYTHONPATH'] as string;
|
||||
if (!path.isAbsolute(PYTHONPATHFromEnvFile) && this.workspacePath === 'string') {
|
||||
PYTHONPATHFromEnvFile = path.resolve(this.workspacePath, PYTHONPATHFromEnvFile);
|
||||
}
|
||||
PYTHONPATH += (PYTHONPATH.length > 0 ? + path.delimiter : '') + PYTHONPATHFromEnvFile;
|
||||
}
|
||||
if (typeof PYTHONPATH === 'string' && PYTHONPATH.length > 0) {
|
||||
filePaths.push(Promise.resolve(PYTHONPATH.trim()));
|
||||
}
|
||||
Promise.all<string>(filePaths).then(paths => {
|
||||
// Last item return a path, we need only the folder
|
||||
if (paths[1].length > 0) {
|
||||
paths[1] = path.dirname(paths[1]);
|
||||
}
|
||||
|
||||
// On windows we also need the libs path (second item will return c:\xxx\lib\site-packages)
|
||||
// This is returned by "from distutils.sysconfig import get_python_lib; print(get_python_lib())"
|
||||
if (IS_WINDOWS && paths[2].length > 0) {
|
||||
paths.splice(3, 0, path.join(paths[2], ".."));
|
||||
}
|
||||
this.additionalAutoCopletePaths = paths.filter(p => p.length > 0);
|
||||
});
|
||||
}
|
||||
|
||||
private getConfig() {
|
||||
// Add support for paths relative to workspace
|
||||
let extraPaths = this.pythonSettings.autoComplete.extraPaths.map(extraPath => {
|
||||
if (path.isAbsolute(extraPath)) {
|
||||
return extraPath;
|
||||
}
|
||||
if (typeof this.workspacePath !== 'string') {
|
||||
return '';
|
||||
}
|
||||
return path.join(this.workspacePath, extraPath);
|
||||
});
|
||||
|
||||
// Always add workspace path into extra paths
|
||||
if (typeof this.workspacePath === 'string') {
|
||||
extraPaths.unshift(this.workspacePath);
|
||||
}
|
||||
|
||||
let distinctExtraPaths = extraPaths.concat(this.additionalAutoCopletePaths)
|
||||
.filter(value => value.length > 0)
|
||||
.filter((value, index, self) => self.indexOf(value) === index);
|
||||
|
||||
return {
|
||||
extraPaths: distinctExtraPaths,
|
||||
useSnippets: false,
|
||||
caseInsensitiveCompletion: true,
|
||||
showDescriptions: true,
|
||||
fuzzyMatcher: true
|
||||
};
|
||||
}
|
||||
}
|
||||
vscode.workspace.onDidChangeConfiguration(onConfigChanged);
|
||||
onConfigChanged();
|
||||
function onConfigChanged() {
|
||||
// We're only interested in changes to the python path
|
||||
if (lastKnownPythonPath === pythonSettings.pythonPath) {
|
||||
return;
|
||||
}
|
||||
|
||||
lastKnownPythonPath = pythonSettings.pythonPath;
|
||||
let filePaths = [
|
||||
// Sysprefix
|
||||
getPathFromPythonCommand(["-c", "import sys;print(sys.prefix)"]),
|
||||
// exeucutable path
|
||||
getPathFromPythonCommand(["-c", "import sys;print(sys.executable)"]),
|
||||
// Python specific site packages
|
||||
getPathFromPythonCommand(["-c", "from distutils.sysconfig import get_python_lib; print(get_python_lib())"]),
|
||||
// Python global site packages, as a fallback in case user hasn't installed them in custom environment
|
||||
getPathFromPythonCommand(["-m", "site", "--user-site"]),
|
||||
];
|
||||
|
||||
let PYTHONPATH: string = process.env['PYTHONPATH'];
|
||||
if (typeof PYTHONPATH !== 'string') {
|
||||
PYTHONPATH = '';
|
||||
}
|
||||
let customEnvironmentVars = getCustomEnvVars();
|
||||
if (customEnvironmentVars && customEnvironmentVars['PYTHONPATH']) {
|
||||
let PYTHONPATHFromEnvFile = customEnvironmentVars['PYTHONPATH'] as string;
|
||||
if (!path.isAbsolute(PYTHONPATHFromEnvFile) && typeof vscode.workspace.rootPath === 'string') {
|
||||
PYTHONPATHFromEnvFile = path.resolve(vscode.workspace.rootPath, PYTHONPATHFromEnvFile);
|
||||
}
|
||||
PYTHONPATH += (PYTHONPATH.length > 0 ? + path.delimiter : '') + PYTHONPATHFromEnvFile;
|
||||
}
|
||||
if (typeof PYTHONPATH === 'string' && PYTHONPATH.length > 0) {
|
||||
filePaths.push(Promise.resolve(PYTHONPATH.trim()));
|
||||
}
|
||||
Promise.all<string>(filePaths).then(paths => {
|
||||
// Last item return a path, we need only the folder
|
||||
if (paths[1].length > 0) {
|
||||
paths[1] = path.dirname(paths[1]);
|
||||
}
|
||||
|
||||
// On windows we also need the libs path (second item will return c:\xxx\lib\site-packages)
|
||||
// This is returned by "from distutils.sysconfig import get_python_lib; print(get_python_lib())"
|
||||
if (IS_WINDOWS && paths[2].length > 0) {
|
||||
paths.splice(3, 0, path.join(paths[2], ".."));
|
||||
}
|
||||
additionalAutoCopletePaths = paths.filter(p => p.length > 0);
|
||||
});
|
||||
}
|
||||
|
||||
function getConfig() {
|
||||
// Add support for paths relative to workspace
|
||||
let extraPaths = pythonSettings.autoComplete.extraPaths.map(extraPath => {
|
||||
if (path.isAbsolute(extraPath)) {
|
||||
return extraPath;
|
||||
}
|
||||
if (typeof vscode.workspace.rootPath !== 'string') {
|
||||
return '';
|
||||
}
|
||||
return path.join(vscode.workspace.rootPath, extraPath);
|
||||
});
|
||||
|
||||
// Always add workspace path into extra paths
|
||||
if (typeof vscode.workspace.rootPath === 'string') {
|
||||
extraPaths.unshift(vscode.workspace.rootPath);
|
||||
}
|
||||
|
||||
let distinctExtraPaths = extraPaths.concat(additionalAutoCopletePaths)
|
||||
.filter(value => value.length > 0)
|
||||
.filter((value, index, self) => self.indexOf(value) === index);
|
||||
|
||||
return {
|
||||
extraPaths: distinctExtraPaths,
|
||||
useSnippets: false,
|
||||
caseInsensitiveCompletion: true,
|
||||
showDescriptions: true,
|
||||
fuzzyMatcher: true
|
||||
};
|
||||
}
|
||||
|
||||
export interface ICommand<T extends ICommandResult> {
|
||||
telemetryEvent?: string;
|
||||
command: CommandType;
|
||||
|
@ -675,19 +673,19 @@ export interface IHoverItem {
|
|||
signature: string;
|
||||
}
|
||||
|
||||
export class JediProxyHandler<R extends ICommandResult> {
|
||||
private jediProxy: JediProxy;
|
||||
export class JediProxyHandler<R extends ICommandResult> implements vscode.Disposable {
|
||||
private commandCancellationTokenSources: Map<CommandType, vscode.CancellationTokenSource>;
|
||||
|
||||
public get JediProxy(): JediProxy {
|
||||
return this.jediProxy;
|
||||
}
|
||||
|
||||
public constructor(context: vscode.ExtensionContext, jediProxy: JediProxy = null) {
|
||||
this.jediProxy = jediProxy ? jediProxy : new JediProxy(context);
|
||||
public constructor(private jediProxy: JediProxy = null) {
|
||||
this.commandCancellationTokenSources = new Map<CommandType, vscode.CancellationTokenSource>();
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
this.jediProxy.dispose();
|
||||
}
|
||||
public sendCommand(cmd: ICommand<R>, token?: vscode.CancellationToken): Promise<R> {
|
||||
var executionCmd = <IExecutionCommand<R>>cmd;
|
||||
executionCmd.id = executionCmd.id || this.jediProxy.getNextCommandId();
|
||||
|
|
|
@ -10,7 +10,7 @@ import * as pylama from './../linters/pylama';
|
|||
import * as flake8 from './../linters/flake8';
|
||||
import * as pydocstyle from './../linters/pydocstyle';
|
||||
import * as mypy from './../linters/mypy';
|
||||
import * as settings from '../common/configSettings';
|
||||
import { PythonSettings } from '../common/configSettings';
|
||||
import * as fs from 'fs';
|
||||
import { LinterErrors } from '../common/constants';
|
||||
const Minimatch = require("minimatch").Minimatch;
|
||||
|
@ -37,37 +37,23 @@ interface DocumentHasJupyterCodeCells {
|
|||
(doc: vscode.TextDocument, token: vscode.CancellationToken): Promise<Boolean>;
|
||||
}
|
||||
export class LintProvider extends vscode.Disposable {
|
||||
private settings: settings.IPythonSettings;
|
||||
private diagnosticCollection: vscode.DiagnosticCollection;
|
||||
private linters: linter.BaseLinter[] = [];
|
||||
private pendingLintings = new Map<string, vscode.CancellationTokenSource>();
|
||||
private outputChannel: vscode.OutputChannel;
|
||||
private context: vscode.ExtensionContext;
|
||||
private disposables: vscode.Disposable[];
|
||||
private ignoreMinmatches: { match: (fname: string) => boolean }[];
|
||||
public constructor(context: vscode.ExtensionContext, outputChannel: vscode.OutputChannel,
|
||||
public documentHasJupyterCodeCells: DocumentHasJupyterCodeCells) {
|
||||
super(() => { });
|
||||
this.outputChannel = outputChannel;
|
||||
this.context = context;
|
||||
this.settings = settings.PythonSettings.getInstance();
|
||||
this.disposables = [];
|
||||
this.ignoreMinmatches = [];
|
||||
this.initialize();
|
||||
|
||||
this.disposables.push(vscode.workspace.onDidChangeConfiguration(this.onConfigChanged.bind(this)));
|
||||
}
|
||||
dispose() {
|
||||
this.disposables.forEach(d => d.dispose());
|
||||
}
|
||||
private onConfigChanged() {
|
||||
this.initializeGlobs();
|
||||
}
|
||||
private initializeGlobs() {
|
||||
this.ignoreMinmatches = settings.PythonSettings.getInstance().linting.ignorePatterns.map(pattern => {
|
||||
return new Minimatch(pattern);
|
||||
});
|
||||
}
|
||||
private isDocumentOpen(uri: vscode.Uri): boolean {
|
||||
return vscode.window.visibleTextEditors.some(editor => editor.document && editor.document.uri.fsPath === uri.fsPath);
|
||||
}
|
||||
|
@ -84,7 +70,8 @@ export class LintProvider extends vscode.Disposable {
|
|||
this.linters.push(new mypy.Linter(this.outputChannel));
|
||||
|
||||
let disposable = vscode.workspace.onDidSaveTextDocument((e) => {
|
||||
if (e.languageId !== 'python' || !this.settings.linting.enabled || !this.settings.linting.lintOnSave) {
|
||||
const settings = PythonSettings.getInstance(e.uri);
|
||||
if (e.languageId !== 'python' || !settings.linting.enabled || !settings.linting.lintOnSave) {
|
||||
return;
|
||||
}
|
||||
this.lintDocument(e, 100);
|
||||
|
@ -92,7 +79,8 @@ export class LintProvider extends vscode.Disposable {
|
|||
this.context.subscriptions.push(disposable);
|
||||
|
||||
vscode.workspace.onDidOpenTextDocument((e) => {
|
||||
if (e.languageId !== 'python' || !this.settings.linting.enabled) {
|
||||
const settings = PythonSettings.getInstance(e.uri);
|
||||
if (e.languageId !== 'python' || !settings.linting.enabled) {
|
||||
return;
|
||||
}
|
||||
// Exclude files opened by vscode when showing a diff view
|
||||
|
@ -116,7 +104,6 @@ export class LintProvider extends vscode.Disposable {
|
|||
}
|
||||
});
|
||||
this.context.subscriptions.push(disposable);
|
||||
this.initializeGlobs();
|
||||
}
|
||||
|
||||
private lastTimeout: number;
|
||||
|
@ -135,8 +122,15 @@ export class LintProvider extends vscode.Disposable {
|
|||
|
||||
private onLintDocument(document: vscode.TextDocument): void {
|
||||
// Check if we need to lint this document
|
||||
const relativeFileName = typeof vscode.workspace.rootPath === 'string' ? path.relative(vscode.workspace.rootPath, document.fileName) : document.fileName;
|
||||
if (this.ignoreMinmatches.some(matcher => matcher.match(document.fileName) || matcher.match(relativeFileName))) {
|
||||
const workspaceFolder = vscode.workspace.getWorkspaceFolder(document.uri);
|
||||
const workspaceRootPath = (workspaceFolder && typeof workspaceFolder.uri.fsPath === 'string') ? workspaceFolder.uri.fsPath : undefined;
|
||||
const relativeFileName = typeof workspaceRootPath === 'string' ? path.relative(workspaceRootPath, document.fileName) : document.fileName;
|
||||
const settings = PythonSettings.getInstance(document.uri);
|
||||
const ignoreMinmatches = settings.linting.ignorePatterns.map(pattern => {
|
||||
return new Minimatch(pattern);
|
||||
});
|
||||
|
||||
if (ignoreMinmatches.some(matcher => matcher.match(document.fileName) || matcher.match(relativeFileName))) {
|
||||
return;
|
||||
}
|
||||
if (this.pendingLintings.has(document.uri.fsPath)) {
|
||||
|
@ -154,14 +148,10 @@ export class LintProvider extends vscode.Disposable {
|
|||
this.pendingLintings.set(document.uri.fsPath, cancelToken);
|
||||
this.outputChannel.clear();
|
||||
let promises: Promise<linter.ILintMessage[]>[] = this.linters.map(linter => {
|
||||
if (!vscode.workspace.rootPath && !this.settings.linting.enabledWithoutWorkspace) {
|
||||
if (typeof workspaceRootPath !== 'string' && !settings.linting.enabledWithoutWorkspace) {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
if (!linter.isEnabled()) {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
// turn off telemetry for linters (at least for now)
|
||||
return linter.runLinter(document, cancelToken.token);
|
||||
return linter.lint(document, cancelToken.token);
|
||||
});
|
||||
this.documentHasJupyterCodeCells(document, cancelToken.token).then(hasJupyterCodeCells => {
|
||||
// linters will resolve asynchronously - keep a track of all
|
||||
|
@ -187,7 +177,7 @@ export class LintProvider extends vscode.Disposable {
|
|||
});
|
||||
|
||||
// Limit the number of messages to the max value
|
||||
diagnostics = diagnostics.filter((value, index) => index <= this.settings.linting.maxNumberOfProblems);
|
||||
diagnostics = diagnostics.filter((value, index) => index <= settings.linting.maxNumberOfProblems);
|
||||
|
||||
if (!this.isDocumentOpen(document.uri)) {
|
||||
diagnostics = [];
|
||||
|
|
|
@ -2,16 +2,18 @@
|
|||
|
||||
import * as vscode from 'vscode';
|
||||
import * as defProvider from './definitionProvider';
|
||||
import { JediFactory } from '../languageServices/jediProxyFactory';
|
||||
|
||||
export function activateGoToObjectDefinitionProvider(context: vscode.ExtensionContext): vscode.Disposable {
|
||||
let def = new PythonObjectDefinitionProvider(context);
|
||||
return vscode.commands.registerCommand("python.goToPythonObject", () => def.goToObjectDefinition());
|
||||
export function activateGoToObjectDefinitionProvider(jediFactory: JediFactory): vscode.Disposable[] {
|
||||
const def = new PythonObjectDefinitionProvider(jediFactory);
|
||||
const commandRegistration = vscode.commands.registerCommand("python.goToPythonObject", () => def.goToObjectDefinition());
|
||||
return [def, commandRegistration] as vscode.Disposable[];
|
||||
}
|
||||
|
||||
export class PythonObjectDefinitionProvider {
|
||||
private readonly _defProvider: defProvider.PythonDefinitionProvider;
|
||||
public constructor(context: vscode.ExtensionContext) {
|
||||
this._defProvider = new defProvider.PythonDefinitionProvider(context);
|
||||
public constructor(jediFactory: JediFactory) {
|
||||
this._defProvider = new defProvider.PythonDefinitionProvider(jediFactory);
|
||||
}
|
||||
|
||||
public async goToObjectDefinition() {
|
||||
|
|
|
@ -2,14 +2,11 @@
|
|||
|
||||
import * as vscode from 'vscode';
|
||||
import * as proxy from './jediProxy';
|
||||
import { JediFactory } from '../languageServices/jediProxyFactory';
|
||||
|
||||
|
||||
export class PythonReferenceProvider implements vscode.ReferenceProvider {
|
||||
private jediProxyHandler: proxy.JediProxyHandler<proxy.IReferenceResult>;
|
||||
|
||||
public constructor(context: vscode.ExtensionContext, jediProxy: proxy.JediProxy = null) {
|
||||
this.jediProxyHandler = new proxy.JediProxyHandler(context, jediProxy);
|
||||
}
|
||||
public constructor(private jediFactory: JediFactory) { }
|
||||
private static parseData(data: proxy.IReferenceResult): vscode.Location[] {
|
||||
if (data && data.references.length > 0) {
|
||||
var references = data.references.filter(ref => {
|
||||
|
@ -52,7 +49,7 @@ export class PythonReferenceProvider implements vscode.ReferenceProvider {
|
|||
cmd.source = document.getText();
|
||||
}
|
||||
|
||||
return this.jediProxyHandler.sendCommand(cmd, token).then(data => {
|
||||
return this.jediFactory.getJediProxyHandler<proxy.IReferenceResult>(document.uri).sendCommand(cmd, token).then(data => {
|
||||
return PythonReferenceProvider.parseData(data);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ import * as path from 'path';
|
|||
import { PythonSettings } from '../common/configSettings';
|
||||
import { Installer, Product } from '../common/installer';
|
||||
|
||||
const pythonSettings = PythonSettings.getInstance();
|
||||
const EXTENSION_DIR = path.join(__dirname, '..', '..', '..');
|
||||
interface RenameResponse {
|
||||
results: [{ diff: string }];
|
||||
|
@ -41,14 +40,20 @@ export class PythonRenameProvider implements vscode.RenameProvider {
|
|||
return;
|
||||
}
|
||||
|
||||
let proxy = new RefactorProxy(EXTENSION_DIR, pythonSettings, vscode.workspace.rootPath);
|
||||
let workspaceFolder = vscode.workspace.getWorkspaceFolder(document.uri);
|
||||
if (!workspaceFolder && Array.isArray(vscode.workspace.workspaceFolders) && vscode.workspace.workspaceFolders.length > 0) {
|
||||
workspaceFolder = vscode.workspace.workspaceFolders[0];
|
||||
}
|
||||
const workspaceRoot = workspaceFolder ? workspaceFolder.uri.fsPath : __dirname;
|
||||
const pythonSettings = PythonSettings.getInstance(workspaceFolder ? workspaceFolder.uri : undefined);
|
||||
|
||||
let proxy = new RefactorProxy(EXTENSION_DIR, pythonSettings, workspaceRoot);
|
||||
return proxy.rename<RenameResponse>(document, newName, document.uri.fsPath, range).then(response => {
|
||||
//return response.results[0].diff;
|
||||
const workspaceEdit = getWorkspaceEditsFromPatch(response.results.map(fileChanges => fileChanges.diff));
|
||||
return workspaceEdit;
|
||||
const fileDiffs = response.results.map(fileChanges => fileChanges.diff);
|
||||
return getWorkspaceEditsFromPatch(fileDiffs, workspaceRoot);
|
||||
}).catch(reason => {
|
||||
if (reason === 'Not installed') {
|
||||
this.installer.promptToInstall(Product.rope);
|
||||
this.installer.promptToInstall(Product.rope, document.uri);
|
||||
return Promise.reject('');
|
||||
}
|
||||
else {
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
import { commands, Disposable, Uri, window, workspace } from 'vscode';
|
||||
import { PythonSettings } from '../common/configSettings';
|
||||
import { Commands } from '../common/constants';
|
||||
import { getPathFromPythonCommand } from '../common/utils';
|
||||
|
||||
export class ReplProvider implements Disposable {
|
||||
private readonly disposables: Disposable[] = [];
|
||||
constructor() {
|
||||
this.registerCommand();
|
||||
}
|
||||
public dispose() {
|
||||
this.disposables.forEach(disposable => disposable.dispose());
|
||||
}
|
||||
private registerCommand() {
|
||||
const disposable = commands.registerCommand(Commands.Start_REPL, this.commandHandler, this);
|
||||
this.disposables.push(disposable);
|
||||
}
|
||||
private async commandHandler() {
|
||||
const pythonPath = await this.getPythonPath();
|
||||
if (!pythonPath) {
|
||||
return;
|
||||
}
|
||||
let pythonInterpreterPath: string;
|
||||
try {
|
||||
pythonInterpreterPath = await getPathFromPythonCommand(pythonPath).catch(() => pythonPath);
|
||||
// tslint:disable-next-line:variable-name
|
||||
} catch (_ex) {
|
||||
pythonInterpreterPath = pythonPath;
|
||||
}
|
||||
const term = window.createTerminal('Python', pythonInterpreterPath);
|
||||
term.show();
|
||||
this.disposables.push(term);
|
||||
}
|
||||
private async getPythonPath(): Promise<string | undefined> {
|
||||
if (!Array.isArray(workspace.workspaceFolders) || workspace.workspaceFolders.length === 0) {
|
||||
return PythonSettings.getInstance().pythonPath;
|
||||
}
|
||||
if (workspace.workspaceFolders.length === 1) {
|
||||
return PythonSettings.getInstance(workspace.workspaceFolders[0].uri).pythonPath;
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:no-any prefer-type-cast
|
||||
const workspaceFolder = await (window as any).showWorkspaceFolderPick({ placeHolder: 'Select a workspace' });
|
||||
return workspace ? PythonSettings.getInstance(workspaceFolder.uri).pythonPath : undefined;
|
||||
}
|
||||
}
|
|
@ -1,72 +0,0 @@
|
|||
"use strict";
|
||||
import * as path from 'path';
|
||||
import * as vscode from 'vscode';
|
||||
import * as settings from './../common/configSettings';
|
||||
import { InterpreterManager } from '../interpreter';
|
||||
import { PythonInterpreter } from '../interpreter/contracts';
|
||||
import { ShebangCodeLensProvider } from './shebangCodeLensProvider';
|
||||
|
||||
|
||||
interface PythonPathQuickPickItem extends vscode.QuickPickItem {
|
||||
path: string;
|
||||
}
|
||||
|
||||
export class SetInterpreterProvider implements vscode.Disposable {
|
||||
private disposables: vscode.Disposable[] = [];
|
||||
constructor(private interpreterManager: InterpreterManager) {
|
||||
this.disposables.push(vscode.commands.registerCommand("python.setInterpreter", this.setInterpreter.bind(this)));
|
||||
this.disposables.push(vscode.commands.registerCommand("python.setShebangInterpreter", this.setShebangInterpreter.bind(this)));
|
||||
}
|
||||
public dispose() {
|
||||
this.disposables.forEach(disposable => disposable.dispose());
|
||||
}
|
||||
|
||||
private suggestionToQuickPickItem(suggestion: PythonInterpreter): PythonPathQuickPickItem {
|
||||
let detail = suggestion.path;
|
||||
if (vscode.workspace.rootPath && suggestion.path.startsWith(vscode.workspace.rootPath)) {
|
||||
detail = `.${path.sep}` + path.relative(vscode.workspace.rootPath!, suggestion.path);
|
||||
}
|
||||
return {
|
||||
label: suggestion.displayName!,
|
||||
description: suggestion.companyDisplayName || '',
|
||||
detail: detail,
|
||||
path: suggestion.path
|
||||
};
|
||||
}
|
||||
private presentQuickPick() {
|
||||
this.getSuggestions().then(suggestions => {
|
||||
let currentPythonPath = settings.PythonSettings.getInstance().pythonPath;
|
||||
if (vscode.workspace.rootPath && currentPythonPath.startsWith(vscode.workspace.rootPath)) {
|
||||
currentPythonPath = `.${path.sep}` + path.relative(vscode.workspace.rootPath, currentPythonPath);
|
||||
}
|
||||
const quickPickOptions: vscode.QuickPickOptions = {
|
||||
matchOnDetail: true,
|
||||
matchOnDescription: true,
|
||||
placeHolder: `current: ${currentPythonPath}`
|
||||
};
|
||||
vscode.window.showQuickPick(suggestions, quickPickOptions).then(
|
||||
value => {
|
||||
if (value !== undefined) {
|
||||
this.interpreterManager.setPythonPath(value.path);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private getSuggestions() {
|
||||
return this.interpreterManager.getInterpreters()
|
||||
.then(interpreters => interpreters.sort((a, b) => a.displayName! > b.displayName! ? 1 : -1))
|
||||
.then(interpreters => interpreters.map(this.suggestionToQuickPickItem));
|
||||
}
|
||||
|
||||
private setInterpreter() {
|
||||
this.presentQuickPick();
|
||||
}
|
||||
|
||||
private async setShebangInterpreter() {
|
||||
const shebang = await ShebangCodeLensProvider.detectShebang(vscode.window.activeTextEditor.document);
|
||||
if (shebang) {
|
||||
this.interpreterManager.setPythonPath(shebang);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,8 +1,9 @@
|
|||
"use strict";
|
||||
|
||||
import * as vscode from "vscode";
|
||||
import * as vscode from 'vscode';
|
||||
import * as proxy from './jediProxy';
|
||||
import { TextDocument, Position, CancellationToken, SignatureHelp } from "vscode";
|
||||
import * as proxy from "./jediProxy";
|
||||
import { JediFactory } from '../languageServices/jediProxyFactory';
|
||||
|
||||
const DOCSTRING_PARAM_PATTERNS = [
|
||||
"\\s*:type\\s*PARAMNAME:\\s*([^\\n, ]+)", // Sphinx
|
||||
|
@ -43,11 +44,7 @@ function extractParamDocString(paramName: string, docString: string): string {
|
|||
return paramDocString.trim();
|
||||
}
|
||||
export class PythonSignatureProvider implements vscode.SignatureHelpProvider {
|
||||
private jediProxyHandler: proxy.JediProxyHandler<proxy.IArgumentsResult>;
|
||||
|
||||
public constructor(context: vscode.ExtensionContext, jediProxy: proxy.JediProxy = null) {
|
||||
this.jediProxyHandler = new proxy.JediProxyHandler(context, jediProxy);
|
||||
}
|
||||
public constructor(private jediFactory: JediFactory) { }
|
||||
private static parseData(data: proxy.IArgumentsResult): vscode.SignatureHelp {
|
||||
if (data && Array.isArray(data.definitions) && data.definitions.length > 0) {
|
||||
let signature = new SignatureHelp();
|
||||
|
@ -86,7 +83,7 @@ export class PythonSignatureProvider implements vscode.SignatureHelpProvider {
|
|||
lineIndex: position.line,
|
||||
source: document.getText()
|
||||
};
|
||||
return this.jediProxyHandler.sendCommand(cmd, token).then(data => {
|
||||
return this.jediFactory.getJediProxyHandler<proxy.IArgumentsResult>(document.uri).sendCommand(cmd, token).then(data => {
|
||||
return PythonSignatureProvider.parseData(data);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
import * as vscode from 'vscode';
|
||||
import { RefactorProxy } from '../refactor/proxy';
|
||||
import { getTextEditsFromPatch } from '../common/editor';
|
||||
import { PythonSettings, IPythonSettings } from '../common/configSettings';
|
||||
import { PythonSettings } from '../common/configSettings';
|
||||
import { Installer, Product } from '../common/installer';
|
||||
|
||||
interface RenameResponse {
|
||||
|
@ -34,8 +34,14 @@ export function activateSimplePythonRefactorProvider(context: vscode.ExtensionCo
|
|||
|
||||
// Exported for unit testing
|
||||
export function extractVariable(extensionDir: string, textEditor: vscode.TextEditor, range: vscode.Range,
|
||||
outputChannel: vscode.OutputChannel, workspaceRoot: string = vscode.workspace.rootPath,
|
||||
pythonSettings: IPythonSettings = PythonSettings.getInstance()): Promise<any> {
|
||||
outputChannel: vscode.OutputChannel): Promise<any> {
|
||||
|
||||
let workspaceFolder = vscode.workspace.getWorkspaceFolder(textEditor.document.uri);
|
||||
if (!workspaceFolder && Array.isArray(vscode.workspace.workspaceFolders) && vscode.workspace.workspaceFolders.length > 0) {
|
||||
workspaceFolder = vscode.workspace.workspaceFolders[0];
|
||||
}
|
||||
const workspaceRoot = workspaceFolder ? workspaceFolder.uri.fsPath : __dirname;
|
||||
const pythonSettings = PythonSettings.getInstance(workspaceFolder ? workspaceFolder.uri : undefined);
|
||||
|
||||
return validateDocumentForRefactor(textEditor).then(() => {
|
||||
let newName = 'newvariable' + new Date().getMilliseconds().toString();
|
||||
|
@ -50,8 +56,14 @@ export function extractVariable(extensionDir: string, textEditor: vscode.TextEdi
|
|||
|
||||
// Exported for unit testing
|
||||
export function extractMethod(extensionDir: string, textEditor: vscode.TextEditor, range: vscode.Range,
|
||||
outputChannel: vscode.OutputChannel, workspaceRoot: string = vscode.workspace.rootPath,
|
||||
pythonSettings: IPythonSettings = PythonSettings.getInstance()): Promise<any> {
|
||||
outputChannel: vscode.OutputChannel): Promise<any> {
|
||||
|
||||
let workspaceFolder = vscode.workspace.getWorkspaceFolder(textEditor.document.uri);
|
||||
if (!workspaceFolder && Array.isArray(vscode.workspace.workspaceFolders) && vscode.workspace.workspaceFolders.length > 0) {
|
||||
workspaceFolder = vscode.workspace.workspaceFolders[0];
|
||||
}
|
||||
const workspaceRoot = workspaceFolder ? workspaceFolder.uri.fsPath : __dirname;
|
||||
const pythonSettings = PythonSettings.getInstance(workspaceFolder ? workspaceFolder.uri : undefined);
|
||||
|
||||
return validateDocumentForRefactor(textEditor).then(() => {
|
||||
let newName = 'newmethod' + new Date().getMilliseconds().toString();
|
||||
|
@ -127,7 +139,7 @@ function extractName(extensionDir: string, textEditor: vscode.TextEditor, range:
|
|||
}
|
||||
}).catch(error => {
|
||||
if (error === 'Not installed') {
|
||||
installer.promptToInstall(Product.rope);
|
||||
installer.promptToInstall(Product.rope, textEditor.document.uri);
|
||||
return Promise.reject('');
|
||||
}
|
||||
let errorMessage = error + '';
|
||||
|
|
|
@ -2,13 +2,10 @@
|
|||
|
||||
import * as vscode from 'vscode';
|
||||
import * as proxy from './jediProxy';
|
||||
import { JediFactory } from '../languageServices/jediProxyFactory';
|
||||
|
||||
export class PythonSymbolProvider implements vscode.DocumentSymbolProvider {
|
||||
private jediProxyHandler: proxy.JediProxyHandler<proxy.ISymbolResult>;
|
||||
|
||||
public constructor(context: vscode.ExtensionContext, jediProxy: proxy.JediProxy = null) {
|
||||
this.jediProxyHandler = new proxy.JediProxyHandler(context, jediProxy);
|
||||
}
|
||||
public constructor(private jediFactory: JediFactory) { }
|
||||
private static parseData(document: vscode.TextDocument, data: proxy.ISymbolResult): vscode.SymbolInformation[] {
|
||||
if (data) {
|
||||
let symbols = data.definitions.filter(sym => sym.fileName === document.fileName);
|
||||
|
@ -38,7 +35,7 @@ export class PythonSymbolProvider implements vscode.DocumentSymbolProvider {
|
|||
cmd.source = document.getText();
|
||||
}
|
||||
|
||||
return this.jediProxyHandler.sendCommand(cmd, token).then(data => {
|
||||
return this.jediFactory.getJediProxyHandler<proxy.ISymbolResult>(document.uri).sendCommand(cmd, token).then(data => {
|
||||
return PythonSymbolProvider.parseData(document, data);
|
||||
});
|
||||
}
|
||||
|
@ -56,7 +53,7 @@ export class PythonSymbolProvider implements vscode.DocumentSymbolProvider {
|
|||
cmd.source = document.getText();
|
||||
}
|
||||
|
||||
return this.jediProxyHandler.sendCommandNonCancellableCommand(cmd, token).then(data => {
|
||||
return this.jediFactory.getJediProxyHandler<proxy.ISymbolResult>(document.uri).sendCommandNonCancellableCommand(cmd, token).then(data => {
|
||||
return PythonSymbolProvider.parseData(document, data);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import * as path from 'path';
|
|||
import * as child_process from 'child_process';
|
||||
import { IPythonSettings } from '../common/configSettings';
|
||||
import { REFACTOR } from '../common/telemetryContracts';
|
||||
import { IS_WINDOWS, getCustomEnvVars, getWindowsLineEndingCount } from '../common/utils';
|
||||
import { getCustomEnvVars, getCustomEnvVarsSync, getWindowsLineEndingCount, IS_WINDOWS } from '../common/utils';
|
||||
import { mergeEnvVariables } from '../common/envFileParser';
|
||||
|
||||
export class RefactorProxy extends vscode.Disposable {
|
||||
|
@ -17,7 +17,7 @@ export class RefactorProxy extends vscode.Disposable {
|
|||
private _commandResolve: (value?: any | PromiseLike<any>) => void;
|
||||
private _commandReject: (reason?: any) => void;
|
||||
private _initializeReject: (reason?: any) => void;
|
||||
constructor(extensionDir: string, private pythonSettings: IPythonSettings, private workspaceRoot: string = vscode.workspace.rootPath) {
|
||||
constructor(extensionDir: string, private pythonSettings: IPythonSettings, private workspaceRoot: string) {
|
||||
super(() => { });
|
||||
this._extensionDir = extensionDir;
|
||||
}
|
||||
|
@ -106,7 +106,7 @@ export class RefactorProxy extends vscode.Disposable {
|
|||
return new Promise<any>((resolve, reject) => {
|
||||
this._initializeReject = reject;
|
||||
let environmentVariables = { 'PYTHONUNBUFFERED': '1' };
|
||||
let customEnvironmentVars = getCustomEnvVars();
|
||||
let customEnvironmentVars = getCustomEnvVarsSync(vscode.Uri.file(this.workspaceRoot));
|
||||
if (customEnvironmentVars) {
|
||||
environmentVariables = mergeEnvVariables(environmentVariables, customEnvironmentVars);
|
||||
}
|
||||
|
@ -188,4 +188,4 @@ export class RefactorProxy extends vscode.Disposable {
|
|||
this._commandResolve(response[0]);
|
||||
this._commandResolve = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,4 +46,4 @@ export function activate(context: vscode.ExtensionContext, outChannel: vscode.Ou
|
|||
});
|
||||
|
||||
context.subscriptions.push(disposable);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +1,17 @@
|
|||
import * as vscode from 'vscode';
|
||||
import * as constants from '../../common/constants';
|
||||
|
||||
import { TestFileCodeLensProvider } from './testFiles';
|
||||
import { PythonSymbolProvider } from '../../providers/symbolProvider';
|
||||
import { ITestCollectionStorageService } from '../common/types';
|
||||
import { TestFileCodeLensProvider } from './testFiles';
|
||||
|
||||
export function activateCodeLenses(onDidChange: vscode.EventEmitter<void>,
|
||||
symboldProvider: PythonSymbolProvider, testCollectionStorage: ITestCollectionStorageService): vscode.Disposable {
|
||||
|
||||
export function activateCodeLenses(onDidChange: vscode.EventEmitter<void>, symboldProvider: PythonSymbolProvider): vscode.Disposable {
|
||||
const disposables: vscode.Disposable[] = [];
|
||||
disposables.push(vscode.languages.registerCodeLensProvider(constants.PythonLanguage, new TestFileCodeLensProvider(onDidChange, symboldProvider)));
|
||||
const codeLensProvider = new TestFileCodeLensProvider(onDidChange, symboldProvider, testCollectionStorage);
|
||||
disposables.push(vscode.languages.registerCodeLensProvider(constants.PythonLanguage, codeLensProvider));
|
||||
|
||||
return {
|
||||
dispose: function () {
|
||||
disposables.forEach(d => d.dispose());
|
||||
}
|
||||
dispose: () => { disposables.forEach(d => d.dispose()); }
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,124 +1,129 @@
|
|||
'use strict';
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import { CodeLensProvider, TextDocument, CancellationToken, CodeLens, SymbolInformation } from 'vscode';
|
||||
import { TestFile, TestsToRun, TestSuite, TestFunction, TestStatus } from '../common/contracts';
|
||||
import { CancellationToken, CancellationTokenSource, CodeLens, CodeLensProvider, Event, EventEmitter, Position, Range, SymbolInformation, SymbolKind, TextDocument, workspace } from 'vscode';
|
||||
import { Uri } from 'vscode';
|
||||
import * as constants from '../../common/constants';
|
||||
import { getDiscoveredTests } from '../common/testUtils';
|
||||
import { PythonSymbolProvider } from '../../providers/symbolProvider';
|
||||
import { ITestCollectionStorageService, TestFile, TestFunction, TestStatus, TestsToRun, TestSuite } from '../common/types';
|
||||
|
||||
interface CodeLensData {
|
||||
symbolKind: vscode.SymbolKind;
|
||||
symbolName: string;
|
||||
fileName: string;
|
||||
}
|
||||
interface FunctionsAndSuites {
|
||||
type FunctionsAndSuites = {
|
||||
functions: TestFunction[];
|
||||
suites: TestSuite[];
|
||||
}
|
||||
};
|
||||
|
||||
export class TestFileCodeLensProvider implements CodeLensProvider {
|
||||
constructor(private _onDidChange: vscode.EventEmitter<void>, private symbolProvider: PythonSymbolProvider) {
|
||||
// tslint:disable-next-line:variable-name
|
||||
constructor(private _onDidChange: EventEmitter<void>,
|
||||
private symbolProvider: PythonSymbolProvider,
|
||||
private testCollectionStorage: ITestCollectionStorageService) {
|
||||
}
|
||||
|
||||
get onDidChangeCodeLenses(): vscode.Event<void> {
|
||||
get onDidChangeCodeLenses(): Event<void> {
|
||||
return this._onDidChange.event;
|
||||
}
|
||||
|
||||
public provideCodeLenses(document: TextDocument, token: CancellationToken): Thenable<CodeLens[]> {
|
||||
let testItems = getDiscoveredTests();
|
||||
public async provideCodeLenses(document: TextDocument, token: CancellationToken) {
|
||||
const wkspace = workspace.getWorkspaceFolder(document.uri);
|
||||
if (!wkspace) {
|
||||
return [];
|
||||
}
|
||||
const testItems = this.testCollectionStorage.getTests(wkspace.uri);
|
||||
if (!testItems || testItems.testFiles.length === 0 || testItems.testFunctions.length === 0) {
|
||||
return Promise.resolve([]);
|
||||
return [];
|
||||
}
|
||||
|
||||
let cancelTokenSrc = new vscode.CancellationTokenSource();
|
||||
const cancelTokenSrc = new CancellationTokenSource();
|
||||
token.onCancellationRequested(() => { cancelTokenSrc.cancel(); });
|
||||
|
||||
// Strop trying to build the code lenses if unable to get a list of
|
||||
// symbols in this file afrer x time
|
||||
// symbols in this file afrer x time.
|
||||
setTimeout(() => {
|
||||
if (!cancelTokenSrc.token.isCancellationRequested) {
|
||||
cancelTokenSrc.cancel();
|
||||
}
|
||||
}, constants.Delays.MaxUnitTestCodeLensDelay);
|
||||
|
||||
return getCodeLenses(document, token, this.symbolProvider);
|
||||
return this.getCodeLenses(document, token, this.symbolProvider);
|
||||
}
|
||||
|
||||
resolveCodeLens(codeLens: CodeLens, token: CancellationToken): CodeLens | Thenable<CodeLens> {
|
||||
public resolveCodeLens(codeLens: CodeLens, token: CancellationToken): CodeLens | Thenable<CodeLens> {
|
||||
codeLens.command = { command: 'python.runtests', title: 'Test' };
|
||||
return Promise.resolve(codeLens);
|
||||
}
|
||||
}
|
||||
|
||||
function getCodeLenses(document: vscode.TextDocument, token: vscode.CancellationToken, symbolProvider: PythonSymbolProvider): Thenable<CodeLens[]> {
|
||||
const documentUri = document.uri;
|
||||
const tests = getDiscoveredTests();
|
||||
if (!tests) {
|
||||
return null;
|
||||
private async getCodeLenses(document: TextDocument, token: CancellationToken, symbolProvider: PythonSymbolProvider) {
|
||||
const wkspace = workspace.getWorkspaceFolder(document.uri);
|
||||
if (!wkspace) {
|
||||
return [];
|
||||
}
|
||||
const tests = this.testCollectionStorage.getTests(wkspace.uri);
|
||||
if (!tests) {
|
||||
return [];
|
||||
}
|
||||
const file = tests.testFiles.find(item => item.fullPath === document.uri.fsPath);
|
||||
if (!file) {
|
||||
return [];
|
||||
}
|
||||
const allFuncsAndSuites = getAllTestSuitesAndFunctionsPerFile(file);
|
||||
|
||||
return symbolProvider.provideDocumentSymbolsForInternalUse(document, token)
|
||||
.then((symbols: SymbolInformation[]) => {
|
||||
return symbols.filter(symbol => {
|
||||
return symbol.kind === SymbolKind.Function ||
|
||||
symbol.kind === SymbolKind.Method ||
|
||||
symbol.kind === SymbolKind.Class;
|
||||
}).map(symbol => {
|
||||
// This is bloody crucial, if the start and end columns are the same
|
||||
// then vscode goes bonkers when ever you edit a line (start scrolling magically).
|
||||
const range = new Range(symbol.location.range.start,
|
||||
new Position(symbol.location.range.end.line,
|
||||
symbol.location.range.end.character + 1));
|
||||
|
||||
return this.getCodeLens(document.uri, allFuncsAndSuites,
|
||||
range, symbol.name, symbol.kind, symbol.containerName);
|
||||
}).reduce((previous, current) => previous.concat(current), []).filter(codeLens => codeLens !== null);
|
||||
}, reason => {
|
||||
if (token.isCancellationRequested) {
|
||||
return [];
|
||||
}
|
||||
return Promise.reject(reason);
|
||||
});
|
||||
}
|
||||
const file = tests.testFiles.find(file => file.fullPath === documentUri.fsPath);
|
||||
if (!file) {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
const allFuncsAndSuites = getAllTestSuitesAndFunctionsPerFile(file);
|
||||
|
||||
return symbolProvider.provideDocumentSymbolsForInternalUse(document, token)
|
||||
.then((symbols: vscode.SymbolInformation[]) => {
|
||||
return symbols.filter(symbol => {
|
||||
return symbol.kind === vscode.SymbolKind.Function ||
|
||||
symbol.kind === vscode.SymbolKind.Method ||
|
||||
symbol.kind === vscode.SymbolKind.Class;
|
||||
}).map(symbol => {
|
||||
// This is bloody crucial, if the start and end columns are the same
|
||||
// then vscode goes bonkers when ever you edit a line (start scrolling magically)
|
||||
const range = new vscode.Range(symbol.location.range.start,
|
||||
new vscode.Position(symbol.location.range.end.line,
|
||||
symbol.location.range.end.character + 1));
|
||||
private getCodeLens(file: Uri, allFuncsAndSuites: FunctionsAndSuites,
|
||||
range: Range, symbolName: string, symbolKind: SymbolKind, symbolContainer: string): CodeLens[] {
|
||||
|
||||
return getCodeLens(documentUri.fsPath, allFuncsAndSuites,
|
||||
range, symbol.name, symbol.kind, symbol.containerName);
|
||||
}).reduce((previous, current) => previous.concat(current), []).filter(codeLens => codeLens !== null);
|
||||
}, reason => {
|
||||
if (token.isCancellationRequested) {
|
||||
switch (symbolKind) {
|
||||
case SymbolKind.Function:
|
||||
case SymbolKind.Method: {
|
||||
return getFunctionCodeLens(file, allFuncsAndSuites, symbolName, range, symbolContainer);
|
||||
}
|
||||
case SymbolKind.Class: {
|
||||
const cls = allFuncsAndSuites.suites.find(item => item.name === symbolName);
|
||||
if (!cls) {
|
||||
return null;
|
||||
}
|
||||
return [
|
||||
new CodeLens(range, {
|
||||
title: getTestStatusIcon(cls.status) + constants.Text.CodeLensRunUnitTest,
|
||||
command: constants.Commands.Tests_Run,
|
||||
arguments: [file, <TestsToRun>{ testSuite: [cls] }]
|
||||
}),
|
||||
new CodeLens(range, {
|
||||
title: getTestStatusIcon(cls.status) + constants.Text.CodeLensDebugUnitTest,
|
||||
command: constants.Commands.Tests_Debug,
|
||||
arguments: [file, <TestsToRun>{ testSuite: [cls] }]
|
||||
})
|
||||
];
|
||||
}
|
||||
default: {
|
||||
return [];
|
||||
}
|
||||
return Promise.reject(reason);
|
||||
});
|
||||
}
|
||||
|
||||
function getCodeLens(fileName: string, allFuncsAndSuites: FunctionsAndSuites,
|
||||
range: vscode.Range, symbolName: string, symbolKind: vscode.SymbolKind, symbolContainer: string): vscode.CodeLens[] {
|
||||
|
||||
switch (symbolKind) {
|
||||
case vscode.SymbolKind.Function:
|
||||
case vscode.SymbolKind.Method: {
|
||||
return getFunctionCodeLens(fileName, allFuncsAndSuites, symbolName, range, symbolContainer);
|
||||
}
|
||||
case vscode.SymbolKind.Class: {
|
||||
const cls = allFuncsAndSuites.suites.find(cls => cls.name === symbolName);
|
||||
if (!cls) {
|
||||
return null;
|
||||
}
|
||||
return [
|
||||
new CodeLens(range, {
|
||||
title: getTestStatusIcon(cls.status) + constants.Text.CodeLensRunUnitTest,
|
||||
command: constants.Commands.Tests_Run,
|
||||
arguments: [<TestsToRun>{ testSuite: [cls] }]
|
||||
}),
|
||||
new CodeLens(range, {
|
||||
title: getTestStatusIcon(cls.status) + constants.Text.CodeLensDebugUnitTest,
|
||||
command: constants.Commands.Tests_Debug,
|
||||
arguments: [<TestsToRun>{ testSuite: [cls] }]
|
||||
})
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function getTestStatusIcon(status: TestStatus): string {
|
||||
function getTestStatusIcon(status?: TestStatus): string {
|
||||
switch (status) {
|
||||
case TestStatus.Pass: {
|
||||
return '✔ ';
|
||||
|
@ -137,7 +142,7 @@ function getTestStatusIcon(status: TestStatus): string {
|
|||
}
|
||||
|
||||
function getTestStatusIcons(fns: TestFunction[]): string {
|
||||
let statuses: string[] = [];
|
||||
const statuses: string[] = [];
|
||||
let count = fns.filter(fn => fn.status === TestStatus.Pass).length;
|
||||
if (count > 0) {
|
||||
statuses.push(`✔ ${count}`);
|
||||
|
@ -153,15 +158,14 @@ function getTestStatusIcons(fns: TestFunction[]): string {
|
|||
|
||||
return statuses.join(' ');
|
||||
}
|
||||
function getFunctionCodeLens(filePath: string, functionsAndSuites: FunctionsAndSuites,
|
||||
symbolName: string, range: vscode.Range, symbolContainer: string): vscode.CodeLens[] {
|
||||
function getFunctionCodeLens(file: Uri, functionsAndSuites: FunctionsAndSuites,
|
||||
symbolName: string, range: Range, symbolContainer: string): CodeLens[] {
|
||||
|
||||
let fn: TestFunction;
|
||||
let fn: TestFunction | undefined;
|
||||
if (symbolContainer.length === 0) {
|
||||
fn = functionsAndSuites.functions.find(fn => fn.name === symbolName);
|
||||
}
|
||||
else {
|
||||
// Assume single levels for now
|
||||
fn = functionsAndSuites.functions.find(func => func.name === symbolName);
|
||||
} else {
|
||||
// Assume single levels for now.
|
||||
functionsAndSuites.suites
|
||||
.filter(s => s.name === symbolContainer)
|
||||
.forEach(s => {
|
||||
|
@ -177,54 +181,55 @@ function getFunctionCodeLens(filePath: string, functionsAndSuites: FunctionsAndS
|
|||
new CodeLens(range, {
|
||||
title: getTestStatusIcon(fn.status) + constants.Text.CodeLensRunUnitTest,
|
||||
command: constants.Commands.Tests_Run,
|
||||
arguments: [<TestsToRun>{ testFunction: [fn] }]
|
||||
arguments: [file, <TestsToRun>{ testFunction: [fn] }]
|
||||
}),
|
||||
new CodeLens(range, {
|
||||
title: getTestStatusIcon(fn.status) + constants.Text.CodeLensDebugUnitTest,
|
||||
command: constants.Commands.Tests_Debug,
|
||||
arguments: [<TestsToRun>{ testFunction: [fn] }]
|
||||
arguments: [file, <TestsToRun>{ testFunction: [fn] }]
|
||||
})
|
||||
];
|
||||
}
|
||||
|
||||
// Ok, possible we're dealing with parameterized unit tests
|
||||
// If we have [ in the name, then this is a parameterized function
|
||||
let functions = functionsAndSuites.functions.filter(fn => fn.name.startsWith(symbolName + '[') && fn.name.endsWith(']'));
|
||||
// Ok, possible we're dealing with parameterized unit tests.
|
||||
// If we have [ in the name, then this is a parameterized function.
|
||||
const functions = functionsAndSuites.functions.filter(func => func.name.startsWith(`${symbolName}[`) && func.name.endsWith(']'));
|
||||
if (functions.length === 0) {
|
||||
return null;
|
||||
return [];
|
||||
}
|
||||
if (functions.length === 0) {
|
||||
return [
|
||||
new CodeLens(range, {
|
||||
title: constants.Text.CodeLensRunUnitTest,
|
||||
command: constants.Commands.Tests_Run,
|
||||
arguments: [<TestsToRun>{ testFunction: functions }]
|
||||
arguments: [file, <TestsToRun>{ testFunction: functions }]
|
||||
}),
|
||||
new CodeLens(range, {
|
||||
title: constants.Text.CodeLensDebugUnitTest,
|
||||
command: constants.Commands.Tests_Debug,
|
||||
arguments: [<TestsToRun>{ testFunction: functions }]
|
||||
arguments: [file, <TestsToRun>{ testFunction: functions }]
|
||||
})
|
||||
];
|
||||
}
|
||||
|
||||
// Find all flattened functions
|
||||
// Find all flattened functions.
|
||||
return [
|
||||
new CodeLens(range, {
|
||||
title: getTestStatusIcons(functions) + constants.Text.CodeLensRunUnitTest + ' (Multiple)',
|
||||
title: `${getTestStatusIcons(functions)}${constants.Text.CodeLensRunUnitTest} (Multiple)`,
|
||||
command: constants.Commands.Tests_Picker_UI,
|
||||
arguments: [filePath, functions]
|
||||
arguments: [file, functions]
|
||||
}),
|
||||
new CodeLens(range, {
|
||||
title: getTestStatusIcons(functions) + constants.Text.CodeLensDebugUnitTest + ' (Multiple)',
|
||||
title: `${getTestStatusIcons(functions)}${constants.Text.CodeLensDebugUnitTest} (Multiple)`,
|
||||
command: constants.Commands.Tests_Picker_UI_Debug,
|
||||
arguments: [filePath, functions]
|
||||
arguments: [file, functions]
|
||||
})
|
||||
];
|
||||
}
|
||||
|
||||
function getAllTestSuitesAndFunctionsPerFile(testFile: TestFile): FunctionsAndSuites {
|
||||
const all = { functions: testFile.functions, suites: [] };
|
||||
// tslint:disable-next-line:prefer-type-cast
|
||||
const all = { functions: testFile.functions, suites: [] as TestSuite[] };
|
||||
testFile.suites.forEach(suite => {
|
||||
all.suites.push(suite);
|
||||
|
||||
|
@ -247,4 +252,4 @@ function getAllTestSuitesAndFunctions(testSuite: TestSuite): FunctionsAndSuites
|
|||
all.suites.push(...allChildItems.suites);
|
||||
});
|
||||
return all;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,36 +1,52 @@
|
|||
// import {TestFolder, TestsToRun, Tests, TestFile, TestSuite, TestFunction, TestStatus, FlattenedTestFunction, FlattenedTestSuite, CANCELLATION_REASON} from './contracts';
|
||||
import { Tests, TestStatus, TestsToRun, CANCELLATION_REASON } from './contracts';
|
||||
import * as vscode from 'vscode';
|
||||
import { resetTestResults, displayTestErrorMessage, storeDiscoveredTests } from './testUtils';
|
||||
import { Installer, Product } from '../../common/installer';
|
||||
import { Uri, workspace } from 'vscode';
|
||||
import { IPythonSettings, PythonSettings } from '../../common/configSettings';
|
||||
import { isNotInstalledError } from '../../common/helpers';
|
||||
import { Installer, Product } from '../../common/installer';
|
||||
import { CANCELLATION_REASON } from './constants';
|
||||
import { displayTestErrorMessage } from './testUtils';
|
||||
import { ITestCollectionStorageService, ITestResultsService, ITestsHelper, Tests, TestStatus, TestsToRun } from './types';
|
||||
|
||||
enum CancellationTokenType {
|
||||
testDicovery,
|
||||
testDiscovery,
|
||||
testRunner
|
||||
}
|
||||
|
||||
export abstract class BaseTestManager {
|
||||
public readonly workspace: Uri;
|
||||
protected readonly settings: IPythonSettings;
|
||||
private tests: Tests;
|
||||
// tslint:disable-next-line:variable-name
|
||||
private _status: TestStatus = TestStatus.Unknown;
|
||||
private testDiscoveryCancellationTokenSource: vscode.CancellationTokenSource;
|
||||
private testRunnerCancellationTokenSource: vscode.CancellationTokenSource;
|
||||
private installer: Installer;
|
||||
protected get testDiscoveryCancellationToken(): vscode.CancellationToken {
|
||||
if (this.testDiscoveryCancellationTokenSource) {
|
||||
return this.testDiscoveryCancellationTokenSource.token;
|
||||
}
|
||||
private discoverTestsPromise: Promise<Tests>;
|
||||
constructor(private testProvider: string, private product: Product, protected rootDirectory: string,
|
||||
protected outputChannel: vscode.OutputChannel, private testCollectionStorage: ITestCollectionStorageService,
|
||||
protected testResultsService: ITestResultsService, protected testsHelper: ITestsHelper) {
|
||||
this._status = TestStatus.Unknown;
|
||||
this.installer = new Installer();
|
||||
this.settings = PythonSettings.getInstance(this.rootDirectory ? Uri.file(this.rootDirectory) : undefined);
|
||||
this.workspace = workspace.getWorkspaceFolder(Uri.file(this.rootDirectory)).uri;
|
||||
}
|
||||
protected get testRunnerCancellationToken(): vscode.CancellationToken {
|
||||
if (this.testRunnerCancellationTokenSource) {
|
||||
return this.testRunnerCancellationTokenSource.token;
|
||||
}
|
||||
protected get testDiscoveryCancellationToken(): vscode.CancellationToken | undefined {
|
||||
return this.testDiscoveryCancellationTokenSource ? this.testDiscoveryCancellationTokenSource.token : undefined;
|
||||
}
|
||||
protected get testRunnerCancellationToken(): vscode.CancellationToken | undefined {
|
||||
return this.testRunnerCancellationTokenSource ? this.testRunnerCancellationTokenSource.token : undefined;
|
||||
}
|
||||
public dispose() {
|
||||
this.stop();
|
||||
}
|
||||
public get status(): TestStatus {
|
||||
return this._status;
|
||||
}
|
||||
public get workingDirectory(): string {
|
||||
const settings = PythonSettings.getInstance(vscode.Uri.file(this.rootDirectory));
|
||||
return settings.unitTest.cwd && settings.unitTest.cwd.length > 0 ? settings.unitTest.cwd : this.rootDirectory;
|
||||
}
|
||||
public stop() {
|
||||
if (this.testDiscoveryCancellationTokenSource) {
|
||||
this.testDiscoveryCancellationTokenSource.cancel();
|
||||
|
@ -39,10 +55,6 @@ export abstract class BaseTestManager {
|
|||
this.testRunnerCancellationTokenSource.cancel();
|
||||
}
|
||||
}
|
||||
constructor(private testProvider: string, private product: Product, protected rootDirectory: string, protected outputChannel: vscode.OutputChannel) {
|
||||
this._status = TestStatus.Unknown;
|
||||
this.installer = new Installer();
|
||||
}
|
||||
public reset() {
|
||||
this._status = TestStatus.Unknown;
|
||||
this.tests = null;
|
||||
|
@ -52,31 +64,9 @@ export abstract class BaseTestManager {
|
|||
return;
|
||||
}
|
||||
|
||||
resetTestResults(this.tests);
|
||||
this.testResultsService.resetResults(this.tests);
|
||||
}
|
||||
private createCancellationToken(tokenType: CancellationTokenType) {
|
||||
this.disposeCancellationToken(tokenType);
|
||||
if (tokenType === CancellationTokenType.testDicovery) {
|
||||
this.testDiscoveryCancellationTokenSource = new vscode.CancellationTokenSource();
|
||||
} else {
|
||||
this.testRunnerCancellationTokenSource = new vscode.CancellationTokenSource();
|
||||
}
|
||||
}
|
||||
private disposeCancellationToken(tokenType: CancellationTokenType) {
|
||||
if (tokenType === CancellationTokenType.testDicovery) {
|
||||
if (this.testDiscoveryCancellationTokenSource) {
|
||||
this.testDiscoveryCancellationTokenSource.dispose();
|
||||
}
|
||||
this.testDiscoveryCancellationTokenSource = null;
|
||||
} else {
|
||||
if (this.testRunnerCancellationTokenSource) {
|
||||
this.testRunnerCancellationTokenSource.dispose();
|
||||
}
|
||||
this.testRunnerCancellationTokenSource = null;
|
||||
}
|
||||
}
|
||||
private discoverTestsPromise: Promise<Tests>;
|
||||
discoverTests(ignoreCache: boolean = false, quietMode: boolean = false, isUserInitiated: boolean = true): Promise<Tests> {
|
||||
public async discoverTests(ignoreCache: boolean = false, quietMode: boolean = false, userInitiated: boolean = false): Promise<Tests> {
|
||||
if (this.discoverTestsPromise) {
|
||||
return this.discoverTestsPromise;
|
||||
}
|
||||
|
@ -86,10 +76,14 @@ export abstract class BaseTestManager {
|
|||
return Promise.resolve(this.tests);
|
||||
}
|
||||
this._status = TestStatus.Discovering;
|
||||
if (isUserInitiated) {
|
||||
|
||||
// If ignoreCache is true, its an indication of the fact that its a user invoked operation.
|
||||
// Hence we can stop the debugger.
|
||||
if (userInitiated) {
|
||||
this.stop();
|
||||
}
|
||||
this.createCancellationToken(CancellationTokenType.testDicovery);
|
||||
|
||||
this.createCancellationToken(CancellationTokenType.testDiscovery);
|
||||
return this.discoverTestsPromise = this.discoverTestsImpl(ignoreCache)
|
||||
.then(tests => {
|
||||
this.tests = tests;
|
||||
|
@ -111,13 +105,15 @@ export abstract class BaseTestManager {
|
|||
if (haveErrorsInDiscovering && !quietMode) {
|
||||
displayTestErrorMessage('There were some errors in disovering unit tests');
|
||||
}
|
||||
storeDiscoveredTests(tests);
|
||||
this.disposeCancellationToken(CancellationTokenType.testDicovery);
|
||||
const wkspace = vscode.workspace.getWorkspaceFolder(vscode.Uri.file(this.rootDirectory)).uri;
|
||||
this.testCollectionStorage.storeTests(wkspace, tests);
|
||||
this.disposeCancellationToken(CancellationTokenType.testDiscovery);
|
||||
|
||||
return tests;
|
||||
}).catch(reason => {
|
||||
if (isNotInstalledError(reason) && !quietMode) {
|
||||
this.installer.promptToInstall(this.product);
|
||||
// tslint:disable-next-line:no-floating-promises
|
||||
this.installer.promptToInstall(this.product, this.workspace);
|
||||
}
|
||||
|
||||
this.tests = null;
|
||||
|
@ -125,24 +121,20 @@ export abstract class BaseTestManager {
|
|||
if (this.testDiscoveryCancellationToken && this.testDiscoveryCancellationToken.isCancellationRequested) {
|
||||
reason = CANCELLATION_REASON;
|
||||
this._status = TestStatus.Idle;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
this._status = TestStatus.Error;
|
||||
this.outputChannel.appendLine('Test Disovery failed: ');
|
||||
// tslint:disable-next-line:prefer-template
|
||||
this.outputChannel.appendLine('' + reason);
|
||||
}
|
||||
storeDiscoveredTests(null);
|
||||
this.disposeCancellationToken(CancellationTokenType.testDicovery);
|
||||
const wkspace = vscode.workspace.getWorkspaceFolder(vscode.Uri.file(this.rootDirectory)).uri;
|
||||
this.testCollectionStorage.storeTests(wkspace, null);
|
||||
this.disposeCancellationToken(CancellationTokenType.testDiscovery);
|
||||
return Promise.reject(reason);
|
||||
});
|
||||
}
|
||||
abstract discoverTestsImpl(ignoreCache: boolean, debug?: boolean): Promise<Tests>;
|
||||
public runTest(testsToRun?: TestsToRun, debug?: boolean): Promise<Tests>;
|
||||
public runTest(runFailedTests?: boolean, debug?: boolean): Promise<Tests>;
|
||||
public runTest(args: any, debug?: boolean): Promise<Tests> {
|
||||
let runFailedTests = false;
|
||||
let testsToRun: TestsToRun = null;
|
||||
let moreInfo = {
|
||||
public runTest(testsToRun?: TestsToRun, runFailedTests?: boolean, debug?: boolean): Promise<Tests> {
|
||||
const moreInfo = {
|
||||
Test_Provider: this.testProvider,
|
||||
Run_Failed_Tests: 'false',
|
||||
Run_Specific_File: 'false',
|
||||
|
@ -150,12 +142,11 @@ export abstract class BaseTestManager {
|
|||
Run_Specific_Function: 'false'
|
||||
};
|
||||
|
||||
if (typeof args === 'boolean') {
|
||||
runFailedTests = args === true;
|
||||
if (runFailedTests === true) {
|
||||
// tslint:disable-next-line:prefer-template
|
||||
moreInfo.Run_Failed_Tests = runFailedTests + '';
|
||||
}
|
||||
if (typeof args === 'object' && args !== null) {
|
||||
testsToRun = args;
|
||||
if (testsToRun && typeof testsToRun === 'object') {
|
||||
if (Array.isArray(testsToRun.testFile) && testsToRun.testFile.length > 0) {
|
||||
moreInfo.Run_Specific_File = 'true';
|
||||
}
|
||||
|
@ -172,7 +163,6 @@ export abstract class BaseTestManager {
|
|||
|
||||
this._status = TestStatus.Running;
|
||||
this.stop();
|
||||
this.createCancellationToken(CancellationTokenType.testDicovery);
|
||||
// If running failed tests, then don't clear the previously build UnitTests
|
||||
// If we do so, then we end up re-discovering the unit tests and clearing previously cached list of failed tests
|
||||
// Similarly, if running a specific test or test file, don't clear the cache (possible tests have some state information retained)
|
||||
|
@ -184,7 +174,7 @@ export abstract class BaseTestManager {
|
|||
}
|
||||
displayTestErrorMessage('Errors in discovering tests, continuing with tests');
|
||||
return <Tests>{
|
||||
rootTestFolders: [], testFiles: [], testFolders: [], testFunctions: [], testSuits: [],
|
||||
rootTestFolders: [], testFiles: [], testFolders: [], testFunctions: [], testSuites: [],
|
||||
summary: { errors: 0, failures: 0, passed: 0, skipped: 0 }
|
||||
};
|
||||
})
|
||||
|
@ -199,13 +189,35 @@ export abstract class BaseTestManager {
|
|||
if (this.testRunnerCancellationToken && this.testRunnerCancellationToken.isCancellationRequested) {
|
||||
reason = CANCELLATION_REASON;
|
||||
this._status = TestStatus.Idle;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
this._status = TestStatus.Error;
|
||||
}
|
||||
this.disposeCancellationToken(CancellationTokenType.testRunner);
|
||||
return Promise.reject<Tests>(reason);
|
||||
});
|
||||
}
|
||||
abstract runTestImpl(tests: Tests, testsToRun?: TestsToRun, runFailedTests?: boolean, debug?: boolean): Promise<any>;
|
||||
// tslint:disable-next-line:no-any
|
||||
protected abstract runTestImpl(tests: Tests, testsToRun?: TestsToRun, runFailedTests?: boolean, debug?: boolean): Promise<any>;
|
||||
protected abstract discoverTestsImpl(ignoreCache: boolean, debug?: boolean): Promise<Tests>;
|
||||
private createCancellationToken(tokenType: CancellationTokenType) {
|
||||
this.disposeCancellationToken(tokenType);
|
||||
if (tokenType === CancellationTokenType.testDiscovery) {
|
||||
this.testDiscoveryCancellationTokenSource = new vscode.CancellationTokenSource();
|
||||
} else {
|
||||
this.testRunnerCancellationTokenSource = new vscode.CancellationTokenSource();
|
||||
}
|
||||
}
|
||||
private disposeCancellationToken(tokenType: CancellationTokenType) {
|
||||
if (tokenType === CancellationTokenType.testDiscovery) {
|
||||
if (this.testDiscoveryCancellationTokenSource) {
|
||||
this.testDiscoveryCancellationTokenSource.dispose();
|
||||
}
|
||||
this.testDiscoveryCancellationTokenSource = null;
|
||||
} else {
|
||||
if (this.testRunnerCancellationTokenSource) {
|
||||
this.testRunnerCancellationTokenSource.dispose();
|
||||
}
|
||||
this.testRunnerCancellationTokenSource = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
import { ConfigurationTarget, Uri, workspace, WorkspaceConfiguration } from 'vscode';
|
||||
import { Product } from '../../common/installer';
|
||||
import { ITestConfigSettingsService, UnitTestProduct } from './types';
|
||||
|
||||
export class TestConfigSettingsService implements ITestConfigSettingsService {
|
||||
private static getTestArgSetting(product: UnitTestProduct) {
|
||||
switch (product) {
|
||||
case Product.unittest:
|
||||
return 'unitTest.unittestArgs';
|
||||
case Product.pytest:
|
||||
return 'unitTest.pyTestArgs';
|
||||
case Product.nosetest:
|
||||
return 'unitTest.nosetestArgs';
|
||||
default:
|
||||
throw new Error('Invalid Test Product');
|
||||
}
|
||||
}
|
||||
private static getTestEnablingSetting(product: UnitTestProduct) {
|
||||
switch (product) {
|
||||
case Product.unittest:
|
||||
return 'unitTest.unittestEnabled';
|
||||
case Product.pytest:
|
||||
return 'unitTest.pyTestEnabled';
|
||||
case Product.nosetest:
|
||||
return 'unitTest.nosetestsEnabled';
|
||||
default:
|
||||
throw new Error('Invalid Test Product');
|
||||
}
|
||||
}
|
||||
// tslint:disable-next-line:no-any
|
||||
private static async updateSetting(testDirectory: string | Uri, setting: string, value: any) {
|
||||
let pythonConfig: WorkspaceConfiguration;
|
||||
let configTarget: ConfigurationTarget;
|
||||
const resource = typeof testDirectory === 'string' ? Uri.file(testDirectory) : testDirectory;
|
||||
if (!Array.isArray(workspace.workspaceFolders) || workspace.workspaceFolders.length === 0) {
|
||||
configTarget = ConfigurationTarget.Workspace;
|
||||
pythonConfig = workspace.getConfiguration('python');
|
||||
} else if (workspace.workspaceFolders.length === 1) {
|
||||
configTarget = ConfigurationTarget.Workspace;
|
||||
pythonConfig = workspace.getConfiguration('python', workspace.workspaceFolders[0].uri);
|
||||
} else {
|
||||
configTarget = ConfigurationTarget.Workspace;
|
||||
const workspaceFolder = workspace.getWorkspaceFolder(resource);
|
||||
if (!workspaceFolder) {
|
||||
throw new Error(`Test directory does not belong to any workspace (${testDirectory})`);
|
||||
}
|
||||
// tslint:disable-next-line:no-non-null-assertion
|
||||
pythonConfig = workspace.getConfiguration('python', workspaceFolder!.uri);
|
||||
}
|
||||
|
||||
return pythonConfig.update(setting, value);
|
||||
}
|
||||
public async updateTestArgs(testDirectory: string | Uri, product: UnitTestProduct, args: string[]) {
|
||||
const setting = TestConfigSettingsService.getTestArgSetting(product);
|
||||
return TestConfigSettingsService.updateSetting(testDirectory, setting, args);
|
||||
}
|
||||
|
||||
public async enable(testDirectory: string | Uri, product: UnitTestProduct): Promise<void> {
|
||||
const setting = TestConfigSettingsService.getTestEnablingSetting(product);
|
||||
return TestConfigSettingsService.updateSetting(testDirectory, setting, true);
|
||||
}
|
||||
|
||||
public async disable(testDirectory: string | Uri, product: UnitTestProduct): Promise<void> {
|
||||
const setting = TestConfigSettingsService.getTestEnablingSetting(product);
|
||||
return TestConfigSettingsService.updateSetting(testDirectory, setting, false);
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
export const CANCELLATION_REASON = 'cancelled_user_request';
|
|
@ -1,89 +0,0 @@
|
|||
export const CANCELLATION_REASON = 'cancelled_user_request';
|
||||
|
||||
export interface TestFolder extends TestResult {
|
||||
name: string;
|
||||
testFiles: TestFile[];
|
||||
nameToRun: string;
|
||||
status?: TestStatus;
|
||||
folders: TestFolder[];
|
||||
}
|
||||
export interface TestFile extends TestResult {
|
||||
name: string;
|
||||
fullPath: string;
|
||||
functions: TestFunction[];
|
||||
suites: TestSuite[];
|
||||
nameToRun: string;
|
||||
xmlName: string;
|
||||
status?: TestStatus;
|
||||
errorsWhenDiscovering?: string;
|
||||
}
|
||||
export interface TestSuite extends TestResult {
|
||||
name: string;
|
||||
functions: TestFunction[];
|
||||
suites: TestSuite[];
|
||||
isUnitTest: Boolean;
|
||||
isInstance: Boolean;
|
||||
nameToRun: string;
|
||||
xmlName: string;
|
||||
status?: TestStatus;
|
||||
}
|
||||
export interface TestFunction extends TestResult {
|
||||
name: string;
|
||||
nameToRun: string;
|
||||
status?: TestStatus;
|
||||
}
|
||||
export interface TestResult extends Node {
|
||||
passed?: boolean;
|
||||
time: number;
|
||||
line?: number;
|
||||
message?: string;
|
||||
traceback?: string;
|
||||
functionsPassed?: number;
|
||||
functionsFailed?: number;
|
||||
functionsDidNotRun?: number;
|
||||
}
|
||||
export interface Node {
|
||||
expanded?: Boolean;
|
||||
}
|
||||
export interface FlattenedTestFunction {
|
||||
testFunction: TestFunction;
|
||||
parentTestSuite?: TestSuite;
|
||||
parentTestFile: TestFile;
|
||||
xmlClassName: string;
|
||||
}
|
||||
export interface FlattenedTestSuite {
|
||||
testSuite: TestSuite;
|
||||
parentTestSuite?: TestSuite;
|
||||
parentTestFile: TestFile;
|
||||
xmlClassName: string;
|
||||
}
|
||||
export interface TestSummary {
|
||||
passed: number;
|
||||
failures: number;
|
||||
errors: number;
|
||||
skipped: number;
|
||||
}
|
||||
export interface Tests {
|
||||
summary: TestSummary;
|
||||
testFiles: TestFile[];
|
||||
testFunctions: FlattenedTestFunction[];
|
||||
testSuits: FlattenedTestSuite[];
|
||||
testFolders: TestFolder[];
|
||||
rootTestFolders: TestFolder[];
|
||||
}
|
||||
export enum TestStatus {
|
||||
Unknown,
|
||||
Discovering,
|
||||
Idle,
|
||||
Running,
|
||||
Fail,
|
||||
Error,
|
||||
Skipped,
|
||||
Pass
|
||||
}
|
||||
export interface TestsToRun {
|
||||
testFolder?: TestFolder[];
|
||||
testFile?: TestFile[];
|
||||
testSuite?: TestSuite[];
|
||||
testFunction?: TestFunction[];
|
||||
}
|
|
@ -1,63 +1,68 @@
|
|||
import * as os from 'os';
|
||||
import { CancellationToken, debug, OutputChannel, workspace, Uri } from 'vscode';
|
||||
import { CancellationToken, debug, OutputChannel, Uri, workspace } from 'vscode';
|
||||
import { PythonSettings } from '../../common/configSettings';
|
||||
import { execPythonFile } from './../../common/utils';
|
||||
import { createDeferred } from './../../common/helpers';
|
||||
import { execPythonFile } from './../../common/utils';
|
||||
import { ITestDebugLauncher } from './types';
|
||||
|
||||
const pythonSettings = PythonSettings.getInstance();
|
||||
export function launchDebugger(rootDirectory: string, testArgs: string[], token?: CancellationToken, outChannel?: OutputChannel) {
|
||||
const def = createDeferred<any>();
|
||||
const launchDef = createDeferred<any>();
|
||||
let outputChannelShown = false;
|
||||
execPythonFile(pythonSettings.pythonPath, testArgs, rootDirectory, true, (data: string) => {
|
||||
if (data.startsWith('READY' + os.EOL)) {
|
||||
// debug socket server has started.
|
||||
launchDef.resolve();
|
||||
data = data.substring(('READY' + os.EOL).length);
|
||||
}
|
||||
export class DebugLauncher implements ITestDebugLauncher {
|
||||
public async launchDebugger(rootDirectory: string, testArgs: string[], token?: CancellationToken, outChannel?: OutputChannel) {
|
||||
const pythonSettings = PythonSettings.getInstance(rootDirectory ? Uri.file(rootDirectory) : undefined);
|
||||
// tslint:disable-next-line:no-any
|
||||
const def = createDeferred<any>();
|
||||
// tslint:disable-next-line:no-any
|
||||
const launchDef = createDeferred<any>();
|
||||
let outputChannelShown = false;
|
||||
execPythonFile(rootDirectory, pythonSettings.pythonPath, testArgs, rootDirectory, true, (data: string) => {
|
||||
if (data.startsWith(`READY${os.EOL}`)) {
|
||||
// debug socket server has started.
|
||||
launchDef.resolve();
|
||||
data = data.substring((`READY${os.EOL}`).length);
|
||||
}
|
||||
|
||||
if (!outputChannelShown) {
|
||||
outputChannelShown = true;
|
||||
outChannel.show();
|
||||
}
|
||||
outChannel.append(data);
|
||||
}, token).catch(reason => {
|
||||
if (!def.rejected && !def.resolved) {
|
||||
def.reject(reason);
|
||||
}
|
||||
}).then(() => {
|
||||
if (!def.rejected && !def.resolved) {
|
||||
def.resolve();
|
||||
}
|
||||
}).catch(reason => {
|
||||
if (!def.rejected && !def.resolved) {
|
||||
def.reject(reason);
|
||||
}
|
||||
});
|
||||
|
||||
launchDef.promise.then(() => {
|
||||
if (!Array.isArray(workspace.workspaceFolders) || workspace.workspaceFolders.length === 0) {
|
||||
throw new Error('Please open a workspace');
|
||||
}
|
||||
let workspaceFolder = workspace.getWorkspaceFolder(Uri.file(rootDirectory));
|
||||
if (!workspaceFolder) {
|
||||
workspaceFolder = workspace.workspaceFolders[0];
|
||||
}
|
||||
return debug.startDebugging(workspaceFolder, {
|
||||
"name": "Debug Unit Test",
|
||||
"type": "python",
|
||||
"request": "attach",
|
||||
"localRoot": rootDirectory,
|
||||
"remoteRoot": rootDirectory,
|
||||
"port": pythonSettings.unitTest.debugPort,
|
||||
"secret": "my_secret",
|
||||
"host": "localhost"
|
||||
if (!outputChannelShown) {
|
||||
outputChannelShown = true;
|
||||
outChannel.show();
|
||||
}
|
||||
outChannel.append(data);
|
||||
}, token).catch(reason => {
|
||||
if (!def.rejected && !def.resolved) {
|
||||
def.reject(reason);
|
||||
}
|
||||
}).then(() => {
|
||||
if (!def.rejected && !def.resolved) {
|
||||
def.resolve();
|
||||
}
|
||||
}).catch(reason => {
|
||||
if (!def.rejected && !def.resolved) {
|
||||
def.reject(reason);
|
||||
}
|
||||
});
|
||||
}).catch(reason => {
|
||||
if (!def.rejected && !def.resolved) {
|
||||
def.reject(reason);
|
||||
}
|
||||
});
|
||||
|
||||
return def.promise;
|
||||
launchDef.promise.then(() => {
|
||||
if (!Array.isArray(workspace.workspaceFolders) || workspace.workspaceFolders.length === 0) {
|
||||
throw new Error('Please open a workspace');
|
||||
}
|
||||
let workspaceFolder = workspace.getWorkspaceFolder(Uri.file(rootDirectory));
|
||||
if (!workspaceFolder) {
|
||||
workspaceFolder = workspace.workspaceFolders[0];
|
||||
}
|
||||
return debug.startDebugging(workspaceFolder, {
|
||||
name: 'Debug Unit Test',
|
||||
type: 'python',
|
||||
request: 'attach',
|
||||
localRoot: rootDirectory,
|
||||
remoteRoot: rootDirectory,
|
||||
port: pythonSettings.unitTest.debugPort,
|
||||
secret: 'my_secret',
|
||||
host: 'localhost'
|
||||
});
|
||||
}).catch(reason => {
|
||||
if (!def.rejected && !def.resolved) {
|
||||
def.reject(reason);
|
||||
}
|
||||
});
|
||||
|
||||
return def.promise;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,44 +1,7 @@
|
|||
import { execPythonFile } from './../../common/utils';
|
||||
import { CancellationToken, OutputChannel, window, workspace } from 'vscode';
|
||||
import { getPythonInterpreterDirectory, IS_WINDOWS, PATH_VARIABLE_NAME } from '../../common/utils';
|
||||
import { IS_WINDOWS, PATH_VARIABLE_NAME } from '../../common/utils';
|
||||
import { execPythonFile } from './../../common/utils';
|
||||
|
||||
let terminal = null;
|
||||
export function run(file: string, args: string[], cwd: string, token?: CancellationToken, outChannel?: OutputChannel): Promise<string> {
|
||||
return execPythonFile(file, args, cwd, true, (data: string) => outChannel.append(data), token);
|
||||
|
||||
// Bug, we cannot resolve this
|
||||
// Resolving here means that tests have completed
|
||||
// We need a way to determine that the tests have completed succefully.. hmm
|
||||
// We could use a hack, such as generating a textfile at the end of the command and monitoring.. hack hack hack
|
||||
// Or we could generate a shell script file and embed all of the hacks in here... hack hack hack...
|
||||
// return runTestInTerminal(file, args, cwd);
|
||||
export async function run(file: string, args: string[], cwd: string, token?: CancellationToken, outChannel?: OutputChannel): Promise<string> {
|
||||
return execPythonFile(cwd, file, args, cwd, true, (data: string) => outChannel.append(data), token);
|
||||
}
|
||||
|
||||
function runTestInTerminal(file: string, args: string[], cwd: string): Promise<any> {
|
||||
return getPythonInterpreterDirectory().then(pyPath => {
|
||||
let commands = [];
|
||||
if (IS_WINDOWS) {
|
||||
commands.push(`set ${PATH_VARIABLE_NAME}=%${PATH_VARIABLE_NAME}%;${pyPath}`);
|
||||
}
|
||||
else {
|
||||
commands.push(`export ${PATH_VARIABLE_NAME}=$${PATH_VARIABLE_NAME}:${pyPath}`);
|
||||
}
|
||||
if (cwd !== workspace.rootPath && typeof cwd === 'string') {
|
||||
commands.push(`cd ${cwd}`);
|
||||
}
|
||||
commands.push(`${file} ${args.join(' ')}`);
|
||||
terminal = window.createTerminal(`Python Test Log`);
|
||||
|
||||
return new Promise<any>((resolve) => {
|
||||
setTimeout(function () {
|
||||
terminal.show();
|
||||
terminal.sendText(commands.join(' && '));
|
||||
|
||||
// Bug, we cannot resolve this
|
||||
// Resolving here means that tests have completed
|
||||
// We need a way to determine that the tests have completed succefully.. hmm
|
||||
resolve();
|
||||
}, 1000);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
import { Uri, workspace } from 'vscode';
|
||||
import { ITestCollectionStorageService, Tests } from './types';
|
||||
|
||||
export class TestCollectionStorageService implements ITestCollectionStorageService {
|
||||
private testsIndexedByWorkspaceUri = new Map<string, Tests | null | undefined>();
|
||||
public getTests(wkspace: Uri): Tests | undefined {
|
||||
const workspaceFolder = this.getWorkspaceFolderPath(wkspace) || '';
|
||||
return this.testsIndexedByWorkspaceUri.has(workspaceFolder) ? this.testsIndexedByWorkspaceUri.get(workspaceFolder) : undefined;
|
||||
}
|
||||
public storeTests(wkspace: Uri, tests: Tests | null | undefined): void {
|
||||
const workspaceFolder = this.getWorkspaceFolderPath(wkspace) || '';
|
||||
this.testsIndexedByWorkspaceUri.set(workspaceFolder, tests);
|
||||
}
|
||||
public dispose() {
|
||||
this.testsIndexedByWorkspaceUri.clear();
|
||||
}
|
||||
private getWorkspaceFolderPath(resource: Uri): string | undefined {
|
||||
const folder = workspace.getWorkspaceFolder(resource);
|
||||
return folder ? folder.uri.path : undefined;
|
||||
}
|
||||
}
|
|
@ -1,14 +1,26 @@
|
|||
import * as vscode from 'vscode';
|
||||
import { createDeferred } from '../../common/helpers';
|
||||
import { getSubDirectories } from '../../common/utils';
|
||||
import * as path from 'path';
|
||||
import * as vscode from 'vscode';
|
||||
import { Uri } from 'vscode';
|
||||
import { createDeferred } from '../../common/helpers';
|
||||
import { Installer } from '../../common/installer';
|
||||
import { getSubDirectories } from '../../common/utils';
|
||||
import { ITestConfigSettingsService, UnitTestProduct } from './types';
|
||||
|
||||
export abstract class TestConfigurationManager {
|
||||
public abstract enable(): Thenable<any>;
|
||||
public abstract disable(): Thenable<any>;
|
||||
constructor(protected readonly outputChannel: vscode.OutputChannel) { }
|
||||
public abstract configure(rootDir: string): Promise<any>;
|
||||
|
||||
constructor(protected workspace: Uri,
|
||||
protected product: UnitTestProduct,
|
||||
protected readonly outputChannel: vscode.OutputChannel,
|
||||
protected installer: Installer,
|
||||
protected testConfigSettingsService: ITestConfigSettingsService) { }
|
||||
// tslint:disable-next-line:no-any
|
||||
public abstract configure(wkspace: Uri): Promise<any>;
|
||||
public async enable() {
|
||||
return this.testConfigSettingsService.enable(this.workspace, this.product);
|
||||
}
|
||||
// tslint:disable-next-line:no-any
|
||||
public async disable() {
|
||||
return this.testConfigSettingsService.enable(this.workspace, this.product);
|
||||
}
|
||||
protected selectTestDir(rootDir: string, subDirs: string[], customOptions: vscode.QuickPickItem[] = []): Promise<string> {
|
||||
const options = {
|
||||
matchOnDescription: true,
|
||||
|
@ -22,7 +34,7 @@ export abstract class TestConfigurationManager {
|
|||
}
|
||||
return <vscode.QuickPickItem>{
|
||||
label: dirName,
|
||||
description: '',
|
||||
description: ''
|
||||
};
|
||||
}).filter(item => item !== null);
|
||||
|
||||
|
@ -46,12 +58,12 @@ export abstract class TestConfigurationManager {
|
|||
matchOnDetail: true,
|
||||
placeHolder: 'Select the pattern to identify test files'
|
||||
};
|
||||
let items: vscode.QuickPickItem[] = [
|
||||
{ label: '*test.py', description: `Python Files ending with 'test'` },
|
||||
{ label: '*_test.py', description: `Python Files ending with '_test'` },
|
||||
{ label: 'test*.py', description: `Python Files begining with 'test'` },
|
||||
{ label: 'test_*.py', description: `Python Files begining with 'test_'` },
|
||||
{ label: '*test*.py', description: `Python Files containing the word 'test'` }
|
||||
const items: vscode.QuickPickItem[] = [
|
||||
{ label: '*test.py', description: 'Python Files ending with \'test\'' },
|
||||
{ label: '*_test.py', description: 'Python Files ending with \'_test\'' },
|
||||
{ label: 'test*.py', description: 'Python Files begining with \'test\'' },
|
||||
{ label: 'test_*.py', description: 'Python Files begining with \'test_\'' },
|
||||
{ label: '*test*.py', description: 'Python Files containing the word \'test\'' }
|
||||
];
|
||||
|
||||
const def = createDeferred<string>();
|
||||
|
@ -65,17 +77,17 @@ export abstract class TestConfigurationManager {
|
|||
|
||||
return def.promise;
|
||||
}
|
||||
protected getTestDirs(rootDir): Promise<string[]> {
|
||||
protected getTestDirs(rootDir: string): Promise<string[]> {
|
||||
return getSubDirectories(rootDir).then(subDirs => {
|
||||
subDirs.sort();
|
||||
|
||||
// Find out if there are any dirs with the name test and place them on the top
|
||||
let possibleTestDirs = subDirs.filter(dir => dir.match(/test/i));
|
||||
let nonTestDirs = subDirs.filter(dir => possibleTestDirs.indexOf(dir) === -1);
|
||||
// Find out if there are any dirs with the name test and place them on the top.
|
||||
const possibleTestDirs = subDirs.filter(dir => dir.match(/test/i));
|
||||
const nonTestDirs = subDirs.filter(dir => possibleTestDirs.indexOf(dir) === -1);
|
||||
possibleTestDirs.push(...nonTestDirs);
|
||||
|
||||
// The test dirs are now on top
|
||||
// The test dirs are now on top.
|
||||
return possibleTestDirs;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
import { OutputChannel, Uri } from 'vscode';
|
||||
import { PythonSettings } from '../../common/configSettings';
|
||||
import { Product } from '../../common/installer';
|
||||
import { TestManager as NoseTestManager } from '../nosetest/main';
|
||||
import { TestManager as PyTestTestManager } from '../pytest/main';
|
||||
import { TestManager as UnitTestTestManager } from '../unittest/main';
|
||||
import { BaseTestManager } from './baseTestManager';
|
||||
import { ITestCollectionStorageService, ITestDebugLauncher, ITestManagerService, ITestResultsService, ITestsHelper, UnitTestProduct } from './types';
|
||||
|
||||
type TestManagerInstanceInfo = { instance?: BaseTestManager, create(rootDirectory: string): BaseTestManager };
|
||||
|
||||
export class TestManagerService implements ITestManagerService {
|
||||
private testManagers = new Map<Product, TestManagerInstanceInfo>();
|
||||
constructor(private wkspace: Uri, private outChannel: OutputChannel,
|
||||
testCollectionStorage: ITestCollectionStorageService, testResultsService: ITestResultsService,
|
||||
testsHelper: ITestsHelper, debugLauncher: ITestDebugLauncher) {
|
||||
this.testManagers.set(Product.nosetest, {
|
||||
create: (rootDirectory: string) => new NoseTestManager(rootDirectory, this.outChannel, testCollectionStorage, testResultsService, testsHelper, debugLauncher)
|
||||
});
|
||||
this.testManagers.set(Product.pytest, {
|
||||
create: (rootDirectory: string) => new PyTestTestManager(rootDirectory, this.outChannel, testCollectionStorage, testResultsService, testsHelper, debugLauncher)
|
||||
});
|
||||
this.testManagers.set(Product.unittest, {
|
||||
create: (rootDirectory: string) => new UnitTestTestManager(rootDirectory, this.outChannel, testCollectionStorage, testResultsService, testsHelper, debugLauncher)
|
||||
});
|
||||
}
|
||||
public dispose() {
|
||||
this.testManagers.forEach(info => {
|
||||
if (info.instance) {
|
||||
info.instance.dispose();
|
||||
}
|
||||
});
|
||||
}
|
||||
public getTestManager(): BaseTestManager | undefined {
|
||||
const preferredTestManager = this.getPreferredTestManager();
|
||||
if (typeof preferredTestManager !== 'number') {
|
||||
return;
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:no-non-null-assertion
|
||||
const info = this.testManagers.get(preferredTestManager)!;
|
||||
if (!info.instance) {
|
||||
const testDirectory = this.getTestWorkingDirectory();
|
||||
info.instance = info.create(testDirectory);
|
||||
}
|
||||
return info.instance;
|
||||
}
|
||||
public getTestWorkingDirectory() {
|
||||
const settings = PythonSettings.getInstance(this.wkspace);
|
||||
return settings.unitTest.cwd && settings.unitTest.cwd.length > 0 ? settings.unitTest.cwd : this.wkspace.fsPath;
|
||||
}
|
||||
public getPreferredTestManager(): UnitTestProduct | undefined {
|
||||
const settings = PythonSettings.getInstance(this.wkspace);
|
||||
if (settings.unitTest.nosetestsEnabled) {
|
||||
return Product.nosetest;
|
||||
} else if (settings.unitTest.pyTestEnabled) {
|
||||
return Product.pytest;
|
||||
} else if (settings.unitTest.unittestEnabled) {
|
||||
return Product.unittest;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
import { OutputChannel, Uri } from 'vscode';
|
||||
import { TestManagerService } from './testManagerService';
|
||||
import { ITestCollectionStorageService, ITestDebugLauncher, ITestManagerService, ITestManagerServiceFactory, ITestResultsService, ITestsHelper } from './types';
|
||||
|
||||
export class TestManagerServiceFactory implements ITestManagerServiceFactory {
|
||||
constructor(private outChannel: OutputChannel, private testCollectionStorage: ITestCollectionStorageService,
|
||||
private testResultsService: ITestResultsService, private testsHelper: ITestsHelper,
|
||||
private debugLauncher: ITestDebugLauncher) { }
|
||||
public createTestManagerService(wkspace: Uri): ITestManagerService {
|
||||
return new TestManagerService(wkspace, this.outChannel, this.testCollectionStorage, this.testResultsService, this.testsHelper, this.debugLauncher);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,110 @@
|
|||
import { TestResultResetVisitor } from './testVisitors/resultResetVisitor';
|
||||
import { ITestResultsService, TestFile, TestFolder, Tests, TestStatus, TestSuite } from './types';
|
||||
|
||||
export class TestResultsService implements ITestResultsService {
|
||||
public resetResults(tests: Tests): void {
|
||||
const resultResetVisitor = new TestResultResetVisitor();
|
||||
tests.testFolders.forEach(f => resultResetVisitor.visitTestFolder(f));
|
||||
tests.testFunctions.forEach(fn => resultResetVisitor.visitTestFunction(fn.testFunction));
|
||||
tests.testSuites.forEach(suite => resultResetVisitor.visitTestSuite(suite.testSuite));
|
||||
tests.testFiles.forEach(testFile => resultResetVisitor.visitTestFile(testFile));
|
||||
}
|
||||
public updateResults(tests: Tests): void {
|
||||
tests.testFiles.forEach(test => this.updateTestFileResults(test));
|
||||
tests.testFolders.forEach(folder => this.updateTestFolderResults(folder));
|
||||
}
|
||||
private updateTestSuiteResults(test: TestSuite): void {
|
||||
this.updateTestSuiteAndFileResults(test);
|
||||
}
|
||||
private updateTestFileResults(test: TestFile): void {
|
||||
this.updateTestSuiteAndFileResults(test);
|
||||
}
|
||||
private updateTestFolderResults(testFolder: TestFolder): void {
|
||||
let allFilesPassed = true;
|
||||
let allFilesRan = true;
|
||||
|
||||
testFolder.testFiles.forEach(fl => {
|
||||
if (allFilesPassed && typeof fl.passed === 'boolean') {
|
||||
if (!fl.passed) {
|
||||
allFilesPassed = false;
|
||||
}
|
||||
} else {
|
||||
allFilesRan = false;
|
||||
}
|
||||
|
||||
testFolder.functionsFailed += fl.functionsFailed;
|
||||
testFolder.functionsPassed += fl.functionsPassed;
|
||||
});
|
||||
|
||||
let allFoldersPassed = true;
|
||||
let allFoldersRan = true;
|
||||
|
||||
testFolder.folders.forEach(folder => {
|
||||
this.updateTestFolderResults(folder);
|
||||
if (allFoldersPassed && typeof folder.passed === 'boolean') {
|
||||
if (!folder.passed) {
|
||||
allFoldersPassed = false;
|
||||
}
|
||||
} else {
|
||||
allFoldersRan = false;
|
||||
}
|
||||
|
||||
testFolder.functionsFailed += folder.functionsFailed;
|
||||
testFolder.functionsPassed += folder.functionsPassed;
|
||||
});
|
||||
|
||||
if (allFilesRan && allFoldersRan) {
|
||||
testFolder.passed = allFilesPassed && allFoldersPassed;
|
||||
testFolder.status = testFolder.passed ? TestStatus.Idle : TestStatus.Fail;
|
||||
} else {
|
||||
testFolder.passed = null;
|
||||
testFolder.status = TestStatus.Unknown;
|
||||
}
|
||||
}
|
||||
private updateTestSuiteAndFileResults(test: TestSuite | TestFile): void {
|
||||
let totalTime = 0;
|
||||
let allFunctionsPassed = true;
|
||||
let allFunctionsRan = true;
|
||||
|
||||
test.functions.forEach(fn => {
|
||||
totalTime += fn.time;
|
||||
if (typeof fn.passed === 'boolean') {
|
||||
if (fn.passed) {
|
||||
test.functionsPassed += 1;
|
||||
} else {
|
||||
test.functionsFailed += 1;
|
||||
allFunctionsPassed = false;
|
||||
}
|
||||
} else {
|
||||
allFunctionsRan = false;
|
||||
}
|
||||
});
|
||||
|
||||
let allSuitesPassed = true;
|
||||
let allSuitesRan = true;
|
||||
|
||||
test.suites.forEach(suite => {
|
||||
this.updateTestSuiteResults(suite);
|
||||
totalTime += suite.time;
|
||||
if (allSuitesRan && typeof suite.passed === 'boolean') {
|
||||
if (!suite.passed) {
|
||||
allSuitesPassed = false;
|
||||
}
|
||||
} else {
|
||||
allSuitesRan = false;
|
||||
}
|
||||
|
||||
test.functionsFailed += suite.functionsFailed;
|
||||
test.functionsPassed += suite.functionsPassed;
|
||||
});
|
||||
|
||||
test.time = totalTime;
|
||||
if (allSuitesRan && allFunctionsRan) {
|
||||
test.passed = allFunctionsPassed && allSuitesPassed;
|
||||
test.status = test.passed ? TestStatus.Idle : TestStatus.Error;
|
||||
} else {
|
||||
test.passed = null;
|
||||
test.status = TestStatus.Unknown;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,9 +1,24 @@
|
|||
import {TestFolder, TestsToRun, Tests, TestFile, TestSuite, TestStatus, FlattenedTestFunction, FlattenedTestSuite} from './contracts';
|
||||
import * as vscode from 'vscode';
|
||||
import * as path from 'path';
|
||||
import * as vscode from 'vscode';
|
||||
import { Uri, workspace } from 'vscode';
|
||||
import { window } from 'vscode';
|
||||
import * as constants from '../../common/constants';
|
||||
import { TestFlatteningVisitor } from './testVisitors/flatteningVisitor';
|
||||
import { TestResultResetVisitor } from './testVisitors/resultResetVisitor';
|
||||
import { TestFile, TestFolder, Tests, TestsToRun } from './types';
|
||||
import { ITestsHelper } from './types';
|
||||
|
||||
let discoveredTests: Tests;
|
||||
export async function selectTestWorkspace(): Promise<Uri | undefined> {
|
||||
if (!Array.isArray(workspace.workspaceFolders) || workspace.workspaceFolders.length === 0) {
|
||||
return undefined;
|
||||
} else if (workspace.workspaceFolders.length === 1) {
|
||||
return workspace.workspaceFolders[0].uri;
|
||||
} else {
|
||||
// tslint:disable-next-line:no-any prefer-type-cast
|
||||
const workspaceFolder = await (window as any).showWorkspaceFolderPick({ placeHolder: 'Select a workspace' });
|
||||
return workspaceFolder ? workspaceFolder.uri : undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export function displayTestErrorMessage(message: string) {
|
||||
vscode.window.showErrorMessage(message, constants.Button_Text_Tests_View_Output).then(action => {
|
||||
|
@ -13,257 +28,90 @@ export function displayTestErrorMessage(message: string) {
|
|||
});
|
||||
|
||||
}
|
||||
export function getDiscoveredTests(): Tests {
|
||||
return discoveredTests;
|
||||
}
|
||||
export function storeDiscoveredTests(tests: Tests) {
|
||||
discoveredTests = tests;
|
||||
}
|
||||
|
||||
export function resolveValueAsTestToRun(name: string, rootDirectory: string): TestsToRun {
|
||||
// TODO: We need a better way to match (currently we have raw name, name, xmlname, etc = which one do we
|
||||
// use to identify a file given the full file name, similary for a folder and function
|
||||
// Perhaps something like a parser or methods like TestFunction.fromString()... something)
|
||||
let tests = getDiscoveredTests();
|
||||
if (!tests) { return null; }
|
||||
const absolutePath = path.isAbsolute(name) ? name : path.resolve(rootDirectory, name);
|
||||
let testFolders = tests.testFolders.filter(folder => folder.nameToRun === name || folder.name === name || folder.name === absolutePath);
|
||||
if (testFolders.length > 0) { return { testFolder: testFolders }; };
|
||||
|
||||
let testFiles = tests.testFiles.filter(file => file.nameToRun === name || file.name === name || file.fullPath === absolutePath);
|
||||
if (testFiles.length > 0) { return { testFile: testFiles }; };
|
||||
|
||||
let testFns = tests.testFunctions.filter(fn => fn.testFunction.nameToRun === name || fn.testFunction.name === name).map(fn => fn.testFunction);
|
||||
if (testFns.length > 0) { return { testFunction: testFns }; };
|
||||
|
||||
// Just return this as a test file
|
||||
return <TestsToRun>{ testFile: [{ name: name, nameToRun: name, functions: [], suites: [], xmlName: name, fullPath: '', time: 0 }] };
|
||||
}
|
||||
export function extractBetweenDelimiters(content: string, startDelimiter: string, endDelimiter: string): string {
|
||||
content = content.substring(content.indexOf(startDelimiter) + startDelimiter.length);
|
||||
return content.substring(0, content.lastIndexOf(endDelimiter));
|
||||
}
|
||||
|
||||
export function convertFileToPackage(filePath: string): string {
|
||||
let lastIndex = filePath.lastIndexOf('.');
|
||||
const lastIndex = filePath.lastIndexOf('.');
|
||||
return filePath.substring(0, lastIndex).replace(/\//g, '.').replace(/\\/g, '.');
|
||||
}
|
||||
|
||||
export function updateResults(tests: Tests) {
|
||||
tests.testFiles.forEach(updateResultsUpstream);
|
||||
tests.testFolders.forEach(updateFolderResultsUpstream);
|
||||
}
|
||||
export class TestsHelper implements ITestsHelper {
|
||||
public flattenTestFiles(testFiles: TestFile[]): Tests {
|
||||
const flatteningVisitor = new TestFlatteningVisitor();
|
||||
testFiles.forEach(testFile => flatteningVisitor.visitTestFile(testFile));
|
||||
|
||||
export function updateFolderResultsUpstream(testFolder: TestFolder) {
|
||||
let allFilesPassed = true;
|
||||
let allFilesRan = true;
|
||||
const tests = <Tests>{
|
||||
testFiles: testFiles,
|
||||
testFunctions: flatteningVisitor.flattenedTestFunctions,
|
||||
testSuites: flatteningVisitor.flattenedTestSuites,
|
||||
testFolders: [],
|
||||
rootTestFolders: [],
|
||||
summary: { passed: 0, failures: 0, errors: 0, skipped: 0 }
|
||||
};
|
||||
|
||||
testFolder.testFiles.forEach(fl => {
|
||||
if (allFilesPassed && typeof fl.passed === 'boolean') {
|
||||
if (!fl.passed) {
|
||||
allFilesPassed = false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
allFilesRan = false;
|
||||
}
|
||||
this.placeTestFilesIntoFolders(tests);
|
||||
|
||||
testFolder.functionsFailed += fl.functionsFailed;
|
||||
testFolder.functionsPassed += fl.functionsPassed;
|
||||
});
|
||||
|
||||
let allFoldersPassed = true;
|
||||
let allFoldersRan = true;
|
||||
|
||||
testFolder.folders.forEach(folder => {
|
||||
updateFolderResultsUpstream(folder);
|
||||
if (allFoldersPassed && typeof folder.passed === 'boolean') {
|
||||
if (!folder.passed) {
|
||||
allFoldersPassed = false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
allFoldersRan = false;
|
||||
}
|
||||
|
||||
testFolder.functionsFailed += folder.functionsFailed;
|
||||
testFolder.functionsPassed += folder.functionsPassed;
|
||||
});
|
||||
|
||||
if (allFilesRan && allFoldersRan) {
|
||||
testFolder.passed = allFilesPassed && allFoldersPassed;
|
||||
testFolder.status = testFolder.passed ? TestStatus.Idle : TestStatus.Fail;
|
||||
return tests;
|
||||
}
|
||||
else {
|
||||
testFolder.passed = null;
|
||||
testFolder.status = TestStatus.Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
export function updateResultsUpstream(test: TestSuite | TestFile) {
|
||||
let totalTime = 0;
|
||||
let allFunctionsPassed = true;
|
||||
let allFunctionsRan = true;
|
||||
|
||||
test.functions.forEach(fn => {
|
||||
totalTime += fn.time;
|
||||
if (typeof fn.passed === 'boolean') {
|
||||
if (fn.passed) {
|
||||
test.functionsPassed += 1;
|
||||
public placeTestFilesIntoFolders(tests: Tests): void {
|
||||
// First get all the unique folders
|
||||
const folders: string[] = [];
|
||||
tests.testFiles.forEach(file => {
|
||||
const dir = path.dirname(file.name);
|
||||
if (folders.indexOf(dir) === -1) {
|
||||
folders.push(dir);
|
||||
}
|
||||
else {
|
||||
test.functionsFailed += 1;
|
||||
allFunctionsPassed = false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
allFunctionsRan = false;
|
||||
}
|
||||
});
|
||||
|
||||
let allSuitesPassed = true;
|
||||
let allSuitesRan = true;
|
||||
|
||||
test.suites.forEach(suite => {
|
||||
updateResultsUpstream(suite);
|
||||
totalTime += suite.time;
|
||||
if (allSuitesRan && typeof suite.passed === 'boolean') {
|
||||
if (!suite.passed) {
|
||||
allSuitesPassed = false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
allSuitesRan = false;
|
||||
}
|
||||
|
||||
test.functionsFailed += suite.functionsFailed;
|
||||
test.functionsPassed += suite.functionsPassed;
|
||||
});
|
||||
|
||||
test.time = totalTime;
|
||||
if (allSuitesRan && allFunctionsRan) {
|
||||
test.passed = allFunctionsPassed && allSuitesPassed;
|
||||
test.status = test.passed ? TestStatus.Idle : TestStatus.Error;
|
||||
}
|
||||
else {
|
||||
test.passed = null;
|
||||
test.status = TestStatus.Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
export function placeTestFilesInFolders(tests: Tests) {
|
||||
// First get all the unique folders
|
||||
const folders: string[] = [];
|
||||
tests.testFiles.forEach(file => {
|
||||
let dir = path.dirname(file.name);
|
||||
if (folders.indexOf(dir) === -1) {
|
||||
folders.push(dir);
|
||||
}
|
||||
});
|
||||
|
||||
tests.testFolders = [];
|
||||
const folderMap = new Map<string, TestFolder>();
|
||||
folders.sort();
|
||||
|
||||
folders.forEach(dir => {
|
||||
dir.split(path.sep).reduce((parentPath, currentName, index, values) => {
|
||||
let newPath = currentName;
|
||||
let parentFolder: TestFolder;
|
||||
if (parentPath.length > 0) {
|
||||
parentFolder = folderMap.get(parentPath);
|
||||
newPath = path.join(parentPath, currentName);
|
||||
}
|
||||
if (!folderMap.has(newPath)) {
|
||||
const testFolder: TestFolder = { name: newPath, testFiles: [], folders: [], nameToRun: newPath, time: 0 };
|
||||
folderMap.set(newPath, testFolder);
|
||||
if (parentFolder) {
|
||||
parentFolder.folders.push(testFolder);
|
||||
}
|
||||
else {
|
||||
tests.rootTestFolders.push(testFolder);
|
||||
}
|
||||
tests.testFiles.filter(fl => path.dirname(fl.name) === newPath).forEach(testFile => {
|
||||
testFolder.testFiles.push(testFile);
|
||||
});
|
||||
tests.testFolders.push(testFolder);
|
||||
}
|
||||
return newPath;
|
||||
}, '');
|
||||
});
|
||||
}
|
||||
export function flattenTestFiles(testFiles: TestFile[]): Tests {
|
||||
let fns: FlattenedTestFunction[] = [];
|
||||
let suites: FlattenedTestSuite[] = [];
|
||||
testFiles.forEach(testFile => {
|
||||
// sample test_three (file name without extension and all / replaced with ., meaning this is the package)
|
||||
const packageName = convertFileToPackage(testFile.name);
|
||||
|
||||
testFile.functions.forEach(fn => {
|
||||
fns.push({ testFunction: fn, xmlClassName: packageName, parentTestFile: testFile });
|
||||
});
|
||||
|
||||
testFile.suites.forEach(suite => {
|
||||
suites.push({ parentTestFile: testFile, testSuite: suite, xmlClassName: suite.xmlName });
|
||||
flattenTestSuites(fns, suites, testFile, suite);
|
||||
tests.testFolders = [];
|
||||
const folderMap = new Map<string, TestFolder>();
|
||||
folders.sort();
|
||||
|
||||
folders.forEach(dir => {
|
||||
dir.split(path.sep).reduce((parentPath, currentName, index, values) => {
|
||||
let newPath = currentName;
|
||||
let parentFolder: TestFolder;
|
||||
if (parentPath.length > 0) {
|
||||
parentFolder = folderMap.get(parentPath);
|
||||
newPath = path.join(parentPath, currentName);
|
||||
}
|
||||
if (!folderMap.has(newPath)) {
|
||||
const testFolder: TestFolder = { name: newPath, testFiles: [], folders: [], nameToRun: newPath, time: 0 };
|
||||
folderMap.set(newPath, testFolder);
|
||||
if (parentFolder) {
|
||||
parentFolder.folders.push(testFolder);
|
||||
} else {
|
||||
tests.rootTestFolders.push(testFolder);
|
||||
}
|
||||
tests.testFiles.filter(fl => path.dirname(fl.name) === newPath).forEach(testFile => {
|
||||
testFolder.testFiles.push(testFile);
|
||||
});
|
||||
tests.testFolders.push(testFolder);
|
||||
}
|
||||
return newPath;
|
||||
}, '');
|
||||
});
|
||||
});
|
||||
}
|
||||
public parseTestName(name: string, rootDirectory: string, tests: Tests): TestsToRun {
|
||||
// TODO: We need a better way to match (currently we have raw name, name, xmlname, etc = which one do we.
|
||||
// Use to identify a file given the full file name, similarly for a folder and function.
|
||||
// Perhaps something like a parser or methods like TestFunction.fromString()... something).
|
||||
if (!tests) { return null; }
|
||||
const absolutePath = path.isAbsolute(name) ? name : path.resolve(rootDirectory, name);
|
||||
const testFolders = tests.testFolders.filter(folder => folder.nameToRun === name || folder.name === name || folder.name === absolutePath);
|
||||
if (testFolders.length > 0) { return { testFolder: testFolders }; }
|
||||
|
||||
let tests = <Tests>{
|
||||
testFiles: testFiles,
|
||||
testFunctions: fns, testSuits: suites,
|
||||
testFolders: [],
|
||||
rootTestFolders: [],
|
||||
summary: { passed: 0, failures: 0, errors: 0, skipped: 0 }
|
||||
};
|
||||
const testFiles = tests.testFiles.filter(file => file.nameToRun === name || file.name === name || file.fullPath === absolutePath);
|
||||
if (testFiles.length > 0) { return { testFile: testFiles }; }
|
||||
|
||||
placeTestFilesInFolders(tests);
|
||||
const testFns = tests.testFunctions.filter(fn => fn.testFunction.nameToRun === name || fn.testFunction.name === name).map(fn => fn.testFunction);
|
||||
if (testFns.length > 0) { return { testFunction: testFns }; }
|
||||
|
||||
return tests;
|
||||
// Just return this as a test file.
|
||||
return <TestsToRun>{ testFile: [{ name: name, nameToRun: name, functions: [], suites: [], xmlName: name, fullPath: '', time: 0 }] };
|
||||
}
|
||||
}
|
||||
export function flattenTestSuites(flattenedFns: FlattenedTestFunction[], flattenedSuites: FlattenedTestSuite[], testFile: TestFile, testSuite: TestSuite) {
|
||||
testSuite.functions.forEach(fn => {
|
||||
flattenedFns.push({ testFunction: fn, xmlClassName: testSuite.xmlName, parentTestFile: testFile, parentTestSuite: testSuite });
|
||||
});
|
||||
|
||||
// We may have child classes
|
||||
testSuite.suites.forEach(suite => {
|
||||
flattenedSuites.push({ parentTestFile: testFile, testSuite: suite, xmlClassName: suite.xmlName });
|
||||
flattenTestSuites(flattenedFns, flattenedSuites, testFile, suite);
|
||||
});
|
||||
}
|
||||
|
||||
export function resetTestResults(tests: Tests) {
|
||||
tests.testFolders.forEach(f => {
|
||||
f.functionsDidNotRun = 0;
|
||||
f.functionsFailed = 0;
|
||||
f.functionsPassed = 0;
|
||||
f.passed = null;
|
||||
f.status = TestStatus.Unknown;
|
||||
});
|
||||
tests.testFunctions.forEach(fn => {
|
||||
fn.testFunction.passed = null;
|
||||
fn.testFunction.time = 0;
|
||||
fn.testFunction.message = '';
|
||||
fn.testFunction.traceback = '';
|
||||
fn.testFunction.status = TestStatus.Unknown;
|
||||
fn.testFunction.functionsFailed = 0;
|
||||
fn.testFunction.functionsPassed = 0;
|
||||
fn.testFunction.functionsDidNotRun = 0;
|
||||
});
|
||||
tests.testSuits.forEach(suite => {
|
||||
suite.testSuite.passed = null;
|
||||
suite.testSuite.time = 0;
|
||||
suite.testSuite.status = TestStatus.Unknown;
|
||||
suite.testSuite.functionsFailed = 0;
|
||||
suite.testSuite.functionsPassed = 0;
|
||||
suite.testSuite.functionsDidNotRun = 0;
|
||||
});
|
||||
tests.testFiles.forEach(testFile => {
|
||||
testFile.passed = null;
|
||||
testFile.time = 0;
|
||||
testFile.status = TestStatus.Unknown;
|
||||
testFile.functionsFailed = 0;
|
||||
testFile.functionsPassed = 0;
|
||||
testFile.functionsDidNotRun = 0;
|
||||
});
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
import { convertFileToPackage } from '../testUtils';
|
||||
import {
|
||||
FlattenedTestFunction,
|
||||
FlattenedTestSuite,
|
||||
ITestVisitor,
|
||||
TestFile,
|
||||
TestFolder,
|
||||
TestFunction,
|
||||
TestSuite
|
||||
} from '../types';
|
||||
|
||||
export class TestFlatteningVisitor implements ITestVisitor {
|
||||
// tslint:disable-next-line:variable-name
|
||||
private _flattedTestFunctions = new Map<string, FlattenedTestFunction>();
|
||||
// tslint:disable-next-line:variable-name
|
||||
private _flattenedTestSuites = new Map<string, FlattenedTestSuite>();
|
||||
public get flattenedTestFunctions(): FlattenedTestFunction[] {
|
||||
return [...this._flattedTestFunctions.values()];
|
||||
}
|
||||
public get flattenedTestSuites(): FlattenedTestSuite[] {
|
||||
return [...this._flattenedTestSuites.values()];
|
||||
}
|
||||
// tslint:disable-next-line:no-empty
|
||||
public visitTestFunction(testFunction: TestFunction): void { }
|
||||
// tslint:disable-next-line:no-empty
|
||||
public visitTestSuite(testSuite: TestSuite): void { }
|
||||
public visitTestFile(testFile: TestFile): void {
|
||||
// sample test_three (file name without extension and all / replaced with ., meaning this is the package)
|
||||
const packageName = convertFileToPackage(testFile.name);
|
||||
|
||||
testFile.functions.forEach(fn => this.addTestFunction(fn, testFile, packageName));
|
||||
testFile.suites.forEach(suite => this.visitTestSuiteOfAFile(suite, testFile));
|
||||
}
|
||||
// tslint:disable-next-line:no-empty
|
||||
public visitTestFolder(testFile: TestFolder) { }
|
||||
private visitTestSuiteOfAFile(testSuite: TestSuite, parentTestFile: TestFile): void {
|
||||
testSuite.functions.forEach(fn => this.visitTestFunctionOfASuite(fn, testSuite, parentTestFile));
|
||||
testSuite.suites.forEach(suite => this.visitTestSuiteOfAFile(suite, parentTestFile));
|
||||
this.addTestSuite(testSuite, parentTestFile);
|
||||
}
|
||||
private visitTestFunctionOfASuite(testFunction: TestFunction, parentTestSuite: TestSuite, parentTestFile: TestFile) {
|
||||
const key = `Function:${testFunction.name},Suite:${parentTestSuite.name},SuiteXmlName:${parentTestSuite.xmlName},ParentFile:${parentTestFile.fullPath}`;
|
||||
if (this._flattenedTestSuites.has(key)) {
|
||||
return;
|
||||
}
|
||||
const flattenedFunction = { testFunction, xmlClassName: parentTestSuite.xmlName, parentTestFile, parentTestSuite };
|
||||
this._flattedTestFunctions.set(key, flattenedFunction);
|
||||
}
|
||||
private addTestSuite(testSuite: TestSuite, parentTestFile: TestFile) {
|
||||
const key = `Suite:${testSuite.name},SuiteXmlName:${testSuite.xmlName},ParentFile:${parentTestFile.fullPath}`;
|
||||
if (this._flattenedTestSuites.has(key)) {
|
||||
return;
|
||||
}
|
||||
const flattenedSuite = { parentTestFile, testSuite, xmlClassName: testSuite.xmlName };
|
||||
this._flattenedTestSuites.set(key, flattenedSuite);
|
||||
}
|
||||
private addTestFunction(testFunction: TestFunction, parentTestFile: TestFile, parentTestPackage: string) {
|
||||
const key = `Function:${testFunction.name},ParentFile:${parentTestFile.fullPath}`;
|
||||
if (this._flattedTestFunctions.has(key)) {
|
||||
return;
|
||||
}
|
||||
const flattendFunction = { testFunction, xmlClassName: parentTestPackage, parentTestFile };
|
||||
this._flattedTestFunctions.set(key, flattendFunction);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
import * as path from 'path';
|
||||
import { ITestVisitor, TestFile, TestFolder, TestFunction, TestSuite } from '../types';
|
||||
|
||||
export class TestFolderGenerationVisitor implements ITestVisitor {
|
||||
// tslint:disable-next-line:variable-name
|
||||
private _testFolders: TestFolder[] = [];
|
||||
// tslint:disable-next-line:variable-name
|
||||
private _rootTestFolders: TestFolder[] = [];
|
||||
private folderMap = new Map<string, TestFolder>();
|
||||
public get testFolders(): Readonly<TestFolder[]> {
|
||||
return [...this._testFolders];
|
||||
}
|
||||
public get rootTestFolders(): Readonly<TestFolder[]> {
|
||||
return [...this._rootTestFolders];
|
||||
}
|
||||
// tslint:disable-next-line:no-empty
|
||||
public visitTestFunction(testFunction: TestFunction): void { }
|
||||
// tslint:disable-next-line:no-empty
|
||||
public visitTestSuite(testSuite: TestSuite): void { }
|
||||
public visitTestFile(testFile: TestFile): void {
|
||||
// First get all the unique folders
|
||||
const folders: string[] = [];
|
||||
const dir = path.dirname(testFile.name);
|
||||
if (this.folderMap.has(dir)) {
|
||||
const folder = this.folderMap.get(dir);
|
||||
folder.testFiles.push(testFile);
|
||||
return;
|
||||
}
|
||||
|
||||
dir.split(path.sep).reduce((accumulatedPath, currentName, index) => {
|
||||
let newPath = currentName;
|
||||
let parentFolder: TestFolder;
|
||||
if (accumulatedPath.length > 0) {
|
||||
parentFolder = this.folderMap.get(accumulatedPath);
|
||||
newPath = path.join(accumulatedPath, currentName);
|
||||
}
|
||||
if (!this.folderMap.has(newPath)) {
|
||||
const testFolder: TestFolder = { name: newPath, testFiles: [], folders: [], nameToRun: newPath, time: 0 };
|
||||
this.folderMap.set(newPath, testFolder);
|
||||
if (parentFolder) {
|
||||
parentFolder.folders.push(testFolder);
|
||||
} else {
|
||||
this._rootTestFolders.push(testFolder);
|
||||
}
|
||||
this._testFolders.push(testFolder);
|
||||
}
|
||||
return newPath;
|
||||
}, '');
|
||||
|
||||
// tslint:disable-next-line:no-non-null-assertion
|
||||
this.folderMap.get(dir)!.testFiles.push(testFile);
|
||||
}
|
||||
// tslint:disable-next-line:no-empty
|
||||
public visitTestFolder(testFile: TestFolder) { }
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
import { ITestVisitor, TestFile, TestFolder, TestFunction, TestStatus, TestSuite } from '../types';
|
||||
|
||||
export class TestResultResetVisitor implements ITestVisitor {
|
||||
public visitTestFunction(testFunction: TestFunction): void {
|
||||
testFunction.passed = null;
|
||||
testFunction.time = 0;
|
||||
testFunction.message = '';
|
||||
testFunction.traceback = '';
|
||||
testFunction.status = TestStatus.Unknown;
|
||||
testFunction.functionsFailed = 0;
|
||||
testFunction.functionsPassed = 0;
|
||||
testFunction.functionsDidNotRun = 0;
|
||||
}
|
||||
public visitTestSuite(testSuite: TestSuite): void {
|
||||
testSuite.passed = null;
|
||||
testSuite.time = 0;
|
||||
testSuite.status = TestStatus.Unknown;
|
||||
testSuite.functionsFailed = 0;
|
||||
testSuite.functionsPassed = 0;
|
||||
testSuite.functionsDidNotRun = 0;
|
||||
}
|
||||
public visitTestFile(testFile: TestFile): void {
|
||||
testFile.passed = null;
|
||||
testFile.time = 0;
|
||||
testFile.status = TestStatus.Unknown;
|
||||
testFile.functionsFailed = 0;
|
||||
testFile.functionsPassed = 0;
|
||||
testFile.functionsDidNotRun = 0;
|
||||
}
|
||||
public visitTestFolder(testFolder: TestFolder) {
|
||||
testFolder.functionsDidNotRun = 0;
|
||||
testFolder.functionsFailed = 0;
|
||||
testFolder.functionsPassed = 0;
|
||||
testFolder.passed = null;
|
||||
testFolder.status = TestStatus.Unknown;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,152 @@
|
|||
import { CancellationToken, Disposable, OutputChannel, Uri } from 'vscode';
|
||||
import { Product } from '../../common/installer';
|
||||
import { BaseTestManager } from './baseTestManager';
|
||||
|
||||
export type TestFolder = TestResult & {
|
||||
name: string;
|
||||
testFiles: TestFile[];
|
||||
nameToRun: string;
|
||||
status?: TestStatus;
|
||||
folders: TestFolder[];
|
||||
};
|
||||
|
||||
export type TestFile = TestResult & {
|
||||
name: string;
|
||||
fullPath: string;
|
||||
functions: TestFunction[];
|
||||
suites: TestSuite[];
|
||||
nameToRun: string;
|
||||
xmlName: string;
|
||||
status?: TestStatus;
|
||||
errorsWhenDiscovering?: string;
|
||||
};
|
||||
|
||||
export type TestSuite = TestResult & {
|
||||
name: string;
|
||||
functions: TestFunction[];
|
||||
suites: TestSuite[];
|
||||
isUnitTest: Boolean;
|
||||
isInstance: Boolean;
|
||||
nameToRun: string;
|
||||
xmlName: string;
|
||||
status?: TestStatus;
|
||||
};
|
||||
|
||||
export type TestFunction = TestResult & {
|
||||
name: string;
|
||||
nameToRun: string;
|
||||
status?: TestStatus;
|
||||
};
|
||||
|
||||
export type TestResult = Node & {
|
||||
passed?: boolean;
|
||||
time: number;
|
||||
line?: number;
|
||||
message?: string;
|
||||
traceback?: string;
|
||||
functionsPassed?: number;
|
||||
functionsFailed?: number;
|
||||
functionsDidNotRun?: number;
|
||||
};
|
||||
|
||||
export type Node = {
|
||||
expanded?: Boolean;
|
||||
};
|
||||
|
||||
export type FlattenedTestFunction = {
|
||||
testFunction: TestFunction;
|
||||
parentTestSuite?: TestSuite;
|
||||
parentTestFile: TestFile;
|
||||
xmlClassName: string;
|
||||
};
|
||||
|
||||
export type FlattenedTestSuite = {
|
||||
testSuite: TestSuite;
|
||||
parentTestSuite?: TestSuite;
|
||||
parentTestFile: TestFile;
|
||||
xmlClassName: string;
|
||||
};
|
||||
|
||||
export type TestSummary = {
|
||||
passed: number;
|
||||
failures: number;
|
||||
errors: number;
|
||||
skipped: number;
|
||||
};
|
||||
|
||||
export type Tests = {
|
||||
summary: TestSummary;
|
||||
testFiles: TestFile[];
|
||||
testFunctions: FlattenedTestFunction[];
|
||||
testSuites: FlattenedTestSuite[];
|
||||
testFolders: TestFolder[];
|
||||
rootTestFolders: TestFolder[];
|
||||
};
|
||||
|
||||
export enum TestStatus {
|
||||
Unknown,
|
||||
Discovering,
|
||||
Idle,
|
||||
Running,
|
||||
Fail,
|
||||
Error,
|
||||
Skipped,
|
||||
Pass
|
||||
}
|
||||
|
||||
export type TestsToRun = {
|
||||
testFolder?: TestFolder[];
|
||||
testFile?: TestFile[];
|
||||
testSuite?: TestSuite[];
|
||||
testFunction?: TestFunction[];
|
||||
};
|
||||
|
||||
export type UnitTestProduct = Product.nosetest | Product.pytest | Product.unittest;
|
||||
|
||||
export interface ITestConfigSettingsService {
|
||||
updateTestArgs(testDirectory: string, product: UnitTestProduct, args: string[]): Promise<void>;
|
||||
enable(testDirectory: string | Uri, product: UnitTestProduct): Promise<void>;
|
||||
disable(testDirectory: string | Uri, product: UnitTestProduct): Promise<void>;
|
||||
}
|
||||
|
||||
export interface ITestManagerService extends Disposable {
|
||||
getTestManager(): BaseTestManager | undefined;
|
||||
getTestWorkingDirectory(): string;
|
||||
getPreferredTestManager(): UnitTestProduct | undefined;
|
||||
}
|
||||
|
||||
export interface ITestManagerServiceFactory {
|
||||
createTestManagerService(wkspace: Uri): ITestManagerService;
|
||||
}
|
||||
|
||||
export interface IWorkspaceTestManagerService extends Disposable {
|
||||
getTestManager(resource: Uri): BaseTestManager | undefined;
|
||||
getTestWorkingDirectory(resource: Uri): string;
|
||||
getPreferredTestManager(resource: Uri): UnitTestProduct | undefined;
|
||||
}
|
||||
|
||||
export interface ITestsHelper {
|
||||
flattenTestFiles(testFiles: TestFile[]): Tests;
|
||||
placeTestFilesIntoFolders(tests: Tests): void;
|
||||
}
|
||||
|
||||
export interface ITestVisitor {
|
||||
visitTestFunction(testFunction: TestFunction): void;
|
||||
visitTestSuite(testSuite: TestSuite): void;
|
||||
visitTestFile(testFile: TestFile): void;
|
||||
visitTestFolder(testFile: TestFolder): void;
|
||||
}
|
||||
|
||||
export interface ITestCollectionStorageService extends Disposable {
|
||||
getTests(wkspace: Uri): Tests | undefined;
|
||||
storeTests(wkspace: Uri, tests: Tests | null | undefined): void;
|
||||
}
|
||||
|
||||
export interface ITestResultsService {
|
||||
resetResults(tests: Tests): void;
|
||||
updateResults(tests: Tests): void;
|
||||
}
|
||||
|
||||
export interface ITestDebugLauncher {
|
||||
launchDebugger(rootDirectory: string, testArgs: string[], token?: CancellationToken, outChannel?: OutputChannel): Promise<Tests>;
|
||||
}
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче