diff --git a/.gitignore b/.gitignore index 81f31ac0a202..58ce4158ddbe 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,5 @@ node_modules src/test/.vscode/** **/testFiles/**/.cache/** *.noseids +.vscode-test +__pycache__ diff --git a/.vscode/launch.json b/.vscode/launch.json index 0d64d8b63590..284cb90bbaa5 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -30,7 +30,9 @@ ], "stopOnEntry": false, "sourceMaps": true, - "outDir": "${workspaceRoot}/out", + "outFiles": [ + "${workspaceRoot}/out/**/*.js" + ], "preLaunchTask": "npm" }, { @@ -43,7 +45,9 @@ "--server=4711" ], "sourceMaps": true, - "outDir": "${workspaceRoot}/out/client", + "outFiles": [ + "${workspaceRoot}/out/client/**/*.js" + ], "cwd": "${workspaceRoot}" }, { @@ -58,7 +62,6 @@ ], "stopOnEntry": false, "sourceMaps": true, - "xxoutDir": "${workspaceRoot}/out/test", "outFiles": [ "${workspaceRoot}/out/**/*.js" ], diff --git a/package.json b/package.json index c764221f46cd..e19878709162 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "theme": "dark" }, "engines": { - "vscode": "^1.9.0" + "vscode": "^1.15.0" }, "recommendations": [ "donjayamanne.jupyter" @@ -922,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", @@ -955,7 +961,8 @@ "default": [], "items": { "type": "string" - } + }, + "scope": "resource" }, "python.disablePromptForFeatures": { "type": "array", @@ -974,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", @@ -1040,7 +1059,8 @@ "Error", "Information", "Warning" - ] + ], + "scope": "resource" }, "python.linting.pylintCategorySeverity.refactor": { "type": "string", @@ -1051,7 +1071,8 @@ "Error", "Information", "Warning" - ] + ], + "scope": "resource" }, "python.linting.pylintCategorySeverity.warning": { "type": "string", @@ -1062,7 +1083,8 @@ "Error", "Information", "Warning" - ] + ], + "scope": "resource" }, "python.linting.pylintCategorySeverity.error": { "type": "string", @@ -1073,7 +1095,8 @@ "Error", "Information", "Warning" - ] + ], + "scope": "resource" }, "python.linting.pylintCategorySeverity.fatal": { "type": "string", @@ -1084,7 +1107,8 @@ "Error", "Information", "Warning" - ] + ], + "scope": "resource" }, "python.linting.pep8CategorySeverity.W": { "type": "string", @@ -1095,7 +1119,8 @@ "Error", "Information", "Warning" - ] + ], + "scope": "resource" }, "python.linting.pep8CategorySeverity.E": { "type": "string", @@ -1106,7 +1131,8 @@ "Error", "Information", "Warning" - ] + ], + "scope": "resource" }, "python.linting.flake8CategorySeverity.F": { "type": "string", @@ -1117,7 +1143,8 @@ "Error", "Information", "Warning" - ] + ], + "scope": "resource" }, "python.linting.flake8CategorySeverity.E": { "type": "string", @@ -1128,7 +1155,8 @@ "Error", "Information", "Warning" - ] + ], + "scope": "resource" }, "python.linting.flake8CategorySeverity.W": { "type": "string", @@ -1139,7 +1167,8 @@ "Error", "Information", "Warning" - ] + ], + "scope": "resource" }, "python.linting.mypyCategorySeverity.error": { "type": "string", @@ -1150,7 +1179,8 @@ "Error", "Information", "Warning" - ] + ], + "scope": "resource" }, "python.linting.mypyCategorySeverity.note": { "type": "string", @@ -1161,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", @@ -1199,7 +1236,8 @@ "default": [], "items": { "type": "string" - } + }, + "scope": "resource" }, "python.linting.pylintArgs": { "type": "array", @@ -1207,7 +1245,8 @@ "default": [], "items": { "type": "string" - } + }, + "scope": "resource" }, "python.linting.pep8Args": { "type": "array", @@ -1215,7 +1254,8 @@ "default": [], "items": { "type": "string" - } + }, + "scope": "resource" }, "python.linting.flake8Args": { "type": "array", @@ -1223,7 +1263,8 @@ "default": [], "items": { "type": "string" - } + }, + "scope": "resource" }, "python.linting.pydocstyleArgs": { "type": "array", @@ -1231,7 +1272,8 @@ "default": [], "items": { "type": "string" - } + }, + "scope": "resource" }, "python.linting.mypyArgs": { "type": "array", @@ -1242,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", @@ -1257,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", @@ -1275,7 +1322,8 @@ "default": [], "items": { "type": "string" - } + }, + "scope": "resource" }, "python.formatting.yapfArgs": { "type": "array", @@ -1283,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", @@ -1301,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", @@ -1346,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", @@ -1389,7 +1456,8 @@ "default": [], "items": { "type": "string" - } + }, + "scope": "resource" }, "python.unitTest.pyTestArgs": { "type": "array", @@ -1397,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", @@ -1416,7 +1486,8 @@ ], "items": { "type": "string" - } + }, + "scope": "resource" }, "python.linting.ignorePatterns": { "type": "array", @@ -1427,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", @@ -1445,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", @@ -1480,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" } } }, @@ -1538,11 +1619,12 @@ "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.0.3" }, "devDependencies": { "@types/fs-extra": "^4.0.2", @@ -1561,13 +1643,12 @@ "babel-loader": "^6.2.5", "babel-preset-es2015": "^6.14.0", "ignore-loader": "^0.1.1", - "mocha": "^2.3.3", + "mocha": "^3.2.0", "retyped-diff-match-patch-tsd-ambient": "^1.0.0-0", "sinon": "^2.3.6", "ts-loader": "^0.8.2", "tslint": "^3.15.1", "typescript": "^2.3.2", - "vscode": "^1.0.3", "webpack": "^1.13.2" } } diff --git a/src/client/common/configSettings.ts b/src/client/common/configSettings.ts index 2897b50eee1b..32ef9ec060fb 100644 --- a/src/client/common/configSettings.ts +++ b/src/client/common/configSettings.ts @@ -3,6 +3,7 @@ import * as vscode from 'vscode'; import * as path from 'path'; import * as child_process from 'child_process'; +import { Uri } from 'vscode'; import { SystemVariables } from './systemVariables'; import { EventEmitter } from 'events'; const untildify = require('untildify'); @@ -133,26 +134,35 @@ export interface JupyterSettings { const IS_TEST_EXECUTION = process.env['PYTHON_DONJAYAMANNE_TEST'] === '1'; export class PythonSettings extends EventEmitter implements IPythonSettings { - private static pythonSettings: PythonSettings = new PythonSettings(); + private static pythonSettings: Map = new Map(); + private workspaceRoot?: vscode.Uri; private disposables: vscode.Disposable[] = []; - constructor() { + 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; + public static getInstance(resource?: Uri): PythonSettings { + const workspaceFolder = resource ? vscode.workspace.getWorkspaceFolder(resource) : undefined; + let workspaceFolderUri: Uri = 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); + } + return PythonSettings.pythonSettings.get(workspaceFolderKey); } 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'); + const workspaceRoot = (IS_TEST_EXECUTION || !this.workspaceRoot) ? __dirname : this.workspaceRoot.fsPath; + const pythonSettings = vscode.workspace.getConfiguration('python', this.workspaceRoot); this.pythonPath = systemVariables.resolveAny(pythonSettings.get('pythonPath'))!; this.pythonPath = getAbsolutePath(this.pythonPath, IS_TEST_EXECUTION ? __dirname : workspaceRoot); this.venvPath = systemVariables.resolveAny(pythonSettings.get('venvPath'))!; diff --git a/src/client/extension.ts b/src/client/extension.ts index 4a468327b959..90f5c7d2e2ce 100644 --- a/src/client/extension.ts +++ b/src/client/extension.ts @@ -67,7 +67,7 @@ export async function activate(context: vscode.ExtensionContext) { context.subscriptions.push(...activateExecInTerminalProvider()); context.subscriptions.push(activateUpdateSparkLibraryProvider()); activateSimplePythonRefactorProvider(context, formatOutChannel); - context.subscriptions.push(activateFormatOnSaveProvider(PYTHON, settings.PythonSettings.getInstance(), formatOutChannel)); + context.subscriptions.push(activateFormatOnSaveProvider(PYTHON, formatOutChannel)); context.subscriptions.push(activateGoToObjectDefinitionProvider(context)); context.subscriptions.push(vscode.commands.registerCommand(Commands.Start_REPL, () => { @@ -114,7 +114,7 @@ export async function activate(context: vscode.ExtensionContext) { context.subscriptions.push(vscode.languages.registerSignatureHelpProvider(PYTHON, new PythonSignatureProvider(context, jediProx), '(', ',')); } 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)); } diff --git a/src/client/formatters/autoPep8Formatter.ts b/src/client/formatters/autoPep8Formatter.ts index 8a81c1dc01be..35afef4a57cf 100644 --- a/src/client/formatters/autoPep8Formatter.ts +++ b/src/client/formatters/autoPep8Formatter.ts @@ -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 { - 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); } -} \ No newline at end of file +} diff --git a/src/client/formatters/baseFormatter.ts b/src/client/formatters/baseFormatter.ts index 032f9bf61619..3fcce0d76661 100644 --- a/src/client/formatters/baseFormatter.ts +++ b/src/client/formatters/baseFormatter.ts @@ -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; - + 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 { 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 diff --git a/src/client/formatters/dummyFormatter.ts b/src/client/formatters/dummyFormatter.ts index a008987fde92..481b57c69ae6 100644 --- a/src/client/formatters/dummyFormatter.ts +++ b/src/client/formatters/dummyFormatter.ts @@ -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 { return Promise.resolve([]); } -} \ No newline at end of file +} diff --git a/src/client/formatters/yapfFormatter.ts b/src/client/formatters/yapfFormatter.ts index 7a858bd9f869..ff8e40e21066 100644 --- a/src/client/formatters/yapfFormatter.ts +++ b/src/client/formatters/yapfFormatter.ts @@ -1,25 +1,27 @@ 'use strict'; import * as vscode from 'vscode'; +import * as path from 'path'; import { BaseFormatter } from './baseFormatter'; -import * as settings from './../common/configSettings'; +import { PythonSettings } from '../common/configSettings'; import { Product } from '../common/installer'; -import * as path from 'path'; 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 { - 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); } -} \ No newline at end of file +} diff --git a/src/client/linters/baseLinter.ts b/src/client/linters/baseLinter.ts index f4c80314c1f2..9e1dcd8a54f5 100644 --- a/src/client/linters/baseLinter.ts +++ b/src/client/linters/baseLinter.ts @@ -1,6 +1,6 @@ 'use strict'; +import { IPythonSettings, PythonSettings } from '../common/configSettings'; import { execPythonFile } from './../common/utils'; -import * as settings from './../common/configSettings'; import { OutputChannel } from 'vscode'; import { Installer, Product } from '../common/installer'; import * as vscode from 'vscode'; @@ -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; - + public lint(document: vscode.TextDocument, cancellation: vscode.CancellationToken): Promise { + this._pythonSettings = PythonSettings.getInstance(document.uri); + return this.runLinter(document, cancellation); + } + protected abstract runLinter(document: vscode.TextDocument, cancellation: vscode.CancellationToken): Promise; protected parseMessagesSeverity(error: string, categorySeverity: any): LintMessageSeverity { if (categorySeverity[error]) { let severityName = categorySeverity[error]; diff --git a/src/client/linters/flake8.ts b/src/client/linters/flake8.ts index e2d574c63d88..00bca18c28d4 100644 --- a/src/client/linters/flake8.ts +++ b/src/client/linters/flake8.ts @@ -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 { + protected runLinter(document: TextDocument, cancellation: CancellationToken): Promise { if (!this.pythonSettings.linting.flake8Enabled) { return Promise.resolve([]); } @@ -29,7 +26,7 @@ export class Linter extends baseLinter.BaseLinter { } return new Promise((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); }); diff --git a/src/client/linters/main.ts b/src/client/linters/main.ts index a3d8691c3508..a54d75fc7df9 100644 --- a/src/client/linters/main.ts +++ b/src/client/linters/main.ts @@ -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]}''`); } } } -} \ No newline at end of file +} diff --git a/src/client/linters/mypy.ts b/src/client/linters/mypy.ts index db9d9d4c5edb..8696d4db5a78 100644 --- a/src/client/linters/mypy.ts +++ b/src/client/linters/mypy.ts @@ -8,14 +8,11 @@ import { TextDocument, CancellationToken } from 'vscode'; const REGEX = '(?.py):(?\\d+): (?\\w+): (?.*)\\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 { + protected runLinter(document: TextDocument, cancellation: CancellationToken): Promise { if (!this.pythonSettings.linting.mypyEnabled) { return Promise.resolve([]); } @@ -29,7 +26,7 @@ export class Linter extends baseLinter.BaseLinter { } return new Promise((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); }); } -} \ No newline at end of file +} diff --git a/src/client/linters/pep8Linter.ts b/src/client/linters/pep8Linter.ts index 51a4170b1e34..18f77e4bce54 100644 --- a/src/client/linters/pep8Linter.ts +++ b/src/client/linters/pep8Linter.ts @@ -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); - } - public isEnabled(): Boolean { - return this.pythonSettings.linting.pep8Enabled; + constructor(outputChannel: OutputChannel) { + super('pep8', Product.pep8, outputChannel); } - public runLinter(document: TextDocument, cancellation: CancellationToken): Promise { + + protected runLinter(document: TextDocument, cancellation: CancellationToken): Promise { 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(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); }); diff --git a/src/client/linters/prospector.ts b/src/client/linters/prospector.ts index 1d8a16544bbe..e02a10f210be 100644 --- a/src/client/linters/prospector.ts +++ b/src/client/linters/prospector.ts @@ -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 { + protected runLinter(document: TextDocument, cancellation: CancellationToken): Promise { if (!this.pythonSettings.linting.prospectorEnabled) { return Promise.resolve([]); } @@ -46,7 +43,7 @@ export class Linter extends baseLinter.BaseLinter { } return new Promise((resolve, reject) => { - execPythonFile(prospectorPath, prospectorArgs.concat(['--absolute-paths', '--output-format=json', document.uri.fsPath]), this.workspaceRootPath, false, null, cancellation).then(data => { + execPythonFile(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); diff --git a/src/client/linters/pydocstyle.ts b/src/client/linters/pydocstyle.ts index 98f32bbe210b..e58fa4edd5a5 100644 --- a/src/client/linters/pydocstyle.ts +++ b/src/client/linters/pydocstyle.ts @@ -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 { + protected runLinter(document: TextDocument, cancellation: CancellationToken): Promise { 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((resolve, reject) => { - execPythonFile(commandLine, args, this.workspaceRootPath, true, null, cancellation).then(data => { + execPythonFile(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); diff --git a/src/client/linters/pylama.ts b/src/client/linters/pylama.ts index 9faba1adeb33..f0251ee728ea 100644 --- a/src/client/linters/pylama.ts +++ b/src/client/linters/pylama.ts @@ -10,14 +10,11 @@ const REGEX = '(?.py):(?\\d+):(?\\d+): \\[(?\\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 { + protected runLinter(document: TextDocument, cancellation: CancellationToken): Promise { if (!this.pythonSettings.linting.pylamaEnabled) { return Promise.resolve([]); } @@ -31,7 +28,7 @@ export class Linter extends baseLinter.BaseLinter { } return new Promise(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; diff --git a/src/client/linters/pylint.ts b/src/client/linters/pylint.ts index 5dfe6aaf0502..5649d079f008 100644 --- a/src/client/linters/pylint.ts +++ b/src/client/linters/pylint.ts @@ -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 { + protected runLinter(document: TextDocument, cancellation: CancellationToken): Promise { if (!this.pythonSettings.linting.pylintEnabled) { return Promise.resolve([]); } @@ -27,7 +24,7 @@ export class Linter extends baseLinter.BaseLinter { } return new Promise((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); }); } -} \ No newline at end of file +} diff --git a/src/client/providers/formatOnSaveProvider.ts b/src/client/providers/formatOnSaveProvider.ts index 4e972ff38c9b..1b550f0d6cc4 100644 --- a/src/client/providers/formatOnSaveProvider.ts +++ b/src/client/providers/formatOnSaveProvider.ts @@ -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(); - 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(); + 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); -} \ No newline at end of file +} diff --git a/src/client/providers/formatProvider.ts b/src/client/providers/formatProvider.ts index 5df3ab9e2cca..58844bca269c 100644 --- a/src/client/providers/formatProvider.ts +++ b/src/client/providers/formatProvider.ts @@ -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(); - 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 { - 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); } diff --git a/src/client/providers/lintProvider.ts b/src/client/providers/lintProvider.ts index 830fc96e771f..b9b5b92107a7 100644 --- a/src/client/providers/lintProvider.ts +++ b/src/client/providers/lintProvider.ts @@ -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; } export class LintProvider extends vscode.Disposable { - private settings: settings.IPythonSettings; private diagnosticCollection: vscode.DiagnosticCollection; private linters: linter.BaseLinter[] = []; private pendingLintings = new Map(); 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[] = this.linters.map(linter => { - if (!vscode.workspace.rootPath && !this.settings.linting.enabledWithoutWorkspace) { - return Promise.resolve([]); - } - if (!linter.isEnabled()) { + if (typeof workspaceRootPath !== 'string' && !settings.linting.enabledWithoutWorkspace) { 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 = []; diff --git a/src/test/format/extension.format.test.ts b/src/test/format/extension.format.test.ts index 5cfa45dabd86..986d64e976a9 100644 --- a/src/test/format/extension.format.test.ts +++ b/src/test/format/extension.format.test.ts @@ -12,6 +12,7 @@ import * as vscode from 'vscode'; import * as path from 'path'; import * as settings from '../../client/common/configSettings'; import * as fs from 'fs-extra'; +import { EOL } from 'os'; import { AutoPep8Formatter } from '../../client/formatters/autoPep8Formatter'; import { initialize, IS_TRAVIS, closeActiveWindows } from '../initialize'; import { YapfFormatter } from '../../client/formatters/yapfFormatter'; @@ -21,6 +22,7 @@ const pythonSettings = settings.PythonSettings.getInstance(); const ch = vscode.window.createOutputChannel('Tests'); const pythoFilesPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'formatting'); +const workspaceRootPath = path.join(__dirname, '..', '..', '..', 'src', 'test'); const originalUnformattedFile = path.join(pythoFilesPath, 'fileToFormat.py'); const autoPep8FileToFormat = path.join(pythoFilesPath, 'autoPep8FileToFormat.py'); @@ -38,8 +40,8 @@ suite('Formatting', () => { fs.copySync(originalUnformattedFile, file, { overwrite: true }); }); fs.ensureDirSync(path.dirname(autoPep8FileToFormat)); - const yapf = execPythonFile('yapf', [originalUnformattedFile], pythoFilesPath, false); - const autoPep8 = execPythonFile('autopep8', [originalUnformattedFile], pythoFilesPath, false); + const yapf = execPythonFile('yapf', [originalUnformattedFile], workspaceRootPath, false); + const autoPep8 = execPythonFile('autopep8', [originalUnformattedFile], workspaceRootPath, false); await Promise.all([yapf, autoPep8]).then(formattedResults => { formattedYapf = formattedResults[0]; formattedAutoPep8 = formattedResults[1]; @@ -76,11 +78,11 @@ suite('Formatting', () => { }); } test('AutoPep8', done => { - testFormatting(new AutoPep8Formatter(ch, pythonSettings, pythoFilesPath), formattedAutoPep8, autoPep8FileToFormat).then(done, done); + testFormatting(new AutoPep8Formatter(ch), formattedAutoPep8, autoPep8FileToFormat).then(done, done); }); test('Yapf', done => { - testFormatting(new YapfFormatter(ch, pythonSettings, pythoFilesPath), formattedYapf, yapfFileToFormat).then(done, done); + testFormatting(new YapfFormatter(ch), formattedYapf, yapfFileToFormat).then(done, done); }); function testAutoFormatting(formatter: string, formattedContents: string, fileToFormat: string): PromiseLike { @@ -104,17 +106,18 @@ suite('Formatting', () => { }, 5000); }); }).then(() => { - assert.equal(textDocument.getText(), formattedContents, 'Formatted contents are not the same'); + const text = textDocument.getText(); + assert.equal(text === formattedContents, true, 'Formatted contents are not the same'); }); } test('AutoPep8 autoformat on save', done => { - testAutoFormatting('autopep8', '#\n' + formattedAutoPep8, autoPep8FileToAutoFormat).then(done, done); + testAutoFormatting('autopep8', `#${EOL}` + formattedAutoPep8, autoPep8FileToAutoFormat).then(done, done); }); // For some reason doesn't ever work on travis if (!IS_TRAVIS) { test('Yapf autoformat on save', done => { - testAutoFormatting('yapf', '#\n' + formattedYapf, yapfFileToAutoFormat).then(done, done); + testAutoFormatting('yapf', `#${EOL}` + formattedYapf, yapfFileToAutoFormat).then(done, done); }); } }); diff --git a/src/test/initialize.ts b/src/test/initialize.ts index a298c234c4a9..1dcd6e247fd4 100644 --- a/src/test/initialize.ts +++ b/src/test/initialize.ts @@ -71,7 +71,7 @@ function getPythonPath(): string { const pythonPaths = ['/home/travis/virtualenv/python3.5.2/bin/python', '/xUsers/travis/.pyenv/versions/3.5.1/envs/MYVERSION/bin/python', '/xUsers/donjayamanne/Projects/PythonEnvs/p361/bin/python', - 'C:/Users/dojayama/nine/python.exe', + 'cC:/Users/dojayama/nine/python.exe', 'C:/Development/PythonEnvs/p27/scripts/python.exe', '/Users/donjayamanne/Projects/PythonEnvs/p27/bin/python']; for (let counter = 0; counter < pythonPaths.length; counter++) { diff --git a/src/test/linters/lint.test.ts b/src/test/linters/lint.test.ts index 3f52789584cf..bf96de28cfc3 100644 --- a/src/test/linters/lint.test.ts +++ b/src/test/linters/lint.test.ts @@ -141,34 +141,51 @@ suite('Linting', () => { pythonSettings.linting.pydocstyleEnabled = true; }); suiteTeardown(() => closeActiveWindows()); - teardown(() => closeActiveWindows()); + teardown(() => { + closeActiveWindows(); + pythonSettings.linting.lintOnSave = false; + pythonSettings.linting.lintOnTextChange = false; + pythonSettings.linting.enabled = true; + pythonSettings.linting.pylintEnabled = true; + pythonSettings.linting.flake8Enabled = true; + pythonSettings.linting.pep8Enabled = true; + pythonSettings.linting.prospectorEnabled = true; + pythonSettings.linting.pydocstyleEnabled = true; + }); - function testEnablingDisablingOfLinter(linter: baseLinter.BaseLinter, propertyName: string) { + async function testEnablingDisablingOfLinter(linter: baseLinter.BaseLinter, propertyName: string) { pythonSettings.linting[propertyName] = true; - assert.equal(true, linter.isEnabled()); - pythonSettings.linting[propertyName] = false; - assert.equal(false, linter.isEnabled()); + let cancelToken = new vscode.CancellationTokenSource(); + disableAllButThisLinter(linter.product); + const document = await vscode.workspace.openTextDocument(fileToLint); + const editor = await vscode.window.showTextDocument(document); + try { + const messages = await linter.lint(editor.document, cancelToken.token); + assert.equal(messages.length, 0, 'Errors returned even when linter is disabled'); + } catch (error) { + assert.fail(error, null, 'Linter error'); + } } test('Enable and Disable Pylint', () => { let ch = new MockOutputChannel('Lint'); - testEnablingDisablingOfLinter(new pyLint.Linter(ch, pythoFilesPath), 'pylintEnabled'); + testEnablingDisablingOfLinter(new pyLint.Linter(ch), 'pylintEnabled'); }); test('Enable and Disable Pep8', () => { let ch = new MockOutputChannel('Lint'); - testEnablingDisablingOfLinter(new pep8.Linter(ch, pythoFilesPath), 'pep8Enabled'); + testEnablingDisablingOfLinter(new pep8.Linter(ch), 'pep8Enabled'); }); test('Enable and Disable Flake8', () => { let ch = new MockOutputChannel('Lint'); - testEnablingDisablingOfLinter(new flake8.Linter(ch, pythoFilesPath), 'flake8Enabled'); + testEnablingDisablingOfLinter(new flake8.Linter(ch), 'flake8Enabled'); }); test('Enable and Disable Prospector', () => { let ch = new MockOutputChannel('Lint'); - testEnablingDisablingOfLinter(new prospector.Linter(ch, pythoFilesPath), 'prospectorEnabled'); + testEnablingDisablingOfLinter(new prospector.Linter(ch), 'prospectorEnabled'); }); test('Enable and Disable Pydocstyle', () => { let ch = new MockOutputChannel('Lint'); - testEnablingDisablingOfLinter(new pydocstyle.Linter(ch, pythoFilesPath), 'pydocstyleEnabled'); + testEnablingDisablingOfLinter(new pydocstyle.Linter(ch), 'pydocstyleEnabled'); }); function disableAllButThisLinter(linterToEnable: Product) { @@ -187,7 +204,7 @@ suite('Linting', () => { return vscode.workspace.openTextDocument(pythonFile) .then(document => vscode.window.showTextDocument(document)) .then(editor => { - return linter.runLinter(editor.document, cancelToken.token); + return linter.lint(editor.document, cancelToken.token); }) .then(messages => { // Different versions of python return different errors, @@ -215,23 +232,23 @@ suite('Linting', () => { } test('PyLint', done => { let ch = new MockOutputChannel('Lint'); - let linter = new pyLint.Linter(ch, pythoFilesPath); + let linter = new pyLint.Linter(ch); testLinterMessages(linter, ch, fileToLint, pylintMessagesToBeReturned).then(done, done); }); test('Flake8', done => { let ch = new MockOutputChannel('Lint'); - let linter = new flake8.Linter(ch, pythoFilesPath); + let linter = new flake8.Linter(ch); testLinterMessages(linter, ch, fileToLint, flake8MessagesToBeReturned).then(done, done); }); test('Pep8', done => { let ch = new MockOutputChannel('Lint'); - let linter = new pep8.Linter(ch, pythoFilesPath); + let linter = new pep8.Linter(ch); testLinterMessages(linter, ch, fileToLint, pep8MessagesToBeReturned).then(done, done); }); if (!isPython3) { test('Pydocstyle', done => { let ch = new MockOutputChannel('Lint'); - let linter = new pydocstyle.Linter(ch, pythoFilesPath); + let linter = new pydocstyle.Linter(ch); testLinterMessages(linter, ch, fileToLint, pydocstyleMessagseToBeReturned).then(done, done); }); } @@ -242,26 +259,26 @@ suite('Linting', () => { const messagesToBeReturned = value ? filteredPylint3MessagesToBeReturned : filteredPylintMessagesToBeReturned; test('PyLint with config in root', done => { let ch = new MockOutputChannel('Lint'); - let linter = new pyLint.Linter(ch, pylintConfigPath); + let linter = new pyLint.Linter(ch); testLinterMessages(linter, ch, path.join(pylintConfigPath, 'file.py'), messagesToBeReturned).then(done, done); }); }); } test('Flake8 with config in root', done => { let ch = new MockOutputChannel('Lint'); - let linter = new flake8.Linter(ch, flake8ConfigPath); + let linter = new flake8.Linter(ch); testLinterMessages(linter, ch, path.join(flake8ConfigPath, 'file.py'), filteredFlake8MessagesToBeReturned).then(done, done); }); test('Pep8 with config in root', done => { let ch = new MockOutputChannel('Lint'); - let linter = new pep8.Linter(ch, pep8ConfigPath); + let linter = new pep8.Linter(ch); testLinterMessages(linter, ch, path.join(pep8ConfigPath, 'file.py'), filteredPep88MessagesToBeReturned).then(done, done); }); isPython3.then(value => { const messagesToBeReturned = value ? [] : fiteredPydocstyleMessagseToBeReturned; test('Pydocstyle with config in root', done => { let ch = new MockOutputChannel('Lint'); - let linter = new pydocstyle.Linter(ch, pydocstyleConfigPath27); + let linter = new pydocstyle.Linter(ch); testLinterMessages(linter, ch, path.join(pydocstyleConfigPath27, 'file.py'), messagesToBeReturned).then(done, done); }); });