diff --git a/extensions/positron-python/.github/actions/build-vsix/action.yml b/extensions/positron-python/.github/actions/build-vsix/action.yml
index ae7b8fddba6..b632eb0a25c 100644
--- a/extensions/positron-python/.github/actions/build-vsix/action.yml
+++ b/extensions/positron-python/.github/actions/build-vsix/action.yml
@@ -16,7 +16,7 @@ runs:
using: 'composite'
steps:
- name: Install Node
- uses: actions/setup-node@v3
+ uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node_version }}
cache: 'npm'
diff --git a/extensions/positron-python/.github/actions/lint/action.yml b/extensions/positron-python/.github/actions/lint/action.yml
index 1d302b055be..1e4fd0712f7 100644
--- a/extensions/positron-python/.github/actions/lint/action.yml
+++ b/extensions/positron-python/.github/actions/lint/action.yml
@@ -10,7 +10,7 @@ runs:
using: 'composite'
steps:
- name: Install Node
- uses: actions/setup-node@v3
+ uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node_version }}
cache: 'npm'
diff --git a/extensions/positron-python/.github/dependabot.yml b/extensions/positron-python/.github/dependabot.yml
index de5ebfe9158..14c8e18d475 100644
--- a/extensions/positron-python/.github/dependabot.yml
+++ b/extensions/positron-python/.github/dependabot.yml
@@ -37,7 +37,6 @@ updates:
- dependency-name: prospector # Due to Python 2.7 and #14477.
- dependency-name: pytest # Due to Python 2.7 and #13776.
- dependency-name: py # Due to Python 2.7.
- - dependency-name: isort
- dependency-name: jedi-language-server
labels:
- 'no-changelog'
diff --git a/extensions/positron-python/.github/workflows/build.yml b/extensions/positron-python/.github/workflows/build.yml
index 56d9c04f0cd..d17d5fff4b9 100644
--- a/extensions/positron-python/.github/workflows/build.yml
+++ b/extensions/positron-python/.github/workflows/build.yml
@@ -168,7 +168,7 @@ jobs:
path: ${{ env.special-working-directory-relative }}
- name: Install Node
- uses: actions/setup-node@v3
+ uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
diff --git a/extensions/positron-python/.github/workflows/pr-check.yml b/extensions/positron-python/.github/workflows/pr-check.yml
index 9229393ce5c..4ade4fd2af1 100644
--- a/extensions/positron-python/.github/workflows/pr-check.yml
+++ b/extensions/positron-python/.github/workflows/pr-check.yml
@@ -143,7 +143,7 @@ jobs:
path: ${{ env.special-working-directory-relative }}
- name: Install Node
- uses: actions/setup-node@v3
+ uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
@@ -356,7 +356,7 @@ jobs:
uses: actions/checkout@v4
- name: Install Node
- uses: actions/setup-node@v3
+ uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
diff --git a/extensions/positron-python/.vscode/extensions.json b/extensions/positron-python/.vscode/extensions.json
index 5ade8dec488..93a73827e7a 100644
--- a/extensions/positron-python/.vscode/extensions.json
+++ b/extensions/positron-python/.vscode/extensions.json
@@ -7,6 +7,8 @@
"dbaeumer.vscode-eslint",
"ms-python.python",
"ms-python.black-formatter",
- "ms-python.vscode-pylance"
+ "ms-python.vscode-pylance",
+ "ms-python.isort",
+ "ms-python.flake8"
]
}
diff --git a/extensions/positron-python/.vscode/settings.json b/extensions/positron-python/.vscode/settings.json
index 86b34bfd81d..eaba16a0ac4 100644
--- a/extensions/positron-python/.vscode/settings.json
+++ b/extensions/positron-python/.vscode/settings.json
@@ -46,12 +46,8 @@
"editor.formatOnSave": true
},
"typescript.tsdk": "./node_modules/typescript/lib", // we want to use the TS server from our node_modules folder to control its version
- "python.linting.enabled": false,
- "python.formatting.provider": "black",
- "python.sortImports.args": ["--profile", "black"],
"typescript.preferences.quoteStyle": "single",
"javascript.preferences.quoteStyle": "single",
- "typescriptHero.imports.stringQuoteStyle": "'",
"prettier.printWidth": 120,
"prettier.singleQuote": true,
"editor.codeActionsOnSave": {
@@ -72,12 +68,16 @@
"pythonFiles/tests"
],
"typescript.preferences.importModuleSpecifier": "relative",
- "debug.javascript.usePreview": false,
// Branch name suggestion.
"git.branchProtectionPrompt": "alwaysCommitToNewBranch",
"git.branchRandomName.enable": true,
"git.branchProtection": ["main", "release/*"],
"git.pullBeforeCheckout": true,
// Open merge editor for resolving conflicts.
- "git.mergeEditor": true
+ "git.mergeEditor": true,
+ "python.testing.pytestArgs": [
+ "pythonFiles/tests"
+ ],
+ "python.testing.unittestEnabled": false,
+ "python.testing.pytestEnabled": true
}
diff --git a/extensions/positron-python/README.md b/extensions/positron-python/README.md
index 0a8766f086a..8029aa09658 100644
--- a/extensions/positron-python/README.md
+++ b/extensions/positron-python/README.md
@@ -60,7 +60,7 @@ Open the Command Palette (Command+Shift+P on macOS and Ctrl+Shift+P on Windows/L
| `Python: Select Interpreter` | Switch between Python interpreters, versions, and environments. |
| `Python: Start REPL` | Start an interactive Python REPL using the selected interpreter in the VS Code terminal. |
| `Python: Run Python File in Terminal` | Runs the active Python file in the VS Code terminal. You can also run a Python file by right-clicking on the file and selecting `Run Python File in Terminal`. |
-| `Format Document` | Formats code using the provided [formatter](https://code.visualstudio.com/docs/python/editing#_formatting) in the `settings.json` file. |
+| `Format Document` | Formats code using the provided [formatter](https://code.visualstudio.com/docs/python/formatting) in the `settings.json` file. |
| `Python: Configure Tests` | Select a test framework and configure it to display the Test Explorer. |
To see all available Python commands, open the Command Palette and type `Python`. For Jupyter extension commands, just type `Jupyter`.
@@ -71,16 +71,11 @@ Learn more about the rich features of the Python extension:
- [IntelliSense](https://code.visualstudio.com/docs/python/editing#_autocomplete-and-intellisense): Edit your code with auto-completion, code navigation, syntax checking and more
- [Linting](https://code.visualstudio.com/docs/python/linting): Get additional code analysis with Pylint, Flake8 and more
-- [Code formatting](https://code.visualstudio.com/docs/python/editing#_formatting): Format your code with black, autopep or yapf
-
+- [Code formatting](https://code.visualstudio.com/docs/python/formatting): Format your code with black, autopep or yapf
- [Debugging](https://code.visualstudio.com/docs/python/debugging): Debug your Python scripts, web apps, remote or multi-threaded processes
-
- [Testing](https://code.visualstudio.com/docs/python/unit-testing): Run and debug tests through the Test Explorer with unittest or pytest.
-
- [Jupyter Notebooks](https://code.visualstudio.com/docs/python/jupyter-support): Create and edit Jupyter Notebooks, add and run code cells, render plots, visualize variables through the variable explorer, visualize dataframes with the data viewer, and more
-
- [Environments](https://code.visualstudio.com/docs/python/environments): Automatically activate and switch between virtualenv, venv, pipenv, conda and pyenv environments
-
- [Refactoring](https://code.visualstudio.com/docs/python/editing#_refactoring): Restructure your Python code with variable extraction and method extraction. Additionally, there is componentized support to enable additional refactoring, such as import sorting, through extensions including [isort](https://marketplace.visualstudio.com/items?itemName=ms-python.isort) and [Ruff](https://marketplace.visualstudio.com/items?itemName=charliermarsh.ruff).
diff --git a/extensions/positron-python/ThirdPartyNotices-Repository.txt b/extensions/positron-python/ThirdPartyNotices-Repository.txt
index c8854a208e5..9e7e822af1b 100644
--- a/extensions/positron-python/ThirdPartyNotices-Repository.txt
+++ b/extensions/positron-python/ThirdPartyNotices-Repository.txt
@@ -6,18 +6,17 @@ Microsoft Python extension for Visual Studio Code incorporates third party mater
1. Go for Visual Studio Code (https://github.com/Microsoft/vscode-go)
2. Files from the Python Project (https://www.python.org/)
-3. Google Diff Match and Patch (https://github.com/GerHobbelt/google-diff-match-patch)
-4. omnisharp-vscode (https://github.com/OmniSharp/omnisharp-vscode)
-5. PTVS (https://github.com/Microsoft/PTVS)
-6. Python documentation (https://docs.python.org/)
-7. python-functools32 (https://github.com/MiCHiLU/python-functools32/blob/master/functools32/functools32.py)
-8. pythonVSCode (https://github.com/DonJayamanne/pythonVSCode)
-9. Sphinx (http://sphinx-doc.org/)
-10. nteract (https://github.com/nteract/nteract)
-11. less-plugin-inline-urls (https://github.com/less/less-plugin-inline-urls/)
-12. vscode-cpptools (https://github.com/microsoft/vscode-cpptools)
-13. mocha (https://github.com/mochajs/mocha)
-14. get-pip (https://github.com/pypa/get-pip)
+3. omnisharp-vscode (https://github.com/OmniSharp/omnisharp-vscode)
+4. PTVS (https://github.com/Microsoft/PTVS)
+5. Python documentation (https://docs.python.org/)
+6. python-functools32 (https://github.com/MiCHiLU/python-functools32/blob/master/functools32/functools32.py)
+7. pythonVSCode (https://github.com/DonJayamanne/pythonVSCode)
+8. Sphinx (http://sphinx-doc.org/)
+9. nteract (https://github.com/nteract/nteract)
+10. less-plugin-inline-urls (https://github.com/less/less-plugin-inline-urls/)
+11. vscode-cpptools (https://github.com/microsoft/vscode-cpptools)
+12. mocha (https://github.com/mochajs/mocha)
+13. get-pip (https://github.com/pypa/get-pip)
%%
Go for Visual Studio Code NOTICES, INFORMATION, AND LICENSE BEGIN HERE
@@ -244,25 +243,6 @@ OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
=========================================
END OF Files from the Python Project NOTICES, INFORMATION, AND LICENSE
-%% Google Diff Match and Patch NOTICES, INFORMATION, AND LICENSE BEGIN HERE
-=========================================
- * Copyright 2006 Google Inc.
- * http://code.google.com/p/google-diff-match-patch/
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
-=========================================
-END OF Google Diff Match and Patch NOTICES, INFORMATION, AND LICENSE
-
%% omnisharp-vscode NOTICES, INFORMATION, AND LICENSE BEGIN HERE
=========================================
Copyright (c) Microsoft Corporation
diff --git a/extensions/positron-python/build/test-requirements.txt b/extensions/positron-python/build/test-requirements.txt
index a489e7ae742..d2c24b6bfbd 100644
--- a/extensions/positron-python/build/test-requirements.txt
+++ b/extensions/positron-python/build/test-requirements.txt
@@ -1,13 +1,8 @@
# pin setoptconf to prevent issue with 'use_2to3'
setoptconf==0.3.0
-# Install flake8 first, as both flake8 and autopep8 require pycodestyle,
-# but flake8 has a tighter pinning.
flake8
-autopep8
bandit
-black
-yapf
pylint
pycodestyle
pydocstyle
@@ -18,7 +13,6 @@ flask
fastapi
uvicorn
django
-isort
# Integrated TensorBoard tests
tensorboard
diff --git a/extensions/positron-python/build/webpack/common.js b/extensions/positron-python/build/webpack/common.js
index b248b29fdd6..fca1b1a900f 100644
--- a/extensions/positron-python/build/webpack/common.js
+++ b/extensions/positron-python/build/webpack/common.js
@@ -21,7 +21,6 @@ exports.nodeModulesToExternalize = [
'unicode/category/Nd',
'unicode/category/Pc',
'source-map-support',
- 'diff-match-patch',
'sudo-prompt',
'node-stream-zip',
'xml2js',
diff --git a/extensions/positron-python/build/webpack/webpack.extension.config.js b/extensions/positron-python/build/webpack/webpack.extension.config.js
index 79a6556d708..9c1188d53fe 100644
--- a/extensions/positron-python/build/webpack/webpack.extension.config.js
+++ b/extensions/positron-python/build/webpack/webpack.extension.config.js
@@ -72,6 +72,7 @@ const config = {
resolve: {
extensions: ['.ts', '.js'],
plugins: [new tsconfig_paths_webpack_plugin.TsconfigPathsPlugin({ configFile: configFileName })],
+ conditionNames: ['import', 'require', 'node'],
},
output: {
filename: '[name].js',
diff --git a/extensions/positron-python/gulpfile.js b/extensions/positron-python/gulpfile.js
index d5579b381d3..656adc09ae0 100644
--- a/extensions/positron-python/gulpfile.js
+++ b/extensions/positron-python/gulpfile.js
@@ -20,7 +20,9 @@ const nativeDependencyChecker = require('node-has-native-dependencies');
const flat = require('flat');
const { argv } = require('yargs');
const os = require('os');
+// --- Start Positron ---
const rmrf = require('rimraf');
+// --- End Positron ---
const typescript = require('typescript');
const tsProject = ts.createProject('./tsconfig.json', { typescript });
@@ -247,6 +249,7 @@ gulp.task('prePublishBundle', gulp.series('webpack', 'renameSourceMaps'));
gulp.task('checkDependencies', gulp.series('checkNativeDependencies'));
gulp.task('prePublishNonBundle', gulp.series('compile'));
+// --- Start Positron ---
gulp.task('installPythonRequirements', async () => {
let args = [
'-m',
@@ -264,9 +267,7 @@ gulp.task('installPythonRequirements', async () => {
'-r',
'./requirements.txt',
];
- // --- Start Positron ---
await spawnAsync(pythonCommand, args, undefined, true)
- // --- End Positron ---
.then(() => true)
.catch((ex) => {
console.error("Failed to install requirements using 'python'", ex);
@@ -289,9 +290,7 @@ gulp.task('installPythonRequirements', async () => {
'-r',
'./pythonFiles/jedilsp_requirements/requirements.txt',
];
- // --- Start Positron ---
await spawnAsync(pythonCommand, args, undefined, true)
- // --- End Positron ---
.then(() => true)
.catch((ex) => {
console.error("Failed to install Jedi LSP requirements using 'python'", ex);
@@ -313,9 +312,7 @@ gulp.task('installDebugpy', async () => {
'-r',
'./build/build-install-requirements.txt',
];
- // --- Start Positron ---
await spawnAsync(pythonCommand, depsArgs, undefined, true)
- // --- End Positron ---
.then(() => true)
.catch((ex) => {
console.error("Failed to install dependencies need by 'install_debugpy.py' using 'python'", ex);
@@ -325,9 +322,7 @@ gulp.task('installDebugpy', async () => {
// Install new DEBUGPY with wheels for python
const wheelsArgs = ['./pythonFiles/install_debugpy.py'];
const wheelsEnv = { PYTHONPATH: './pythonFiles/lib/temp' };
- // --- Start Positron ---
await spawnAsync(pythonCommand, wheelsArgs, wheelsEnv, true)
- // --- End Positron ---
.then(() => true)
.catch((ex) => {
console.error("Failed to install DEBUGPY wheels using 'python'", ex);
@@ -349,7 +344,6 @@ gulp.task('installDebugpy', async () => {
gulp.task('installPythonLibs', gulp.series('installPythonRequirements', 'installDebugpy'));
-// --- Start Positron ---
function locatePython() {
let pythonPath = process.env.CI_PYTHON_PATH || 'python3';
const whichCommand = os.platform() === 'win32' ? 'where' : 'which';
diff --git a/extensions/positron-python/noxfile.py b/extensions/positron-python/noxfile.py
new file mode 100644
index 00000000000..b9ebba64544
--- /dev/null
+++ b/extensions/positron-python/noxfile.py
@@ -0,0 +1,50 @@
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License.
+
+import pathlib
+import nox
+import shutil
+
+
+@nox.session()
+def install_python_libs(session: nox.Session):
+ requirements = [
+ ("./pythonFiles/lib/python", "./requirements.txt"),
+ (
+ "./pythonFiles/lib/jedilsp",
+ "./pythonFiles/jedilsp_requirements/requirements.txt",
+ ),
+ ]
+ for target, file in requirements:
+ session.install(
+ "-t",
+ target,
+ "--no-cache-dir",
+ "--implementation",
+ "py",
+ "--no-deps",
+ "--require-hashes",
+ "--only-binary",
+ ":all:",
+ "-r",
+ file,
+ )
+
+ session.install("packaging")
+
+ # Install debugger
+ session.run(
+ "python",
+ "./pythonFiles/install_debugpy.py",
+ env={"PYTHONPATH": "./pythonFiles/lib/temp"},
+ )
+
+ # Download get-pip script
+ session.run(
+ "python",
+ "./pythonFiles/download_get_pip.py",
+ env={"PYTHONPATH": "./pythonFiles/lib/temp"},
+ )
+
+ if pathlib.Path("./pythonFiles/lib/temp").exists():
+ shutil.rmtree("./pythonFiles/lib/temp")
diff --git a/extensions/positron-python/package.json b/extensions/positron-python/package.json
index a08ca84e734..46fa5a4dc6e 100644
--- a/extensions/positron-python/package.json
+++ b/extensions/positron-python/package.json
@@ -22,7 +22,8 @@
"quickPickSortByLabel",
"testObserver",
"quickPickItemTooltip",
- "saveEditor"
+ "saveEditor",
+ "terminalDataWriteEvent"
],
"author": {
"name": "Microsoft Corporation"
@@ -526,14 +527,18 @@
"pythonSurveyNotification",
"pythonPromptNewToolsExt",
"pythonTerminalEnvVarActivation",
- "pythonTestAdapter"
+ "pythonTestAdapter",
+ "pythonREPLSmartSend",
+ "pythonRecommendTensorboardExt"
],
"enumDescriptions": [
"%python.experiments.All.description%",
"%python.experiments.pythonSurveyNotification.description%",
"%python.experiments.pythonPromptNewToolsExt.description%",
"%python.experiments.pythonTerminalEnvVarActivation.description%",
- "%python.experiments.pythonTestAdapter.description%"
+ "%python.experiments.pythonTestAdapter.description%",
+ "%python.experiments.pythonREPLSmartSend.description%",
+ "%python.experiments.pythonRecommendTensorboardExt.description%"
]
},
"scope": "machine",
@@ -549,91 +554,22 @@
"pythonSurveyNotification",
"pythonPromptNewToolsExt",
"pythonTerminalEnvVarActivation",
- "pythonTestAdapter"
+ "pythonTestAdapter",
+ "pythonREPLSmartSend"
],
"enumDescriptions": [
"%python.experiments.All.description%",
"%python.experiments.pythonSurveyNotification.description%",
"%python.experiments.pythonPromptNewToolsExt.description%",
"%python.experiments.pythonTerminalEnvVarActivation.description%",
- "%python.experiments.pythonTestAdapter.description%"
+ "%python.experiments.pythonTestAdapter.description%",
+ "%python.experiments.pythonREPLSmartSend.description%"
]
},
"scope": "machine",
"type": "array",
"uniqueItems": true
},
- "python.formatting.autopep8Args": {
- "default": [],
- "description": "%python.formatting.autopep8Args.description%",
- "items": {
- "type": "string"
- },
- "scope": "resource",
- "type": "array",
- "markdownDeprecationMessage": "%python.formatting.autopep8Args.markdownDeprecationMessage%",
- "deprecationMessage": "%python.formatting.autopep8Args.deprecationMessage%"
- },
- "python.formatting.autopep8Path": {
- "default": "autopep8",
- "description": "%python.formatting.autopep8Path.description%",
- "scope": "machine-overridable",
- "type": "string",
- "markdownDeprecationMessage": "%python.formatting.autopep8Path.markdownDeprecationMessage%",
- "deprecationMessage": "%python.formatting.autopep8Path.deprecationMessage%"
- },
- "python.formatting.blackArgs": {
- "default": [],
- "description": "%python.formatting.blackArgs.description%",
- "items": {
- "type": "string"
- },
- "scope": "resource",
- "type": "array",
- "markdownDeprecationMessage": "%python.formatting.blackArgs.markdownDeprecationMessage%",
- "deprecationMessage": "%python.formatting.blackArgs.deprecationMessage%"
- },
- "python.formatting.blackPath": {
- "default": "black",
- "description": "%python.formatting.blackPath.description%",
- "scope": "machine-overridable",
- "type": "string",
- "markdownDeprecationMessage": "%python.formatting.blackPath.markdownDeprecationMessage%",
- "deprecationMessage": "%python.formatting.blackPath.deprecationMessage%"
- },
- "python.formatting.provider": {
- "default": "autopep8",
- "description": "%python.formatting.provider.description%",
- "enum": [
- "autopep8",
- "black",
- "none",
- "yapf"
- ],
- "scope": "resource",
- "type": "string",
- "markdownDeprecationMessage": "%python.formatting.provider.markdownDeprecationMessage%",
- "deprecationMessage": "%python.formatting.provider.deprecationMessage%"
- },
- "python.formatting.yapfArgs": {
- "default": [],
- "description": "%python.formatting.yapfArgs.description%",
- "items": {
- "type": "string"
- },
- "scope": "resource",
- "type": "array",
- "markdownDeprecationMessage": "%python.formatting.yapfArgs.markdownDeprecationMessage%",
- "deprecationMessage": "%python.formatting.yapfArgs.deprecationMessage%"
- },
- "python.formatting.yapfPath": {
- "default": "yapf",
- "description": "%python.formatting.yapfPath.description%",
- "scope": "machine-overridable",
- "type": "string",
- "markdownDeprecationMessage": "%python.formatting.yapfPath.markdownDeprecationMessage%",
- "deprecationMessage": "%python.formatting.yapfPath.deprecationMessage%"
- },
"python.globalModuleInstallation": {
"default": false,
"description": "%python.globalModuleInstallation.description%",
@@ -659,88 +595,6 @@
"scope": "application",
"type": "string"
},
- "python.linting.banditArgs": {
- "default": [],
- "description": "%python.linting.banditArgs.description%",
- "items": {
- "type": "string"
- },
- "scope": "resource",
- "type": "array",
- "markdownDeprecationMessage": "%python.linting.banditArgs.markdownDeprecationMessage%",
- "deprecationMessage": "%python.linting.banditArgs.deprecationMessage%"
- },
- "python.linting.banditEnabled": {
- "default": false,
- "description": "%python.linting.banditEnabled.description%",
- "scope": "resource",
- "type": "boolean",
- "markdownDeprecationMessage": "%python.linting.banditArgs.markdownDeprecationMessage%",
- "deprecationMessage": "%python.linting.banditArgs.deprecationMessage%"
- },
- "python.linting.banditPath": {
- "default": "bandit",
- "description": "%python.linting.banditPath.description%",
- "scope": "machine-overridable",
- "type": "string",
- "markdownDeprecationMessage": "%python.linting.banditPath.markdownDeprecationMessage%",
- "deprecationMessage": "%python.linting.banditPath.deprecationMessage%"
- },
- "python.linting.cwd": {
- "default": null,
- "description": "%python.linting.cwd.description%",
- "scope": "resource",
- "type": "string",
- "markdownDeprecationMessage": "%python.linting.cwd.markdownDeprecationMessage%",
- "deprecationMessage": "%python.linting.cwd.deprecationMessage%"
- },
- "python.linting.enabled": {
- "default": true,
- "description": "%python.linting.enabled.description%",
- "scope": "resource",
- "type": "boolean",
- "markdownDeprecationMessage": "%python.linting.enabled.markdownDeprecationMessage%",
- "deprecationMessage": "%python.linting.enabled.deprecationMessage%"
- },
- "python.linting.flake8Args": {
- "default": [],
- "description": "%python.linting.flake8Args.description%",
- "items": {
- "type": "string"
- },
- "scope": "resource",
- "type": "array",
- "markdownDeprecationMessage": "%python.linting.flake8Args.markdownDeprecationMessage%",
- "deprecationMessage": "%python.linting.flake8Args.deprecationMessage%"
- },
- "python.linting.flake8CategorySeverity.E": {
- "default": "Error",
- "description": "%python.linting.flake8CategorySeverity.E.description%",
- "enum": [
- "Error",
- "Hint",
- "Information",
- "Warning"
- ],
- "scope": "resource",
- "type": "string",
- "markdownDeprecationMessage": "%python.linting.flake8CategorySeverity.E.markdownDeprecationMessage%",
- "deprecationMessage": "%python.linting.flake8CategorySeverity.E.deprecationMessage%"
- },
- "python.linting.flake8CategorySeverity.F": {
- "default": "Error",
- "description": "%python.linting.flake8CategorySeverity.F.description%",
- "enum": [
- "Error",
- "Hint",
- "Information",
- "Warning"
- ],
- "scope": "resource",
- "type": "string",
- "markdownDeprecationMessage": "%python.linting.flake8CategorySeverity.F.markdownDeprecationMessage%",
- "deprecationMessage": "%python.linting.flake8CategorySeverity.F.deprecationMessage%"
- },
"python.interpreter.infoVisibility": {
"default": "never",
"description": "%python.interpreter.infoVisibility.description%",
@@ -757,360 +611,6 @@
"scope": "machine",
"type": "string"
},
- "python.linting.flake8CategorySeverity.W": {
- "default": "Warning",
- "description": "%python.linting.flake8CategorySeverity.W.description%",
- "enum": [
- "Error",
- "Hint",
- "Information",
- "Warning"
- ],
- "scope": "resource",
- "type": "string",
- "markdownDeprecationMessage": "%python.linting.flake8CategorySeverity.W.markdownDeprecationMessage%",
- "deprecationMessage": "%python.linting.flake8CategorySeverity.W.deprecationMessage%"
- },
- "python.linting.flake8Enabled": {
- "default": false,
- "description": "%python.linting.flake8Enabled.description%",
- "scope": "resource",
- "type": "boolean",
- "markdownDeprecationMessage": "%python.linting.flake8Enabled.markdownDeprecationMessage%",
- "deprecationMessage": "%python.linting.flake8Enabled.deprecationMessage%"
- },
- "python.linting.flake8Path": {
- "default": "flake8",
- "description": "%python.linting.flake8Path.description%",
- "scope": "machine-overridable",
- "type": "string",
- "markdownDeprecationMessage": "%python.linting.flake8Path.markdownDeprecationMessage%",
- "deprecationMessage": "%python.linting.flake8Path.deprecationMessage%"
- },
- "python.linting.ignorePatterns": {
- "default": [
- "**/site-packages/**/*.py",
- ".vscode/*.py"
- ],
- "description": "%python.linting.ignorePatterns.description%",
- "items": {
- "type": "string"
- },
- "scope": "resource",
- "type": "array",
- "uniqueItems": true,
- "markdownDeprecationMessage": "%python.linting.ignorePatterns.markdownDeprecationMessage%",
- "deprecationMessage": "%python.linting.ignorePatterns.deprecationMessage%"
- },
- "python.linting.lintOnSave": {
- "default": true,
- "description": "%python.linting.lintOnSave.description%",
- "scope": "resource",
- "type": "boolean",
- "markdownDeprecationMessage": "%python.linting.lintOnSave.markdownDeprecationMessage%",
- "deprecationMessage": "%python.linting.lintOnSave.deprecationMessage%"
- },
- "python.linting.maxNumberOfProblems": {
- "default": 100,
- "description": "%python.linting.maxNumberOfProblems.description%",
- "scope": "resource",
- "type": "number",
- "markdownDeprecationMessage": "%python.linting.maxNumberOfProblems.markdownDeprecationMessage%",
- "deprecationMessage": "%python.linting.maxNumberOfProblems.deprecationMessage%"
- },
- "python.linting.mypyArgs": {
- "default": [
- "--follow-imports=silent",
- "--ignore-missing-imports",
- "--show-column-numbers",
- "--no-pretty"
- ],
- "description": "%python.linting.mypyArgs.description%",
- "items": {
- "type": "string"
- },
- "scope": "resource",
- "type": "array",
- "markdownDeprecationMessage": "%python.linting.mypyArgs.markdownDeprecationMessage%",
- "deprecationMessage": "%python.linting.mypyArgs.deprecationMessage%"
- },
- "python.linting.mypyCategorySeverity.error": {
- "default": "Error",
- "description": "%python.linting.mypyCategorySeverity.error.description%",
- "enum": [
- "Error",
- "Hint",
- "Information",
- "Warning"
- ],
- "scope": "resource",
- "type": "string",
- "markdownDeprecationMessage": "%python.linting.mypyCategorySeverity.error.markdownDeprecationMessage%",
- "deprecationMessage": "%python.linting.mypyCategorySeverity.error.deprecationMessage%"
- },
- "python.linting.mypyCategorySeverity.note": {
- "default": "Information",
- "description": "%python.linting.mypyCategorySeverity.note.description%",
- "enum": [
- "Error",
- "Hint",
- "Information",
- "Warning"
- ],
- "scope": "resource",
- "type": "string",
- "markdownDeprecationMessage": "%python.linting.mypyCategorySeverity.note.markdownDeprecationMessage%",
- "deprecationMessage": "%python.linting.mypyCategorySeverity.note.deprecationMessage%"
- },
- "python.linting.mypyEnabled": {
- "default": false,
- "description": "%python.linting.mypyEnabled.description%",
- "scope": "resource",
- "type": "boolean",
- "markdownDeprecationMessage": "%python.linting.mypyEnabled.markdownDeprecationMessage%",
- "deprecationMessage": "%python.linting.mypyEnabled.deprecationMessage%"
- },
- "python.linting.mypyPath": {
- "default": "mypy",
- "description": "%python.linting.mypyPath.description%",
- "scope": "machine-overridable",
- "type": "string",
- "markdownDeprecationMessage": "%python.linting.mypyPath.markdownDeprecationMessage%",
- "deprecationMessage": "%python.linting.mypyPath.deprecationMessage%"
- },
- "python.linting.prospectorArgs": {
- "default": [],
- "description": "%python.linting.prospectorArgs.description%",
- "items": {
- "type": "string"
- },
- "scope": "resource",
- "type": "array",
- "markdownDeprecationMessage": "%python.linting.prospectorArgs.markdownDeprecationMessage%",
- "deprecationMessage": "%python.linting.prospectorArgs.deprecationMessage%"
- },
- "python.linting.prospectorEnabled": {
- "default": false,
- "description": "%python.linting.prospectorEnabled.description%",
- "scope": "resource",
- "type": "boolean",
- "markdownDeprecationMessage": "%python.linting.prospectorEnabled.markdownDeprecationMessage%",
- "deprecationMessage": "%python.linting.prospectorEnabled.deprecationMessage%"
- },
- "python.linting.prospectorPath": {
- "default": "prospector",
- "description": "%python.linting.prospectorPath.description%",
- "scope": "machine-overridable",
- "type": "string",
- "markdownDeprecationMessage": "%python.linting.prospectorPath.markdownDeprecationMessage%",
- "deprecationMessage": "%python.linting.prospectorPath.deprecationMessage%"
- },
- "python.linting.pycodestyleArgs": {
- "default": [],
- "description": "%python.linting.pycodestyleArgs.description%",
- "items": {
- "type": "string"
- },
- "scope": "resource",
- "type": "array",
- "markdownDeprecationMessage": "%python.linting.pycodestyleArgs.markdownDeprecationMessage%",
- "deprecationMessage": "%python.linting.pycodestyleArgs.deprecationMessage%"
- },
- "python.linting.pycodestyleCategorySeverity.E": {
- "default": "Error",
- "description": "%python.linting.pycodestyleCategorySeverity.E.description%",
- "enum": [
- "Error",
- "Hint",
- "Information",
- "Warning"
- ],
- "scope": "resource",
- "type": "string",
- "markdownDeprecationMessage": "%python.linting.pycodestyleCategorySeverity.E.markdownDeprecationMessage%",
- "deprecationMessage": "%python.linting.pycodestyleCategorySeverity.E.deprecationMessage%"
- },
- "python.linting.pycodestyleCategorySeverity.W": {
- "default": "Warning",
- "description": "%python.linting.pycodestyleCategorySeverity.W.description%",
- "enum": [
- "Error",
- "Hint",
- "Information",
- "Warning"
- ],
- "scope": "resource",
- "type": "string",
- "markdownDeprecationMessage": "%python.linting.pycodestyleCategorySeverity.W.markdownDeprecationMessage%",
- "deprecationMessage": "%python.linting.pycodestyleCategorySeverity.W.deprecationMessage%"
- },
- "python.linting.pycodestyleEnabled": {
- "default": false,
- "description": "%python.linting.pycodestyleEnabled.description%",
- "scope": "resource",
- "type": "boolean",
- "markdownDeprecationMessage": "%python.linting.pycodestyleEnabled.markdownDeprecationMessage%",
- "deprecationMessage": "%python.linting.pycodestyleEnabled.deprecationMessage%"
- },
- "python.linting.pycodestylePath": {
- "default": "pycodestyle",
- "description": "%python.linting.pycodestylePath.description%",
- "scope": "machine-overridable",
- "type": "string",
- "markdownDeprecationMessage": "%python.linting.pycodestylePath.markdownDeprecationMessage%",
- "deprecationMessage": "%python.linting.pycodestylePath.deprecationMessage%"
- },
- "python.linting.pydocstyleArgs": {
- "default": [],
- "description": "%python.linting.pydocstyleArgs.description%",
- "items": {
- "type": "string"
- },
- "scope": "resource",
- "type": "array",
- "markdownDeprecationMessage": "%python.linting.pydocstyleArgs.markdownDeprecationMessage%",
- "deprecationMessage": "%python.linting.pydocstyleArgs.deprecationMessage%"
- },
- "python.linting.pydocstyleEnabled": {
- "default": false,
- "description": "%python.linting.pydocstyleEnabled.description%",
- "scope": "resource",
- "type": "boolean",
- "markdownDeprecationMessage": "%python.linting.pydocstyleEnabled.markdownDeprecationMessage%",
- "deprecationMessage": "%python.linting.pydocstyleEnabled.deprecationMessage%"
- },
- "python.linting.pydocstylePath": {
- "default": "pydocstyle",
- "description": "%python.linting.pydocstylePath.description%",
- "scope": "machine-overridable",
- "type": "string",
- "markdownDeprecationMessage": "%python.linting.pydocstylePath.markdownDeprecationMessage%",
- "deprecationMessage": "%python.linting.pydocstylePath.deprecationMessage%"
- },
- "python.linting.pylamaArgs": {
- "default": [],
- "description": "%python.linting.pylamaArgs.description%",
- "items": {
- "type": "string"
- },
- "scope": "resource",
- "type": "array",
- "markdownDeprecationMessage": "%python.linting.pylamaArgs.markdownDeprecationMessage%",
- "deprecationMessage": "%python.linting.pylamaArgs.deprecationMessage%"
- },
- "python.linting.pylamaEnabled": {
- "default": false,
- "description": "%python.linting.pylamaEnabled.description%",
- "scope": "resource",
- "type": "boolean",
- "markdownDeprecationMessage": "%python.linting.pylamaEnabled.markdownDeprecationMessage%",
- "deprecationMessage": "%python.linting.pylamaEnabled.deprecationMessage%"
- },
- "python.linting.pylamaPath": {
- "default": "pylama",
- "description": "%python.linting.pylamaPath.description%",
- "scope": "machine-overridable",
- "type": "string",
- "markdownDeprecationMessage": "%python.linting.pylamaPath.markdownDeprecationMessage%",
- "deprecationMessage": "%python.linting.pylamaPath.deprecationMessage%"
- },
- "python.linting.pylintArgs": {
- "default": [],
- "description": "%python.linting.pylintArgs.description%",
- "items": {
- "type": "string"
- },
- "scope": "resource",
- "type": "array",
- "markdownDeprecationMessage": "%python.linting.pylintArgs.markdownDeprecationMessage%",
- "deprecationMessage": "%python.linting.pylintArgs.deprecationMessage%"
- },
- "python.linting.pylintCategorySeverity.convention": {
- "default": "Information",
- "description": "%python.linting.pylintCategorySeverity.convention.description%",
- "enum": [
- "Error",
- "Hint",
- "Information",
- "Warning"
- ],
- "scope": "resource",
- "type": "string",
- "markdownDeprecationMessage": "%python.linting.pylintCategorySeverity.convention.markdownDeprecationMessage%",
- "deprecationMessage": "%python.linting.pylintCategorySeverity.convention.deprecationMessage%"
- },
- "python.linting.pylintCategorySeverity.error": {
- "default": "Error",
- "description": "%python.linting.pylintCategorySeverity.error.description%",
- "enum": [
- "Error",
- "Hint",
- "Information",
- "Warning"
- ],
- "scope": "resource",
- "type": "string",
- "markdownDeprecationMessage": "%python.linting.pylintCategorySeverity.error.markdownDeprecationMessage%",
- "deprecationMessage": "%python.linting.pylintCategorySeverity.error.deprecationMessage%"
- },
- "python.linting.pylintCategorySeverity.fatal": {
- "default": "Error",
- "description": "%python.linting.pylintCategorySeverity.fatal.description%",
- "enum": [
- "Error",
- "Hint",
- "Information",
- "Warning"
- ],
- "scope": "resource",
- "type": "string",
- "markdownDeprecationMessage": "%python.linting.pylintCategorySeverity.fatal.markdownDeprecationMessage%",
- "deprecationMessage": "%python.linting.pylintCategorySeverity.fatal.deprecationMessage%"
- },
- "python.linting.pylintCategorySeverity.refactor": {
- "default": "Hint",
- "description": "%python.linting.pylintCategorySeverity.refactor.description%",
- "enum": [
- "Error",
- "Hint",
- "Information",
- "Warning"
- ],
- "scope": "resource",
- "type": "string",
- "markdownDeprecationMessage": "%python.linting.pylintCategorySeverity.refactor.markdownDeprecationMessage%",
- "deprecationMessage": "%python.linting.pylintCategorySeverity.refactor.deprecationMessage%"
- },
- "python.linting.pylintCategorySeverity.warning": {
- "default": "Warning",
- "description": "%python.linting.pylintCategorySeverity.warning.description%",
- "enum": [
- "Error",
- "Hint",
- "Information",
- "Warning"
- ],
- "scope": "resource",
- "type": "string",
- "markdownDeprecationMessage": "%python.linting.pylintCategorySeverity.warning.markdownDeprecationMessage%",
- "deprecationMessage": "%python.linting.pylintCategorySeverity.warning.deprecationMessage%"
- },
- "python.linting.pylintEnabled": {
- "default": false,
- "description": "%python.linting.pylintEnabled.description%",
- "scope": "resource",
- "type": "boolean",
- "markdownDeprecationMessage": "%python.linting.pylintEnabled.markdownDeprecationMessage%",
- "deprecationMessage": "%python.linting.pylintEnabled.deprecationMessage%"
- },
- "python.linting.pylintPath": {
- "default": "pylint",
- "description": "%python.linting.pylintPath.description%",
- "scope": "machine-overridable",
- "type": "string",
- "markdownDeprecationMessage": "%python.linting.pylintPath.markdownDeprecationMessage%",
- "deprecationMessage": "%python.linting.pylintPath.deprecationMessage%"
- },
"python.logging.level": {
"default": "error",
"deprecationMessage": "%python.logging.level.deprecation%",
@@ -1152,28 +652,13 @@
"scope": "machine-overridable",
"type": "string"
},
- "python.sortImports.args": {
- "default": [],
- "description": "%python.sortImports.args.description%",
- "items": {
- "type": "string"
- },
- "scope": "resource",
- "type": "array",
- "deprecationMessage": "%python.sortImports.args.deprecationMessage%"
- },
- "python.sortImports.path": {
- "default": "",
- "description": "%python.sortImports.path.description%",
- "scope": "machine-overridable",
- "type": "string",
- "deprecationMessage": "%python.sortImports.path.deprecationMessage%"
- },
"python.tensorBoard.logDirectory": {
"default": "",
"description": "%python.tensorBoard.logDirectory.description%",
"scope": "resource",
- "type": "string"
+ "type": "string",
+ "markdownDeprecationMessage": "%python.tensorBoard.logDirectory.markdownDeprecationMessage%",
+ "deprecationMessage": "%python.tensorBoard.logDirectory.deprecationMessage%"
},
"python.terminal.activateEnvInCurrentTerminal": {
"default": false,
@@ -1845,7 +1330,7 @@
"category": "Python",
"command": "python.launchTensorBoard",
"title": "%python.command.python.launchTensorBoard.title%",
- "when": "!virtualWorkspace && shellExecutionSupported"
+ "when": "!virtualWorkspace && shellExecutionSupported && !python.tensorboardExtInstalled"
},
{
"category": "Python",
@@ -1853,7 +1338,7 @@
"enablement": "python.hasActiveTensorBoardSession",
"icon": "$(refresh)",
"title": "%python.command.python.refreshTensorBoard.title%",
- "when": "!virtualWorkspace && shellExecutionSupported"
+ "when": "!virtualWorkspace && shellExecutionSupported && !python.tensorboardExtInstalled"
},
{
"category": "Python",
@@ -2089,7 +1574,6 @@
"@vscode/extension-telemetry": "^0.8.4",
"@vscode/jupyter-lsp-middleware": "^0.2.50",
"arch": "^2.1.0",
- "diff-match-patch": "^1.0.0",
"fs-extra": "^10.0.1",
"glob": "^7.2.0",
"hash.js": "^1.1.7",
@@ -2097,7 +1581,6 @@
"inversify": "^6.0.1",
"jsonc-parser": "^3.0.0",
"lodash": "^4.17.21",
- "md5": "^2.2.1",
"minimatch": "^5.0.1",
"named-js-regexp": "^1.3.3",
"node-stream-zip": "^1.6.0",
@@ -2113,12 +1596,10 @@
"uint64be": "^3.0.0",
"unicode": "^14.0.0",
"untildify": "^4.0.0",
- "vscode-debugadapter": "^1.28.0",
"vscode-debugprotocol": "^1.28.0",
- "vscode-jsonrpc": "8.0.2-next.1",
- "vscode-languageclient": "^8.1.0",
- "vscode-languageserver": "^8.1.0",
- "vscode-languageserver-protocol": "^3.17.3",
+ "vscode-jsonrpc": "^8.2.0",
+ "vscode-languageclient": "^9.0.1",
+ "vscode-languageserver-protocol": "^3.17.5",
"vscode-tas-client": "^0.1.63",
"which": "^2.0.2",
"winreg": "^1.2.4",
@@ -2130,21 +1611,17 @@
"@types/chai": "^4.1.2",
"@types/chai-arrays": "^2.0.0",
"@types/chai-as-promised": "^7.1.0",
- "@types/diff-match-patch": "^1.0.32",
"@types/download": "^8.0.1",
"@types/fs-extra": "^9.0.13",
"@types/glob": "^7.2.0",
"@types/lodash": "^4.14.104",
- "@types/md5": "^2.1.32",
"@types/mocha": "^9.1.0",
- "@types/nock": "^10.0.3",
"@types/node": "^18.17.1",
"@types/semver": "^5.5.0",
"@types/shortid": "^0.0.29",
"@types/sinon": "^10.0.11",
"@types/stack-trace": "0.0.29",
"@types/tmp": "^0.0.33",
- "@types/uuid": "^8.3.4",
"@types/vscode": "^1.81.0",
"@types/which": "^2.0.1",
"@types/winreg": "^1.2.30",
@@ -2161,7 +1638,6 @@
"cross-spawn": "^6.0.5",
"del": "^6.0.0",
"download": "^8.0.0",
- "es5-ext": "0.10.53",
"eslint": "^7.2.0",
"eslint-config-airbnb": "^18.2.0",
"eslint-config-prettier": "^8.3.0",
@@ -2177,7 +1653,7 @@
"mocha": "^9.2.2",
"mocha-junit-reporter": "^2.0.2",
"mocha-multi-reporters": "^1.1.7",
- "nock": "^10.0.6",
+
"node-has-native-dependencies": "^1.0.2",
"node-loader": "^1.0.2",
"node-polyfill-webpack-plugin": "^1.1.4",
@@ -2195,7 +1671,6 @@
"typemoq": "^2.1.0",
"typescript": "4.5.5",
"uuid": "^8.3.2",
- "vscode-debugadapter-testsupport": "^1.27.0",
"webpack": "^5.76.0",
"webpack-bundle-analyzer": "^4.5.0",
"webpack-cli": "^4.9.2",
diff --git a/extensions/positron-python/package.nls.json b/extensions/positron-python/package.nls.json
index 0160d70e452..65437b33cef 100644
--- a/extensions/positron-python/package.nls.json
+++ b/extensions/positron-python/package.nls.json
@@ -1,7 +1,6 @@
{
"displayName": "Python",
"description": "IntelliSense (Pylance), Linting, Debugging (multi-threaded, remote), Jupyter Notebooks, code formatting, refactoring, unit tests, and more.",
- "python.command.python.sortImports.title": "Sort Imports",
"python.command.python.startREPL.title": "Start REPL",
"python.command.python.createEnvironment.title": "Create Environment...",
"python.command.python.createNewFile.title": "New Python File",
@@ -45,27 +44,8 @@
"python.experiments.pythonPromptNewToolsExt.description": "Denotes the Python Prompt New Tools Extension experiment.",
"python.experiments.pythonTerminalEnvVarActivation.description": "Enables use of environment variables to activate terminals instead of sending activation commands.",
"python.experiments.pythonTestAdapter.description": "Denotes the Python Test Adapter experiment.",
- "python.formatting.autopep8Args.description": "Arguments passed in. Each argument is a separate item in the array.",
- "python.formatting.autopep8Args.markdownDeprecationMessage": "This setting will soon be deprecated. Please use the [Autopep8 extension](https://marketplace.visualstudio.com/items?itemName=ms-python.autopep8).
Learn more [here](https://aka.ms/AAlgvkb).",
- "python.formatting.autopep8Args.deprecationMessage": "This setting will soon be deprecated. Please use the Autopep8 extension. Learn more here: https://aka.ms/AAlgvkb.",
- "python.formatting.autopep8Path.description": "Path to autopep8, you can use a custom version of autopep8 by modifying this setting to include the full path.",
- "python.formatting.autopep8Path.markdownDeprecationMessage": "This setting will soon be deprecated. Please use the [Autopep8 extension](https://marketplace.visualstudio.com/items?itemName=ms-python.autopep8).
Learn more [here](https://aka.ms/AAlgvkb).",
- "python.formatting.autopep8Path.deprecationMessage": "This setting will soon be deprecated. Please use the Autopep8 extension. Learn more here: https://aka.ms/AAlgvkb.",
- "python.formatting.blackArgs.description": "Arguments passed in. Each argument is a separate item in the array.",
- "python.formatting.blackArgs.markdownDeprecationMessage": "This setting will soon be deprecated. Please use the [Black Formatter extension](https://marketplace.visualstudio.com/items?itemName=ms-python.black-formatter).
Learn more [here](https://aka.ms/AAlgvkb).",
- "python.formatting.blackArgs.deprecationMessage": "This setting will soon be deprecated. Please use the Black Formatter extension. Learn more here: https://aka.ms/AAlgvkb.",
- "python.formatting.blackPath.description": "Path to Black, you can use a custom version of Black by modifying this setting to include the full path.",
- "python.formatting.blackPath.markdownDeprecationMessage": "This setting will soon be deprecated. Please use the [Black Formatter extension](https://marketplace.visualstudio.com/items?itemName=ms-python.black-formatter).
Learn more [here](https://aka.ms/AAlgvkb).",
- "python.formatting.blackPath.deprecationMessage": "This setting will soon be deprecated. Please use the Black Formatter extension. Learn more here: https://aka.ms/AAlgvkb.",
- "python.formatting.provider.description": "Provider for formatting. Possible options include 'autopep8', 'black', and 'yapf'.",
- "python.formatting.provider.markdownDeprecationMessage": "This setting will soon be deprecated. Please use a dedicated formatter extension.
Learn more [here](https://aka.ms/AAlgvkb).",
- "python.formatting.provider.deprecationMessage": "This setting will soon be deprecated. Please use a dedicated formatter extension. Learn more here: https://aka.ms/AAlgvkb.",
- "python.formatting.yapfArgs.description": "Arguments passed in. Each argument is a separate item in the array.",
- "python.formatting.yapfArgs.markdownDeprecationMessage": "Built-in Yapf support will soon be deprecated. Learn more [here](https://aka.ms/AAlgvkb).",
- "python.formatting.yapfArgs.deprecationMessage": "Built-in Yapf support will soon be deprecated. Learn more here: https://aka.ms/AAlgvkb.",
- "python.formatting.yapfPath.description": "Path to yapf, you can use a custom version of yapf by modifying this setting to include the full path.",
- "python.formatting.yapfPath.markdownDeprecationMessage": "Yapf support will soon be deprecated.
Learn more [here](https://aka.ms/AAlgvkb).",
- "python.formatting.yapfPath.deprecationMessage": "Built-in Yapf support will soon be deprecated. Learn more here: https://aka.ms/AAlgvkb.",
+ "python.experiments.pythonREPLSmartSend.description": "Denotes the Python REPL Smart Send experiment.",
+ "python.experiments.pythonRecommendTensorboardExt.description": "Denotes the Tensorboard Extension recommendation experiment.",
"python.globalModuleInstallation.description": "Whether to install Python modules globally when not using an environment.",
"python.languageServerDebug.description": "Whether debug should be enabled for Positron's Python language server.",
"python.languageServerLogLevel.description": "Controls the [logging level](https://docs.python.org/3/library/logging.html#levels) of Positron's Python language server. Requires a restart to take effect.",
@@ -74,141 +54,18 @@
"python.languageServer.jediDescription": "Use Jedi behind the Language Server Protocol (LSP) as a language server.",
"python.languageServer.pylanceDescription": "Use Pylance as a language server.",
"python.languageServer.noneDescription": "Disable language server capabilities.",
- "python.linting.banditArgs.description": "Arguments passed in. Each argument is a separate item in the array.",
- "python.linting.banditArgs.markdownDeprecationMessage": "Bandit support will soon be deprecated. Learn more [here](https://aka.ms/AAlgvkb).",
- "python.linting.banditArgs.deprecationMessage": "Bandit support will soon be deprecated. Learn more here: https://aka.ms/AAlgvkb.",
- "python.linting.banditEnabled.description": "Whether to lint Python files using bandit.",
- "python.linting.banditEnabled.markdownDeprecationMessage": "Bandit support will soon be deprecated. Learn more [here](https://aka.ms/AAlgvkb).",
- "python.linting.banditEnabled.deprecationMessage": "Bandit support will soon be deprecated. Learn more here: https://aka.ms/AAlgvkb.",
- "python.linting.banditPath.description": "Path to bandit, you can use a custom version of bandit by modifying this setting to include the full path.",
- "python.linting.banditPath.markdownDeprecationMessage": "Bandit support will soon be deprecated. Learn more [here](https://aka.ms/AAlgvkb).",
- "python.linting.banditPath.deprecationMessage": "Bandit support will soon be deprecated. Learn more here: https://aka.ms/AAlgvkb.",
- "python.linting.cwd.description": "Optional working directory for linters.",
- "python.linting.cwd.markdownDeprecationMessage": "This setting will soon be deprecated. Learn more [here](https://aka.ms/AAlgvkb).",
- "python.linting.cwd.deprecationMessage": "This setting will soon be deprecated. Learn more here: https://aka.ms/AAlgvkb.",
- "python.linting.enabled.description": "Whether to lint Python files.",
- "python.linting.enabled.markdownDeprecationMessage": "This setting will soon be deprecated. Learn more [here](https://aka.ms/AAlgvkb).",
- "python.linting.enabled.deprecationMessage": "This setting will soon be deprecated. Learn more here: https://aka.ms/AAlgvkb.",
- "python.linting.flake8Args.description": "Arguments passed in. Each argument is a separate item in the array.",
- "python.linting.flake8Args.markdownDeprecationMessage": "This setting will soon be deprecated. Please use the [Flake8 extension](https://marketplace.visualstudio.com/items?itemName=ms-python.flake8).
Learn more [here](https://aka.ms/AAlgvkb).",
- "python.linting.flake8Args.deprecationMessage": "This setting will soon be deprecated. Please use the Flake8 extension. Learn more here: https://aka.ms/AAlgvkb.",
- "python.linting.flake8CategorySeverity.E.description": "Severity of Flake8 message type 'E'.",
- "python.linting.flake8CategorySeverity.E.markdownDeprecationMessage": "This setting will soon be deprecated. Please use the [Flake8 extension](https://marketplace.visualstudio.com/items?itemName=ms-python.flake8).
Learn more [here](https://aka.ms/AAlgvkb).",
- "python.linting.flake8CategorySeverity.E.deprecationMessage": "This setting will soon be deprecated. Please use the Flake8 extension. Learn more here: https://aka.ms/AAlgvkb.",
- "python.linting.flake8CategorySeverity.F.description": "Severity of Flake8 message type 'F'.",
- "python.linting.flake8CategorySeverity.F.markdownDeprecationMessage": "This setting will soon be deprecated. Please use the [Flake8 extension](https://marketplace.visualstudio.com/items?itemName=ms-python.flake8).
Learn more [here](https://aka.ms/AAlgvkb).",
- "python.linting.flake8CategorySeverity.F.deprecationMessage": "This setting will soon be deprecated. Please use the Flake8 extension. Learn more here: https://aka.ms/AAlgvkb.",
- "python.linting.flake8CategorySeverity.W.description": "Severity of Flake8 message type 'W'.",
- "python.linting.flake8CategorySeverity.W.markdownDeprecationMessage": "This setting will soon be deprecated. Please use the [Flake8 extension](https://marketplace.visualstudio.com/items?itemName=ms-python.flake8).
Learn more [here](https://aka.ms/AAlgvkb).",
- "python.linting.flake8CategorySeverity.W.deprecationMessage": "This setting will soon be deprecated. Please use the Flake8 extension. Learn more here: https://aka.ms/AAlgvkb.",
- "python.linting.flake8Enabled.description": "Whether to lint Python files using flake8.",
- "python.linting.flake8Enabled.markdownDeprecationMessage": "This setting will soon be deprecated. Please use the [Flake8 extension](https://marketplace.visualstudio.com/items?itemName=ms-python.flake8).
Learn more [here](https://aka.ms/AAlgvkb).",
- "python.linting.flake8Enabled.deprecationMessage": "This setting will soon be deprecated. Please use the Flake8 extension. Learn more here: https://aka.ms/AAlgvkb.",
- "python.linting.flake8Path.description": "Path to flake8, you can use a custom version of flake8 by modifying this setting to include the full path.",
- "python.linting.flake8Path.markdownDeprecationMessage": "This setting will soon be deprecated. Please use the [Flake8 extension](https://marketplace.visualstudio.com/items?itemName=ms-python.flake8).
Learn more [here](https://aka.ms/AAlgvkb).",
- "python.linting.flake8Path.deprecationMessage": "This setting will soon be deprecated. Please use the Flake8 extension. Learn more here: https://aka.ms/AAlgvkb.",
- "python.linting.ignorePatterns.description": "Patterns used to exclude files or folders from being linted.",
- "python.linting.ignorePatterns.markdownDeprecationMessage": "This setting will soon be deprecated. Learn more [here](https://aka.ms/AAlgvkb).",
- "python.linting.ignorePatterns.deprecationMessage": "This setting will soon be deprecated. Learn more here: https://aka.ms/AAlgvkb.",
"python.interpreter.infoVisibility.description": "Controls when to display information of selected interpreter in the status bar.",
"python.interpreter.infoVisibility.never.description": "Never display information.",
"python.interpreter.infoVisibility.onPythonRelated.description": "Only display information if Python-related files are opened.",
"python.interpreter.infoVisibility.always.description": "Always display information.",
- "python.linting.lintOnSave.description": "Whether to lint Python files when saved.",
- "python.linting.lintOnSave.markdownDeprecationMessage": "This setting will soon be deprecated. Learn more [here](https://aka.ms/AAlgvkb).",
- "python.linting.lintOnSave.deprecationMessage": "This setting will soon be deprecated. Learn more here: https://aka.ms/AAlgvkb.",
- "python.linting.maxNumberOfProblems.description": "Controls the maximum number of problems produced by the server.",
- "python.linting.maxNumberOfProblems.markdownDeprecationMessage": "This setting will soon be deprecated. Learn more [here](https://aka.ms/AAlgvkb).",
- "python.linting.maxNumberOfProblems.deprecationMessage": "This setting will soon be deprecated. Learn more here: https://aka.ms/AAlgvkb.",
- "python.linting.mypyArgs.description": "Arguments passed in. Each argument is a separate item in the array.",
- "python.linting.mypyArgs.markdownDeprecationMessage": "This setting will soon be deprecated. Please use the [Mypy Type Checker extension](https://marketplace.visualstudio.com/items?itemName=ms-python.mypy-type-checker).
Learn more [here](https://aka.ms/AAlgvkb).",
- "python.linting.mypyArgs.deprecationMessage": "This setting will soon be deprecated. Please use the Mypy Type Checker extension. Learn more here: https://aka.ms/AAlgvkb.",
- "python.linting.mypyCategorySeverity.error.description": "Severity of Mypy message type 'Error'.",
- "python.linting.mypyCategorySeverity.error.markdownDeprecationMessage": "This setting will soon be deprecated. Please use the [Mypy Type Checker extension](https://marketplace.visualstudio.com/items?itemName=ms-python.mypy-type-checker).
Learn more [here](https://aka.ms/AAlgvkb).",
- "python.linting.mypyCategorySeverity.error.deprecationMessage": "This setting will soon be deprecated. Please use the Mypy Type Checker extension. Learn more here: https://aka.ms/AAlgvkb.",
- "python.linting.mypyCategorySeverity.note.description": "Severity of Mypy message type 'Note'.",
- "python.linting.mypyCategorySeverity.note.markdownDeprecationMessage": "This setting will soon be deprecated. Please use the [Mypy Type Checker extension](https://marketplace.visualstudio.com/items?itemName=ms-python.mypy-type-checker).
Learn more [here](https://aka.ms/AAlgvkb).",
- "python.linting.mypyCategorySeverity.note.deprecationMessage": "This setting will soon be deprecated. Please use the Mypy Type Checker extension. Learn more here: https://aka.ms/AAlgvkb.",
- "python.linting.mypyEnabled.description": "Whether to lint Python files using mypy.",
- "python.linting.mypyEnabled.markdownDeprecationMessage": "This setting will soon be deprecated. Please use the [Mypy Type Checker extension](https://marketplace.visualstudio.com/items?itemName=ms-python.mypy-type-checker).
Learn more [here](https://aka.ms/AAlgvkb).",
- "python.linting.mypyEnabled.deprecationMessage": "This setting will soon be deprecated. Please use the Mypy Type Checker extension. Learn more here: https://aka.ms/AAlgvkb.",
- "python.linting.mypyPath.description": "Path to mypy, you can use a custom version of mypy by modifying this setting to include the full path.",
- "python.linting.mypyPath.markdownDeprecationMessage": "This setting will soon be deprecated. Please use the [Mypy Type Checker extension](https://marketplace.visualstudio.com/items?itemName=ms-python.mypy-type-checker).
Learn more [here](https://aka.ms/AAlgvkb).",
- "python.linting.mypyPath.deprecationMessage": "This setting will soon be deprecated. Please use the Mypy Type Checker extension. Learn more here: https://aka.ms/AAlgvkb.",
- "python.linting.prospectorArgs.description": "Arguments passed in. Each argument is a separate item in the array.",
- "python.linting.prospectorArgs.markdownDeprecationMessage": "Prospector support will soon be deprecated. Learn more [here](https://aka.ms/AAlgvkb).",
- "python.linting.prospectorArgs.deprecationMessage": "Prospector support will soon be deprecated. Learn more here: https://aka.ms/AAlgvkb.",
- "python.linting.prospectorEnabled.description": "Whether to lint Python files using prospector.",
- "python.linting.prospectorEnabled.markdownDeprecationMessage": "Prospector support will soon be deprecated. Learn more [here](https://aka.ms/AAlgvkb).",
- "python.linting.prospectorEnabled.deprecationMessage": "Prospector support will soon be deprecated. Learn more here: https://aka.ms/AAlgvkb.",
- "python.linting.prospectorPath.description": "Path to Prospector, you can use a custom version of prospector by modifying this setting to include the full path.",
- "python.linting.prospectorPath.markdownDeprecationMessage": "Prospector support will soon be deprecated. Learn more [here](https://aka.ms/AAlgvkb).",
- "python.linting.prospectorPath.deprecationMessage": "Prospector support will soon be deprecated. Learn more here: https://aka.ms/AAlgvkb.",
- "python.linting.pycodestyleArgs.description": "Arguments passed in. Each argument is a separate item in the array.",
- "python.linting.pycodestyleArgs.markdownDeprecationMessage": "Pycodestyle support will soon be deprecated. Learn more [here](https://aka.ms/AAlgvkb).",
- "python.linting.pycodestyleArgs.deprecationMessage": "Pycodestyle support will soon be deprecated. Learn more here: https://aka.ms/AAlgvkb.",
- "python.linting.pycodestyleCategorySeverity.E.description": "Severity of pycodestyle message type 'E'.",
- "python.linting.pycodestyleCategorySeverity.E.markdownDeprecationMessage": "Pycodestyle support will soon be deprecated. Learn more [here](https://aka.ms/AAlgvkb).",
- "python.linting.pycodestyleCategorySeverity.E.deprecationMessage": "Pycodestyle support will soon be deprecated. Learn more here: https://aka.ms/AAlgvkb.",
- "python.linting.pycodestyleCategorySeverity.W.description": "Severity of pycodestyle message type 'W'.",
- "python.linting.pycodestyleCategorySeverity.W.markdownDeprecationMessage": "Pycodestyle support will soon be deprecated. Learn more [here](https://aka.ms/AAlgvkb).",
- "python.linting.pycodestyleCategorySeverity.W.deprecationMessage": "Pycodestyle support will soon be deprecated. Learn more here: https://aka.ms/AAlgvkb.",
- "python.linting.pycodestyleEnabled.description": "Whether to lint Python files using pycodestyle.",
- "python.linting.pycodestyleEnabled.markdownDeprecationMessage": "Pycodestyle support will soon be deprecated. Learn more [here](https://aka.ms/AAlgvkb).",
- "python.linting.pycodestyleEnabled.deprecationMessage": "Pycodestyle support will soon be deprecated. Learn more here: https://aka.ms/AAlgvkb.",
- "python.linting.pycodestylePath.description": "Path to pycodestyle, you can use a custom version of pycodestyle by modifying this setting to include the full path.",
- "python.linting.pycodestylePath.markdownDeprecationMessage": "Pycodestyle support will soon be deprecated. Learn more [here](https://aka.ms/AAlgvkb).",
- "python.linting.pycodestylePath.deprecationMessage": "Pycodestyle support will soon be deprecated. Learn more here: https://aka.ms/AAlgvkb.",
- "python.linting.pydocstyleArgs.description": "Arguments passed in. Each argument is a separate item in the array.",
- "python.linting.pydocstyleArgs.markdownDeprecationMessage": "Pydocstyle support will soon be deprecated. Learn more [here](https://aka.ms/AAlgvkb).",
- "python.linting.pydocstyleArgs.deprecationMessage": "Pydocstyle support will soon be deprecated. Learn more here: https://aka.ms/AAlgvkb.",
- "python.linting.pydocstyleEnabled.description": "Whether to lint Python files using pydocstyle.",
- "python.linting.pydocstyleEnabled.markdownDeprecationMessage": "Pydocstyle support will soon be deprecated. Learn more [here](https://aka.ms/AAlgvkb).",
- "python.linting.pydocstyleEnabled.deprecationMessage": "Pydocstyle support will soon be deprecated. Learn more here: https://aka.ms/AAlgvkb.",
- "python.linting.pydocstylePath.description": "Path to pydocstyle, you can use a custom version of pydocstyle by modifying this setting to include the full path.",
- "python.linting.pydocstylePath.markdownDeprecationMessage": "Pydocstyle support will soon be deprecated. Learn more [here](https://aka.ms/AAlgvkb).",
- "python.linting.pydocstylePath.deprecationMessage": "Pydocstyle support will soon be deprecated. Learn more here: https://aka.ms/AAlgvkb.",
- "python.linting.pylamaArgs.description": "Arguments passed in. Each argument is a separate item in the array.",
- "python.linting.pylamaArgs.markdownDeprecationMessage": "Pylama support will soon be deprecated. Learn more [here](https://aka.ms/AAlgvkb).",
- "python.linting.pylamaArgs.deprecationMessage": "Pylama support will soon be deprecated. Learn more here: https://aka.ms/AAlgvkb.",
- "python.linting.pylamaEnabled.description": "Whether to lint Python files using pylama.",
- "python.linting.pylamaEnabled.markdownDeprecationMessage": "Pylama support will soon be deprecated. Learn more [here](https://aka.ms/AAlgvkb).",
- "python.linting.pylamaEnabled.deprecationMessage": "Pylama support will soon be deprecated. Learn more here: https://aka.ms/AAlgvkb.",
- "python.linting.pylamaPath.description": "Path to pylama, you can use a custom version of pylama by modifying this setting to include the full path.",
- "python.linting.pylamaPath.markdownDeprecationMessage": "Pylama support will soon be deprecated. Learn more [here](https://aka.ms/AAlgvkb).",
- "python.linting.pylamaPath.deprecationMessage": "Pylama support will soon be deprecated. Learn more here: https://aka.ms/AAlgvkb.",
- "python.linting.pylintArgs.description": "Arguments passed in. Each argument is a separate item in the array.",
- "python.linting.pylintArgs.markdownDeprecationMessage": "This setting will soon be deprecated. Please use the [Pylint extension](https://marketplace.visualstudio.com/items?itemName=ms-python.pylint).
Learn more [here](https://aka.ms/AAlgvkb).",
- "python.linting.pylintArgs.deprecationMessage": "This setting will soon be deprecated. Please use the Pylint extension. Learn more here: https://aka.ms/AAlgvkb.",
- "python.linting.pylintCategorySeverity.convention.description": "Severity of Pylint message type 'Convention/C'.",
- "python.linting.pylintCategorySeverity.convention.markdownDeprecationMessage": "This setting will soon be deprecated. Please use the [Pylint extension](https://marketplace.visualstudio.com/items?itemName=ms-python.pylint).
Learn more [here](https://aka.ms/AAlgvkb).",
- "python.linting.pylintCategorySeverity.convention.deprecationMessage": "This setting will soon be deprecated. Please use the Pylint extension. Learn more here: https://aka.ms/AAlgvkb.",
- "python.linting.pylintCategorySeverity.error.description": "Severity of Pylint message type 'Error/E'.",
- "python.linting.pylintCategorySeverity.error.markdownDeprecationMessage": "This setting will soon be deprecated. Please use the [Pylint extension](https://marketplace.visualstudio.com/items?itemName=ms-python.pylint).
Learn more [here](https://aka.ms/AAlgvkb).",
- "python.linting.pylintCategorySeverity.error.deprecationMessage": "This setting will soon be deprecated. Please use the Pylint extension. Learn more here: https://aka.ms/AAlgvkb.",
- "python.linting.pylintCategorySeverity.fatal.description": "Severity of Pylint message type 'Error/F'.",
- "python.linting.pylintCategorySeverity.fatal.markdownDeprecationMessage": "This setting will soon be deprecated. Please use the [Pylint extension](https://marketplace.visualstudio.com/items?itemName=ms-python.pylint).
Learn more [here](https://aka.ms/AAlgvkb).",
- "python.linting.pylintCategorySeverity.fatal.deprecationMessage": "This setting will soon be deprecated. Please use the Pylint extension. Learn more here: https://aka.ms/AAlgvkb.",
- "python.linting.pylintCategorySeverity.refactor.description": "Severity of Pylint message type 'Refactor/R'.",
- "python.linting.pylintCategorySeverity.refactor.markdownDeprecationMessage": "This setting will soon be deprecated. Please use the [Pylint extension](https://marketplace.visualstudio.com/items?itemName=ms-python.pylint).
Learn more [here](https://aka.ms/AAlgvkb).",
- "python.linting.pylintCategorySeverity.refactor.deprecationMessage": "This setting will soon be deprecated. Please use the Pylint extension. Learn more here: https://aka.ms/AAlgvkb.",
- "python.linting.pylintCategorySeverity.warning.description": "Severity of Pylint message type 'Warning/W'.",
- "python.linting.pylintCategorySeverity.warning.markdownDeprecationMessage": "This setting will soon be deprecated. Please use the [Pylint extension](https://marketplace.visualstudio.com/items?itemName=ms-python.pylint).
Learn more [here](https://aka.ms/AAlgvkb).",
- "python.linting.pylintCategorySeverity.warning.deprecationMessage": "This setting will soon be deprecated. Please use the Pylint extension. Learn more here: https://aka.ms/AAlgvkb.",
- "python.linting.pylintEnabled.description": "Whether to lint Python files using pylint.",
- "python.linting.pylintEnabled.markdownDeprecationMessage": "This setting will soon be deprecated. Please use the [Pylint extension](https://marketplace.visualstudio.com/items?itemName=ms-python.pylint).
Learn more [here](https://aka.ms/AAlgvkb).",
- "python.linting.pylintEnabled.deprecationMessage": "This setting will soon be deprecated. Please use the Pylint extension. Learn more here: https://aka.ms/AAlgvkb.",
- "python.linting.pylintPath.description": "Path to Pylint, you can use a custom version of pylint by modifying this setting to include the full path.",
- "python.linting.pylintPath.markdownDeprecationMessage": "This setting will soon be deprecated. Please use the [Pylint extension](https://marketplace.visualstudio.com/items?itemName=ms-python.pylint).
Learn more [here](https://aka.ms/AAlgvkb).",
- "python.linting.pylintPath.deprecationMessage": "This setting will soon be deprecated. Please use the Pylint extension. Learn more here: https://aka.ms/AAlgvkb.",
"python.logging.level.description": "The logging level the extension logs at, defaults to 'error'",
"python.logging.level.deprecation": "This setting is deprecated. Please use command `Developer: Set Log Level...` to set logging level.",
"python.missingPackage.severity.description": "Set severity of missing packages in requirements.txt or pyproject.toml",
"python.pipenvPath.description": "Path to the pipenv executable to use for activation.",
"python.poetryPath.description": "Path to the poetry executable.",
- "python.sortImports.args.description": "Arguments passed in. Each argument is a separate item in the array.",
- "python.sortImports.path.description": "Path to isort script, default using inner version",
"python.tensorBoard.logDirectory.description": "Set this setting to your preferred TensorBoard log directory to skip log directory prompt when starting TensorBoard.",
+ "python.tensorBoard.logDirectory.markdownDeprecationMessage": "Tensorboard support has been moved to the extension [Tensorboard extension](https://marketplace.visualstudio.com/items?itemName=ms-toolsai.tensorboard). Instead use the setting `tensorBoard.logDirectory`.",
+ "python.tensorBoard.logDirectory.deprecationMessage": "Tensorboard support has been moved to the extension Tensorboard extension. Instead use the setting `tensorBoard.logDirectory`.",
"python.terminal.activateEnvInCurrentTerminal.description": "Activate Python Environment in the current Terminal on load of the Extension.",
"python.terminal.activateEnvironment.description": "Activate Python Environment in all Terminals created.",
"python.terminal.executeInFileDir.description": "When executing a file in the terminal, whether to use execute in the file's directory, instead of the current open folder.",
@@ -225,8 +82,6 @@
"python.testing.unittestEnabled.description": "Enable testing using unittest.",
"python.venvFolders.description": "Folders in your home directory to look into for virtual environments (supports pyenv, direnv and virtualenvwrapper by default).",
"python.venvPath.description": "Path to folder with a list of Virtual Environments (e.g. ~/.pyenv, ~/Envs, ~/.virtualenvs).",
- "python.sortImports.args.deprecationMessage": "This setting will be removed soon. Use 'isort.args' instead.",
- "python.sortImports.path.deprecationMessage": "This setting will be removed soon. Use 'isort.path' instead.",
"walkthrough.pythonWelcome.title": "Get Started with Python Development",
"walkthrough.pythonWelcome.description": "Your first steps to set up a Python project with all the powerful tools and features that the Python extension has to offer!",
"walkthrough.step.python.createPythonFile.title": "Create a Python file",
diff --git a/extensions/positron-python/pythonFiles/deactivate b/extensions/positron-python/pythonFiles/deactivate
new file mode 100644
index 00000000000..6ede3da311a
--- /dev/null
+++ b/extensions/positron-python/pythonFiles/deactivate
@@ -0,0 +1,33 @@
+# Same as deactivate in "/bin/activate"
+deactivate () {
+ if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then
+ PATH="${_OLD_VIRTUAL_PATH:-}"
+ export PATH
+ unset _OLD_VIRTUAL_PATH
+ fi
+ if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then
+ PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}"
+ export PYTHONHOME
+ unset _OLD_VIRTUAL_PYTHONHOME
+ fi
+ if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then
+ hash -r 2> /dev/null
+ fi
+ if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then
+ PS1="${_OLD_VIRTUAL_PS1:-}"
+ export PS1
+ unset _OLD_VIRTUAL_PS1
+ fi
+ unset VIRTUAL_ENV
+ unset VIRTUAL_ENV_PROMPT
+ if [ ! "${1:-}" = "nondestructive" ] ; then
+ unset -f deactivate
+ fi
+}
+
+# Initialize the variables required by deactivate function
+_OLD_VIRTUAL_PS1="${PS1:-}"
+_OLD_VIRTUAL_PATH="$PATH"
+if [ -n "${PYTHONHOME:-}" ] ; then
+ _OLD_VIRTUAL_PYTHONHOME="${PYTHONHOME:-}"
+fi
diff --git a/extensions/positron-python/pythonFiles/deactivate.csh b/extensions/positron-python/pythonFiles/deactivate.csh
new file mode 100644
index 00000000000..ef4d0d39389
--- /dev/null
+++ b/extensions/positron-python/pythonFiles/deactivate.csh
@@ -0,0 +1,6 @@
+# Same as deactivate in "/bin/activate.csh"
+alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PATH" && unset _OLD_VIRTUAL_PATH; rehash; test $?_OLD_VIRTUAL_PROMPT != 0 && set prompt="$_OLD_VIRTUAL_PROMPT" && unset _OLD_VIRTUAL_PROMPT; unsetenv VIRTUAL_ENV; unsetenv VIRTUAL_ENV_PROMPT; test "\!:*" != "nondestructive" && unalias deactivate'
+
+# Initialize the variables required by deactivate function
+set _OLD_VIRTUAL_PROMPT="$prompt"
+set _OLD_VIRTUAL_PATH="$PATH"
diff --git a/extensions/positron-python/pythonFiles/deactivate.fish b/extensions/positron-python/pythonFiles/deactivate.fish
new file mode 100644
index 00000000000..c652a8c1e3d
--- /dev/null
+++ b/extensions/positron-python/pythonFiles/deactivate.fish
@@ -0,0 +1,36 @@
+# Same as deactivate in "/bin/activate.fish"
+function deactivate -d "Exit virtual environment and return to normal shell environment"
+ # reset old environment variables
+ if test -n "$_OLD_VIRTUAL_PATH"
+ set -gx PATH $_OLD_VIRTUAL_PATH
+ set -e _OLD_VIRTUAL_PATH
+ end
+ if test -n "$_OLD_VIRTUAL_PYTHONHOME"
+ set -gx PYTHONHOME $_OLD_VIRTUAL_PYTHONHOME
+ set -e _OLD_VIRTUAL_PYTHONHOME
+ end
+
+ if test -n "$vscode_python_old_fish_prompt_OVERRIDE"
+ set -e vscode_python_old_fish_prompt_OVERRIDE
+ if functions -q vscode_python_old_fish_prompt
+ functions -e fish_prompt
+ functions -c vscode_python_old_fish_prompt fish_prompt
+ functions -e vscode_python_old_fish_prompt
+ end
+ end
+
+ set -e VIRTUAL_ENV
+ set -e VIRTUAL_ENV_PROMPT
+ if test "$argv[1]" != "nondestructive"
+ functions -e deactivate
+ end
+end
+
+# Initialize the variables required by deactivate function
+set -gx _OLD_VIRTUAL_PATH $PATH
+if test -z "$VIRTUAL_ENV_DISABLE_PROMPT"
+ functions -c fish_prompt vscode_python_old_fish_prompt
+end
+if set -q PYTHONHOME
+ set -gx _OLD_VIRTUAL_PYTHONHOME $PYTHONHOME
+end
diff --git a/extensions/positron-python/pythonFiles/deactivate.ps1 b/extensions/positron-python/pythonFiles/deactivate.ps1
new file mode 100644
index 00000000000..65dd80907d9
--- /dev/null
+++ b/extensions/positron-python/pythonFiles/deactivate.ps1
@@ -0,0 +1,31 @@
+# Same as deactivate in "Activate.ps1"
+function global:deactivate ([switch]$NonDestructive) {
+ if (Test-Path function:_OLD_VIRTUAL_PROMPT) {
+ copy-item function:_OLD_VIRTUAL_PROMPT function:prompt
+ remove-item function:_OLD_VIRTUAL_PROMPT
+ }
+ if (Test-Path env:_OLD_VIRTUAL_PYTHONHOME) {
+ copy-item env:_OLD_VIRTUAL_PYTHONHOME env:PYTHONHOME
+ remove-item env:_OLD_VIRTUAL_PYTHONHOME
+ }
+ if (Test-Path env:_OLD_VIRTUAL_PATH) {
+ copy-item env:_OLD_VIRTUAL_PATH env:PATH
+ remove-item env:_OLD_VIRTUAL_PATH
+ }
+ if (Test-Path env:VIRTUAL_ENV) {
+ remove-item env:VIRTUAL_ENV
+ }
+ if (!$NonDestructive) {
+ remove-item function:deactivate
+ }
+}
+
+# Initialize the variables required by deactivate function
+if (! $env:VIRTUAL_ENV_DISABLE_PROMPT) {
+ function global:_OLD_VIRTUAL_PROMPT {""}
+ copy-item function:prompt function:_OLD_VIRTUAL_PROMPT
+}
+if (Test-Path env:PYTHONHOME) {
+ copy-item env:PYTHONHOME env:_OLD_VIRTUAL_PYTHONHOME
+}
+copy-item env:PATH env:_OLD_VIRTUAL_PATH
diff --git a/extensions/positron-python/pythonFiles/normalizeSelection.py b/extensions/positron-python/pythonFiles/normalizeSelection.py
index 0363702717a..7ace42daa90 100644
--- a/extensions/positron-python/pythonFiles/normalizeSelection.py
+++ b/extensions/positron-python/pythonFiles/normalizeSelection.py
@@ -6,6 +6,7 @@
import re
import sys
import textwrap
+from typing import Iterable
def split_lines(source):
@@ -118,6 +119,8 @@ def normalize_lines(selection):
# Insert a newline between each top-level statement, and append a newline to the selection.
source = "\n".join(statements) + "\n"
+ if selection[-2] == "}" or selection[-2] == "]":
+ source = source[:-1]
except Exception:
# If there's a problem when parsing statements,
# append a blank line to end the block and send it as-is.
@@ -126,17 +129,159 @@ def normalize_lines(selection):
return source
+top_level_nodes = []
+min_key = None
+
+
+def check_exact_exist(top_level_nodes, start_line, end_line):
+ exact_nodes = []
+ for node in top_level_nodes:
+ if node.lineno == start_line and node.end_lineno == end_line:
+ exact_nodes.append(node)
+
+ return exact_nodes
+
+
+def traverse_file(wholeFileContent, start_line, end_line, was_highlighted):
+ """
+ Intended to traverse through a user's given file content and find, collect all appropriate lines
+ that should be sent to the REPL in case of smart selection.
+ This could be exact statement such as just a single line print statement,
+ or a multiline dictionary, or differently styled multi-line list comprehension, etc.
+ Then call the normalize_lines function to normalize our smartly selected code block.
+ """
+
+ parsed_file_content = ast.parse(wholeFileContent)
+ smart_code = ""
+ should_run_top_blocks = []
+
+ # Purpose of this loop is to fetch and collect all the
+ # AST top level nodes, and its node.body as child nodes.
+ # Individual nodes will contain information like
+ # the start line, end line and get source segment information
+ # that will be used to smartly select, and send normalized code.
+ for node in ast.iter_child_nodes(parsed_file_content):
+ top_level_nodes.append(node)
+
+ ast_types_with_nodebody = (
+ ast.Module,
+ ast.Interactive,
+ ast.Expression,
+ ast.FunctionDef,
+ ast.AsyncFunctionDef,
+ ast.ClassDef,
+ ast.For,
+ ast.AsyncFor,
+ ast.While,
+ ast.If,
+ ast.With,
+ ast.AsyncWith,
+ ast.Try,
+ ast.Lambda,
+ ast.IfExp,
+ ast.ExceptHandler,
+ )
+ if isinstance(node, ast_types_with_nodebody) and isinstance(
+ node.body, Iterable
+ ):
+ for child_nodes in node.body:
+ top_level_nodes.append(child_nodes)
+
+ exact_nodes = check_exact_exist(top_level_nodes, start_line, end_line)
+
+ # Just return the exact top level line, if present.
+ if len(exact_nodes) > 0:
+ which_line_next = 0
+ for same_line_node in exact_nodes:
+ should_run_top_blocks.append(same_line_node)
+ smart_code += (
+ f"{ast.get_source_segment(wholeFileContent, same_line_node)}\n"
+ )
+ which_line_next = get_next_block_lineno(should_run_top_blocks)
+ return {
+ "normalized_smart_result": smart_code,
+ "which_line_next": which_line_next,
+ }
+
+ # For each of the nodes in the parsed file content,
+ # add the appropriate source code line(s) to be sent to the REPL, dependent on
+ # user is trying to send and execute single line/statement or multiple with smart selection.
+ for top_node in ast.iter_child_nodes(parsed_file_content):
+ if start_line == top_node.lineno and end_line == top_node.end_lineno:
+ should_run_top_blocks.append(top_node)
+
+ smart_code += f"{ast.get_source_segment(wholeFileContent, top_node)}\n"
+ break # If we found exact match, don't waste computation in parsing extra nodes.
+ elif start_line >= top_node.lineno and end_line <= top_node.end_lineno:
+ # Case to apply smart selection for multiple line.
+ # This is the case for when we have to add multiple lines that should be included in the smart send.
+ # For example:
+ # 'my_dictionary': {
+ # 'Audi': 'Germany',
+ # 'BMW': 'Germany',
+ # 'Genesis': 'Korea',
+ # }
+ # with the mouse cursor at 'BMW': 'Germany', should send all of the lines that pertains to my_dictionary.
+
+ should_run_top_blocks.append(top_node)
+
+ smart_code += str(ast.get_source_segment(wholeFileContent, top_node))
+ smart_code += "\n"
+
+ normalized_smart_result = normalize_lines(smart_code)
+ which_line_next = get_next_block_lineno(should_run_top_blocks)
+ return {
+ "normalized_smart_result": normalized_smart_result,
+ "which_line_next": which_line_next,
+ }
+
+
+# Look at the last top block added, find lineno for the next upcoming block,
+# This will be used in calculating lineOffset to move cursor in VS Code.
+def get_next_block_lineno(which_line_next):
+ last_ran_lineno = int(which_line_next[-1].end_lineno)
+ next_lineno = int(which_line_next[-1].end_lineno)
+
+ for reverse_node in top_level_nodes:
+ if reverse_node.lineno > last_ran_lineno:
+ next_lineno = reverse_node.lineno
+ break
+ return next_lineno
+
+
if __name__ == "__main__":
# Content is being sent from the extension as a JSON object.
# Decode the data from the raw bytes.
stdin = sys.stdin if sys.version_info < (3,) else sys.stdin.buffer
raw = stdin.read()
contents = json.loads(raw.decode("utf-8"))
+ # Empty highlight means user has not explicitly selected specific text.
+ empty_Highlight = contents.get("emptyHighlight", False)
- normalized = normalize_lines(contents["code"])
+ # We also get the activeEditor selection start line and end line from the typescript VS Code side.
+ # Remember to add 1 to each of the received since vscode starts line counting from 0 .
+ vscode_start_line = contents["startLine"] + 1
+ vscode_end_line = contents["endLine"] + 1
# Send the normalized code back to the extension in a JSON object.
- data = json.dumps({"normalized": normalized})
+ data = None
+ which_line_next = 0
+
+ if empty_Highlight and contents.get("smartSendExperimentEnabled"):
+ result = traverse_file(
+ contents["wholeFileContent"],
+ vscode_start_line,
+ vscode_end_line,
+ not empty_Highlight,
+ )
+ normalized = result["normalized_smart_result"]
+ which_line_next = result["which_line_next"]
+ data = json.dumps(
+ {"normalized": normalized, "nextBlockLineno": result["which_line_next"]}
+ )
+ else:
+ normalized = normalize_lines(contents["code"])
+ data = json.dumps({"normalized": normalized})
stdout = sys.stdout if sys.version_info < (3,) else sys.stdout.buffer
stdout.write(data.encode("utf-8"))
diff --git a/extensions/positron-python/pythonFiles/tests/debug_adapter/test_install_debugpy.py b/extensions/positron-python/pythonFiles/tests/debug_adapter/test_install_debugpy.py
index 19565c19675..8e2ed33a1da 100644
--- a/extensions/positron-python/pythonFiles/tests/debug_adapter/test_install_debugpy.py
+++ b/extensions/positron-python/pythonFiles/tests/debug_adapter/test_install_debugpy.py
@@ -9,7 +9,6 @@ def _check_binaries(dir_path):
"win_amd64.pyd",
"win32.pyd",
"darwin.so",
- "i386-linux-gnu.so",
"x86_64-linux-gnu.so",
)
@@ -18,10 +17,6 @@ def _check_binaries(dir_path):
assert len(binaries) == len(expected_endswith)
-@pytest.mark.skipif(
- sys.version_info[:2] != (3, 7),
- reason="DEBUGPY wheels shipped for Python 3.7 only",
-)
def test_install_debugpy(tmpdir):
import install_debugpy
diff --git a/extensions/positron-python/pythonFiles/tests/pytestadapter/.data/test_env_vars.py b/extensions/positron-python/pythonFiles/tests/pytestadapter/.data/test_env_vars.py
new file mode 100644
index 00000000000..c8a3add5676
--- /dev/null
+++ b/extensions/positron-python/pythonFiles/tests/pytestadapter/.data/test_env_vars.py
@@ -0,0 +1,32 @@
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License.
+
+import os
+
+
+def test_clear_env(monkeypatch):
+ # Clear all environment variables
+ monkeypatch.setattr(os, "environ", {})
+
+ # Now os.environ should be empty
+ assert not os.environ
+
+ # After the test finishes, the environment variables will be reset to their original state
+
+
+def test_check_env():
+ # This test will have access to the original environment variables
+ assert "PATH" in os.environ
+
+
+def test_clear_env_unsafe():
+ # Clear all environment variables
+ os.environ.clear()
+ # Now os.environ should be empty
+ assert not os.environ
+
+
+def test_check_env_unsafe():
+ # ("PATH" in os.environ) is False here if it runs after test_clear_env_unsafe.
+ # Regardless, this test will pass and TEST_PORT and TEST_UUID will still be set correctly
+ assert "PATH" not in os.environ
diff --git a/extensions/positron-python/pythonFiles/tests/pytestadapter/.data/test_logging.py b/extensions/positron-python/pythonFiles/tests/pytestadapter/.data/test_logging.py
new file mode 100644
index 00000000000..058ad807571
--- /dev/null
+++ b/extensions/positron-python/pythonFiles/tests/pytestadapter/.data/test_logging.py
@@ -0,0 +1,35 @@
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License.
+import logging
+import sys
+
+
+def test_logging2(caplog):
+ logger = logging.getLogger(__name__)
+ caplog.set_level(logging.DEBUG) # Set minimum log level to capture
+
+ logger.debug("This is a debug message.")
+ logger.info("This is an info message.")
+ logger.warning("This is a warning message.")
+ logger.error("This is an error message.")
+ logger.critical("This is a critical message.")
+
+ # Printing to stdout and stderr
+ print("This is a stdout message.")
+ print("This is a stderr message.", file=sys.stderr)
+ assert False
+
+
+def test_logging(caplog):
+ logger = logging.getLogger(__name__)
+ caplog.set_level(logging.DEBUG) # Set minimum log level to capture
+
+ logger.debug("This is a debug message.")
+ logger.info("This is an info message.")
+ logger.warning("This is a warning message.")
+ logger.error("This is an error message.")
+ logger.critical("This is a critical message.")
+
+ # Printing to stdout and stderr
+ print("This is a stdout message.")
+ print("This is a stderr message.", file=sys.stderr)
diff --git a/extensions/positron-python/pythonFiles/tests/pytestadapter/expected_execution_test_output.py b/extensions/positron-python/pythonFiles/tests/pytestadapter/expected_execution_test_output.py
index 76d21b3e251..44f3d3d0abc 100644
--- a/extensions/positron-python/pythonFiles/tests/pytestadapter/expected_execution_test_output.py
+++ b/extensions/positron-python/pythonFiles/tests/pytestadapter/expected_execution_test_output.py
@@ -596,3 +596,91 @@
"subtest": None,
}
}
+
+
+# This is the expected output for the test logging file.
+# └── test_logging.py
+# └── test_logging2: failure
+# └── test_logging: success
+test_logging_path = TEST_DATA_PATH / "test_logging.py"
+
+logging_test_expected_execution_output = {
+ get_absolute_test_id("test_logging.py::test_logging2", test_logging_path): {
+ "test": get_absolute_test_id(
+ "test_logging.py::test_logging2", test_logging_path
+ ),
+ "outcome": "failure",
+ "message": "ERROR MESSAGE",
+ "traceback": None,
+ "subtest": None,
+ },
+ get_absolute_test_id("test_logging.py::test_logging", test_logging_path): {
+ "test": get_absolute_test_id(
+ "test_logging.py::test_logging", test_logging_path
+ ),
+ "outcome": "success",
+ "message": None,
+ "traceback": None,
+ "subtest": None,
+ },
+}
+
+# This is the expected output for the test safe clear env vars file.
+# └── test_env_vars.py
+# └── test_clear_env: success
+# └── test_check_env: success
+
+test_safe_clear_env_vars_path = TEST_DATA_PATH / "test_env_vars.py"
+safe_clear_env_vars_expected_execution_output = {
+ get_absolute_test_id(
+ "test_env_vars.py::test_clear_env", test_safe_clear_env_vars_path
+ ): {
+ "test": get_absolute_test_id(
+ "test_env_vars.py::test_clear_env", test_safe_clear_env_vars_path
+ ),
+ "outcome": "success",
+ "message": None,
+ "traceback": None,
+ "subtest": None,
+ },
+ get_absolute_test_id(
+ "test_env_vars.py::test_check_env", test_safe_clear_env_vars_path
+ ): {
+ "test": get_absolute_test_id(
+ "test_env_vars.py::test_check_env", test_safe_clear_env_vars_path
+ ),
+ "outcome": "success",
+ "message": None,
+ "traceback": None,
+ "subtest": None,
+ },
+}
+
+# This is the expected output for the test unsafe clear env vars file.
+# └── test_env_vars.py
+# └── test_clear_env_unsafe: success
+# └── test_check_env_unsafe: success
+unsafe_clear_env_vars_expected_execution_output = {
+ get_absolute_test_id(
+ "test_env_vars.py::test_clear_env_unsafe", test_safe_clear_env_vars_path
+ ): {
+ "test": get_absolute_test_id(
+ "test_env_vars.py::test_clear_env_unsafe", test_safe_clear_env_vars_path
+ ),
+ "outcome": "success",
+ "message": None,
+ "traceback": None,
+ "subtest": None,
+ },
+ get_absolute_test_id(
+ "test_env_vars.py::test_check_env_unsafe", test_safe_clear_env_vars_path
+ ): {
+ "test": get_absolute_test_id(
+ "test_env_vars.py::test_check_env_unsafe", test_safe_clear_env_vars_path
+ ),
+ "outcome": "success",
+ "message": None,
+ "traceback": None,
+ "subtest": None,
+ },
+}
diff --git a/extensions/positron-python/pythonFiles/tests/pytestadapter/helpers.py b/extensions/positron-python/pythonFiles/tests/pytestadapter/helpers.py
index b534e950945..2d36da59956 100644
--- a/extensions/positron-python/pythonFiles/tests/pytestadapter/helpers.py
+++ b/extensions/positron-python/pythonFiles/tests/pytestadapter/helpers.py
@@ -129,6 +129,7 @@ def runner_with_cwd(
"pytest",
"-p",
"vscode_pytest",
+ "-s",
] + args
listener: socket.socket = create_server()
_, port = listener.getsockname()
diff --git a/extensions/positron-python/pythonFiles/tests/pytestadapter/test_execution.py b/extensions/positron-python/pythonFiles/tests/pytestadapter/test_execution.py
index 37a392f66d4..dd32b61fa26 100644
--- a/extensions/positron-python/pythonFiles/tests/pytestadapter/test_execution.py
+++ b/extensions/positron-python/pythonFiles/tests/pytestadapter/test_execution.py
@@ -131,6 +131,13 @@ def test_bad_id_error_execution():
@pytest.mark.parametrize(
"test_ids, expected_const",
[
+ (
+ [
+ "test_env_vars.py::test_clear_env",
+ "test_env_vars.py::test_check_env",
+ ],
+ expected_execution_test_output.safe_clear_env_vars_expected_execution_output,
+ ),
(
[
"skip_tests.py::test_something",
@@ -215,23 +222,30 @@ def test_bad_id_error_execution():
],
expected_execution_test_output.doctest_pytest_expected_execution_output,
),
+ (
+ ["test_logging.py::test_logging2", "test_logging.py::test_logging"],
+ expected_execution_test_output.logging_test_expected_execution_output,
+ ),
],
)
def test_pytest_execution(test_ids, expected_const):
"""
Test that pytest discovery works as expected where run pytest is always successful
but the actual test results are both successes and failures.:
- 1. uf_execution_expected_output: unittest tests run on multiple files.
- 2. uf_single_file_expected_output: test run on a single file.
- 3. uf_single_method_execution_expected_output: test run on a single method in a file.
- 4. uf_non_adjacent_tests_execution_expected_output: test run on unittests in two files with single selection in test explorer.
- 5. unit_pytest_same_file_execution_expected_output: test run on a file with both unittest and pytest tests.
- 6. dual_level_nested_folder_execution_expected_output: test run on a file with one test file
+ 1: skip_tests_execution_expected_output: test run on a file with skipped tests.
+ 2. error_raised_exception_execution_expected_output: test run on a file that raises an exception.
+ 3. uf_execution_expected_output: unittest tests run on multiple files.
+ 4. uf_single_file_expected_output: test run on a single file.
+ 5. uf_single_method_execution_expected_output: test run on a single method in a file.
+ 6. uf_non_adjacent_tests_execution_expected_output: test run on unittests in two files with single selection in test explorer.
+ 7. unit_pytest_same_file_execution_expected_output: test run on a file with both unittest and pytest tests.
+ 8. dual_level_nested_folder_execution_expected_output: test run on a file with one test file
at the top level and one test file in a nested folder.
- 7. double_nested_folder_expected_execution_output: test run on a double nested folder.
- 8. parametrize_tests_expected_execution_output: test run on a parametrize test with 3 inputs.
- 9. single_parametrize_tests_expected_execution_output: test run on single parametrize test.
- 10. doctest_pytest_expected_execution_output: test run on doctest file.
+ 9. double_nested_folder_expected_execution_output: test run on a double nested folder.
+ 10. parametrize_tests_expected_execution_output: test run on a parametrize test with 3 inputs.
+ 11. single_parametrize_tests_expected_execution_output: test run on single parametrize test.
+ 12. doctest_pytest_expected_execution_output: test run on doctest file.
+ 13. logging_test_expected_execution_output: test run on a file with logging.
Keyword arguments:
diff --git a/extensions/positron-python/pythonFiles/tests/test_dynamic_cursor.py b/extensions/positron-python/pythonFiles/tests/test_dynamic_cursor.py
new file mode 100644
index 00000000000..7aea59427aa
--- /dev/null
+++ b/extensions/positron-python/pythonFiles/tests/test_dynamic_cursor.py
@@ -0,0 +1,203 @@
+import importlib
+import textwrap
+
+import normalizeSelection
+
+
+def test_dictionary_mouse_mover():
+ """
+ Having the mouse cursor on second line,
+ 'my_dict = {'
+ and pressing shift+enter should bring the
+ mouse cursor to line 6, on and to be able to run
+ 'print('only send the dictionary')'
+ """
+ importlib.reload(normalizeSelection)
+ src = textwrap.dedent(
+ """\
+ not_dictionary = 'hi'
+ my_dict = {
+ "key1": "value1",
+ "key2": "value2"
+ }
+ print('only send the dictionary')
+ """
+ )
+
+ result = normalizeSelection.traverse_file(src, 2, 2, False)
+
+ assert result["which_line_next"] == 6
+
+
+def test_beginning_func():
+ """
+ Pressing shift+enter on the very first line,
+ of function definition, such as 'my_func():'
+ It should properly skip the comment and assert the
+ next executable line to be executed is line 5 at
+ 'my_dict = {'
+ """
+ importlib.reload(normalizeSelection)
+ src = textwrap.dedent(
+ """\
+ def my_func():
+ print("line 2")
+ print("line 3")
+ # Skip line 4 because it is a comment
+ my_dict = {
+ "key1": "value1",
+ "key2": "value2"
+ }
+ """
+ )
+
+ result = normalizeSelection.traverse_file(src, 1, 1, False)
+
+ assert result["which_line_next"] == 5
+
+
+def test_cursor_forloop():
+ importlib.reload(normalizeSelection)
+ src = textwrap.dedent(
+ """\
+ lucid_dream = ["Corgi", "Husky", "Pomsky"]
+ for dogs in lucid_dream: # initial starting position
+ print(dogs)
+ print("I wish I had a dog!")
+
+ print("This should be the next block that should be ran")
+ """
+ )
+
+ result = normalizeSelection.traverse_file(src, 2, 2, False)
+
+ assert result["which_line_next"] == 6
+
+
+def test_inside_forloop():
+ importlib.reload(normalizeSelection)
+ src = textwrap.dedent(
+ """\
+ for food in lucid_dream:
+ print("We are starting") # initial starting position
+ print("Next cursor should be here!")
+
+ """
+ )
+
+ result = normalizeSelection.traverse_file(src, 2, 2, False)
+
+ assert result["which_line_next"] == 3
+
+
+def test_skip_sameline_statements():
+ importlib.reload(normalizeSelection)
+ src = textwrap.dedent(
+ """\
+ print("Audi");print("BMW");print("Mercedes")
+ print("Next line to be run is here!")
+ """
+ )
+ result = normalizeSelection.traverse_file(src, 1, 1, False)
+
+ assert result["which_line_next"] == 2
+
+
+def test_skip_multi_comp_lambda():
+ importlib.reload(normalizeSelection)
+ src = textwrap.dedent(
+ """\
+ (
+ my_first_var
+ for my_first_var in range(1, 10)
+ if my_first_var % 2 == 0
+ )
+
+ my_lambda = lambda x: (
+ x + 1
+ )
+ """
+ )
+
+ result = normalizeSelection.traverse_file(src, 1, 1, False)
+ # Shift enter from the very first ( should make
+ # next executable statement as the lambda expression
+ assert result["which_line_next"] == 7
+
+
+def test_move_whole_class():
+ """
+ Shift+enter on a class definition
+ should move the cursor after running whole class.
+ """
+ importlib.reload(normalizeSelection)
+ src = textwrap.dedent(
+ """\
+ class Stub(object):
+ def __init__(self):
+ self.calls = []
+
+ def add_call(self, name, args=None, kwargs=None):
+ self.calls.append((name, args, kwargs))
+ print("We should be here after running whole class")
+ """
+ )
+ result = normalizeSelection.traverse_file(src, 1, 1, False)
+
+ assert result["which_line_next"] == 7
+
+
+def test_def_to_def():
+ importlib.reload(normalizeSelection)
+ src = textwrap.dedent(
+ """\
+ def my_dogs():
+ print("Corgi")
+ print("Husky")
+ print("Corgi2")
+ print("Husky2")
+ print("no dogs")
+
+ # Skip here
+ def next_func():
+ print("Not here but above")
+ """
+ )
+ result = normalizeSelection.traverse_file(src, 1, 1, False)
+
+ assert result["which_line_next"] == 9
+
+
+def test_try_catch_move():
+ importlib.reload(normalizeSelection)
+ src = textwrap.dedent(
+ """\
+ try:
+ 1+1
+ except:
+ print("error")
+
+ print("Should be here afterwards")
+ """
+ )
+
+ result = normalizeSelection.traverse_file(src, 1, 1, False)
+ assert result["which_line_next"] == 6
+
+
+def test_skip_nested():
+ importlib.reload(normalizeSelection)
+ src = textwrap.dedent(
+ """\
+ for i in range(1, 6):
+ for j in range(1, 6):
+ for x in range(1, 5):
+ for y in range(1, 5):
+ for z in range(1,10):
+ print(i, j, x, y, z)
+
+ print("Cursor should be here after running line 1")
+ """
+ )
+ result = normalizeSelection.traverse_file(src, 1, 1, False)
+ assert result["which_line_next"] == 8
diff --git a/extensions/positron-python/pythonFiles/tests/test_normalize_selection.py b/extensions/positron-python/pythonFiles/tests/test_normalize_selection.py
index 138c5ad2f52..5f4d6d7d4a1 100644
--- a/extensions/positron-python/pythonFiles/tests/test_normalize_selection.py
+++ b/extensions/positron-python/pythonFiles/tests/test_normalize_selection.py
@@ -1,8 +1,12 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
+
+import importlib
import textwrap
+# __file__ = "/Users/anthonykim/Desktop/vscode-python/pythonFiles/normalizeSelection.py"
+# sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__))))
import normalizeSelection
@@ -215,3 +219,52 @@ def show_something():
)
result = normalizeSelection.normalize_lines(src)
assert result == expected
+
+ def test_fstring(self):
+ importlib.reload(normalizeSelection)
+ src = textwrap.dedent(
+ """\
+ name = "Ahri"
+ age = 10
+
+ print(f'My name is {name}')
+ """
+ )
+
+ expected = textwrap.dedent(
+ """\
+ name = "Ahri"
+ age = 10
+ print(f'My name is {name}')
+ """
+ )
+ result = normalizeSelection.normalize_lines(src)
+
+ assert result == expected
+
+ def test_list_comp(self):
+ importlib.reload(normalizeSelection)
+ src = textwrap.dedent(
+ """\
+ names = ['Ahri', 'Bobby', 'Charlie']
+ breed = ['Pomeranian', 'Welsh Corgi', 'Siberian Husky']
+ dogs = [(name, breed) for name, breed in zip(names, breed)]
+
+ print(dogs)
+ my_family_dog = 'Corgi'
+ """
+ )
+
+ expected = textwrap.dedent(
+ """\
+ names = ['Ahri', 'Bobby', 'Charlie']
+ breed = ['Pomeranian', 'Welsh Corgi', 'Siberian Husky']
+ dogs = [(name, breed) for name, breed in zip(names, breed)]
+ print(dogs)
+ my_family_dog = 'Corgi'
+ """
+ )
+
+ result = normalizeSelection.normalize_lines(src)
+
+ assert result == expected
diff --git a/extensions/positron-python/pythonFiles/tests/test_smart_selection.py b/extensions/positron-python/pythonFiles/tests/test_smart_selection.py
new file mode 100644
index 00000000000..b86e6f9dc82
--- /dev/null
+++ b/extensions/positron-python/pythonFiles/tests/test_smart_selection.py
@@ -0,0 +1,388 @@
+import importlib
+import textwrap
+
+import normalizeSelection
+
+
+def test_part_dictionary():
+ importlib.reload(normalizeSelection)
+ src = textwrap.dedent(
+ """\
+ not_dictionary = 'hi'
+ my_dict = {
+ "key1": "value1",
+ "key2": "value2"
+ }
+ print('only send the dictionary')
+ """
+ )
+
+ expected = textwrap.dedent(
+ """\
+ my_dict = {
+ "key1": "value1",
+ "key2": "value2"
+ }
+ """
+ )
+
+ result = normalizeSelection.traverse_file(src, 3, 3, False)
+ assert result["normalized_smart_result"] == expected
+
+
+def test_nested_loop():
+ importlib.reload(normalizeSelection)
+ src = textwrap.dedent(
+ """\
+ for i in range(1, 6):
+ for j in range(1, 6):
+ for x in range(1, 5):
+ for y in range(1, 5):
+ for z in range(1,10):
+ print(i, j, x, y, z)
+ """
+ )
+ expected = textwrap.dedent(
+ """\
+ for i in range(1, 6):
+ for j in range(1, 6):
+ for x in range(1, 5):
+ for y in range(1, 5):
+ for z in range(1,10):
+ print(i, j, x, y, z)
+
+ """
+ )
+ result = normalizeSelection.traverse_file(src, 1, 1, False)
+ assert result["normalized_smart_result"] == expected
+
+
+def test_smart_shift_enter_multiple_statements():
+ importlib.reload(normalizeSelection)
+ src = textwrap.dedent(
+ """\
+ import textwrap
+ import ast
+
+ print("Porsche")
+ print("Genesis")
+
+
+ print("Audi");print("BMW");print("Mercedes")
+
+ print("dont print me")
+
+ """
+ )
+ # Expected to printing statement line by line,
+ # for when multiple print statements are ran
+ # from the same line.
+ expected = textwrap.dedent(
+ """\
+ print("Audi")
+ print("BMW")
+ print("Mercedes")
+ """
+ )
+ result = normalizeSelection.traverse_file(src, 8, 8, False)
+ assert result["normalized_smart_result"] == expected
+
+
+def test_two_layer_dictionary():
+ importlib.reload(normalizeSelection)
+ src = textwrap.dedent(
+ """\
+ print("dont print me")
+
+ two_layered_dictionary = {
+ 'inner_dict_one': {
+ 'Audi': 'Germany',
+ 'BMW': 'Germnay',
+ 'Genesis': 'Korea',
+ },
+ 'inner_dict_two': {
+ 'Mercedes': 'Germany',
+ 'Porsche': 'Germany',
+ 'Lamborghini': 'Italy',
+ 'Ferrari': 'Italy',
+ 'Maserati': 'Italy'
+ }
+ }
+ """
+ )
+ expected = textwrap.dedent(
+ """\
+ two_layered_dictionary = {
+ 'inner_dict_one': {
+ 'Audi': 'Germany',
+ 'BMW': 'Germnay',
+ 'Genesis': 'Korea',
+ },
+ 'inner_dict_two': {
+ 'Mercedes': 'Germany',
+ 'Porsche': 'Germany',
+ 'Lamborghini': 'Italy',
+ 'Ferrari': 'Italy',
+ 'Maserati': 'Italy'
+ }
+ }
+ """
+ )
+ result = normalizeSelection.traverse_file(src, 6, 7, False)
+
+ assert result["normalized_smart_result"] == expected
+
+
+def test_run_whole_func():
+ importlib.reload(normalizeSelection)
+ src = textwrap.dedent(
+ """\
+ print("Decide which dog you will choose")
+ def my_dogs():
+ print("Corgi")
+ print("Husky")
+ print("Corgi2")
+ print("Husky2")
+ print("no dogs")
+ """
+ )
+
+ expected = textwrap.dedent(
+ """\
+ def my_dogs():
+ print("Corgi")
+ print("Husky")
+ print("Corgi2")
+ print("Husky2")
+ print("no dogs")
+
+ """
+ )
+ result = normalizeSelection.traverse_file(src, 2, 2, False)
+
+ assert result["normalized_smart_result"] == expected
+
+
+def test_small_forloop():
+ importlib.reload(normalizeSelection)
+ src = textwrap.dedent(
+ """\
+ for i in range(1, 6):
+ print(i)
+ print("Please also send this print statement")
+ """
+ )
+ expected = textwrap.dedent(
+ """\
+ for i in range(1, 6):
+ print(i)
+ print("Please also send this print statement")
+
+ """
+ )
+
+ # Cover the whole for loop block with multiple inner statements
+ # Make sure to contain all of the print statements included.
+ result = normalizeSelection.traverse_file(src, 1, 1, False)
+
+ assert result["normalized_smart_result"] == expected
+
+
+def inner_for_loop_component():
+ """
+ Pressing shift+enter inside a for loop,
+ specifically on a viable expression
+ by itself, such as print(i)
+ should only return that exact expression
+ """
+ importlib.reload(normalizeSelection)
+ src = textwrap.dedent(
+ """\
+ for i in range(1, 6):
+ print(i)
+ print("Please also send this print statement")
+ """
+ )
+ result = normalizeSelection.traverse_file(src, 2, 2, False)
+ expected = textwrap.dedent(
+ """\
+ print(i)
+ """
+ )
+
+ assert result["normalized_smart_result"] == expected
+
+
+def test_dict_comprehension():
+ """
+ Having the mouse cursor on the first line,
+ and pressing shift+enter should return the
+ whole dictionary comp, respecting user's code style.
+ """
+
+ importlib.reload
+ src = textwrap.dedent(
+ """\
+ my_dict_comp = {temp_mover:
+ temp_mover for temp_mover in range(1, 7)}
+ """
+ )
+
+ expected = textwrap.dedent(
+ """\
+ my_dict_comp = {temp_mover:
+ temp_mover for temp_mover in range(1, 7)}
+ """
+ )
+
+ result = normalizeSelection.traverse_file(src, 1, 1, False)
+
+ assert result["normalized_smart_result"] == expected
+
+
+def test_send_whole_generator():
+ """
+ Pressing shift+enter on the first line, which is the '('
+ should be returning the whole generator expression instead of just the '('
+ """
+
+ importlib.reload(normalizeSelection)
+ src = textwrap.dedent(
+ """\
+ (
+ my_first_var
+ for my_first_var in range(1, 10)
+ if my_first_var % 2 == 0
+ )
+ """
+ )
+
+ expected = textwrap.dedent(
+ """\
+ (
+ my_first_var
+ for my_first_var in range(1, 10)
+ if my_first_var % 2 == 0
+ )
+
+ """
+ )
+
+ result = normalizeSelection.traverse_file(src, 1, 1, False)
+
+ assert result["normalized_smart_result"] == expected
+
+
+def test_multiline_lambda():
+ """
+ Shift+enter on part of the lambda expression
+ should return the whole lambda expression,
+ regardless of whether all the component of
+ lambda expression is on the same or not.
+ """
+
+ importlib.reload(normalizeSelection)
+ src = textwrap.dedent(
+ """\
+ my_lambda = lambda x: (
+ x + 1
+ )
+ """
+ )
+ expected = textwrap.dedent(
+ """\
+ my_lambda = lambda x: (
+ x + 1
+ )
+
+ """
+ )
+
+ result = normalizeSelection.traverse_file(src, 1, 1, False)
+ assert result["normalized_smart_result"] == expected
+
+
+def test_send_whole_class():
+ """
+ Shift+enter on a class definition
+ should send the whole class definition
+ """
+ importlib.reload(normalizeSelection)
+ src = textwrap.dedent(
+ """\
+ class Stub(object):
+ def __init__(self):
+ self.calls = []
+
+ def add_call(self, name, args=None, kwargs=None):
+ self.calls.append((name, args, kwargs))
+ print("We should be here after running whole class")
+ """
+ )
+ result = normalizeSelection.traverse_file(src, 1, 1, False)
+ expected = textwrap.dedent(
+ """\
+ class Stub(object):
+ def __init__(self):
+ self.calls = []
+ def add_call(self, name, args=None, kwargs=None):
+ self.calls.append((name, args, kwargs))
+
+ """
+ )
+ assert result["normalized_smart_result"] == expected
+
+
+def test_send_whole_if_statement():
+ """
+ Shift+enter on an if statement
+ should send the whole if statement
+ including statements inside and else.
+ """
+ importlib.reload(normalizeSelection)
+ src = textwrap.dedent(
+ """\
+ if True:
+ print('send this')
+ else:
+ print('also send this')
+
+ print('cursor here afterwards')
+ """
+ )
+ expected = textwrap.dedent(
+ """\
+ if True:
+ print('send this')
+ else:
+ print('also send this')
+
+ """
+ )
+ result = normalizeSelection.traverse_file(src, 1, 1, False)
+ assert result["normalized_smart_result"] == expected
+
+
+def test_send_try():
+ importlib.reload(normalizeSelection)
+ src = textwrap.dedent(
+ """\
+ try:
+ 1+1
+ except:
+ print("error")
+
+ print("Not running this")
+ """
+ )
+ expected = textwrap.dedent(
+ """\
+ try:
+ 1+1
+ except:
+ print("error")
+
+ """
+ )
+ result = normalizeSelection.traverse_file(src, 1, 1, False)
+ assert result["normalized_smart_result"] == expected
diff --git a/extensions/positron-python/pythonFiles/tests/unittestadapter/test_discovery.py b/extensions/positron-python/pythonFiles/tests/unittestadapter/test_discovery.py
index 67e52f43b70..7d7db772a4a 100644
--- a/extensions/positron-python/pythonFiles/tests/unittestadapter/test_discovery.py
+++ b/extensions/positron-python/pythonFiles/tests/unittestadapter/test_discovery.py
@@ -18,7 +18,7 @@
[
(
["-s", "something", "-p", "other*", "-t", "else"],
- ("something", "other*", "else"),
+ ("something", "other*", "else", 1, None, None),
),
(
[
@@ -29,11 +29,35 @@
"--top-level-directory",
"baz",
],
- ("foo", "bar*", "baz"),
+ ("foo", "bar*", "baz", 1, None, None),
),
(
["--foo", "something"],
- (".", "test*.py", None),
+ (".", "test*.py", None, 1, None, None),
+ ),
+ (
+ ["--foo", "something", "-v"],
+ (".", "test*.py", None, 2, None, None),
+ ),
+ (
+ ["--foo", "something", "-f"],
+ (".", "test*.py", None, 1, True, None),
+ ),
+ (
+ ["--foo", "something", "--verbose", "-f"],
+ (".", "test*.py", None, 2, True, None),
+ ),
+ (
+ ["--foo", "something", "-q", "--failfast"],
+ (".", "test*.py", None, 0, True, None),
+ ),
+ (
+ ["--foo", "something", "--quiet"],
+ (".", "test*.py", None, 0, None, None),
+ ),
+ (
+ ["--foo", "something", "--quiet", "--locals"],
+ (".", "test*.py", None, 0, None, True),
),
],
)
diff --git a/extensions/positron-python/pythonFiles/tests/unittestadapter/test_execution.py b/extensions/positron-python/pythonFiles/tests/unittestadapter/test_execution.py
index f7306e37662..7d11c656b57 100644
--- a/extensions/positron-python/pythonFiles/tests/unittestadapter/test_execution.py
+++ b/extensions/positron-python/pythonFiles/tests/unittestadapter/test_execution.py
@@ -22,7 +22,7 @@ def test_no_ids_run() -> None:
start_dir: str = os.fspath(TEST_DATA_PATH)
testids = []
pattern = "discovery_simple*"
- actual = run_tests(start_dir, testids, pattern, None, "fake-uuid")
+ actual = run_tests(start_dir, testids, pattern, None, "fake-uuid", 1, None)
assert actual
assert all(item in actual for item in ("cwd", "status"))
assert actual["status"] == "success"
@@ -41,7 +41,13 @@ def test_single_ids_run() -> None:
"""
id = "discovery_simple.DiscoverySimple.test_one"
actual = run_tests(
- os.fspath(TEST_DATA_PATH), [id], "discovery_simple*", None, "fake-uuid"
+ os.fspath(TEST_DATA_PATH),
+ [id],
+ "discovery_simple*",
+ None,
+ "fake-uuid",
+ 1,
+ None,
)
assert actual
assert all(item in actual for item in ("cwd", "status"))
@@ -65,7 +71,13 @@ def test_subtest_run() -> None:
"""
id = "test_subtest.NumbersTest.test_even"
actual = run_tests(
- os.fspath(TEST_DATA_PATH), [id], "test_subtest.py", None, "fake-uuid"
+ os.fspath(TEST_DATA_PATH),
+ [id],
+ "test_subtest.py",
+ None,
+ "fake-uuid",
+ 1,
+ None,
)
subtests_ids = [
"test_subtest.NumbersTest.test_even (i=0)",
@@ -162,7 +174,7 @@ def test_multiple_ids_run(test_ids, pattern, cwd, expected_outcome) -> None:
All tests should have the outcome of `success`.
"""
- actual = run_tests(cwd, test_ids, pattern, None, "fake-uuid")
+ actual = run_tests(cwd, test_ids, pattern, None, "fake-uuid", 1, None)
assert actual
assert all(item in actual for item in ("cwd", "status"))
assert actual["status"] == "success"
@@ -186,7 +198,13 @@ def test_failed_tests():
"test_fail_simple.RunFailSimple.test_two_fail",
]
actual = run_tests(
- os.fspath(TEST_DATA_PATH), test_ids, "test_fail_simple*", None, "fake-uuid"
+ os.fspath(TEST_DATA_PATH),
+ test_ids,
+ "test_fail_simple*",
+ None,
+ "fake-uuid",
+ 1,
+ None,
)
assert actual
assert all(item in actual for item in ("cwd", "status"))
@@ -202,6 +220,9 @@ def test_failed_tests():
assert "outcome" in id_result
assert id_result["outcome"] == "failure"
assert "message" and "traceback" in id_result
+ assert "2 not greater than 3" in str(id_result["message"]) or "1 == 1" in str(
+ id_result["traceback"]
+ )
assert True
@@ -211,7 +232,13 @@ def test_unknown_id():
"""
test_ids = ["unknown_id"]
actual = run_tests(
- os.fspath(TEST_DATA_PATH), test_ids, "test_fail_simple*", None, "fake-uuid"
+ os.fspath(TEST_DATA_PATH),
+ test_ids,
+ "test_fail_simple*",
+ None,
+ "fake-uuid",
+ 1,
+ None,
)
assert actual
assert all(item in actual for item in ("cwd", "status"))
@@ -239,6 +266,8 @@ def test_incorrect_path():
"test_fail_simple*",
None,
"fake-uuid",
+ 1,
+ None,
)
assert actual
assert all(item in actual for item in ("cwd", "status", "error"))
diff --git a/extensions/positron-python/pythonFiles/unittestadapter/discovery.py b/extensions/positron-python/pythonFiles/unittestadapter/discovery.py
index 7e07e45d120..274fb5e5e66 100644
--- a/extensions/positron-python/pythonFiles/unittestadapter/discovery.py
+++ b/extensions/positron-python/pythonFiles/unittestadapter/discovery.py
@@ -19,7 +19,7 @@
# If I use from utils then there will be an import error in test_discovery.py.
from unittestadapter.utils import TestNode, build_test_tree, parse_unittest_args
-DEFAULT_PORT = "45454"
+DEFAULT_PORT = 45454
class PayloadDict(TypedDict):
@@ -37,7 +37,10 @@ class EOTPayloadDict(TypedDict):
def discover_tests(
- start_dir: str, pattern: str, top_level_dir: Optional[str], uuid: Optional[str]
+ start_dir: str,
+ pattern: str,
+ top_level_dir: Optional[str],
+ uuid: Optional[str],
) -> PayloadDict:
"""Returns a dictionary containing details of the discovered tests.
@@ -119,14 +122,27 @@ def post_response(
argv = sys.argv[1:]
index = argv.index("--udiscovery")
- start_dir, pattern, top_level_dir = parse_unittest_args(argv[index + 1 :])
+ (
+ start_dir,
+ pattern,
+ top_level_dir,
+ _verbosity,
+ _failfast,
+ _locals,
+ ) = parse_unittest_args(argv[index + 1 :])
- # Perform test discovery.
testPort = int(os.environ.get("TEST_PORT", DEFAULT_PORT))
testUuid = os.environ.get("TEST_UUID")
- # Post this discovery payload.
+ if testPort is DEFAULT_PORT:
+ print(
+ "Error[vscode-unittest]: TEST_PORT is not set.",
+ " TEST_UUID = ",
+ testUuid,
+ )
if testUuid is not None:
+ # Perform test discovery.
payload = discover_tests(start_dir, pattern, top_level_dir, testUuid)
+ # Post this discovery payload.
post_response(payload, testPort, testUuid)
# Post EOT token.
eot_payload: EOTPayloadDict = {"command_type": "discovery", "eot": True}
diff --git a/extensions/positron-python/pythonFiles/unittestadapter/execution.py b/extensions/positron-python/pythonFiles/unittestadapter/execution.py
index 0684ada8e44..769d70afc0d 100644
--- a/extensions/positron-python/pythonFiles/unittestadapter/execution.py
+++ b/extensions/positron-python/pythonFiles/unittestadapter/execution.py
@@ -21,14 +21,13 @@
from typing_extensions import Literal, NotRequired, TypeAlias, TypedDict
from unittestadapter.utils import parse_unittest_args
-DEFAULT_PORT = "45454"
-
ErrorType = Union[
Tuple[Type[BaseException], BaseException, TracebackType], Tuple[None, None, None]
]
testPort = 0
testUuid = 0
START_DIR = ""
+DEFAULT_PORT = 45454
class TestOutcomeEnum(str, enum.Enum):
@@ -104,13 +103,18 @@ def formatResult(
subtest: Union[unittest.TestCase, None] = None,
):
tb = None
- if error and error[2] is not None:
- # Format traceback
+
+ message = ""
+ # error is a tuple of the form returned by sys.exc_info(): (type, value, traceback).
+ if error is not None:
+ try:
+ message = f"{error[0]} {error[1]}"
+ except Exception:
+ message = "Error occurred, unknown type or value"
formatted = traceback.format_exception(*error)
+ tb = "".join(formatted)
# Remove the 'Traceback (most recent call last)'
formatted = formatted[1:]
- tb = "".join(formatted)
-
if subtest:
test_id = subtest.id()
else:
@@ -119,7 +123,7 @@ def formatResult(
result = {
"test": test.id(),
"outcome": outcome,
- "message": str(error),
+ "message": message,
"traceback": tb,
"subtest": subtest.id() if subtest else None,
}
@@ -163,6 +167,9 @@ def run_tests(
pattern: str,
top_level_dir: Optional[str],
uuid: Optional[str],
+ verbosity: int,
+ failfast: Optional[bool],
+ locals: Optional[bool] = None,
) -> PayloadDict:
cwd = os.path.abspath(start_dir)
status = TestExecutionStatus.error
@@ -186,8 +193,18 @@ def run_tests(
}
suite = loader.discover(start_dir, pattern, top_level_dir) # noqa: F841
- # Run tests.
- runner = unittest.TextTestRunner(resultclass=UnittestTestResult)
+ if failfast is None:
+ failfast = False
+ if locals is None:
+ locals = False
+ if verbosity is None:
+ verbosity = 1
+ runner = unittest.TextTestRunner(
+ resultclass=UnittestTestResult,
+ tb_locals=locals,
+ failfast=failfast,
+ verbosity=verbosity,
+ )
# lets try to tailer our own suite so we can figure out running only the ones we want
loader = unittest.TestLoader()
tailor: unittest.TestSuite = loader.loadTestsFromNames(test_ids)
@@ -258,13 +275,21 @@ def post_response(
argv = sys.argv[1:]
index = argv.index("--udiscovery")
- start_dir, pattern, top_level_dir = parse_unittest_args(argv[index + 1 :])
+ (
+ start_dir,
+ pattern,
+ top_level_dir,
+ verbosity,
+ failfast,
+ locals,
+ ) = parse_unittest_args(argv[index + 1 :])
run_test_ids_port = os.environ.get("RUN_TEST_IDS_PORT")
run_test_ids_port_int = (
int(run_test_ids_port) if run_test_ids_port is not None else 0
)
-
+ if run_test_ids_port_int == 0:
+ print("Error[vscode-unittest]: RUN_TEST_IDS_PORT env var is not set.")
# get data from socket
test_ids_from_buffer = []
try:
@@ -288,8 +313,6 @@ def post_response(
)
# Clear the buffer as complete JSON object is received
buffer = b""
-
- # Process the JSON data
break
except json.JSONDecodeError:
# JSON decoding error, the complete JSON object is not yet received
@@ -300,10 +323,30 @@ def post_response(
testPort = int(os.environ.get("TEST_PORT", DEFAULT_PORT))
testUuid = os.environ.get("TEST_UUID")
+ if testPort is DEFAULT_PORT:
+ print(
+ "Error[vscode-unittest]: TEST_PORT is not set.",
+ " TEST_UUID = ",
+ testUuid,
+ )
+ if testUuid is None:
+ print(
+ "Error[vscode-unittest]: TEST_UUID is not set.",
+ " TEST_PORT = ",
+ testPort,
+ )
+ testUuid = "unknown"
if test_ids_from_buffer:
# Perform test execution.
payload = run_tests(
- start_dir, test_ids_from_buffer, pattern, top_level_dir, testUuid
+ start_dir,
+ test_ids_from_buffer,
+ pattern,
+ top_level_dir,
+ testUuid,
+ verbosity,
+ failfast,
+ locals,
)
else:
cwd = os.path.abspath(start_dir)
diff --git a/extensions/positron-python/pythonFiles/unittestadapter/utils.py b/extensions/positron-python/pythonFiles/unittestadapter/utils.py
index 64f08217f38..2c5ebf09abc 100644
--- a/extensions/positron-python/pythonFiles/unittestadapter/utils.py
+++ b/extensions/positron-python/pythonFiles/unittestadapter/utils.py
@@ -217,7 +217,9 @@ def build_test_tree(
return root, error
-def parse_unittest_args(args: List[str]) -> Tuple[str, str, Union[str, None]]:
+def parse_unittest_args(
+ args: List[str],
+) -> Tuple[str, str, Union[str, None], int, Union[bool, None], Union[bool, None]]:
"""Parse command-line arguments that should be forwarded to unittest to perform discovery.
Valid unittest arguments are: -v, -s, -p, -t and their long-form counterparts,
@@ -234,11 +236,24 @@ def parse_unittest_args(args: List[str]) -> Tuple[str, str, Union[str, None]]:
arg_parser.add_argument("--start-directory", "-s", default=".")
arg_parser.add_argument("--pattern", "-p", default="test*.py")
arg_parser.add_argument("--top-level-directory", "-t", default=None)
+ arg_parser.add_argument("--failfast", "-f", action="store_true", default=None)
+ arg_parser.add_argument("--verbose", "-v", action="store_true", default=None)
+ arg_parser.add_argument("-q", "--quiet", action="store_true", default=None)
+ arg_parser.add_argument("--locals", action="store_true", default=None)
parsed_args, _ = arg_parser.parse_known_args(args)
+ verbosity: int = 1
+ if parsed_args.quiet:
+ verbosity = 0
+ elif parsed_args.verbose:
+ verbosity = 2
+
return (
parsed_args.start_directory,
parsed_args.pattern,
parsed_args.top_level_directory,
+ verbosity,
+ parsed_args.failfast,
+ parsed_args.locals,
)
diff --git a/extensions/positron-python/pythonFiles/vscode_pytest/__init__.py b/extensions/positron-python/pythonFiles/vscode_pytest/__init__.py
index 2fab4d77c2f..6f5687357f1 100644
--- a/extensions/positron-python/pythonFiles/vscode_pytest/__init__.py
+++ b/extensions/positron-python/pythonFiles/vscode_pytest/__init__.py
@@ -6,7 +6,6 @@
import os
import pathlib
import sys
-import time
import traceback
import pytest
@@ -20,6 +19,8 @@
from testing_tools import socket_manager
from typing_extensions import Literal, TypedDict
+DEFAULT_PORT = 45454
+
class TestData(TypedDict):
"""A general class that all test objects inherit from."""
@@ -54,9 +55,21 @@ def __init__(self, message):
IS_DISCOVERY = False
map_id_to_path = dict()
collected_tests_so_far = list()
+TEST_PORT = os.getenv("TEST_PORT")
+TEST_UUID = os.getenv("TEST_UUID")
def pytest_load_initial_conftests(early_config, parser, args):
+ global TEST_PORT
+ global TEST_UUID
+ TEST_PORT = os.getenv("TEST_PORT")
+ TEST_UUID = os.getenv("TEST_UUID")
+ error_string = (
+ "PYTEST ERROR: TEST_UUID and/or TEST_PORT are not set at the time of pytest starting. Please confirm these environment variables are not being"
+ " changed or removed as they are required for successful test discovery and execution."
+ f" \nTEST_UUID = {TEST_UUID}\nTEST_PORT = {TEST_PORT}\n"
+ )
+ print(error_string, file=sys.stderr)
if "--collect-only" in args:
global IS_DISCOVERY
IS_DISCOVERY = True
@@ -181,6 +194,7 @@ class testRunResultDict(Dict[str, Dict[str, TestOutcome]]):
tests: Dict[str, TestOutcome]
+@pytest.hookimpl(hookwrapper=True, trylast=True)
def pytest_report_teststatus(report, config):
"""
A pytest hook that is called when a test is called. It is called 3 times per test,
@@ -221,6 +235,7 @@ def pytest_report_teststatus(report, config):
"success",
collected_test if collected_test else None,
)
+ yield
ERROR_MESSAGE_CONST = {
@@ -231,6 +246,7 @@ def pytest_report_teststatus(report, config):
}
+@pytest.hookimpl(hookwrapper=True, trylast=True)
def pytest_runtest_protocol(item, nextitem):
map_id_to_path[item.nodeid] = get_node_path(item)
skipped = check_skipped_wrapper(item)
@@ -253,6 +269,7 @@ def pytest_runtest_protocol(item, nextitem):
"success",
collected_test if collected_test else None,
)
+ yield
def check_skipped_wrapper(item):
@@ -300,12 +317,12 @@ def pytest_sessionfinish(session, exitstatus):
session -- the pytest session object.
exitstatus -- the status code of the session.
- 0: All tests passed successfully.
- 1: One or more tests failed.
- 2: Pytest was unable to start or run any tests due to issues with test discovery or test collection.
- 3: Pytest was interrupted by the user, for example by pressing Ctrl+C during test execution.
- 4: Pytest encountered an internal error or exception during test execution.
- 5: Pytest was unable to find any tests to run.
+ Exit code 0: All tests were collected and passed successfully
+ Exit code 1: Tests were collected and run but some of the tests failed
+ Exit code 2: Test execution was interrupted by the user
+ Exit code 3: Internal error happened while executing tests
+ Exit code 4: pytest command line usage error
+ Exit code 5: No tests were collected
"""
cwd = pathlib.Path.cwd()
if IS_DISCOVERY:
@@ -616,7 +633,13 @@ class EOTPayloadDict(TypedDict):
def get_node_path(node: Any) -> pathlib.Path:
"""A function that returns the path of a node given the switch to pathlib.Path."""
- return getattr(node, "path", pathlib.Path(node.fspath))
+ path = getattr(node, "path", None) or pathlib.Path(node.fspath)
+
+ if not path:
+ raise VSCodePytestError(
+ f"Unable to find path for node: {node}, node.path: {node.path}, node.fspath: {node.fspath}"
+ )
+ return path
__socket = None
@@ -683,9 +706,19 @@ def send_post_request(
payload -- the payload data to be sent.
cls_encoder -- a custom encoder if needed.
"""
- testPort = os.getenv("TEST_PORT", 45454)
- testUuid = os.getenv("TEST_UUID")
- addr = ("localhost", int(testPort))
+ global TEST_PORT
+ global TEST_UUID
+ if TEST_UUID is None or TEST_PORT is None:
+ # if TEST_UUID or TEST_PORT is None, print an error and fail as these are both critical errors
+ error_msg = (
+ "PYTEST ERROR: TEST_UUID and/or TEST_PORT are not set at the time of pytest starting. Please confirm these environment variables are not being"
+ " changed or removed as they are required for successful pytest discovery and execution."
+ f" \nTEST_UUID = {TEST_UUID}\nTEST_PORT = {TEST_PORT}\n"
+ )
+ print(error_msg, file=sys.stderr)
+ raise VSCodePytestError(error_msg)
+
+ addr = ("localhost", int(TEST_PORT))
global __socket
if __socket is None:
@@ -693,34 +726,34 @@ def send_post_request(
__socket = socket_manager.SocketManager(addr)
__socket.connect()
except Exception as error:
- print(f"Plugin error connection error[vscode-pytest]: {error}")
+ error_msg = f"Error attempting to connect to extension communication socket[vscode-pytest]: {error}"
+ print(error_msg, file=sys.stderr)
+ print(
+ "If you are on a Windows machine, this error may be occurring if any of your tests clear environment variables"
+ " as they are required to communicate with the extension. Please reference https://docs.pytest.org/en/stable/how-to/monkeypatch.html#monkeypatching-environment-variables"
+ "for the correct way to clear environment variables during testing.\n",
+ file=sys.stderr,
+ )
__socket = None
+ raise VSCodePytestError(error_msg)
data = json.dumps(payload, cls=cls_encoder)
request = f"""Content-Length: {len(data)}
Content-Type: application/json
-Request-uuid: {testUuid}
+Request-uuid: {TEST_UUID}
{data}"""
- max_retries = 3
- retries = 0
- while retries < max_retries:
- try:
- if __socket is not None and __socket.socket is not None:
- __socket.socket.sendall(request.encode("utf-8"))
- # print("Post request sent successfully!")
- # print("data sent", payload, "end of data")
- break # Exit the loop if the send was successful
- else:
- print("Plugin error connection error[vscode-pytest]")
- print(f"[vscode-pytest] data: {request}")
- except Exception as error:
- print(f"Plugin error connection error[vscode-pytest]: {error}")
- print(f"[vscode-pytest] data: {request}")
- retries += 1 # Increment retry counter
- if retries < max_retries:
- print(f"Retrying ({retries}/{max_retries}) in 2 seconds...")
- time.sleep(2) # Wait for a short duration before retrying
- else:
- print("Maximum retry attempts reached. Cannot send post request.")
+ try:
+ if __socket is not None and __socket.socket is not None:
+ __socket.socket.sendall(request.encode("utf-8"))
+ else:
+ print(
+ f"Plugin error connection error[vscode-pytest], socket is None \n[vscode-pytest] data: \n{request} \n",
+ file=sys.stderr,
+ )
+ except Exception as error:
+ print(
+ f"Plugin error, exception thrown while attempting to send data[vscode-pytest]: {error} \n[vscode-pytest] data: \n{request}\n",
+ file=sys.stderr,
+ )
diff --git a/extensions/positron-python/pythonFiles/vscode_pytest/run_pytest_script.py b/extensions/positron-python/pythonFiles/vscode_pytest/run_pytest_script.py
index 0fca8208a40..e60ee91f096 100644
--- a/extensions/positron-python/pythonFiles/vscode_pytest/run_pytest_script.py
+++ b/extensions/positron-python/pythonFiles/vscode_pytest/run_pytest_script.py
@@ -28,6 +28,8 @@
run_test_ids_port_int = (
int(run_test_ids_port) if run_test_ids_port is not None else 0
)
+ if run_test_ids_port_int == 0:
+ print("Error[vscode-pytest]: RUN_TEST_IDS_PORT env var is not set.")
test_ids_from_buffer = []
try:
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
@@ -51,8 +53,6 @@
)
# Clear the buffer as complete JSON object is received
buffer = b""
-
- # Process the JSON data
print("Received JSON data in run script")
break
except json.JSONDecodeError:
diff --git a/extensions/positron-python/requirements.in b/extensions/positron-python/requirements.in
index 6701a1ef1b7..22b599619ad 100644
--- a/extensions/positron-python/requirements.in
+++ b/extensions/positron-python/requirements.in
@@ -4,7 +4,7 @@
# 2) pip-compile --generate-hashes requirements.in
# Unittest test adapter
-typing-extensions==4.7.1
+typing-extensions==4.8.0
# Fallback env creator for debian
microvenv
diff --git a/extensions/positron-python/requirements.txt b/extensions/positron-python/requirements.txt
index 205b9fc4804..c24b0b48e39 100644
--- a/extensions/positron-python/requirements.txt
+++ b/extensions/positron-python/requirements.txt
@@ -8,9 +8,9 @@ importlib-metadata==6.7.0 \
--hash=sha256:1aaf550d4f73e5d6783e7acb77aec43d49da8017410afae93822cc9cca98c4d4 \
--hash=sha256:cb52082e659e97afc5dac71e79de97d8681de3aa07ff18578330904a9d18e5b5
# via -r requirements.in
-microvenv==2023.2.0 \
- --hash=sha256:5b46296d6a65992946da504bd9e724a5becf5c256091f2f9383e5b4e9f567f23 \
- --hash=sha256:a07e88a8fb5ee90219b86dd90095cb5646462d45d30285ea3b1a3c7cf33616d3
+microvenv==2023.5 \
+ --hash=sha256:128c0c8ab46e3bbd7b4c902c8a5d6333b694f9ebf871f123b473425cb6fbe19f \
+ --hash=sha256:270977691d207d70308c4239221d2ffbbfd595fa1819d09680c75e8808b21254
# via -r requirements.in
packaging==23.2 \
--hash=sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5 \
@@ -20,9 +20,9 @@ tomli==2.0.1 \
--hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \
--hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f
# via -r requirements.in
-typing-extensions==4.7.1 \
- --hash=sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36 \
- --hash=sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2
+typing-extensions==4.8.0 \
+ --hash=sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0 \
+ --hash=sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef
# via -r requirements.in
zipp==3.15.0 \
--hash=sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b \
diff --git a/extensions/positron-python/resources/report_issue_user_settings.json b/extensions/positron-python/resources/report_issue_user_settings.json
index 778434c5cf0..eea4ca007da 100644
--- a/extensions/positron-python/resources/report_issue_user_settings.json
+++ b/extensions/positron-python/resources/report_issue_user_settings.json
@@ -69,19 +69,6 @@
"memory": true,
"symbolsHierarchyDepthLimit": false
},
- "sortImports": {
- "args": "placeholder",
- "path": "placeholder"
- },
- "formatting": {
- "autopep8Args": "placeholder",
- "autopep8Path": "placeholder",
- "provider": true,
- "blackArgs": "placeholder",
- "blackPath": "placeholder",
- "yapfArgs": "placeholder",
- "yapfPath": "placeholder"
- },
"testing": {
"cwd": "placeholder",
"debugPort": true,
diff --git a/extensions/positron-python/scripts/onCreateCommand.sh b/extensions/positron-python/scripts/onCreateCommand.sh
index 6303d21ef48..05ffe64a8c0 100644
--- a/extensions/positron-python/scripts/onCreateCommand.sh
+++ b/extensions/positron-python/scripts/onCreateCommand.sh
@@ -26,7 +26,8 @@ pyenv exec python3.8 -m venv .venv
source /workspaces/vscode-python/.venv/bin/activate
# Install required Python libraries.
-npx gulp installPythonLibs
+/workspaces/vscode-python/.venv/bin/python -m pip install nox
+nox --session install_python_libs
/workspaces/vscode-python/.venv/bin/python -m pip install -r build/test-requirements.txt
/workspaces/vscode-python/.venv/bin/python -m pip install -r build/functional-test-requirements.txt
diff --git a/extensions/positron-python/src/client/api.ts b/extensions/positron-python/src/client/api.ts
index 23b2553c93d..81a5f676cc2 100644
--- a/extensions/positron-python/src/client/api.ts
+++ b/extensions/positron-python/src/client/api.ts
@@ -21,6 +21,7 @@ import { IDiscoveryAPI } from './pythonEnvironments/base/locator';
import { buildEnvironmentApi } from './environmentApi';
import { ApiForPylance } from './pylanceApi';
import { getTelemetryReporter } from './telemetry';
+import { TensorboardExtensionIntegration } from './tensorBoard/tensorboardIntegration';
export function buildApi(
ready: Promise,
@@ -31,7 +32,14 @@ export function buildApi(
const configurationService = serviceContainer.get(IConfigurationService);
const interpreterService = serviceContainer.get(IInterpreterService);
serviceManager.addSingleton(JupyterExtensionIntegration, JupyterExtensionIntegration);
+ serviceManager.addSingleton(
+ TensorboardExtensionIntegration,
+ TensorboardExtensionIntegration,
+ );
const jupyterIntegration = serviceContainer.get(JupyterExtensionIntegration);
+ const tensorboardIntegration = serviceContainer.get(
+ TensorboardExtensionIntegration,
+ );
const outputChannel = serviceContainer.get(ILanguageServerOutputChannel);
const api: PythonExtension & {
@@ -41,6 +49,12 @@ export function buildApi(
jupyter: {
registerHooks(): void;
};
+ /**
+ * Internal API just for Tensorboard, hence don't include in the official types.
+ */
+ tensorboard: {
+ registerHooks(): void;
+ };
} & {
/**
* @deprecated Temporarily exposed for Pylance until we expose this API generally. Will be removed in an
@@ -92,6 +106,9 @@ export function buildApi(
jupyter: {
registerHooks: () => jupyterIntegration.integrateWithJupyterExtension(),
},
+ tensorboard: {
+ registerHooks: () => tensorboardIntegration.integrateWithTensorboardExtension(),
+ },
debug: {
async getRemoteLauncherCommand(
host: string,
diff --git a/extensions/positron-python/src/client/common/application/applicationShell.ts b/extensions/positron-python/src/client/common/application/applicationShell.ts
index 45466247201..aadf8018690 100644
--- a/extensions/positron-python/src/client/common/application/applicationShell.ts
+++ b/extensions/positron-python/src/client/common/application/applicationShell.ts
@@ -10,6 +10,7 @@ import {
DocumentSelector,
env,
Event,
+ EventEmitter,
InputBox,
InputBoxOptions,
languages,
@@ -37,7 +38,8 @@ import {
WorkspaceFolder,
WorkspaceFolderPickOptions,
} from 'vscode';
-import { IApplicationShell } from './types';
+import { traceError } from '../../logging';
+import { IApplicationShell, TerminalDataWriteEvent } from './types';
@injectable()
export class ApplicationShell implements IApplicationShell {
@@ -172,4 +174,12 @@ export class ApplicationShell implements IApplicationShell {
public createLanguageStatusItem(id: string, selector: DocumentSelector): LanguageStatusItem {
return languages.createLanguageStatusItem(id, selector);
}
+ public get onDidWriteTerminalData(): Event {
+ try {
+ return window.onDidWriteTerminalData;
+ } catch (ex) {
+ traceError('Failed to get proposed API onDidWriteTerminalData', ex);
+ return new EventEmitter().event;
+ }
+ }
}
diff --git a/extensions/positron-python/src/client/common/application/commands.ts b/extensions/positron-python/src/client/common/application/commands.ts
index b408aae5218..8813768d35a 100644
--- a/extensions/positron-python/src/client/common/application/commands.ts
+++ b/extensions/positron-python/src/client/common/application/commands.ts
@@ -93,7 +93,6 @@ export interface ICommandNameArgumentTypeMapping extends ICommandNameWithoutArgu
['workbench.action.openIssueReporter']: [{ extensionId: string; issueBody: string }];
[Commands.GetSelectedInterpreterPath]: [{ workspaceFolder: string } | string[]];
[Commands.TriggerEnvironmentSelection]: [undefined | Uri];
- [Commands.Sort_Imports]: [undefined, Uri];
[Commands.Exec_In_Terminal]: [undefined, Uri];
[Commands.Exec_In_Terminal_Icon]: [undefined, Uri];
[Commands.Debug_In_Terminal]: [Uri];
@@ -103,4 +102,12 @@ export interface ICommandNameArgumentTypeMapping extends ICommandNameWithoutArgu
[Commands.Tests_Configure]: [undefined, undefined | CommandSource, undefined | Uri];
[Commands.LaunchTensorBoard]: [TensorBoardEntrypoint, TensorBoardEntrypointTrigger];
['workbench.view.testing.focus']: [];
+ ['cursorMove']: [
+ {
+ to: string;
+ by: string;
+ value: number;
+ },
+ ];
+ ['cursorEnd']: [];
}
diff --git a/extensions/positron-python/src/client/common/application/progressService.ts b/extensions/positron-python/src/client/common/application/progressService.ts
new file mode 100644
index 00000000000..fb19cad1136
--- /dev/null
+++ b/extensions/positron-python/src/client/common/application/progressService.ts
@@ -0,0 +1,32 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+import { ProgressOptions } from 'vscode';
+import { Deferred, createDeferred } from '../utils/async';
+import { IApplicationShell } from './types';
+
+export class ProgressService {
+ private deferred: Deferred | undefined;
+
+ constructor(private readonly shell: IApplicationShell) {}
+
+ public showProgress(options: ProgressOptions): void {
+ if (!this.deferred) {
+ this.createProgress(options);
+ }
+ }
+
+ public hideProgress(): void {
+ if (this.deferred) {
+ this.deferred.resolve();
+ this.deferred = undefined;
+ }
+ }
+
+ private createProgress(options: ProgressOptions) {
+ this.shell.withProgress(options, () => {
+ this.deferred = createDeferred();
+ return this.deferred.promise;
+ });
+ }
+}
diff --git a/extensions/positron-python/src/client/common/application/types.ts b/extensions/positron-python/src/client/common/application/types.ts
index fa2ced6c45d..863f5e4651b 100644
--- a/extensions/positron-python/src/client/common/application/types.ts
+++ b/extensions/positron-python/src/client/common/application/types.ts
@@ -67,6 +67,17 @@ import { Resource } from '../types';
import { ICommandNameArgumentTypeMapping } from './commands';
import { ExtensionContextKey } from './contextKeys';
+export interface TerminalDataWriteEvent {
+ /**
+ * The {@link Terminal} for which the data was written.
+ */
+ readonly terminal: Terminal;
+ /**
+ * The data being written.
+ */
+ readonly data: string;
+}
+
export const IApplicationShell = Symbol('IApplicationShell');
export interface IApplicationShell {
/**
@@ -75,6 +86,13 @@ export interface IApplicationShell {
*/
readonly onDidChangeWindowState: Event;
+ /**
+ * An event which fires when the terminal's child pseudo-device is written to (the shell).
+ * In other words, this provides access to the raw data stream from the process running
+ * within the terminal, including VT sequences.
+ */
+ readonly onDidWriteTerminalData: Event;
+
showInformationMessage(message: string, ...items: string[]): Thenable;
/**
diff --git a/extensions/positron-python/src/client/common/configSettings.ts b/extensions/positron-python/src/client/common/configSettings.ts
index 09b11b2b372..5a163cc06f1 100644
--- a/extensions/positron-python/src/client/common/configSettings.ts
+++ b/extensions/positron-python/src/client/common/configSettings.ts
@@ -6,7 +6,6 @@ import * as fs from 'fs';
import {
ConfigurationChangeEvent,
ConfigurationTarget,
- DiagnosticSeverity,
Disposable,
Event,
EventEmitter,
@@ -27,12 +26,9 @@ import {
IAutoCompleteSettings,
IDefaultLanguageServer,
IExperiments,
- IFormattingSettings,
IInterpreterPathService,
IInterpreterSettings,
- ILintingSettings,
IPythonSettings,
- ISortImportSettings,
ITensorBoardSettings,
ITerminalSettings,
Resource,
@@ -108,10 +104,6 @@ export class PythonSettings implements IPythonSettings {
public devOptions: string[] = [];
- public linting!: ILintingSettings;
-
- public formatting!: IFormattingSettings;
-
public autoComplete!: IAutoCompleteSettings;
public tensorBoard: ITensorBoardSettings | undefined;
@@ -120,8 +112,6 @@ export class PythonSettings implements IPythonSettings {
public terminal!: ITerminalSettings;
- public sortImports!: ISortImportSettings;
-
public globalModuleInstallation = false;
public experiments!: IExperiments;
@@ -331,130 +321,8 @@ export class PythonSettings implements IPythonSettings {
this.devOptions = systemVariables.resolveAny(pythonSettings.get('devOptions'))!;
this.devOptions = Array.isArray(this.devOptions) ? this.devOptions : [];
- const lintingSettings = systemVariables.resolveAny(pythonSettings.get('linting'))!;
- if (this.linting) {
- Object.assign(this.linting, lintingSettings);
- } else {
- this.linting = lintingSettings;
- }
-
this.globalModuleInstallation = pythonSettings.get('globalModuleInstallation') === true;
- const sortImportSettings = systemVariables.resolveAny(pythonSettings.get('sortImports'))!;
- if (this.sortImports) {
- Object.assign(this.sortImports, sortImportSettings);
- } else {
- this.sortImports = sortImportSettings;
- }
- // Support for travis.
- this.sortImports = this.sortImports ? this.sortImports : { path: '', args: [] };
- // Support for travis.
- this.linting = this.linting
- ? this.linting
- : {
- enabled: false,
- cwd: undefined,
- ignorePatterns: [],
- flake8Args: [],
- flake8Enabled: false,
- flake8Path: 'flake8',
- lintOnSave: false,
- maxNumberOfProblems: 100,
- mypyArgs: [],
- mypyEnabled: false,
- mypyPath: 'mypy',
- banditArgs: [],
- banditEnabled: false,
- banditPath: 'bandit',
- pycodestyleArgs: [],
- pycodestyleEnabled: false,
- pycodestylePath: 'pycodestyle',
- pylamaArgs: [],
- pylamaEnabled: false,
- pylamaPath: 'pylama',
- prospectorArgs: [],
- prospectorEnabled: false,
- prospectorPath: 'prospector',
- pydocstyleArgs: [],
- pydocstyleEnabled: false,
- pydocstylePath: 'pydocstyle',
- pylintArgs: [],
- pylintEnabled: false,
- pylintPath: 'pylint',
- pylintCategorySeverity: {
- convention: DiagnosticSeverity.Hint,
- error: DiagnosticSeverity.Error,
- fatal: DiagnosticSeverity.Error,
- refactor: DiagnosticSeverity.Hint,
- warning: DiagnosticSeverity.Warning,
- },
- pycodestyleCategorySeverity: {
- E: DiagnosticSeverity.Error,
- W: DiagnosticSeverity.Warning,
- },
- flake8CategorySeverity: {
- E: DiagnosticSeverity.Error,
- W: DiagnosticSeverity.Warning,
- // Per http://flake8.pycqa.org/en/latest/glossary.html#term-error-code
- // 'F' does not mean 'fatal as in PyLint but rather 'pyflakes' such as
- // unused imports, variables, etc.
- F: DiagnosticSeverity.Warning,
- },
- mypyCategorySeverity: {
- error: DiagnosticSeverity.Error,
- note: DiagnosticSeverity.Hint,
- },
- };
- this.linting.pylintPath = getAbsolutePath(systemVariables.resolveAny(this.linting.pylintPath), workspaceRoot);
- this.linting.flake8Path = getAbsolutePath(systemVariables.resolveAny(this.linting.flake8Path), workspaceRoot);
- this.linting.pycodestylePath = getAbsolutePath(
- systemVariables.resolveAny(this.linting.pycodestylePath),
- workspaceRoot,
- );
- this.linting.pylamaPath = getAbsolutePath(systemVariables.resolveAny(this.linting.pylamaPath), workspaceRoot);
- this.linting.prospectorPath = getAbsolutePath(
- systemVariables.resolveAny(this.linting.prospectorPath),
- workspaceRoot,
- );
- this.linting.pydocstylePath = getAbsolutePath(
- systemVariables.resolveAny(this.linting.pydocstylePath),
- workspaceRoot,
- );
- this.linting.mypyPath = getAbsolutePath(systemVariables.resolveAny(this.linting.mypyPath), workspaceRoot);
- this.linting.banditPath = getAbsolutePath(systemVariables.resolveAny(this.linting.banditPath), workspaceRoot);
-
- if (this.linting.cwd) {
- this.linting.cwd = getAbsolutePath(systemVariables.resolveAny(this.linting.cwd), workspaceRoot);
- }
-
- const formattingSettings = systemVariables.resolveAny(pythonSettings.get('formatting'))!;
- if (this.formatting) {
- Object.assign(this.formatting, formattingSettings);
- } else {
- this.formatting = formattingSettings;
- }
- // Support for travis.
- this.formatting = this.formatting
- ? this.formatting
- : {
- autopep8Args: [],
- autopep8Path: 'autopep8',
- provider: 'autopep8',
- blackArgs: [],
- blackPath: 'black',
- yapfArgs: [],
- yapfPath: 'yapf',
- };
- this.formatting.autopep8Path = getAbsolutePath(
- systemVariables.resolveAny(this.formatting.autopep8Path),
- workspaceRoot,
- );
- this.formatting.yapfPath = getAbsolutePath(systemVariables.resolveAny(this.formatting.yapfPath), workspaceRoot);
- this.formatting.blackPath = getAbsolutePath(
- systemVariables.resolveAny(this.formatting.blackPath),
- workspaceRoot,
- );
-
const testSettings = systemVariables.resolveAny(pythonSettings.get('testing'))!;
if (this.testing) {
Object.assign(this.testing, testSettings);
diff --git a/extensions/positron-python/src/client/common/constants.ts b/extensions/positron-python/src/client/common/constants.ts
index 07a28301128..1f493dc436c 100644
--- a/extensions/positron-python/src/client/common/constants.ts
+++ b/extensions/positron-python/src/client/common/constants.ts
@@ -23,6 +23,7 @@ export const PYTHON_NOTEBOOKS = [
export const PVSC_EXTENSION_ID = 'ms-python.python';
export const PYLANCE_EXTENSION_ID = 'ms-python.vscode-pylance';
export const JUPYTER_EXTENSION_ID = 'ms-toolsai.jupyter';
+export const TENSORBOARD_EXTENSION_ID = 'ms-toolsai.tensorboard';
export const AppinsightsKey = '0c6ae279ed8443289764825290e4f9e2-1a736e7c-1324-4338-be46-fc2a58ae4d14-7255';
export type Channel = 'stable' | 'insiders';
@@ -61,7 +62,6 @@ export namespace Commands {
export const ReportIssue = 'python.reportIssue';
export const Set_Interpreter = 'python.setInterpreter';
export const Set_ShebangInterpreter = 'python.setShebangInterpreter';
- export const Sort_Imports = 'python.sortImports';
export const Start_REPL = 'python.startREPL';
export const Tests_Configure = 'python.configureTests';
export const TriggerEnvironmentSelection = 'python.triggerEnvSelection';
diff --git a/extensions/positron-python/src/client/common/editor.ts b/extensions/positron-python/src/client/common/editor.ts
deleted file mode 100644
index f08d73194d4..00000000000
--- a/extensions/positron-python/src/client/common/editor.ts
+++ /dev/null
@@ -1,400 +0,0 @@
-import { Diff, diff_match_patch } from 'diff-match-patch';
-import { injectable } from 'inversify';
-import * as md5 from 'md5';
-import { EOL } from 'os';
-import * as path from 'path';
-import { Position, Range, TextDocument, TextEdit, Uri, WorkspaceEdit } from 'vscode';
-import { IFileSystem } from '../common/platform/types';
-import { traceError } from '../logging';
-import { WrappedError } from './errors/errorUtils';
-import { IEditorUtils } from './types';
-import { isNotebookCell } from './utils/misc';
-
-// Code borrowed from goFormat.ts (Go Extension for VS Code)
-enum EditAction {
- Delete,
- Insert,
- Replace,
-}
-
-const NEW_LINE_LENGTH = EOL.length;
-
-class Patch {
- public diffs!: Diff[];
- public start1!: number;
- public start2!: number;
- public length1!: number;
- public length2!: number;
-}
-
-class Edit {
- public action: EditAction;
- public start: Position;
- public end!: Position;
- public text: string;
-
- constructor(action: number, start: Position) {
- this.action = action;
- this.start = start;
- this.text = '';
- }
-
- public apply(): TextEdit {
- switch (this.action) {
- case EditAction.Insert:
- return TextEdit.insert(this.start, this.text);
- case EditAction.Delete:
- return TextEdit.delete(new Range(this.start, this.end));
- case EditAction.Replace:
- return TextEdit.replace(new Range(this.start, this.end), this.text);
- default:
- return new TextEdit(new Range(new Position(0, 0), new Position(0, 0)), '');
- }
- }
-}
-
-export function getTextEditsFromPatch(before: string, patch: string): TextEdit[] {
- if (patch.startsWith('---')) {
- // Strip the first two lines
- patch = patch.substring(patch.indexOf('@@'));
- }
- if (patch.length === 0) {
- return [];
- }
- // Remove the text added by unified_diff
- // # Work around missing newline (http://bugs.python.org/issue2142).
- patch = patch.replace(/\\ No newline at end of file[\r\n]/, '');
-
- const dmp = require('diff-match-patch') as typeof import('diff-match-patch');
- const d = new dmp.diff_match_patch();
- const patches = patch_fromText.call(d, patch);
- if (!Array.isArray(patches) || patches.length === 0) {
- throw new Error('Unable to parse Patch string');
- }
- const textEdits: TextEdit[] = [];
-
- // Add line feeds and build the text edits
- patches.forEach((p) => {
- p.diffs.forEach((diff) => {
- diff[1] += EOL;
- });
- getTextEditsInternal(before, p.diffs, p.start1).forEach((edit) => textEdits.push(edit.apply()));
- });
-
- return textEdits;
-}
-export function getWorkspaceEditsFromPatch(
- filePatches: string[],
- workspaceRoot: string | undefined,
- fs: IFileSystem,
-): WorkspaceEdit {
- const workspaceEdit = new WorkspaceEdit();
- filePatches.forEach((patch) => {
- const indexOfAtAt = patch.indexOf('@@');
- if (indexOfAtAt === -1) {
- return;
- }
- const fileNameLines = patch
- .substring(0, indexOfAtAt)
- .split(/\r?\n/g)
- .map((line) => line.trim())
- .filter((line) => line.length > 0 && line.toLowerCase().endsWith('.py') && line.indexOf(' a') > 0);
-
- if (patch.startsWith('---')) {
- // Strip the first two lines
- patch = patch.substring(indexOfAtAt);
- }
- if (patch.length === 0) {
- return;
- }
- // We can't find the find name
- if (fileNameLines.length === 0) {
- return;
- }
-
- let fileName = fileNameLines[0].substring(fileNameLines[0].indexOf(' a') + 3).trim();
- fileName = workspaceRoot && !path.isAbsolute(fileName) ? path.resolve(workspaceRoot, fileName) : fileName;
- if (!fs.fileExistsSync(fileName)) {
- return;
- }
-
- // Remove the text added by unified_diff
- // # Work around missing newline (http://bugs.python.org/issue2142).
- patch = patch.replace(/\\ No newline at end of file[\r\n]/, '');
-
- const dmp = require('diff-match-patch') as typeof import('diff-match-patch');
- const d = new dmp.diff_match_patch();
- const patches = patch_fromText.call(d, patch);
- if (!Array.isArray(patches) || patches.length === 0) {
- throw new Error('Unable to parse Patch string');
- }
-
- const fileSource = fs.readFileSync(fileName);
- const fileUri = Uri.file(fileName);
-
- // Add line feeds and build the text edits
- patches.forEach((p) => {
- p.diffs.forEach((diff) => {
- diff[1] += EOL;
- });
-
- getTextEditsInternal(fileSource, p.diffs, p.start1).forEach((edit) => {
- switch (edit.action) {
- case EditAction.Delete:
- workspaceEdit.delete(fileUri, new Range(edit.start, edit.end));
- break;
- case EditAction.Insert:
- workspaceEdit.insert(fileUri, edit.start, edit.text);
- break;
- case EditAction.Replace:
- workspaceEdit.replace(fileUri, new Range(edit.start, edit.end), edit.text);
- break;
- default:
- break;
- }
- });
- });
- });
-
- return workspaceEdit;
-}
-
-function getTextEditsInternal(before: string, diffs: [number, string][], startLine: number = 0): Edit[] {
- let line = startLine;
- let character = 0;
- const beforeLines = before.split(/\r?\n/g);
- if (line > 0) {
- beforeLines.filter((_l, i) => i < line).forEach((l) => (character += l.length + NEW_LINE_LENGTH));
- }
- const edits: Edit[] = [];
- let edit: Edit | null = null;
- let end: Position;
-
- for (let i = 0; i < diffs.length; i += 1) {
- let start = new Position(line, character);
- // Compute the line/character after the diff is applied.
-
- for (let curr = 0; curr < diffs[i][1].length; curr += 1) {
- if (diffs[i][1][curr] !== '\n') {
- character += 1;
- } else {
- character = 0;
- line += 1;
- }
- }
-
- const dmp = require('diff-match-patch') as typeof import('diff-match-patch');
-
- switch (diffs[i][0]) {
- case dmp.DIFF_DELETE:
- if (
- beforeLines[line - 1].length === 0 &&
- beforeLines[start.line - 1] &&
- beforeLines[start.line - 1].length === 0
- ) {
- // We're asked to delete an empty line which only contains `/\r?\n/g`. The last line is also empty.
- // Delete the `\n` from the last line instead of deleting `\n` from the current line
- // This change ensures that the last line in the file, which won't contain `\n` is deleted
- start = new Position(start.line - 1, 0);
- end = new Position(line - 1, 0);
- } else {
- end = new Position(line, character);
- }
- if (edit === null) {
- edit = new Edit(EditAction.Delete, start);
- } else if (edit.action !== EditAction.Delete) {
- throw new Error('cannot format due to an internal error.');
- }
- edit.end = end;
- break;
-
- case dmp.DIFF_INSERT:
- if (edit === null) {
- edit = new Edit(EditAction.Insert, start);
- } else if (edit.action === EditAction.Delete) {
- edit.action = EditAction.Replace;
- }
- // insert and replace edits are all relative to the original state
- // of the document, so inserts should reset the current line/character
- // position to the start.
- line = start.line;
- character = start.character;
- edit.text += diffs[i][1];
- break;
-
- case dmp.DIFF_EQUAL:
- if (edit !== null) {
- edits.push(edit);
- edit = null;
- }
- break;
- }
- }
-
- if (edit !== null) {
- edits.push(edit);
- }
-
- return edits;
-}
-
-export async function getTempFileWithDocumentContents(document: TextDocument, fs: IFileSystem): Promise {
- // Don't create file in temp folder since external utilities
- // look into configuration files in the workspace and are not
- // to find custom rules if file is saved in a random disk location.
- // This means temp file has to be created in the same folder
- // as the original one and then removed.
- // Use a .tmp file extension (instead of the original extension)
- // because the language server is watching the file system for Python
- // file add/delete/change and we don't want this temp file to trigger it.
-
- let fileName = `${document.uri.fsPath}.${md5(document.uri.fsPath + document.uri.fragment)}.tmp`;
- try {
- // When dealing with untitled notebooks, there's no original physical file, hence create a temp file.
- if (isNotebookCell(document.uri) && !(await fs.fileExists(document.uri.fsPath))) {
- fileName = (
- await fs.createTemporaryFile(`${path.basename(document.uri.fsPath)}-${document.uri.fragment}.tmp`)
- ).filePath;
- }
- await fs.writeFile(fileName, document.getText());
- } catch (ex) {
- traceError('Failed to create a temporary file', ex);
- const exception = ex as Error;
- throw new WrappedError(`Failed to create a temporary file, ${exception.message}`, exception);
- }
- return fileName;
-}
-
-/**
- * Parse a textual representation of patches and return a list of Patch objects.
- * @param {string} textline Text representation of patches.
- * @return {!Array.} Array of Patch objects.
- * @throws {!Error} If invalid input.
- */
-function patch_fromText(textline: string): Patch[] {
- const patches: Patch[] = [];
- if (!textline) {
- return patches;
- }
- // Start Modification by Don Jayamanne 24/06/2016 Support for CRLF
- const text = textline.split(/[\r\n]/);
- // End Modification
- let textPointer = 0;
- const patchHeader = /^@@ -(\d+),?(\d*) \+(\d+),?(\d*) @@$/;
- while (textPointer < text.length) {
- const m = text[textPointer].match(patchHeader);
- if (!m) {
- throw new Error(`Invalid patch string: ${text[textPointer]}`);
- }
-
- const patch = new (diff_match_patch).patch_obj();
- patches.push(patch);
- patch.start1 = parseInt(m[1], 10);
- if (m[2] === '') {
- patch.start1 -= 1;
- patch.length1 = 1;
- } else if (m[2] === '0') {
- patch.length1 = 0;
- } else {
- patch.start1 -= 1;
- patch.length1 = parseInt(m[2], 10);
- }
-
- patch.start2 = parseInt(m[3], 10);
- if (m[4] === '') {
- patch.start2 -= 1;
- patch.length2 = 1;
- } else if (m[4] === '0') {
- patch.length2 = 0;
- } else {
- patch.start2 -= 1;
- patch.length2 = parseInt(m[4], 10);
- }
- textPointer += 1;
-
- const dmp = require('diff-match-patch') as typeof import('diff-match-patch');
-
- while (textPointer < text.length) {
- const sign = text[textPointer].charAt(0);
- let line: string;
- try {
- //var line = decodeURI(text[textPointer].substring(1));
- // For some reason the patch generated by python files don't encode any characters
- // And this patch module (code from Google) is expecting the text to be encoded!!
- // Temporary solution, disable decoding
- // Issue #188
- line = text[textPointer].substring(1);
- } catch (ex) {
- // Malformed URI sequence.
- throw new Error('Illegal escape in patch_fromText');
- }
- if (sign === '-') {
- // Deletion.
- patch.diffs.push([dmp.DIFF_DELETE, line]);
- } else if (sign === '+') {
- // Insertion.
- patch.diffs.push([dmp.DIFF_INSERT, line]);
- } else if (sign === ' ') {
- // Minor equality.
- patch.diffs.push([dmp.DIFF_EQUAL, line]);
- } else if (sign === '@') {
- // Start of next patch.
- break;
- } else if (sign === '') {
- // Blank line? Whatever.
- } else {
- throw new Error(`Invalid patch mode '${sign}' in: ${line}`);
- }
- textPointer += 1;
- }
- }
- return patches;
-}
-
-@injectable()
-export class EditorUtils implements IEditorUtils {
- public getWorkspaceEditsFromPatch(originalContents: string, patch: string, uri: Uri): WorkspaceEdit {
- const workspaceEdit = new WorkspaceEdit();
- if (patch.startsWith('---')) {
- // Strip the first two lines
- patch = patch.substring(patch.indexOf('@@'));
- }
- if (patch.length === 0) {
- return workspaceEdit;
- }
- // Remove the text added by unified_diff
- // # Work around missing newline (http://bugs.python.org/issue2142).
- patch = patch.replace(/\\ No newline at end of file[\r\n]/, '');
-
- const dmp = require('diff-match-patch') as typeof import('diff-match-patch');
- const d = new dmp.diff_match_patch();
- const patches = patch_fromText.call(d, patch);
- if (!Array.isArray(patches) || patches.length === 0) {
- throw new Error('Unable to parse Patch string');
- }
-
- // Add line feeds and build the text edits
- patches.forEach((p) => {
- p.diffs.forEach((diff) => {
- diff[1] += EOL;
- });
- getTextEditsInternal(originalContents, p.diffs, p.start1).forEach((edit) => {
- switch (edit.action) {
- case EditAction.Delete:
- workspaceEdit.delete(uri, new Range(edit.start, edit.end));
- break;
- case EditAction.Insert:
- workspaceEdit.insert(uri, edit.start, edit.text);
- break;
- case EditAction.Replace:
- workspaceEdit.replace(uri, new Range(edit.start, edit.end), edit.text);
- break;
- default:
- break;
- }
- });
- });
-
- return workspaceEdit;
- }
-}
diff --git a/extensions/positron-python/src/client/common/experiments/groups.ts b/extensions/positron-python/src/client/common/experiments/groups.ts
index 1ee06469095..8f8ecc631ca 100644
--- a/extensions/positron-python/src/client/common/experiments/groups.ts
+++ b/extensions/positron-python/src/client/common/experiments/groups.ts
@@ -11,10 +11,16 @@ export enum TerminalEnvVarActivation {
experiment = 'pythonTerminalEnvVarActivation',
}
-export enum ShowFormatterExtensionPrompt {
- experiment = 'pythonPromptNewFormatterExt',
-}
// Experiment to enable the new testing rewrite.
export enum EnableTestAdapterRewrite {
experiment = 'pythonTestAdapter',
}
+// Experiment to enable smart shift+enter, advance cursor.
+export enum EnableREPLSmartSend {
+ experiment = 'pythonREPLSmartSend',
+}
+
+// Experiment to recommend installing the tensorboard extension.
+export enum RecommendTensobardExtension {
+ experiment = 'pythonRecommendTensorboardExt',
+}
diff --git a/extensions/positron-python/src/client/common/experiments/helpers.ts b/extensions/positron-python/src/client/common/experiments/helpers.ts
index 50a471f2381..079342560db 100644
--- a/extensions/positron-python/src/client/common/experiments/helpers.ts
+++ b/extensions/positron-python/src/client/common/experiments/helpers.ts
@@ -7,10 +7,12 @@ import { env, workspace } from 'vscode';
import { IExperimentService } from '../types';
import { TerminalEnvVarActivation } from './groups';
import { isTestExecution } from '../constants';
+import { traceInfo } from '../../logging';
export function inTerminalEnvVarExperiment(experimentService: IExperimentService): boolean {
- if (!isTestExecution() && workspace.workspaceFile && env.remoteName) {
+ if (!isTestExecution() && env.remoteName && workspace.workspaceFolders && workspace.workspaceFolders.length > 1) {
// TODO: Remove this if statement once https://github.com/microsoft/vscode/issues/180486 is fixed.
+ traceInfo('Not enabling terminal env var experiment in multiroot remote workspaces');
return false;
}
if (!experimentService.inExperimentSync(TerminalEnvVarActivation.experiment)) {
diff --git a/extensions/positron-python/src/client/common/experiments/service.ts b/extensions/positron-python/src/client/common/experiments/service.ts
index 270f9151280..3d85b99a26f 100644
--- a/extensions/positron-python/src/client/common/experiments/service.ts
+++ b/extensions/positron-python/src/client/common/experiments/service.ts
@@ -257,8 +257,10 @@ function sendOptInOptOutTelemetry(optedIn: string[], optedOut: string[], package
const sanitizedOptedIn = optedIn.filter((exp) => optedInEnumValues.includes(exp));
const sanitizedOptedOut = optedOut.filter((exp) => optedOutEnumValues.includes(exp));
+ JSON.stringify(sanitizedOptedIn.sort());
+
sendTelemetryEvent(EventName.PYTHON_EXPERIMENTS_OPT_IN_OPT_OUT_SETTINGS, undefined, {
- optedInto: sanitizedOptedIn,
- optedOutFrom: sanitizedOptedOut,
+ optedInto: JSON.stringify(sanitizedOptedIn.sort()),
+ optedOutFrom: JSON.stringify(sanitizedOptedOut.sort()),
});
}
diff --git a/extensions/positron-python/src/client/common/installer/moduleInstaller.ts b/extensions/positron-python/src/client/common/installer/moduleInstaller.ts
index 0268b26ae7e..062d9366d32 100644
--- a/extensions/positron-python/src/client/common/installer/moduleInstaller.ts
+++ b/extensions/positron-python/src/client/common/installer/moduleInstaller.ts
@@ -31,7 +31,7 @@ export abstract class ModuleInstaller implements IModuleInstaller {
public abstract get type(): ModuleInstallerType;
- constructor(protected serviceContainer: IServiceContainer) { }
+ constructor(protected serviceContainer: IServiceContainer) {}
public async installModule(
productOrModuleName: Product | string,
@@ -243,32 +243,10 @@ export abstract class ModuleInstaller implements IModuleInstaller {
export function translateProductToModule(product: Product): string {
switch (product) {
- case Product.mypy:
- return 'mypy';
- case Product.pylama:
- return 'pylama';
- case Product.prospector:
- return 'prospector';
- case Product.pylint:
- return 'pylint';
case Product.pytest:
return 'pytest';
- case Product.autopep8:
- return 'autopep8';
- case Product.black:
- return 'black';
- case Product.pycodestyle:
- return 'pycodestyle';
- case Product.pydocstyle:
- return 'pydocstyle';
- case Product.yapf:
- return 'yapf';
- case Product.flake8:
- return 'flake8';
case Product.unittest:
return 'unittest';
- case Product.bandit:
- return 'bandit';
case Product.ipykernel:
return 'ipykernel';
case Product.tensorboard:
diff --git a/extensions/positron-python/src/client/common/installer/productNames.ts b/extensions/positron-python/src/client/common/installer/productNames.ts
index f91a815f7c3..4725b01aabb 100644
--- a/extensions/positron-python/src/client/common/installer/productNames.ts
+++ b/extensions/positron-python/src/client/common/installer/productNames.ts
@@ -4,18 +4,7 @@
import { Product } from '../types';
export const ProductNames = new Map();
-ProductNames.set(Product.autopep8, 'autopep8');
-ProductNames.set(Product.bandit, 'bandit');
-ProductNames.set(Product.black, 'black');
-ProductNames.set(Product.flake8, 'flake8');
-ProductNames.set(Product.mypy, 'mypy');
-ProductNames.set(Product.pycodestyle, 'pycodestyle');
-ProductNames.set(Product.pylama, 'pylama');
-ProductNames.set(Product.prospector, 'prospector');
-ProductNames.set(Product.pydocstyle, 'pydocstyle');
-ProductNames.set(Product.pylint, 'pylint');
ProductNames.set(Product.pytest, 'pytest');
-ProductNames.set(Product.yapf, 'yapf');
ProductNames.set(Product.tensorboard, 'tensorboard');
ProductNames.set(Product.torchProfilerInstallName, 'torch-tb-profiler');
ProductNames.set(Product.torchProfilerImportName, 'torch_tb_profiler');
diff --git a/extensions/positron-python/src/client/common/installer/productPath.ts b/extensions/positron-python/src/client/common/installer/productPath.ts
index 5c36a6bbd3b..b06e4b7a48a 100644
--- a/extensions/positron-python/src/client/common/installer/productPath.ts
+++ b/extensions/positron-python/src/client/common/installer/productPath.ts
@@ -6,9 +6,7 @@
import { inject, injectable } from 'inversify';
import * as path from 'path';
import { Uri } from 'vscode';
-import { IFormatterHelper } from '../../formatters/types';
import { IServiceContainer } from '../../ioc/types';
-import { ILinterManager } from '../../linters/types';
import { ITestingService } from '../../testing/types';
import { IConfigurationService, IInstaller, Product } from '../types';
import { IProductPathService } from './types';
@@ -37,30 +35,6 @@ export abstract class BaseProductPathsService implements IProductPathService {
}
}
-@injectable()
-export class FormatterProductPathService extends BaseProductPathsService {
- constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) {
- super(serviceContainer);
- }
- public getExecutableNameFromSettings(product: Product, resource?: Uri): string {
- const settings = this.configService.getSettings(resource);
- const formatHelper = this.serviceContainer.get(IFormatterHelper);
- const settingsPropNames = formatHelper.getSettingsPropertyNames(product);
- return settings.formatting[settingsPropNames.pathName] as string;
- }
-}
-
-@injectable()
-export class LinterProductPathService extends BaseProductPathsService {
- constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) {
- super(serviceContainer);
- }
- public getExecutableNameFromSettings(product: Product, resource?: Uri): string {
- const linterManager = this.serviceContainer.get(ILinterManager);
- return linterManager.getLinterInfo(product).pathName(resource);
- }
-}
-
@injectable()
export class TestFrameworkProductPathService extends BaseProductPathsService {
constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) {
diff --git a/extensions/positron-python/src/client/common/installer/productService.ts b/extensions/positron-python/src/client/common/installer/productService.ts
index b47ff49a691..1a5dca3aeb3 100644
--- a/extensions/positron-python/src/client/common/installer/productService.ts
+++ b/extensions/positron-python/src/client/common/installer/productService.ts
@@ -12,19 +12,8 @@ export class ProductService implements IProductService {
private ProductTypes = new Map();
constructor() {
- this.ProductTypes.set(Product.bandit, ProductType.Linter);
- this.ProductTypes.set(Product.flake8, ProductType.Linter);
- this.ProductTypes.set(Product.mypy, ProductType.Linter);
- this.ProductTypes.set(Product.pycodestyle, ProductType.Linter);
- this.ProductTypes.set(Product.prospector, ProductType.Linter);
- this.ProductTypes.set(Product.pydocstyle, ProductType.Linter);
- this.ProductTypes.set(Product.pylama, ProductType.Linter);
- this.ProductTypes.set(Product.pylint, ProductType.Linter);
this.ProductTypes.set(Product.pytest, ProductType.TestFramework);
this.ProductTypes.set(Product.unittest, ProductType.TestFramework);
- this.ProductTypes.set(Product.autopep8, ProductType.Formatter);
- this.ProductTypes.set(Product.black, ProductType.Formatter);
- this.ProductTypes.set(Product.yapf, ProductType.Formatter);
this.ProductTypes.set(Product.ipykernel, ProductType.DataScience);
this.ProductTypes.set(Product.tensorboard, ProductType.DataScience);
this.ProductTypes.set(Product.torchProfilerInstallName, ProductType.DataScience);
diff --git a/extensions/positron-python/src/client/common/installer/serviceRegistry.ts b/extensions/positron-python/src/client/common/installer/serviceRegistry.ts
index c262c757171..d4d8a05c3a4 100644
--- a/extensions/positron-python/src/client/common/installer/serviceRegistry.ts
+++ b/extensions/positron-python/src/client/common/installer/serviceRegistry.ts
@@ -9,12 +9,7 @@ import { CondaInstaller } from './condaInstaller';
import { PipEnvInstaller } from './pipEnvInstaller';
import { PipInstaller } from './pipInstaller';
import { PoetryInstaller } from './poetryInstaller';
-import {
- DataScienceProductPathService,
- FormatterProductPathService,
- LinterProductPathService,
- TestFrameworkProductPathService,
-} from './productPath';
+import { DataScienceProductPathService, TestFrameworkProductPathService } from './productPath';
import { ProductService } from './productService';
import { IInstallationChannelManager, IModuleInstaller, IProductPathService, IProductService } from './types';
@@ -25,12 +20,6 @@ export function registerTypes(serviceManager: IServiceManager) {
serviceManager.addSingleton(IModuleInstaller, PoetryInstaller);
serviceManager.addSingleton(IInstallationChannelManager, InstallationChannelManager);
serviceManager.addSingleton(IProductService, ProductService);
- serviceManager.addSingleton(
- IProductPathService,
- FormatterProductPathService,
- ProductType.Formatter,
- );
- serviceManager.addSingleton(IProductPathService, LinterProductPathService, ProductType.Linter);
serviceManager.addSingleton(
IProductPathService,
TestFrameworkProductPathService,
diff --git a/extensions/positron-python/src/client/common/platform/fs-paths.ts b/extensions/positron-python/src/client/common/platform/fs-paths.ts
index 2d46fca9852..17df7507f7d 100644
--- a/extensions/positron-python/src/client/common/platform/fs-paths.ts
+++ b/extensions/positron-python/src/client/common/platform/fs-paths.ts
@@ -3,6 +3,7 @@
import * as nodepath from 'path';
import { getSearchPathEnvVarNames } from '../utils/exec';
+import * as fs from 'fs-extra';
import { getOSType, OSType } from '../utils/platform';
import { IExecutables, IFileSystemPaths, IFileSystemPathUtils } from './types';
@@ -170,3 +171,22 @@ export function isParentPath(filePath: string, parentPath: string): boolean {
export function arePathsSame(path1: string, path2: string): boolean {
return normCasePath(path1) === normCasePath(path2);
}
+
+export async function copyFile(src: string, dest: string): Promise {
+ const destDir = nodepath.dirname(dest);
+ if (!(await fs.pathExists(destDir))) {
+ await fs.mkdirp(destDir);
+ }
+
+ await fs.copy(src, dest, {
+ overwrite: true,
+ });
+}
+
+export function pathExists(absPath: string): Promise {
+ return fs.pathExists(absPath);
+}
+
+export function createFile(filename: string): Promise {
+ return fs.createFile(filename);
+}
diff --git a/extensions/positron-python/src/client/common/process/proc.ts b/extensions/positron-python/src/client/common/process/proc.ts
index 0ac610e3eac..18add7daf6f 100644
--- a/extensions/positron-python/src/client/common/process/proc.ts
+++ b/extensions/positron-python/src/client/common/process/proc.ts
@@ -40,13 +40,15 @@ export class ProcessService extends EventEmitter implements IProcessService {
}
public execObservable(file: string, args: string[], options: SpawnOptions = {}): ObservableExecutionResult {
- const result = execObservable(file, args, options, this.env, this.processesToKill);
+ const execOptions = { ...options, doNotLog: true };
+ const result = execObservable(file, args, execOptions, this.env, this.processesToKill);
this.emit('exec', file, args, options);
return result;
}
public exec(file: string, args: string[], options: SpawnOptions = {}): Promise> {
- const promise = plainExec(file, args, options, this.env, this.processesToKill);
+ const execOptions = { ...options, doNotLog: true };
+ const promise = plainExec(file, args, execOptions, this.env, this.processesToKill);
this.emit('exec', file, args, options);
return promise;
}
@@ -54,7 +56,8 @@ export class ProcessService extends EventEmitter implements IProcessService {
public shellExec(command: string, options: ShellOptions = {}): Promise> {
this.emit('exec', command, undefined, options);
const disposables = new Set();
- return shellExec(command, options, this.env, disposables).finally(() => {
+ const shellOptions = { ...options, doNotLog: true };
+ return shellExec(command, shellOptions, this.env, disposables).finally(() => {
// Ensure the process we started is cleaned up.
disposables.forEach((p) => {
try {
diff --git a/extensions/positron-python/src/client/common/process/rawProcessApis.ts b/extensions/positron-python/src/client/common/process/rawProcessApis.ts
index 23c2f3253bb..1e1b742a031 100644
--- a/extensions/positron-python/src/client/common/process/rawProcessApis.ts
+++ b/extensions/positron-python/src/client/common/process/rawProcessApis.ts
@@ -12,6 +12,8 @@ import { ExecutionResult, ObservableExecutionResult, Output, ShellOptions, Spawn
import { noop } from '../utils/misc';
import { decodeBuffer } from './decoder';
import { traceVerbose } from '../../logging';
+import { WorkspaceService } from '../application/workspace';
+import { ProcessLogger } from './logger';
const PS_ERROR_SCREEN_BOGUS = /your [0-9]+x[0-9]+ screen size is bogus\. expect trouble/;
@@ -49,12 +51,16 @@ function getDefaultOptions(options: T, de
export function shellExec(
command: string,
- options: ShellOptions = {},
+ options: ShellOptions & { doNotLog?: boolean } = {},
defaultEnv?: EnvironmentVariables,
disposables?: Set,
): Promise> {
const shellOptions = getDefaultOptions(options, defaultEnv);
traceVerbose(`Shell Exec: ${command} with options: ${JSON.stringify(shellOptions, null, 4)}`);
+ if (!options.doNotLog) {
+ const processLogger = new ProcessLogger(new WorkspaceService());
+ processLogger.logProcess(command, undefined, shellOptions);
+ }
return new Promise((resolve, reject) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const callback = (e: any, stdout: any, stderr: any) => {
@@ -73,7 +79,11 @@ export function shellExec(
const disposable: IDisposable = {
dispose: () => {
if (!proc.killed) {
- proc.kill();
+ if (proc.pid) {
+ killPid(proc.pid);
+ } else {
+ proc.kill();
+ }
}
},
};
@@ -86,12 +96,16 @@ export function shellExec(
export function plainExec(
file: string,
args: string[],
- options: SpawnOptions = {},
+ options: SpawnOptions & { doNotLog?: boolean } = {},
defaultEnv?: EnvironmentVariables,
disposables?: Set,
): Promise> {
const spawnOptions = getDefaultOptions(options, defaultEnv);
const encoding = spawnOptions.encoding ? spawnOptions.encoding : 'utf8';
+ if (!options.doNotLog) {
+ const processLogger = new ProcessLogger(new WorkspaceService());
+ processLogger.logProcess(file, args, options);
+ }
const proc = spawn(file, args, spawnOptions);
// Listen to these errors (unhandled errors in streams tears down the process).
// Errors will be bubbled up to the `error` event in `proc`, hence no need to log.
@@ -101,7 +115,11 @@ export function plainExec(
const disposable: IDisposable = {
dispose: () => {
if (!proc.killed) {
- proc.kill();
+ if (proc.pid) {
+ killPid(proc.pid);
+ } else {
+ proc.kill();
+ }
}
},
};
@@ -184,12 +202,16 @@ function removeCondaRunMarkers(out: string) {
export function execObservable(
file: string,
args: string[],
- options: SpawnOptions = {},
+ options: SpawnOptions & { doNotLog?: boolean } = {},
defaultEnv?: EnvironmentVariables,
disposables?: Set,
): ObservableExecutionResult {
const spawnOptions = getDefaultOptions(options, defaultEnv);
const encoding = spawnOptions.encoding ? spawnOptions.encoding : 'utf8';
+ if (!options.doNotLog) {
+ const processLogger = new ProcessLogger(new WorkspaceService());
+ processLogger.logProcess(file, args, options);
+ }
const proc = spawn(file, args, spawnOptions);
let procExited = false;
const disposable: IDisposable = {
@@ -219,7 +241,11 @@ export function execObservable(
internalDisposables.push(
options.token.onCancellationRequested(() => {
if (!procExited && !proc.killed) {
- proc.kill();
+ if (proc.pid) {
+ killPid(proc.pid);
+ } else {
+ proc.kill();
+ }
procExited = true;
}
}),
@@ -279,6 +305,6 @@ export function killPid(pid: number): void {
process.kill(pid);
}
} catch {
- // Ignore.
+ traceVerbose('Unable to kill process with pid', pid);
}
}
diff --git a/extensions/positron-python/src/client/common/serviceRegistry.ts b/extensions/positron-python/src/client/common/serviceRegistry.ts
index be0559496ac..8c872c3113b 100644
--- a/extensions/positron-python/src/client/common/serviceRegistry.ts
+++ b/extensions/positron-python/src/client/common/serviceRegistry.ts
@@ -5,7 +5,6 @@ import {
IBrowserService,
IConfigurationService,
ICurrentProcess,
- IEditorUtils,
IExperimentService,
IExtensions,
IInstaller,
@@ -51,7 +50,6 @@ import {
import { WorkspaceService } from './application/workspace';
import { ConfigurationService } from './configuration/service';
import { PipEnvExecutionPath } from './configuration/executionSettings/pipEnvExecution';
-import { EditorUtils } from './editor';
import { ExperimentService } from './experiments/service';
import { ProductInstaller } from './installer/productInstaller';
import { InterpreterPathService } from './interpreterPathService';
@@ -130,7 +128,6 @@ export function registerTypes(serviceManager: IServiceManager): void {
serviceManager.addSingleton(IApplicationEnvironment, ApplicationEnvironment);
serviceManager.addSingleton(ILanguageService, LanguageService);
serviceManager.addSingleton(IBrowserService, BrowserService);
- serviceManager.addSingleton(IEditorUtils, EditorUtils);
serviceManager.addSingleton(ITerminalActivator, TerminalActivator);
serviceManager.addSingleton(
ITerminalActivationHandler,
diff --git a/extensions/positron-python/src/client/common/types.ts b/extensions/positron-python/src/client/common/types.ts
index ec72c5f4b57..09aef5e4707 100644
--- a/extensions/positron-python/src/client/common/types.ts
+++ b/extensions/positron-python/src/client/common/types.ts
@@ -8,7 +8,6 @@ import {
CancellationToken,
ConfigurationChangeEvent,
ConfigurationTarget,
- DiagnosticSeverity,
Disposable,
DocumentSymbolProvider,
Event,
@@ -17,7 +16,6 @@ import {
Memento,
LogOutputChannel,
Uri,
- WorkspaceEdit,
OutputChannel,
MessageOptions,
} from 'vscode';
@@ -87,29 +85,14 @@ export enum ProductInstallStatus {
}
export enum ProductType {
- Linter = 'Linter',
- Formatter = 'Formatter',
TestFramework = 'TestFramework',
- RefactoringLibrary = 'RefactoringLibrary',
DataScience = 'DataScience',
Python = 'Python',
}
export enum Product {
pytest = 1,
- pylint = 3,
- flake8 = 4,
- pycodestyle = 5,
- pylama = 6,
- prospector = 7,
- pydocstyle = 8,
- yapf = 9,
- autopep8 = 10,
- mypy = 11,
unittest = 12,
- isort = 15,
- black = 16,
- bandit = 17,
ipykernel = 19,
tensorboard = 24,
torchProfilerInstallName = 25,
@@ -191,12 +174,9 @@ export interface IPythonSettings {
readonly pipenvPath: string;
readonly poetryPath: string;
readonly devOptions: string[];
- readonly linting: ILintingSettings;
- readonly formatting: IFormattingSettings;
readonly testing: ITestingSettings;
readonly autoComplete: IAutoCompleteSettings;
readonly terminal: ITerminalSettings;
- readonly sortImports: ISortImportSettings;
readonly envFile: string;
readonly globalModuleInstallation: boolean;
readonly experiments: IExperiments;
@@ -214,81 +194,11 @@ export interface IPythonSettings {
export interface ITensorBoardSettings {
logDirectory: string | undefined;
}
-export interface ISortImportSettings {
- readonly path: string;
- readonly args: string[];
-}
-
-export interface IPylintCategorySeverity {
- readonly convention: DiagnosticSeverity;
- readonly refactor: DiagnosticSeverity;
- readonly warning: DiagnosticSeverity;
- readonly error: DiagnosticSeverity;
- readonly fatal: DiagnosticSeverity;
-}
-export interface IPycodestyleCategorySeverity {
- readonly W: DiagnosticSeverity;
- readonly E: DiagnosticSeverity;
-}
-export interface Flake8CategorySeverity {
- readonly F: DiagnosticSeverity;
- readonly E: DiagnosticSeverity;
- readonly W: DiagnosticSeverity;
-}
-export interface IMypyCategorySeverity {
- readonly error: DiagnosticSeverity;
- readonly note: DiagnosticSeverity;
-}
export interface IInterpreterSettings {
infoVisibility: 'never' | 'onPythonRelated' | 'always';
}
-export interface ILintingSettings {
- readonly enabled: boolean;
- readonly ignorePatterns: string[];
- readonly prospectorEnabled: boolean;
- readonly prospectorArgs: string[];
- readonly pylintEnabled: boolean;
- readonly pylintArgs: string[];
- readonly pycodestyleEnabled: boolean;
- readonly pycodestyleArgs: string[];
- readonly pylamaEnabled: boolean;
- readonly pylamaArgs: string[];
- readonly flake8Enabled: boolean;
- readonly flake8Args: string[];
- readonly pydocstyleEnabled: boolean;
- readonly pydocstyleArgs: string[];
- readonly lintOnSave: boolean;
- readonly maxNumberOfProblems: number;
- readonly pylintCategorySeverity: IPylintCategorySeverity;
- readonly pycodestyleCategorySeverity: IPycodestyleCategorySeverity;
- readonly flake8CategorySeverity: Flake8CategorySeverity;
- readonly mypyCategorySeverity: IMypyCategorySeverity;
- cwd?: string;
- prospectorPath: string;
- pylintPath: string;
- pycodestylePath: string;
- pylamaPath: string;
- flake8Path: string;
- pydocstylePath: string;
- mypyEnabled: boolean;
- mypyArgs: string[];
- mypyPath: string;
- banditEnabled: boolean;
- banditArgs: string[];
- banditPath: string;
-}
-export interface IFormattingSettings {
- readonly provider: string;
- autopep8Path: string;
- readonly autopep8Args: string[];
- blackPath: string;
- readonly blackArgs: string[];
- yapfPath: string;
- readonly yapfArgs: string[];
-}
-
export interface ITerminalSettings {
readonly executeInFileDir: boolean;
readonly focusAfterLaunch: boolean;
@@ -411,11 +321,6 @@ export interface IBrowserService {
launch(url: string): void;
}
-export const IEditorUtils = Symbol('IEditorUtils');
-export interface IEditorUtils {
- getWorkspaceEditsFromPatch(originalContents: string, patch: string, uri: Uri): WorkspaceEdit;
-}
-
/**
* Stores hash formats
*/
diff --git a/extensions/positron-python/src/client/common/utils/localize.ts b/extensions/positron-python/src/client/common/utils/localize.ts
index bc32c1078ca..95252b361c7 100644
--- a/extensions/positron-python/src/client/common/utils/localize.ts
+++ b/extensions/positron-python/src/client/common/utils/localize.ts
@@ -39,9 +39,6 @@ export namespace Diagnostics {
'Your settings needs to be updated to change the setting "python.unitTest." to "python.testing.", otherwise testing Python code using the extension may not work. Would you like to automatically update your settings now?',
);
export const updateSettings = l10n.t('Yes, update settings');
- export const checkIsort5UpgradeGuide = l10n.t(
- 'We found outdated configuration for sorting imports in this workspace. Check the [isort upgrade guide](https://aka.ms/AA9j5x4) to update your settings.',
- );
export const pylanceDefaultMessage = l10n.t(
"The Python extension now includes Pylance to improve completions, code navigation, overall performance and much more! You can learn more about the update and learn how to change your language server [here](https://aka.ms/new-python-bundle).\n\nRead Pylance's license [here](https://marketplace.visualstudio.com/items/ms-python.vscode-pylance/license).",
);
@@ -64,6 +61,7 @@ export namespace Common {
export const noIWillDoItLater = l10n.t('No, I will do it later');
export const notNow = l10n.t('Not now');
export const doNotShowAgain = l10n.t("Don't show again");
+ export const editSomething = l10n.t('Edit {0}');
export const reload = l10n.t('Reload');
export const moreInfo = l10n.t('More Info');
export const learnMore = l10n.t('Learn more');
@@ -199,7 +197,12 @@ export namespace Interpreters {
export const activatingTerminals = l10n.t('Reactivating terminals...');
export const activateTerminalDescription = l10n.t('Activated environment for');
export const terminalEnvVarCollectionPrompt = l10n.t(
- 'The Python extension automatically activates all terminals using the selected environment, even when the name of the environment{0} is not present in the terminal prompt. [Learn more](https://aka.ms/vscodePythonTerminalActivation).',
+ '{0} environment was successfully activated, even though {1} may not be present in the terminal prompt. [Learn more](https://aka.ms/vscodePythonTerminalActivation).',
+ );
+ export const terminalDeactivateProgress = l10n.t('Editing {0}...');
+ export const restartingTerminal = l10n.t('Restarting terminal and deactivating...');
+ export const terminalDeactivatePrompt = l10n.t(
+ 'Deactivating virtual environments may not work by default. To make it work, edit your "{0}" and then restart your shell. [Learn more](https://aka.ms/AAmx2ft).',
);
export const activatedCondaEnvLaunch = l10n.t(
'We noticed VS Code was launched from an activated conda environment, would you like to select it?',
@@ -509,38 +512,3 @@ export namespace CreateEnv {
export const disableCheckWorkspace = l10n.t('Disable (Workspace)');
}
}
-
-export namespace ToolsExtensions {
- export const flake8PromptMessage = l10n.t(
- 'Use the Flake8 extension to enable easier configuration and new features such as quick fixes.',
- );
- export const pylintPromptMessage = l10n.t(
- 'Use the Pylint extension to enable easier configuration and new features such as quick fixes.',
- );
- export const isortPromptMessage = l10n.t(
- 'To use sort imports, install the isort extension. It provides easier configuration and new features such as code actions.',
- );
- export const installPylintExtension = l10n.t('Install Pylint extension');
- export const installFlake8Extension = l10n.t('Install Flake8 extension');
- export const installISortExtension = l10n.t('Install isort extension');
-
- export const selectBlackFormatterPrompt = l10n.t(
- 'You have the Black formatter extension installed, would you like to use that as the default formatter?',
- );
-
- export const selectAutopep8FormatterPrompt = l10n.t(
- 'You have the Autopep8 formatter extension installed, would you like to use that as the default formatter?',
- );
-
- export const selectMultipleFormattersPrompt = l10n.t(
- 'You have multiple formatters installed, would you like to select one as the default formatter?',
- );
-
- export const installBlackFormatterPrompt = l10n.t(
- 'You triggered formatting with Black, would you like to install one of our new formatter extensions? This will also set it as the default formatter for Python.',
- );
-
- export const installAutopep8FormatterPrompt = l10n.t(
- 'You triggered formatting with Autopep8, would you like to install one of our new formatter extension? This will also set it as the default formatter for Python.',
- );
-}
diff --git a/extensions/positron-python/src/client/common/vscodeApis/windowApis.ts b/extensions/positron-python/src/client/common/vscodeApis/windowApis.ts
index c761ff60fa6..f2d934d322f 100644
--- a/extensions/positron-python/src/client/common/vscodeApis/windowApis.ts
+++ b/extensions/positron-python/src/client/common/vscodeApis/windowApis.ts
@@ -54,6 +54,23 @@ export function showErrorMessage(message: string, ...items: any[]): Thenable<
return window.showErrorMessage(message, ...items);
}
+export function showWarningMessage(message: string, ...items: T[]): Thenable;
+export function showWarningMessage(
+ message: string,
+ options: MessageOptions,
+ ...items: T[]
+): Thenable;
+export function showWarningMessage(message: string, ...items: T[]): Thenable;
+export function showWarningMessage(
+ message: string,
+ options: MessageOptions,
+ ...items: T[]
+): Thenable;
+
+export function showWarningMessage(message: string, ...items: any[]): Thenable {
+ return window.showWarningMessage(message, ...items);
+}
+
export function showInformationMessage(message: string, ...items: T[]): Thenable;
export function showInformationMessage(
message: string,
diff --git a/extensions/positron-python/src/client/extensionActivation.ts b/extensions/positron-python/src/client/extensionActivation.ts
index 807698f3ec2..c4b663fdba6 100644
--- a/extensions/positron-python/src/client/extensionActivation.ts
+++ b/extensions/positron-python/src/client/extensionActivation.ts
@@ -10,7 +10,7 @@ import { IExtensionActivationManager } from './activation/types';
import { registerTypes as appRegisterTypes } from './application/serviceRegistry';
import { IApplicationDiagnostics } from './application/types';
import { IApplicationEnvironment, ICommandManager, IWorkspaceService } from './common/application/types';
-import { Commands, PYTHON, PYTHON_LANGUAGE, UseProposedApi } from './common/constants';
+import { Commands, PYTHON_LANGUAGE, UseProposedApi } from './common/constants';
import { registerTypes as installerRegisterTypes } from './common/installer/serviceRegistry';
import { IFileSystem } from './common/platform/types';
import {
@@ -25,11 +25,8 @@ import { noop } from './common/utils/misc';
import { DebuggerTypeName } from './debugger/constants';
import { registerTypes as debugConfigurationRegisterTypes } from './debugger/extension/serviceRegistry';
import { IDebugConfigurationService, IDynamicDebugConfigurationService } from './debugger/extension/types';
-import { registerTypes as formattersRegisterTypes } from './formatters/serviceRegistry';
import { IInterpreterService } from './interpreter/contracts';
import { getLanguageConfiguration } from './language/languageConfiguration';
-import { registerTypes as lintersRegisterTypes } from './linters/serviceRegistry';
-import { PythonFormattingEditProvider } from './providers/formatProvider';
import { ReplProvider } from './providers/replProvider';
import { registerTypes as providersRegisterTypes } from './providers/serviceRegistry';
import { TerminalProvider } from './providers/terminalProvider';
@@ -51,10 +48,10 @@ import { IDebugSessionEventHandlers } from './debugger/extension/hooks/types';
import { WorkspaceService } from './common/application/workspace';
import { DynamicPythonDebugConfigurationService } from './debugger/extension/configuration/dynamicdebugConfigurationService';
import { IInterpreterQuickPick } from './interpreter/configuration/types';
-import { registerInstallFormatterPrompt } from './providers/prompts/installFormatterPrompt';
import { registerAllCreateEnvironmentFeatures } from './pythonEnvironments/creation/registrations';
import { registerCreateEnvironmentTriggers } from './pythonEnvironments/creation/createEnvironmentTrigger';
import { initializePersistentStateForTriggers } from './common/persistentState';
+import { logAndNotifyOnLegacySettings } from './logging/settingLogs';
export async function activateComponents(
// `ext` is passed to any extra activation funcs.
@@ -110,7 +107,7 @@ export function activateFeatures(ext: ExtensionState, _components: Components):
// See https://github.com/microsoft/vscode-python/issues/10454.
async function activateLegacy(ext: ExtensionState): Promise {
- const { context, legacyIOC } = ext;
+ const { legacyIOC } = ext;
const { serviceManager, serviceContainer } = legacyIOC;
// register "services"
@@ -124,8 +121,6 @@ async function activateLegacy(ext: ExtensionState): Promise {
serviceManager.addSingletonInstance(UseProposedApi, enableProposedApi);
// Feature specific registrations.
unitTestsRegisterTypes(serviceManager);
- lintersRegisterTypes(serviceManager);
- formattersRegisterTypes(serviceManager);
installerRegisterTypes(serviceManager);
commonRegisterTerminalTypes(serviceManager);
debugConfigurationRegisterTypes(serviceManager);
@@ -134,7 +129,6 @@ async function activateLegacy(ext: ExtensionState): Promise {
const extensions = serviceContainer.get(IExtensions);
await setDefaultLanguageServer(extensions, serviceManager);
- const configuration = serviceManager.get(IConfigurationService);
// Settings are dependent on Experiment service, so we need to initialize it after experiments are activated.
serviceContainer.get(IConfigurationService).getSettings().register();
@@ -165,20 +159,9 @@ async function activateLegacy(ext: ExtensionState): Promise {
serviceContainer.get(IApplicationDiagnostics).register();
serviceManager.get(ITerminalAutoActivation).register();
- const pythonSettings = configuration.getSettings();
serviceManager.get(ICodeExecutionManager).registerCommands();
- if (
- pythonSettings &&
- pythonSettings.formatting &&
- pythonSettings.formatting.provider !== 'internalConsole'
- ) {
- const formatProvider = new PythonFormattingEditProvider(context, serviceContainer);
- disposables.push(languages.registerDocumentFormattingEditProvider(PYTHON, formatProvider));
- disposables.push(languages.registerDocumentRangeFormattingEditProvider(PYTHON, formatProvider));
- }
-
disposables.push(new ReplProvider(serviceContainer));
const terminalProvider = new TerminalProvider(serviceContainer);
@@ -200,7 +183,7 @@ async function activateLegacy(ext: ExtensionState): Promise {
),
);
- registerInstallFormatterPrompt(serviceContainer);
+ logAndNotifyOnLegacySettings();
registerCreateEnvironmentTriggers(disposables);
initializePersistentStateForTriggers(ext.context);
}
diff --git a/extensions/positron-python/src/client/formatters/autoPep8Formatter.ts b/extensions/positron-python/src/client/formatters/autoPep8Formatter.ts
deleted file mode 100644
index bf1285a60b5..00000000000
--- a/extensions/positron-python/src/client/formatters/autoPep8Formatter.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-import * as vscode from 'vscode';
-import { Product } from '../common/installer/productInstaller';
-import { IConfigurationService } from '../common/types';
-import { StopWatch } from '../common/utils/stopWatch';
-import { IServiceContainer } from '../ioc/types';
-import { sendTelemetryWhenDone } from '../telemetry';
-import { EventName } from '../telemetry/constants';
-import { BaseFormatter } from './baseFormatter';
-
-export class AutoPep8Formatter extends BaseFormatter {
- constructor(serviceContainer: IServiceContainer) {
- super('autopep8', Product.autopep8, serviceContainer);
- }
-
- public formatDocument(
- document: vscode.TextDocument,
- options: vscode.FormattingOptions,
- token: vscode.CancellationToken,
- range?: vscode.Range,
- ): Thenable {
- const stopWatch = new StopWatch();
- const settings = this.serviceContainer
- .get(IConfigurationService)
- .getSettings(document.uri);
- const hasCustomArgs =
- Array.isArray(settings.formatting.autopep8Args) && settings.formatting.autopep8Args.length > 0;
- const formatSelection = range ? !range.isEmpty : false;
-
- const autoPep8Args = ['--diff'];
- if (formatSelection) {
- autoPep8Args.push(
- ...['--line-range', (range!.start.line + 1).toString(), (range!.end.line + 1).toString()],
- );
- }
- const promise = super.provideDocumentFormattingEdits(document, options, token, autoPep8Args);
- sendTelemetryWhenDone(EventName.FORMAT, promise, stopWatch, {
- tool: 'autopep8',
- hasCustomArgs,
- formatSelection,
- });
- return promise;
- }
-}
diff --git a/extensions/positron-python/src/client/formatters/baseFormatter.ts b/extensions/positron-python/src/client/formatters/baseFormatter.ts
deleted file mode 100644
index 64e7d15a3d4..00000000000
--- a/extensions/positron-python/src/client/formatters/baseFormatter.ts
+++ /dev/null
@@ -1,149 +0,0 @@
-import * as path from 'path';
-import * as vscode from 'vscode';
-import { IApplicationShell, IWorkspaceService } from '../common/application/types';
-import '../common/extensions';
-import { isNotInstalledError } from '../common/helpers';
-import { IFileSystem } from '../common/platform/types';
-import { IPythonToolExecutionService } from '../common/process/types';
-import { IDisposableRegistry, IInstaller, Product } from '../common/types';
-import { isNotebookCell } from '../common/utils/misc';
-import { IServiceContainer } from '../ioc/types';
-import { traceError } from '../logging';
-import { getTempFileWithDocumentContents, getTextEditsFromPatch } from './../common/editor';
-import { IFormatterHelper } from './types';
-import { IInstallFormatterPrompt } from '../providers/prompts/types';
-
-export abstract class BaseFormatter {
- protected readonly workspace: IWorkspaceService;
- private readonly helper: IFormatterHelper;
- private errorShown: boolean = false;
-
- constructor(public Id: string, private product: Product, protected serviceContainer: IServiceContainer) {
- this.helper = serviceContainer.get(IFormatterHelper);
- this.workspace = serviceContainer.get(IWorkspaceService);
- }
-
- 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 = this.workspace.getWorkspaceFolder(document.uri);
- if (workspaceFolder) {
- return workspaceFolder.uri;
- }
- const folders = this.workspace.workspaceFolders;
- if (Array.isArray(folders) && folders.length > 0) {
- return folders[0].uri;
- }
- return vscode.Uri.file(__dirname);
- }
- protected async provideDocumentFormattingEdits(
- document: vscode.TextDocument,
- _options: vscode.FormattingOptions,
- token: vscode.CancellationToken,
- args: string[],
- cwd?: string,
- ): Promise {
- 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.
- // Yet 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.
- // Also, always create temp files for Notebook cells.
- const tempFile = await this.createTempFile(document);
- if (this.checkCancellation(document.fileName, tempFile, token)) {
- return [];
- }
-
- const executionInfo = this.helper.getExecutionInfo(this.product, args, document.uri);
- executionInfo.args.push(tempFile);
- const pythonToolsExecutionService = this.serviceContainer.get(
- IPythonToolExecutionService,
- );
- const promise = pythonToolsExecutionService
- .exec(executionInfo, { cwd, throwOnStdErr: false, token }, document.uri)
- .then((output) => output.stdout)
- .then((data) => {
- if (this.checkCancellation(document.fileName, tempFile, token)) {
- return [] as vscode.TextEdit[];
- }
- return getTextEditsFromPatch(document.getText(), data);
- })
- .catch((error) => {
- if (this.checkCancellation(document.fileName, tempFile, token)) {
- return [] as vscode.TextEdit[];
- }
-
- this.handleError(this.Id, error, document.uri).catch(() => {});
- return [] as vscode.TextEdit[];
- })
- .then((edits) => {
- this.deleteTempFile(document.fileName, tempFile).ignoreErrors();
- return edits;
- });
-
- const appShell = this.serviceContainer.get(IApplicationShell);
- const disposableRegistry = this.serviceContainer.get(IDisposableRegistry);
- const disposable = appShell.setStatusBarMessage(`Formatting with ${this.Id}`, promise);
- disposableRegistry.push(disposable);
- return promise;
- }
-
- protected async handleError(_expectedFileName: string, error: Error, resource?: vscode.Uri) {
- if (isNotInstalledError(error)) {
- const prompt = this.serviceContainer.get(IInstallFormatterPrompt);
- if (!(await prompt.showInstallFormatterPrompt(resource))) {
- const installer = this.serviceContainer.get(IInstaller);
- const isInstalled = await installer.isInstalled(this.product, resource);
- if (!isInstalled && !this.errorShown) {
- traceError(
- `\nPlease install '${this.Id}' into your environment.`,
- "\nIf you don't want to use it you can turn it off or use another formatter in the settings.",
- );
- this.errorShown = true;
- }
- }
- }
-
- traceError(`Formatting with ${this.Id} failed:\n${error}`);
- }
-
- /**
- * Always create a temporary file when formatting notebook cells.
- * This is because there is no physical file associated with notebook cells (they are all virtual).
- */
- private async createTempFile(document: vscode.TextDocument): Promise {
- const fs = this.serviceContainer.get(IFileSystem);
- return document.isDirty || isNotebookCell(document)
- ? getTempFileWithDocumentContents(document, fs)
- : document.fileName;
- }
-
- private deleteTempFile(originalFile: string, tempFile: string): Promise {
- if (originalFile !== tempFile) {
- const fs = this.serviceContainer.get(IFileSystem);
- return fs.deleteFile(tempFile);
- }
- return Promise.resolve();
- }
-
- private checkCancellation(originalFile: string, tempFile: string, token?: vscode.CancellationToken): boolean {
- if (token && token.isCancellationRequested) {
- this.deleteTempFile(originalFile, tempFile).ignoreErrors();
- return true;
- }
- return false;
- }
-}
diff --git a/extensions/positron-python/src/client/formatters/blackFormatter.ts b/extensions/positron-python/src/client/formatters/blackFormatter.ts
deleted file mode 100644
index 0a8109e163e..00000000000
--- a/extensions/positron-python/src/client/formatters/blackFormatter.ts
+++ /dev/null
@@ -1,55 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-'use strict';
-
-import * as path from 'path';
-import * as vscode from 'vscode';
-import { IApplicationShell } from '../common/application/types';
-import { Product } from '../common/installer/productInstaller';
-import { IConfigurationService } from '../common/types';
-import { noop } from '../common/utils/misc';
-import { StopWatch } from '../common/utils/stopWatch';
-import { IServiceContainer } from '../ioc/types';
-import { sendTelemetryWhenDone } from '../telemetry';
-import { EventName } from '../telemetry/constants';
-import { BaseFormatter } from './baseFormatter';
-
-export class BlackFormatter extends BaseFormatter {
- constructor(serviceContainer: IServiceContainer) {
- super('black', Product.black, serviceContainer);
- }
-
- public async formatDocument(
- document: vscode.TextDocument,
- options: vscode.FormattingOptions,
- token: vscode.CancellationToken,
- range?: vscode.Range,
- ): Promise {
- const stopWatch = new StopWatch();
- const settings = this.serviceContainer
- .get(IConfigurationService)
- .getSettings(document.uri);
- const hasCustomArgs = Array.isArray(settings.formatting.blackArgs) && settings.formatting.blackArgs.length > 0;
- const formatSelection = range ? !range.isEmpty : false;
-
- if (formatSelection) {
- const shell = this.serviceContainer.get(IApplicationShell);
- // Black does not support partial formatting on purpose.
- shell
- .showErrorMessage(vscode.l10n.t('Black does not support the "Format Selection" command'))
- .then(noop, noop);
- return [];
- }
-
- const blackArgs = ['--diff', '--quiet'];
-
- if (path.extname(document.fileName) === '.pyi') {
- blackArgs.push('--pyi');
- }
-
- const promise = super.provideDocumentFormattingEdits(document, options, token, blackArgs);
- sendTelemetryWhenDone(EventName.FORMAT, promise, stopWatch, { tool: 'black', hasCustomArgs, formatSelection });
- return promise;
- }
-}
diff --git a/extensions/positron-python/src/client/formatters/dummyFormatter.ts b/extensions/positron-python/src/client/formatters/dummyFormatter.ts
deleted file mode 100644
index b4fdba9fbc0..00000000000
--- a/extensions/positron-python/src/client/formatters/dummyFormatter.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import * as vscode from 'vscode';
-import { Product } from '../common/types';
-import { IServiceContainer } from '../ioc/types';
-import { BaseFormatter } from './baseFormatter';
-
-export class DummyFormatter extends BaseFormatter {
- constructor(serviceContainer: IServiceContainer) {
- super('none', Product.yapf, serviceContainer);
- }
-
- public formatDocument(
- _document: vscode.TextDocument,
- _options: vscode.FormattingOptions,
- _token: vscode.CancellationToken,
- _range?: vscode.Range,
- ): Thenable {
- return Promise.resolve([]);
- }
-}
diff --git a/extensions/positron-python/src/client/formatters/helper.ts b/extensions/positron-python/src/client/formatters/helper.ts
deleted file mode 100644
index ac305b51e78..00000000000
--- a/extensions/positron-python/src/client/formatters/helper.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-import { inject, injectable } from 'inversify';
-import * as path from 'path';
-import { Uri } from 'vscode';
-import { ExecutionInfo, IConfigurationService, IFormattingSettings, Product } from '../common/types';
-import { IServiceContainer } from '../ioc/types';
-import { FormatterId, FormatterSettingsPropertyNames, IFormatterHelper } from './types';
-
-@injectable()
-export class FormatterHelper implements IFormatterHelper {
- constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer) {}
- public translateToId(formatter: Product): FormatterId {
- switch (formatter) {
- case Product.autopep8:
- return 'autopep8';
- case Product.black:
- return 'black';
- case Product.yapf:
- return 'yapf';
- default: {
- throw new Error(`Unrecognized Formatter '${formatter}'`);
- }
- }
- }
- public getSettingsPropertyNames(formatter: Product): FormatterSettingsPropertyNames {
- const id = this.translateToId(formatter);
- return {
- argsName: `${id}Args` as keyof IFormattingSettings,
- pathName: `${id}Path` as keyof IFormattingSettings,
- };
- }
- public getExecutionInfo(formatter: Product, customArgs: string[], resource?: Uri): ExecutionInfo {
- const settings = this.serviceContainer.get(IConfigurationService).getSettings(resource);
- const names = this.getSettingsPropertyNames(formatter);
-
- const execPath = settings.formatting[names.pathName] as string;
- let args: string[] = Array.isArray(settings.formatting[names.argsName])
- ? (settings.formatting[names.argsName] as string[])
- : [];
- args = args.concat(customArgs);
-
- let moduleName: string | undefined;
-
- // If path information is not available, then treat it as a module,
- if (path.basename(execPath) === execPath) {
- moduleName = execPath;
- }
-
- return { execPath, moduleName, args, product: formatter };
- }
-}
diff --git a/extensions/positron-python/src/client/formatters/serviceRegistry.ts b/extensions/positron-python/src/client/formatters/serviceRegistry.ts
deleted file mode 100644
index 196e6c806b5..00000000000
--- a/extensions/positron-python/src/client/formatters/serviceRegistry.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-import { IServiceManager } from '../ioc/types';
-import { FormatterHelper } from './helper';
-import { IFormatterHelper } from './types';
-
-export function registerTypes(serviceManager: IServiceManager) {
- serviceManager.addSingleton(IFormatterHelper, FormatterHelper);
-}
diff --git a/extensions/positron-python/src/client/formatters/types.ts b/extensions/positron-python/src/client/formatters/types.ts
deleted file mode 100644
index 7f4bcf5b752..00000000000
--- a/extensions/positron-python/src/client/formatters/types.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-import { Uri } from 'vscode';
-import { ExecutionInfo, IFormattingSettings, Product } from '../common/types';
-
-export const IFormatterHelper = Symbol('IFormatterHelper');
-
-export type FormatterId = 'autopep8' | 'black' | 'yapf';
-
-export type FormatterSettingsPropertyNames = {
- argsName: keyof IFormattingSettings;
- pathName: keyof IFormattingSettings;
-};
-
-export interface IFormatterHelper {
- translateToId(formatter: Product): FormatterId;
- getSettingsPropertyNames(formatter: Product): FormatterSettingsPropertyNames;
- getExecutionInfo(formatter: Product, customArgs: string[], resource?: Uri): ExecutionInfo;
-}
diff --git a/extensions/positron-python/src/client/formatters/yapfFormatter.ts b/extensions/positron-python/src/client/formatters/yapfFormatter.ts
deleted file mode 100644
index 08729a97694..00000000000
--- a/extensions/positron-python/src/client/formatters/yapfFormatter.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-import * as vscode from 'vscode';
-import { IConfigurationService, Product } from '../common/types';
-import { StopWatch } from '../common/utils/stopWatch';
-import { IServiceContainer } from '../ioc/types';
-import { sendTelemetryWhenDone } from '../telemetry';
-import { EventName } from '../telemetry/constants';
-import { BaseFormatter } from './baseFormatter';
-
-export class YapfFormatter extends BaseFormatter {
- constructor(serviceContainer: IServiceContainer) {
- super('yapf', Product.yapf, serviceContainer);
- }
-
- public formatDocument(
- document: vscode.TextDocument,
- options: vscode.FormattingOptions,
- token: vscode.CancellationToken,
- range?: vscode.Range,
- ): Thenable {
- const stopWatch = new StopWatch();
- const settings = this.serviceContainer
- .get(IConfigurationService)
- .getSettings(document.uri);
- const hasCustomArgs = Array.isArray(settings.formatting.yapfArgs) && settings.formatting.yapfArgs.length > 0;
- const formatSelection = range ? !range.isEmpty : false;
-
- const yapfArgs = ['--diff'];
- if (formatSelection && range !== undefined) {
- yapfArgs.push(...['--lines', `${range.start.line + 1}-${range.end.line + 1}`]);
- }
- // Yapf starts looking for config file starting from the file path.
- const fallbarFolder = this.getWorkspaceUri(document).fsPath;
- const cwd = this.getDocumentPath(document, fallbarFolder);
- const promise = super.provideDocumentFormattingEdits(document, options, token, yapfArgs, cwd);
- sendTelemetryWhenDone(EventName.FORMAT, promise, stopWatch, { tool: 'yapf', hasCustomArgs, formatSelection });
- return promise;
- }
-}
diff --git a/extensions/positron-python/src/client/interpreter/activation/service.ts b/extensions/positron-python/src/client/interpreter/activation/service.ts
index 02d621c0ccd..f97545a5823 100644
--- a/extensions/positron-python/src/client/interpreter/activation/service.ts
+++ b/extensions/positron-python/src/client/interpreter/activation/service.ts
@@ -329,7 +329,15 @@ export class EnvironmentActivationService implements IEnvironmentActivationServi
}
if (result.stderr) {
if (returnedEnv) {
- traceWarn('Got env variables but with errors', result.stderr);
+ traceWarn('Got env variables but with errors', result.stderr, returnedEnv);
+ if (
+ result.stderr.includes('running scripts is disabled') ||
+ result.stderr.includes('FullyQualifiedErrorId : UnauthorizedAccess')
+ ) {
+ throw new Error(
+ `Skipping returned result when powershell execution is disabled, stderr ${result.stderr} for ${command}`,
+ );
+ }
} else {
throw new Error(`StdErr from ShellExec, ${result.stderr} for ${command}`);
}
diff --git a/extensions/positron-python/src/client/interpreter/activation/types.ts b/extensions/positron-python/src/client/interpreter/activation/types.ts
index 2b364cbeb86..e00ef9b62b3 100644
--- a/extensions/positron-python/src/client/interpreter/activation/types.ts
+++ b/extensions/positron-python/src/client/interpreter/activation/types.ts
@@ -21,11 +21,3 @@ export interface IEnvironmentActivationService {
interpreter?: PythonEnvironment,
): Promise;
}
-
-export const ITerminalEnvVarCollectionService = Symbol('ITerminalEnvVarCollectionService');
-export interface ITerminalEnvVarCollectionService {
- /**
- * Returns true if we know with high certainity the terminal prompt is set correctly for a particular resource.
- */
- isTerminalPromptSetCorrectly(resource?: Resource): boolean;
-}
diff --git a/extensions/positron-python/src/client/interpreter/configuration/environmentTypeComparer.ts b/extensions/positron-python/src/client/interpreter/configuration/environmentTypeComparer.ts
index 0631bb594bf..2b1c57c4531 100644
--- a/extensions/positron-python/src/client/interpreter/configuration/environmentTypeComparer.ts
+++ b/extensions/positron-python/src/client/interpreter/configuration/environmentTypeComparer.ts
@@ -234,6 +234,10 @@ export function getEnvLocationHeuristic(environment: PythonEnvironment, workspac
* Compare 2 environment types: return 0 if they are the same, -1 if a comes before b, 1 otherwise.
*/
function compareEnvironmentType(a: PythonEnvironment, b: PythonEnvironment): number {
+ if (!a.type && !b.type) {
+ // Return 0 if two global interpreters are being compared.
+ return 0;
+ }
const envTypeByPriority = getPrioritizedEnvironmentType();
return Math.sign(envTypeByPriority.indexOf(a.envType) - envTypeByPriority.indexOf(b.envType));
}
diff --git a/extensions/positron-python/src/client/interpreter/serviceRegistry.ts b/extensions/positron-python/src/client/interpreter/serviceRegistry.ts
index 018e7abfdc4..422776bd5e4 100644
--- a/extensions/positron-python/src/client/interpreter/serviceRegistry.ts
+++ b/extensions/positron-python/src/client/interpreter/serviceRegistry.ts
@@ -6,9 +6,7 @@
import { IExtensionActivationService, IExtensionSingleActivationService } from '../activation/types';
import { IServiceManager } from '../ioc/types';
import { EnvironmentActivationService } from './activation/service';
-import { TerminalEnvVarCollectionPrompt } from './activation/terminalEnvVarCollectionPrompt';
-import { TerminalEnvVarCollectionService } from './activation/terminalEnvVarCollectionService';
-import { IEnvironmentActivationService, ITerminalEnvVarCollectionService } from './activation/types';
+import { IEnvironmentActivationService } from './activation/types';
import { InterpreterAutoSelectionService } from './autoSelection/index';
import { InterpreterAutoSelectionProxyService } from './autoSelection/proxy';
import { IInterpreterAutoSelectionService, IInterpreterAutoSelectionProxyService } from './autoSelection/types';
@@ -110,13 +108,4 @@ export function registerTypes(serviceManager: IServiceManager): void {
IEnvironmentActivationService,
EnvironmentActivationService,
);
- serviceManager.addSingleton(
- ITerminalEnvVarCollectionService,
- TerminalEnvVarCollectionService,
- );
- serviceManager.addBinding(ITerminalEnvVarCollectionService, IExtensionActivationService);
- serviceManager.addSingleton(
- IExtensionSingleActivationService,
- TerminalEnvVarCollectionPrompt,
- );
}
diff --git a/extensions/positron-python/src/client/interpreter/virtualEnvs/activatedEnvLaunch.ts b/extensions/positron-python/src/client/interpreter/virtualEnvs/activatedEnvLaunch.ts
index 468c2dc72a0..b4dcfe36e09 100644
--- a/extensions/positron-python/src/client/interpreter/virtualEnvs/activatedEnvLaunch.ts
+++ b/extensions/positron-python/src/client/interpreter/virtualEnvs/activatedEnvLaunch.ts
@@ -91,7 +91,10 @@ export class ActivatedEnvironmentLaunch implements IActivatedEnvironmentLaunch {
}
if (process.env.VSCODE_CLI !== '1') {
// We only want to select the interpreter if VS Code was launched from the command line.
- traceVerbose('VS Code was not launched from the command line, not selecting activated interpreter');
+ traceVerbose(
+ 'VS Code was not launched from the command line, not selecting activated interpreter',
+ JSON.stringify(process.env, undefined, 4),
+ );
return undefined;
}
const prefix = await this.getPrefixOfSelectedActivatedEnv();
diff --git a/extensions/positron-python/src/client/linters/bandit.ts b/extensions/positron-python/src/client/linters/bandit.ts
deleted file mode 100644
index bbc8836bfc6..00000000000
--- a/extensions/positron-python/src/client/linters/bandit.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-'use strict';
-
-import { CancellationToken, TextDocument } from 'vscode';
-import '../common/extensions';
-import { Product } from '../common/types';
-import { IServiceContainer } from '../ioc/types';
-import { BaseLinter } from './baseLinter';
-import { ILintMessage, LintMessageSeverity } from './types';
-
-const severityMapping: Record = {
- LOW: LintMessageSeverity.Information,
- MEDIUM: LintMessageSeverity.Warning,
- HIGH: LintMessageSeverity.Error,
-};
-
-export const BANDIT_REGEX =
- '(?\\d+),(?(col)?(\\d+)?),(?\\w+),(?\\w+\\d+):(?.*)\\r?(\\n|$)';
-
-export class Bandit extends BaseLinter {
- constructor(serviceContainer: IServiceContainer) {
- super(Product.bandit, serviceContainer);
- }
-
- protected async runLinter(document: TextDocument, cancellation: CancellationToken): Promise {
- // View all errors in bandit <= 1.5.1 (https://github.com/PyCQA/bandit/issues/371)
- const messages = await this.run([document.uri.fsPath], document, cancellation, BANDIT_REGEX);
-
- messages.forEach((msg) => {
- msg.severity = severityMapping[msg.type];
- });
- return messages;
- }
-}
diff --git a/extensions/positron-python/src/client/linters/baseLinter.ts b/extensions/positron-python/src/client/linters/baseLinter.ts
deleted file mode 100644
index bb24bee1637..00000000000
--- a/extensions/positron-python/src/client/linters/baseLinter.ts
+++ /dev/null
@@ -1,229 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-'use strict';
-
-import * as path from 'path';
-import * as vscode from 'vscode';
-import { IWorkspaceService } from '../common/application/types';
-import { isTestExecution } from '../common/constants';
-import '../common/extensions';
-import { IPythonToolExecutionService } from '../common/process/types';
-import { splitLines } from '../common/stringUtils';
-import {
- ExecutionInfo,
- Flake8CategorySeverity,
- IConfigurationService,
- IMypyCategorySeverity,
- IPycodestyleCategorySeverity,
- IPylintCategorySeverity,
- IPythonSettings,
- Product,
-} from '../common/types';
-import { IServiceContainer } from '../ioc/types';
-import { traceError, traceLog } from '../logging';
-import { ErrorHandler } from './errorHandlers/errorHandler';
-import { ILinter, ILinterInfo, ILinterManager, ILintMessage, LinterId, LintMessageSeverity } from './types';
-
-const namedRegexp = require('named-js-regexp');
-// Allow negative column numbers (https://github.com/PyCQA/pylint/issues/1822)
-// Allow codes with more than one letter (i.e. ABC123)
-const REGEX = '(?\\d+),(?-?\\d+),(?\\w+),(?\\w+\\d+):(?.*)\\r?(\\n|$)';
-
-interface IRegexGroup {
- line: number;
- column: number;
- code: string;
- message: string;
- type: string;
-}
-
-function matchNamedRegEx(data: string, regex: string): IRegexGroup | undefined {
- const compiledRegexp = namedRegexp(regex, 'g');
- const rawMatch = compiledRegexp.exec(data);
- if (rawMatch !== null) {
- return rawMatch.groups();
- }
-
- return undefined;
-}
-
-export function parseLine(line: string, regex: string, linterID: LinterId, colOffset = 0): ILintMessage | undefined {
- const match = matchNamedRegEx(line, regex)!;
- if (!match) {
- return undefined;
- }
-
- match.line = Number(match.line);
-
- match.column = Number(match.column);
-
- return {
- code: match.code,
- message: match.message,
- column: Number.isNaN(match.column) || match.column <= 0 ? 0 : match.column - colOffset,
- line: match.line,
- type: match.type,
- provider: linterID,
- };
-}
-
-export abstract class BaseLinter implements ILinter {
- protected readonly configService: IConfigurationService;
-
- private errorHandler: ErrorHandler;
-
- private _pythonSettings!: IPythonSettings;
-
- private _info: ILinterInfo;
-
- private workspace: IWorkspaceService;
-
- protected get pythonSettings(): IPythonSettings {
- return this._pythonSettings;
- }
-
- constructor(
- product: Product,
- protected readonly serviceContainer: IServiceContainer,
- protected readonly columnOffset = 0,
- ) {
- this._info = serviceContainer.get(ILinterManager).getLinterInfo(product);
- this.errorHandler = new ErrorHandler(this.info.product, serviceContainer);
- this.configService = serviceContainer.get(IConfigurationService);
- this.workspace = serviceContainer.get(IWorkspaceService);
- }
-
- public get info(): ILinterInfo {
- return this._info;
- }
-
- public async lint(document: vscode.TextDocument, cancellation: vscode.CancellationToken): Promise {
- this._pythonSettings = this.configService.getSettings(document.uri);
- return this.runLinter(document, cancellation);
- }
-
- protected getWorkspaceRootPath(document: vscode.TextDocument): string {
- const workspaceFolder = this.workspace.getWorkspaceFolder(document.uri);
- const workspaceRootPath =
- workspaceFolder && typeof workspaceFolder.uri.fsPath === 'string' ? workspaceFolder.uri.fsPath : undefined;
- return typeof workspaceRootPath === 'string' ? workspaceRootPath : path.dirname(document.uri.fsPath);
- }
-
- protected getWorkingDirectoryPath(document: vscode.TextDocument): string {
- return this._pythonSettings.linting.cwd || this.getWorkspaceRootPath(document);
- }
-
- protected abstract runLinter(
- document: vscode.TextDocument,
- cancellation: vscode.CancellationToken,
- ): Promise;
-
- // eslint-disable-next-line class-methods-use-this
- protected parseMessagesSeverity(
- error: string,
- categorySeverity:
- | Flake8CategorySeverity
- | IMypyCategorySeverity
- | IPycodestyleCategorySeverity
- | IPylintCategorySeverity,
- ): LintMessageSeverity {
- const severity = error as keyof typeof categorySeverity;
-
- if (categorySeverity[severity]) {
- const severityName = categorySeverity[severity];
- switch (severityName) {
- case 'Error':
- return LintMessageSeverity.Error;
- case 'Hint':
- return LintMessageSeverity.Hint;
- case 'Information':
- return LintMessageSeverity.Information;
- case 'Warning':
- return LintMessageSeverity.Warning;
- default: {
- if (LintMessageSeverity[severityName]) {
- return (LintMessageSeverity[severityName] as unknown) as LintMessageSeverity;
- }
- }
- }
- }
- return LintMessageSeverity.Information;
- }
-
- protected async run(
- args: string[],
- document: vscode.TextDocument,
- cancellation: vscode.CancellationToken,
- regEx: string = REGEX,
- ): Promise {
- if (!this.info.isEnabled(document.uri)) {
- return [];
- }
- const executionInfo = this.info.getExecutionInfo(args, document.uri);
- const cwd = this.getWorkingDirectoryPath(document);
- const pythonToolsExecutionService = this.serviceContainer.get(
- IPythonToolExecutionService,
- );
- try {
- const result = await pythonToolsExecutionService.execForLinter(
- executionInfo,
- { cwd, token: cancellation, mergeStdOutErr: false },
- document.uri,
- );
- this.displayLinterResultHeader(result.stdout);
- return await this.parseMessages(result.stdout, document, cancellation, regEx);
- } catch (error) {
- await this.handleError(error as Error, document.uri, executionInfo);
- return [];
- }
- }
-
- protected async parseMessages(
- output: string,
- _document: vscode.TextDocument,
- _token: vscode.CancellationToken,
- regEx: string,
- ): Promise {
- const outputLines = splitLines(output, { removeEmptyEntries: false, trim: false });
- return this.parseLines(outputLines, regEx);
- }
-
- protected async handleError(error: Error, resource: vscode.Uri, execInfo: ExecutionInfo): Promise {
- if (isTestExecution()) {
- this.errorHandler.handleError(error, resource, execInfo).ignoreErrors();
- } else {
- this.errorHandler
- .handleError(error, resource, execInfo)
- .catch((ex) => traceError('Error in errorHandler.handleError', ex))
- .ignoreErrors();
- }
- }
-
- private parseLine(line: string, regEx: string): ILintMessage | undefined {
- return parseLine(line, regEx, this.info.id, this.columnOffset);
- }
-
- private parseLines(outputLines: string[], regEx: string): ILintMessage[] {
- const messages: ILintMessage[] = [];
- for (const line of outputLines) {
- try {
- const msg = this.parseLine(line, regEx);
- if (msg) {
- messages.push(msg);
- if (messages.length >= this.pythonSettings.linting.maxNumberOfProblems) {
- break;
- }
- }
- } catch (ex) {
- traceError(`Linter '${this.info.id}' failed to parse the line '${line}.`, ex);
- }
- }
- return messages;
- }
-
- private displayLinterResultHeader(data: string) {
- traceLog(`${'#'.repeat(10)}Linting Output - ${this.info.id}${'#'.repeat(10)}\n`);
- traceLog(data);
- }
-}
diff --git a/extensions/positron-python/src/client/linters/constants.ts b/extensions/positron-python/src/client/linters/constants.ts
deleted file mode 100644
index 27b7c80db7f..00000000000
--- a/extensions/positron-python/src/client/linters/constants.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-'use strict';
-
-import { Product } from '../common/types';
-import { LinterId } from './types';
-
-// All supported linters must be in this map.
-export const LINTERID_BY_PRODUCT = new Map([
- [Product.bandit, LinterId.Bandit],
- [Product.flake8, LinterId.Flake8],
- [Product.pylint, LinterId.PyLint],
- [Product.mypy, LinterId.MyPy],
- [Product.pycodestyle, LinterId.PyCodeStyle],
- [Product.prospector, LinterId.Prospector],
- [Product.pydocstyle, LinterId.PyDocStyle],
- [Product.pylama, LinterId.PyLama],
-]);
diff --git a/extensions/positron-python/src/client/linters/errorHandlers/baseErrorHandler.ts b/extensions/positron-python/src/client/linters/errorHandlers/baseErrorHandler.ts
deleted file mode 100644
index 16c5e93ae01..00000000000
--- a/extensions/positron-python/src/client/linters/errorHandlers/baseErrorHandler.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-import { Uri } from 'vscode';
-import { ExecutionInfo, IInstaller, Product } from '../../common/types';
-import { IServiceContainer } from '../../ioc/types';
-import { IErrorHandler } from '../types';
-
-export abstract class BaseErrorHandler implements IErrorHandler {
- protected installer: IInstaller;
-
- private handler?: IErrorHandler;
-
- constructor(protected product: Product, protected serviceContainer: IServiceContainer) {
- this.installer = this.serviceContainer.get(IInstaller);
- }
-
- protected get nextHandler(): IErrorHandler | undefined {
- return this.handler;
- }
-
- public setNextHandler(handler: IErrorHandler): void {
- this.handler = handler;
- }
-
- public abstract handleError(error: Error, resource: Uri, execInfo: ExecutionInfo): Promise;
-}
diff --git a/extensions/positron-python/src/client/linters/errorHandlers/errorHandler.ts b/extensions/positron-python/src/client/linters/errorHandlers/errorHandler.ts
deleted file mode 100644
index af28dd61c3a..00000000000
--- a/extensions/positron-python/src/client/linters/errorHandlers/errorHandler.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import { Uri } from 'vscode';
-import { ExecutionInfo, Product } from '../../common/types';
-import { IServiceContainer } from '../../ioc/types';
-import { IErrorHandler } from '../types';
-import { BaseErrorHandler } from './baseErrorHandler';
-import { StandardErrorHandler } from './standard';
-
-export class ErrorHandler implements IErrorHandler {
- private handler: BaseErrorHandler;
-
- constructor(product: Product, serviceContainer: IServiceContainer) {
- this.handler = new StandardErrorHandler(product, serviceContainer);
- }
-
- public handleError(error: Error, resource: Uri, execInfo: ExecutionInfo): Promise {
- return this.handler.handleError(error, resource, execInfo);
- }
-}
diff --git a/extensions/positron-python/src/client/linters/errorHandlers/standard.ts b/extensions/positron-python/src/client/linters/errorHandlers/standard.ts
deleted file mode 100644
index 6367da7abe4..00000000000
--- a/extensions/positron-python/src/client/linters/errorHandlers/standard.ts
+++ /dev/null
@@ -1,55 +0,0 @@
-import { l10n, Uri } from 'vscode';
-import { IApplicationShell } from '../../common/application/types';
-import { ExecutionInfo, ILogOutputChannel } from '../../common/types';
-import { traceError, traceLog } from '../../logging';
-import { ILinterManager, LinterId } from '../types';
-import { BaseErrorHandler } from './baseErrorHandler';
-
-export class StandardErrorHandler extends BaseErrorHandler {
- public async handleError(error: Error, resource: Uri, execInfo: ExecutionInfo): Promise {
- if (
- typeof error === 'string' &&
- (error as string).includes("OSError: [Errno 2] No such file or directory: '/")
- ) {
- return this.nextHandler ? this.nextHandler.handleError(error, resource, execInfo) : Promise.resolve(false);
- }
-
- const linterManager = this.serviceContainer.get(ILinterManager);
- const info = linterManager.getLinterInfo(execInfo.product!);
-
- traceError(`There was an error in running the linter ${info.id}`, error);
- if (info.id === LinterId.PyLint) {
- traceError('Support for "pylint" is moved to ms-python.pylint extension.');
- traceError(
- 'Please install the extension from: https://marketplace.visualstudio.com/items?itemName=ms-python.pylint',
- );
- } else if (info.id === LinterId.Flake8) {
- traceError('Support for "flake8" is moved to ms-python.flake8 extension.');
- traceError(
- 'Please install the extension from: https://marketplace.visualstudio.com/items?itemName=ms-python.flake8',
- );
- } else if (info.id === LinterId.MyPy) {
- traceError('Support for "mypy" is moved to ms-python.mypy-type-checker extension.');
- traceError(
- 'Please install the extension from: https://marketplace.visualstudio.com/items?itemName=ms-python.mypy-type-checker',
- );
- }
- traceError(`If the error is due to missing ${info.id}, please install ${info.id} using pip manually.`);
- traceError('Learn more here: https://aka.ms/AAlgvkb');
- traceLog(`Linting with ${info.id} failed.`);
- traceLog(error.toString());
-
- this.displayLinterError(info.id).ignoreErrors();
- return true;
- }
-
- private async displayLinterError(linterId: LinterId) {
- const message = l10n.t("There was an error in running the linter '{0}'", linterId);
- const appShell = this.serviceContainer.get(IApplicationShell);
- const outputChannel = this.serviceContainer.get(ILogOutputChannel);
- const action = await appShell.showErrorMessage(message, 'View Errors');
- if (action === 'View Errors') {
- outputChannel.show();
- }
- }
-}
diff --git a/extensions/positron-python/src/client/linters/flake8.ts b/extensions/positron-python/src/client/linters/flake8.ts
deleted file mode 100644
index e79d0915874..00000000000
--- a/extensions/positron-python/src/client/linters/flake8.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-import { CancellationToken, TextDocument } from 'vscode';
-import '../common/extensions';
-import { Product } from '../common/types';
-import { IServiceContainer } from '../ioc/types';
-import { traceLog } from '../logging';
-import { BaseLinter } from './baseLinter';
-import { isExtensionEnabled } from './prompts/common';
-import { FLAKE8_EXTENSION } from './prompts/flake8Prompt';
-import { IToolsExtensionPrompt } from './prompts/types';
-import { ILintMessage } from './types';
-
-const COLUMN_OFF_SET = 1;
-
-export class Flake8 extends BaseLinter {
- constructor(serviceContainer: IServiceContainer, private readonly prompt: IToolsExtensionPrompt) {
- super(Product.flake8, serviceContainer, COLUMN_OFF_SET);
- }
-
- protected async runLinter(document: TextDocument, cancellation: CancellationToken): Promise {
- await this.prompt.showPrompt();
-
- if (isExtensionEnabled(this.serviceContainer, FLAKE8_EXTENSION)) {
- traceLog(
- 'LINTING: Skipping linting from Python extension, since Flake8 extension is installed and enabled.',
- );
- return [];
- }
-
- const messages = await this.run([document.uri.fsPath], document, cancellation);
- messages.forEach((msg) => {
- msg.severity = this.parseMessagesSeverity(msg.type, this.pythonSettings.linting.flake8CategorySeverity);
- // flake8 uses 0th line for some file-wide problems
- // but diagnostics expects positive line numbers.
- if (msg.line === 0) {
- msg.line = 1;
- }
- });
- return messages;
- }
-}
diff --git a/extensions/positron-python/src/client/linters/linterInfo.ts b/extensions/positron-python/src/client/linters/linterInfo.ts
deleted file mode 100644
index 321f23b0f30..00000000000
--- a/extensions/positron-python/src/client/linters/linterInfo.ts
+++ /dev/null
@@ -1,94 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-import * as path from 'path';
-import { Uri } from 'vscode';
-import { linterScript } from '../common/process/internal/scripts';
-import { ExecutionInfo, IConfigurationService, ILintingSettings, Product } from '../common/types';
-import { ILinterInfo, LinterId } from './types';
-
-export class LinterInfo implements ILinterInfo {
- private _id: LinterId;
-
- private _product: Product;
-
- private _configFileNames: string[];
-
- constructor(
- product: Product,
- id: LinterId,
- protected configService: IConfigurationService,
- configFileNames: string[] = [],
- ) {
- this._product = product;
- this._id = id;
- this._configFileNames = configFileNames;
- }
-
- public get id(): LinterId {
- return this._id;
- }
-
- public get product(): Product {
- return this._product;
- }
-
- public get pathSettingName(): string {
- return `${this.id}Path`;
- }
-
- public get argsSettingName(): string {
- return `${this.id}Args`;
- }
-
- public get enabledSettingName(): string {
- return `${this.id}Enabled`;
- }
-
- public get configFileNames(): string[] {
- return this._configFileNames;
- }
-
- public async enableAsync(enabled: boolean, resource?: Uri): Promise {
- return this.configService.updateSetting(`linting.${this.enabledSettingName}`, enabled, resource);
- }
-
- public isEnabled(resource?: Uri): boolean {
- const settings = this.configService.getSettings(resource);
- const name = this.enabledSettingName as keyof ILintingSettings;
- return settings.linting[name] as boolean;
- }
-
- public pathName(resource?: Uri): string {
- const settings = this.configService.getSettings(resource);
- const name = this.pathSettingName as keyof ILintingSettings;
- return settings.linting[name] as string;
- }
-
- public linterArgs(resource?: Uri): string[] {
- const settings = this.configService.getSettings(resource);
- const name = this.argsSettingName as keyof ILintingSettings;
- const args = settings.linting[name];
- return Array.isArray(args) ? (args as string[]) : [];
- }
-
- public getExecutionInfo(customArgs: string[], resource?: Uri): ExecutionInfo {
- const execPath = this.pathName(resource);
- const args = this.linterArgs(resource).concat(customArgs);
- const script = linterScript();
- if (path.basename(execPath) === execPath) {
- return {
- execPath: undefined,
- args: [script, '-m', this.id, ...args],
- product: this.product,
- moduleName: execPath,
- };
- }
- return {
- execPath,
- moduleName: this.id,
- args: [script, '-p', this.id, execPath, ...args],
- product: this.product,
- };
- }
-}
diff --git a/extensions/positron-python/src/client/linters/linterManager.ts b/extensions/positron-python/src/client/linters/linterManager.ts
deleted file mode 100644
index 72c92aa1c77..00000000000
--- a/extensions/positron-python/src/client/linters/linterManager.ts
+++ /dev/null
@@ -1,134 +0,0 @@
-/* eslint-disable max-classes-per-file */
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-'use strict';
-
-import { inject, injectable } from 'inversify';
-import { CancellationToken, TextDocument, Uri } from 'vscode';
-import { IConfigurationService, Product } from '../common/types';
-import { IServiceContainer } from '../ioc/types';
-import { traceError } from '../logging';
-import { Bandit } from './bandit';
-import { Flake8 } from './flake8';
-import { LinterInfo } from './linterInfo';
-import { MyPy } from './mypy';
-import { getOrCreateFlake8Prompt } from './prompts/flake8Prompt';
-import { getOrCreatePylintPrompt } from './prompts/pylintPrompt';
-import { Prospector } from './prospector';
-import { Pycodestyle } from './pycodestyle';
-import { PyDocStyle } from './pydocstyle';
-import { PyLama } from './pylama';
-import { Pylint } from './pylint';
-import { ILinter, ILinterInfo, ILinterManager, ILintMessage, LinterId } from './types';
-
-class DisabledLinter implements ILinter {
- constructor(private configService: IConfigurationService) {}
-
- public get info() {
- return new LinterInfo(Product.pylint, LinterId.PyLint, this.configService);
- }
-
- // eslint-disable-next-line class-methods-use-this
- public async lint(_document: TextDocument, _cancellation: CancellationToken): Promise {
- return [];
- }
-}
-
-@injectable()
-export class LinterManager implements ILinterManager {
- protected linters: ILinterInfo[];
-
- constructor(@inject(IConfigurationService) private configService: IConfigurationService) {
- // Note that we use unit tests to ensure all the linters are here.
- this.linters = [
- new LinterInfo(Product.bandit, LinterId.Bandit, this.configService),
- new LinterInfo(Product.flake8, LinterId.Flake8, this.configService),
- new LinterInfo(Product.pylint, LinterId.PyLint, this.configService, ['pylintrc', '.pylintrc']),
- new LinterInfo(Product.mypy, LinterId.MyPy, this.configService),
- new LinterInfo(Product.pycodestyle, LinterId.PyCodeStyle, this.configService),
- new LinterInfo(Product.prospector, LinterId.Prospector, this.configService),
- new LinterInfo(Product.pydocstyle, LinterId.PyDocStyle, this.configService),
- new LinterInfo(Product.pylama, LinterId.PyLama, this.configService),
- ];
- }
-
- public getAllLinterInfos(): ILinterInfo[] {
- return this.linters;
- }
-
- public getLinterInfo(product: Product): ILinterInfo {
- const x = this.linters.findIndex((value, _index, _obj) => value.product === product);
- if (x >= 0) {
- return this.linters[x];
- }
- throw new Error(`Invalid linter '${Product[product]}'`);
- }
-
- public async isLintingEnabled(resource?: Uri): Promise {
- const settings = this.configService.getSettings(resource);
- const activeLintersPresent = await this.getActiveLinters(resource);
- return settings.linting.enabled && activeLintersPresent.length > 0;
- }
-
- public async enableLintingAsync(enable: boolean, resource?: Uri): Promise {
- await this.configService.updateSetting('linting.enabled', enable, resource);
- }
-
- public async getActiveLinters(resource?: Uri): Promise {
- return this.linters.filter((x) => x.isEnabled(resource));
- }
-
- public async setActiveLintersAsync(products: Product[], resource?: Uri): Promise {
- // ensure we only allow valid linters to be set, otherwise leave things alone.
- // filter out any invalid products:
- const validProducts = products.filter((product) => {
- const foundIndex = this.linters.findIndex((validLinter) => validLinter.product === product);
- return foundIndex !== -1;
- });
-
- // if we have valid linter product(s), enable only those
- if (validProducts.length > 0) {
- const active = await this.getActiveLinters(resource);
- for (const x of active) {
- await x.enableAsync(false, resource);
- }
- if (products.length > 0) {
- const toActivate = this.linters.filter((x) => products.findIndex((p) => x.product === p) >= 0);
- for (const x of toActivate) {
- await x.enableAsync(true, resource);
- }
- await this.enableLintingAsync(true, resource);
- }
- }
- }
-
- public async createLinter(product: Product, serviceContainer: IServiceContainer, resource?: Uri): Promise {
- if (!(await this.isLintingEnabled(resource))) {
- return new DisabledLinter(this.configService);
- }
- const error = 'Linter manager: Unknown linter';
- switch (product) {
- case Product.bandit:
- return new Bandit(serviceContainer);
- case Product.flake8:
- return new Flake8(serviceContainer, getOrCreateFlake8Prompt(serviceContainer));
- case Product.pylint:
- return new Pylint(serviceContainer, getOrCreatePylintPrompt(serviceContainer));
- case Product.mypy:
- return new MyPy(serviceContainer);
- case Product.prospector:
- return new Prospector(serviceContainer);
- case Product.pylama:
- return new PyLama(serviceContainer);
- case Product.pydocstyle:
- return new PyDocStyle(serviceContainer);
- case Product.pycodestyle:
- return new Pycodestyle(serviceContainer);
- default:
- traceError(error);
- break;
- }
- throw new Error(error);
- }
-}
diff --git a/extensions/positron-python/src/client/linters/lintingEngine.ts b/extensions/positron-python/src/client/linters/lintingEngine.ts
deleted file mode 100644
index 2a4bf4e1084..00000000000
--- a/extensions/positron-python/src/client/linters/lintingEngine.ts
+++ /dev/null
@@ -1,209 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-'use strict';
-
-import { inject, injectable } from 'inversify';
-import { Minimatch } from 'minimatch';
-import * as path from 'path';
-import * as vscode from 'vscode';
-import { ICommandManager, IDocumentManager, IWorkspaceService } from '../common/application/types';
-import { Commands } from '../common/constants';
-import { IFileSystem } from '../common/platform/types';
-import { IConfigurationService } from '../common/types';
-import { isNotebookCell, noop } from '../common/utils/misc';
-import { StopWatch } from '../common/utils/stopWatch';
-import { IInterpreterService } from '../interpreter/contracts';
-import { IServiceContainer } from '../ioc/types';
-import { sendTelemetryWhenDone } from '../telemetry';
-import { EventName } from '../telemetry/constants';
-import { LinterTrigger, LintingTelemetry } from '../telemetry/types';
-import { ILinterInfo, ILinterManager, ILintingEngine, ILintMessage, LintMessageSeverity } from './types';
-
-const PYTHON: vscode.DocumentFilter = { language: 'python' };
-
-const lintSeverityToVSSeverity = new Map();
-lintSeverityToVSSeverity.set(LintMessageSeverity.Error, vscode.DiagnosticSeverity.Error);
-lintSeverityToVSSeverity.set(LintMessageSeverity.Hint, vscode.DiagnosticSeverity.Hint);
-lintSeverityToVSSeverity.set(LintMessageSeverity.Information, vscode.DiagnosticSeverity.Information);
-lintSeverityToVSSeverity.set(LintMessageSeverity.Warning, vscode.DiagnosticSeverity.Warning);
-
-@injectable()
-export class LintingEngine implements ILintingEngine {
- private workspace: IWorkspaceService;
-
- private documents: IDocumentManager;
-
- private configurationService: IConfigurationService;
-
- private linterManager: ILinterManager;
-
- private diagnosticCollection: vscode.DiagnosticCollection;
-
- private pendingLintings = new Map();
-
- private fileSystem: IFileSystem;
-
- constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer) {
- this.documents = serviceContainer.get(IDocumentManager);
- this.workspace = serviceContainer.get(IWorkspaceService);
- this.configurationService = serviceContainer.get(IConfigurationService);
- this.linterManager = serviceContainer.get(ILinterManager);
- this.fileSystem = serviceContainer.get(IFileSystem);
- this.diagnosticCollection = vscode.languages.createDiagnosticCollection('python');
- }
-
- public get diagnostics(): vscode.DiagnosticCollection {
- return this.diagnosticCollection;
- }
-
- public clearDiagnostics(document: vscode.TextDocument): void {
- if (this.diagnosticCollection.has(document.uri)) {
- this.diagnosticCollection.delete(document.uri);
- }
- }
-
- public async lintOpenPythonFiles(trigger: LinterTrigger = 'auto'): Promise {
- this.diagnosticCollection.clear();
- const promises = this.documents.textDocuments.map(async (document) => this.lintDocument(document, trigger));
- await Promise.all(promises);
- return this.diagnosticCollection;
- }
-
- public async lintDocument(document: vscode.TextDocument, trigger: LinterTrigger): Promise {
- if (isNotebookCell(document)) {
- return;
- }
- this.diagnosticCollection.set(document.uri, []);
-
- // Check if we need to lint this document
- if (!(await this.shouldLintDocument(document, trigger))) {
- return;
- }
-
- if (this.pendingLintings.has(document.uri.fsPath)) {
- this.pendingLintings.get(document.uri.fsPath)!.cancel();
- this.pendingLintings.delete(document.uri.fsPath);
- }
-
- const cancelToken = new vscode.CancellationTokenSource();
- cancelToken.token.onCancellationRequested(() => {
- if (this.pendingLintings.has(document.uri.fsPath)) {
- this.pendingLintings.delete(document.uri.fsPath);
- }
- });
-
- this.pendingLintings.set(document.uri.fsPath, cancelToken);
-
- const activeLinters = await this.linterManager.getActiveLinters(document.uri);
- const promises: Promise[] = activeLinters.map(async (info: ILinterInfo) => {
- const stopWatch = new StopWatch();
- const linter = await this.linterManager.createLinter(info.product, this.serviceContainer, document.uri);
- const promise = linter.lint(document, cancelToken.token);
- this.sendLinterRunTelemetry(info, document.uri, promise, stopWatch, trigger);
- return promise;
- });
-
- // linters will resolve asynchronously - keep a track of all
- // diagnostics reported as them come in.
- let diagnostics: vscode.Diagnostic[] = [];
- const settings = this.configurationService.getSettings(document.uri);
-
- for (const p of promises) {
- const msgs = await p;
- if (cancelToken.token.isCancellationRequested) {
- break;
- }
-
- if (this.isDocumentOpen(document.uri)) {
- // Build the message and suffix the message with the name of the linter used.
- for (const m of msgs) {
- diagnostics.push(this.createDiagnostics(m, document));
- }
- // Limit the number of messages to the max value.
- diagnostics = diagnostics.filter((_value, index) => index <= settings.linting.maxNumberOfProblems);
- }
- }
- // Set all diagnostics found in this pass, as this method always clears existing diagnostics.
- this.diagnosticCollection.set(document.uri, diagnostics);
- }
-
- // eslint-disable-next-line class-methods-use-this
- private sendLinterRunTelemetry(
- info: ILinterInfo,
- resource: vscode.Uri,
- promise: Promise,
- stopWatch: StopWatch,
- trigger: LinterTrigger,
- ): void {
- const linterExecutablePathName = info.pathName(resource);
- const properties: LintingTelemetry = {
- tool: info.id,
- hasCustomArgs: info.linterArgs(resource).length > 0,
- trigger,
- executableSpecified: linterExecutablePathName !== info.id,
- };
- sendTelemetryWhenDone(EventName.LINTING, promise, stopWatch, properties);
- }
-
- private isDocumentOpen(uri: vscode.Uri): boolean {
- return this.documents.textDocuments.some((document) => document.uri.fsPath === uri.fsPath);
- }
-
- // eslint-disable-next-line class-methods-use-this
- private createDiagnostics(message: ILintMessage, _document: vscode.TextDocument): vscode.Diagnostic {
- const position = new vscode.Position(message.line - 1, message.column);
- let endPosition: vscode.Position = position;
- if (message.endLine && message.endColumn) {
- endPosition = new vscode.Position(message.endLine - 1, message.endColumn);
- }
- const range = new vscode.Range(position, endPosition);
-
- const severity = lintSeverityToVSSeverity.get(message.severity!)!;
- const diagnostic = new vscode.Diagnostic(range, message.message, severity);
- diagnostic.code = message.code;
- diagnostic.source = message.provider;
- return diagnostic;
- }
-
- private async shouldLintDocument(document: vscode.TextDocument, trigger: LinterTrigger): Promise {
- const interpreterService = this.serviceContainer.get(IInterpreterService);
- const interpreter = await interpreterService.getActiveInterpreter(document.uri);
- if (!interpreter && trigger === 'manual') {
- this.serviceContainer
- .get(ICommandManager)
- .executeCommand(Commands.TriggerEnvironmentSelection, document.uri)
- .then(noop, noop);
- return false;
- }
- if (!(await this.linterManager.isLintingEnabled(document.uri))) {
- this.diagnosticCollection.set(document.uri, []);
- return false;
- }
-
- if (document.languageId !== PYTHON.language) {
- return false;
- }
-
- const workspaceFolder = this.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 = this.configurationService.getSettings(document.uri);
- // { dot: true } is important so dirs like `.venv` will be matched by globs
- const ignoreMinmatches = settings.linting.ignorePatterns.map(
- (pattern) => new Minimatch(pattern, { dot: true }),
- );
- if (ignoreMinmatches.some((matcher) => matcher.match(document.fileName) || matcher.match(relativeFileName))) {
- return false;
- }
- if (document.uri.scheme !== 'file' || !document.uri.fsPath) {
- return false;
- }
- return this.fileSystem.fileExists(document.uri.fsPath);
- }
-}
diff --git a/extensions/positron-python/src/client/linters/mypy.ts b/extensions/positron-python/src/client/linters/mypy.ts
deleted file mode 100644
index f39eef99b42..00000000000
--- a/extensions/positron-python/src/client/linters/mypy.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-import { CancellationToken, TextDocument } from 'vscode';
-import '../common/extensions';
-import { escapeRegExp } from 'lodash';
-import { Product } from '../common/types';
-import { IServiceContainer } from '../ioc/types';
-import { BaseLinter } from './baseLinter';
-import { ILintMessage } from './types';
-
-export function getRegex(filepath: string): string {
- return `${escapeRegExp(filepath)}:(?\\d+)(:(?\\d+))?: (?\\w+): (?.*)\\r?(\\n|$)`;
-}
-const COLUMN_OFF_SET = 1;
-
-export class MyPy extends BaseLinter {
- constructor(serviceContainer: IServiceContainer) {
- super(Product.mypy, serviceContainer, COLUMN_OFF_SET);
- }
-
- protected async runLinter(document: TextDocument, cancellation: CancellationToken): Promise {
- const relativeFilePath = document.uri.fsPath.slice(this.getWorkspaceRootPath(document).length + 1);
- const regex = getRegex(relativeFilePath);
- const messages = await this.run([document.uri.fsPath], document, cancellation, regex);
- messages.forEach((msg) => {
- msg.severity = this.parseMessagesSeverity(msg.type, this.pythonSettings.linting.mypyCategorySeverity);
- msg.code = msg.type;
- });
- return messages;
- }
-}
diff --git a/extensions/positron-python/src/client/linters/prompts/common.ts b/extensions/positron-python/src/client/linters/prompts/common.ts
deleted file mode 100644
index ab88282db60..00000000000
--- a/extensions/positron-python/src/client/linters/prompts/common.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-import * as fs from 'fs-extra';
-import * as path from 'path';
-import { ShowToolsExtensionPrompt } from '../../common/experiments/groups';
-import { IExperimentService, IExtensions, IPersistentState, IPersistentStateFactory } from '../../common/types';
-import { IServiceContainer } from '../../ioc/types';
-import { traceLog } from '../../logging';
-
-export function isExtensionDisabled(serviceContainer: IServiceContainer, extensionId: string): boolean {
- const extensions: IExtensions = serviceContainer.get(IExtensions);
- // When debugging the python extension this `extensionPath` below will point to your repo.
- // If you are debugging this feature then set the `extensionPath` to right location after
- // the next line.
- const pythonExt = extensions.getExtension('ms-python.python');
- if (pythonExt) {
- let found = false;
- traceLog(`Extension search path: ${path.dirname(pythonExt.extensionPath)}`);
- fs.readdirSync(path.dirname(pythonExt.extensionPath), { withFileTypes: false }).forEach((s) => {
- if (s.toString().startsWith(extensionId)) {
- found = true;
- }
- });
- return found;
- }
- return false;
-}
-
-/**
- * Detects if extension is installed and enabled.
- */
-export function isExtensionEnabled(serviceContainer: IServiceContainer, extensionId: string): boolean {
- const extensions: IExtensions = serviceContainer.get(IExtensions);
- const extension = extensions.getExtension(extensionId);
- return extension !== undefined;
-}
-
-export function doNotShowPromptState(
- serviceContainer: IServiceContainer,
- promptKey: string,
-): IPersistentState {
- const persistFactory: IPersistentStateFactory = serviceContainer.get(
- IPersistentStateFactory,
- );
- return persistFactory.createWorkspacePersistentState(promptKey, false);
-}
-
-export function inToolsExtensionsExperiment(serviceContainer: IServiceContainer): Promise {
- const experiments: IExperimentService = serviceContainer.get(IExperimentService);
- return experiments.inExperiment(ShowToolsExtensionPrompt.experiment);
-}
diff --git a/extensions/positron-python/src/client/linters/prompts/flake8Prompt.ts b/extensions/positron-python/src/client/linters/prompts/flake8Prompt.ts
deleted file mode 100644
index fa1969df682..00000000000
--- a/extensions/positron-python/src/client/linters/prompts/flake8Prompt.ts
+++ /dev/null
@@ -1,73 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-import { IApplicationEnvironment } from '../../common/application/types';
-import { Common, ToolsExtensions } from '../../common/utils/localize';
-import { executeCommand } from '../../common/vscodeApis/commandApis';
-import { showInformationMessage } from '../../common/vscodeApis/windowApis';
-import { IServiceContainer } from '../../ioc/types';
-import { sendTelemetryEvent } from '../../telemetry';
-import { EventName } from '../../telemetry/constants';
-import { doNotShowPromptState, inToolsExtensionsExperiment, isExtensionDisabled, isExtensionEnabled } from './common';
-import { IToolsExtensionPrompt } from './types';
-
-export const FLAKE8_EXTENSION = 'ms-python.flake8';
-const FLAKE8_PROMPT_DONOTSHOW_KEY = 'showFlake8ExtensionPrompt';
-
-export class Flake8ExtensionPrompt implements IToolsExtensionPrompt {
- private shownThisSession = false;
-
- public constructor(private readonly serviceContainer: IServiceContainer) {}
-
- public async showPrompt(): Promise {
- const isEnabled = isExtensionEnabled(this.serviceContainer, FLAKE8_EXTENSION);
- if (isEnabled || isExtensionDisabled(this.serviceContainer, FLAKE8_EXTENSION)) {
- sendTelemetryEvent(EventName.TOOLS_EXTENSIONS_ALREADY_INSTALLED, undefined, {
- extensionId: FLAKE8_EXTENSION,
- isEnabled,
- });
- return true;
- }
-
- const doNotShow = doNotShowPromptState(this.serviceContainer, FLAKE8_PROMPT_DONOTSHOW_KEY);
- if (this.shownThisSession || doNotShow.value) {
- return false;
- }
-
- if (!(await inToolsExtensionsExperiment(this.serviceContainer))) {
- return false;
- }
-
- this.shownThisSession = true;
- const response = await showInformationMessage(
- ToolsExtensions.flake8PromptMessage,
- ToolsExtensions.installFlake8Extension,
- Common.doNotShowAgain,
- );
-
- if (response === Common.doNotShowAgain) {
- doNotShow.updateValue(true);
- return false;
- }
-
- if (response === ToolsExtensions.installFlake8Extension) {
- const appEnv: IApplicationEnvironment = this.serviceContainer.get(
- IApplicationEnvironment,
- );
- await executeCommand('workbench.extensions.installExtension', FLAKE8_EXTENSION, {
- installPreReleaseVersion: appEnv.extensionChannel === 'insiders',
- });
- return true;
- }
-
- return false;
- }
-}
-
-let _prompt: IToolsExtensionPrompt | undefined;
-export function getOrCreateFlake8Prompt(serviceContainer: IServiceContainer): IToolsExtensionPrompt {
- if (!_prompt) {
- _prompt = new Flake8ExtensionPrompt(serviceContainer);
- }
- return _prompt;
-}
diff --git a/extensions/positron-python/src/client/linters/prompts/pylintPrompt.ts b/extensions/positron-python/src/client/linters/prompts/pylintPrompt.ts
deleted file mode 100644
index 37e58324307..00000000000
--- a/extensions/positron-python/src/client/linters/prompts/pylintPrompt.ts
+++ /dev/null
@@ -1,86 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-import { IApplicationEnvironment } from '../../common/application/types';
-import { Common, ToolsExtensions } from '../../common/utils/localize';
-import { executeCommand } from '../../common/vscodeApis/commandApis';
-import { showInformationMessage } from '../../common/vscodeApis/windowApis';
-import { IServiceContainer } from '../../ioc/types';
-import { sendTelemetryEvent } from '../../telemetry';
-import { EventName } from '../../telemetry/constants';
-import { doNotShowPromptState, inToolsExtensionsExperiment, isExtensionDisabled, isExtensionEnabled } from './common';
-import { IToolsExtensionPrompt } from './types';
-
-export const PYLINT_EXTENSION = 'ms-python.pylint';
-const PYLINT_PROMPT_DONOTSHOW_KEY = 'showPylintExtensionPrompt';
-
-export class PylintExtensionPrompt implements IToolsExtensionPrompt {
- private shownThisSession = false;
-
- public constructor(private readonly serviceContainer: IServiceContainer) {}
-
- public async showPrompt(): Promise {
- const isEnabled = isExtensionEnabled(this.serviceContainer, PYLINT_EXTENSION);
- if (isEnabled || isExtensionDisabled(this.serviceContainer, PYLINT_EXTENSION)) {
- sendTelemetryEvent(EventName.TOOLS_EXTENSIONS_ALREADY_INSTALLED, undefined, {
- extensionId: PYLINT_EXTENSION,
- isEnabled,
- });
- return true;
- }
-
- const doNotShow = doNotShowPromptState(this.serviceContainer, PYLINT_PROMPT_DONOTSHOW_KEY);
- if (this.shownThisSession || doNotShow.value) {
- return false;
- }
-
- if (!(await inToolsExtensionsExperiment(this.serviceContainer))) {
- return false;
- }
-
- sendTelemetryEvent(EventName.TOOLS_EXTENSIONS_PROMPT_SHOWN, undefined, { extensionId: PYLINT_EXTENSION });
- this.shownThisSession = true;
- const response = await showInformationMessage(
- ToolsExtensions.pylintPromptMessage,
- ToolsExtensions.installPylintExtension,
- Common.doNotShowAgain,
- );
-
- if (response === Common.doNotShowAgain) {
- await doNotShow.updateValue(true);
- sendTelemetryEvent(EventName.TOOLS_EXTENSIONS_PROMPT_DISMISSED, undefined, {
- extensionId: PYLINT_EXTENSION,
- dismissType: 'doNotShow',
- });
- return false;
- }
-
- if (response === ToolsExtensions.installPylintExtension) {
- sendTelemetryEvent(EventName.TOOLS_EXTENSIONS_INSTALL_SELECTED, undefined, {
- extensionId: PYLINT_EXTENSION,
- });
- const appEnv: IApplicationEnvironment = this.serviceContainer.get(
- IApplicationEnvironment,
- );
- await executeCommand('workbench.extensions.installExtension', PYLINT_EXTENSION, {
- installPreReleaseVersion: appEnv.extensionChannel === 'insiders',
- });
- return true;
- }
-
- sendTelemetryEvent(EventName.TOOLS_EXTENSIONS_PROMPT_DISMISSED, undefined, {
- extensionId: PYLINT_EXTENSION,
- dismissType: 'close',
- });
-
- return false;
- }
-}
-
-let _prompt: IToolsExtensionPrompt | undefined;
-export function getOrCreatePylintPrompt(serviceContainer: IServiceContainer): IToolsExtensionPrompt {
- if (!_prompt) {
- _prompt = new PylintExtensionPrompt(serviceContainer);
- }
- return _prompt;
-}
diff --git a/extensions/positron-python/src/client/linters/prompts/types.ts b/extensions/positron-python/src/client/linters/prompts/types.ts
deleted file mode 100644
index d7c884b3a00..00000000000
--- a/extensions/positron-python/src/client/linters/prompts/types.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-export interface IToolsExtensionPrompt {
- showPrompt(): Promise;
-}
diff --git a/extensions/positron-python/src/client/linters/prospector.ts b/extensions/positron-python/src/client/linters/prospector.ts
deleted file mode 100644
index fa4b3907255..00000000000
--- a/extensions/positron-python/src/client/linters/prospector.ts
+++ /dev/null
@@ -1,69 +0,0 @@
-import * as path from 'path';
-import { CancellationToken, TextDocument } from 'vscode';
-import '../common/extensions';
-import { Product } from '../common/types';
-import { IServiceContainer } from '../ioc/types';
-import { traceError, traceLog } from '../logging';
-import { BaseLinter } from './baseLinter';
-import { ILintMessage } from './types';
-
-interface IProspectorResponse {
- messages: IProspectorMessage[];
-}
-interface IProspectorMessage {
- source: string;
- message: string;
- code: string;
- location: IProspectorLocation;
-}
-interface IProspectorLocation {
- function: string;
- path: string;
- line: number;
- character: number;
- module: 'beforeFormat';
-}
-
-export class Prospector extends BaseLinter {
- constructor(serviceContainer: IServiceContainer) {
- super(Product.prospector, serviceContainer);
- }
-
- protected async runLinter(document: TextDocument, cancellation: CancellationToken): Promise {
- const cwd = this.getWorkingDirectoryPath(document);
- const relativePath = path.relative(cwd, document.uri.fsPath);
- return this.run([relativePath], document, cancellation);
- }
-
- protected async parseMessages(
- output: string,
- _document: TextDocument,
- _token: CancellationToken,
- _regEx: string,
- ): Promise {
- let parsedData: IProspectorResponse;
- try {
- parsedData = JSON.parse(output);
- } catch (ex) {
- traceLog(`${'#'.repeat(10)}Linting Output - ${this.info.id}${'#'.repeat(10)}`);
- traceLog(output);
- traceError('Failed to parse Prospector output', ex);
- return [];
- }
- return parsedData.messages
- .filter((_value, index) => index <= this.pythonSettings.linting.maxNumberOfProblems)
- .map((msg) => {
- const lineNumber =
- msg.location.line === null || Number.isNaN(msg.location.line) ? 1 : msg.location.line;
-
- return {
- code: msg.code,
- message: msg.message,
- column: msg.location.character,
- line: lineNumber,
- type: msg.code,
- provider: `${this.info.id} - ${msg.source}`,
- };
- });
- }
-}
diff --git a/extensions/positron-python/src/client/linters/pycodestyle.ts b/extensions/positron-python/src/client/linters/pycodestyle.ts
deleted file mode 100644
index 30517980e83..00000000000
--- a/extensions/positron-python/src/client/linters/pycodestyle.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import { CancellationToken, TextDocument } from 'vscode';
-import '../common/extensions';
-import { Product } from '../common/types';
-import { IServiceContainer } from '../ioc/types';
-import { BaseLinter } from './baseLinter';
-import { ILintMessage } from './types';
-
-const COLUMN_OFF_SET = 1;
-
-export class Pycodestyle extends BaseLinter {
- constructor(serviceContainer: IServiceContainer) {
- super(Product.pycodestyle, serviceContainer, COLUMN_OFF_SET);
- }
-
- protected async runLinter(document: TextDocument, cancellation: CancellationToken): Promise {
- const messages = await this.run([document.uri.fsPath], document, cancellation);
- messages.forEach((msg) => {
- msg.severity = this.parseMessagesSeverity(
- msg.type,
- this.pythonSettings.linting.pycodestyleCategorySeverity,
- );
- });
- return messages;
- }
-}
diff --git a/extensions/positron-python/src/client/linters/pydocstyle.ts b/extensions/positron-python/src/client/linters/pydocstyle.ts
deleted file mode 100644
index 4851190a92a..00000000000
--- a/extensions/positron-python/src/client/linters/pydocstyle.ts
+++ /dev/null
@@ -1,89 +0,0 @@
-import * as path from 'path';
-import { CancellationToken, TextDocument } from 'vscode';
-import '../common/extensions';
-import { Product } from '../common/types';
-import { IServiceContainer } from '../ioc/types';
-import { traceError } from '../logging';
-import { BaseLinter } from './baseLinter';
-import { ILintMessage, LintMessageSeverity } from './types';
-import { isWindows } from '../common/platform/platformService';
-
-export class PyDocStyle extends BaseLinter {
- constructor(serviceContainer: IServiceContainer) {
- super(Product.pydocstyle, serviceContainer);
- }
-
- protected async runLinter(document: TextDocument, cancellation: CancellationToken): Promise {
- const messages = await this.run([document.uri.fsPath], document, cancellation);
- // All messages in pep8 are treated as warnings for now.
- messages.forEach((msg) => {
- msg.severity = LintMessageSeverity.Warning;
- });
-
- return messages;
- }
-
- protected async parseMessages(
- output: string,
- document: TextDocument,
- _token: CancellationToken,
- _regEx: string,
- ): Promise {
- let outputLines = output.split(/\r?\n/g);
- const baseFileName = path.basename(document.uri.fsPath);
-
- // Remember, the first line of the response contains the file name and line number, the next line contains the error message.
- // So we have two lines per message, hence we need to take lines in pairs.
- const maxLines = this.pythonSettings.linting.maxNumberOfProblems * 2;
- // First line is almost always empty.
- const oldOutputLines = outputLines.filter((line) => line.length > 0);
- outputLines = [];
- for (let counter = 0; counter < oldOutputLines.length / 2; counter += 1) {
- outputLines.push(oldOutputLines[2 * counter] + oldOutputLines[2 * counter + 1]);
- }
-
- return (
- outputLines
- .filter((value, index) => index < maxLines && value.indexOf(':') >= 0)
- .map((line) => {
- // Windows will have a : after the drive letter (e.g. c:\).
- if (isWindows()) {
- return line.substring(line.indexOf(`${baseFileName}:`) + baseFileName.length + 1).trim();
- }
- return line.substring(line.indexOf(':') + 1).trim();
- })
- // Iterate through the lines (skipping the messages).
- // So, just iterate the response in pairs.
- .map((line) => {
- try {
- if (line.trim().length === 0) {
- return undefined;
- }
- const lineNumber = parseInt(line.substring(0, line.indexOf(' ')), 10);
- const part = line.substring(line.indexOf(':') + 1).trim();
- const code = part.substring(0, part.indexOf(':')).trim();
- const message = part.substring(part.indexOf(':') + 1).trim();
-
- const sourceLine = document.lineAt(lineNumber - 1).text;
- const trimmedSourceLine = sourceLine.trim();
- const sourceStart = sourceLine.indexOf(trimmedSourceLine);
-
- return {
- code,
- message,
- column: sourceStart,
- line: lineNumber,
- type: '',
- provider: this.info.id,
- } as ILintMessage;
- } catch (ex) {
- traceError(`Failed to parse pydocstyle line '${line}'`, ex);
- }
-
- return undefined;
- })
- .filter((item) => item !== undefined)
- .map((item) => item!)
- );
- }
-}
diff --git a/extensions/positron-python/src/client/linters/pylama.ts b/extensions/positron-python/src/client/linters/pylama.ts
deleted file mode 100644
index d5930c83944..00000000000
--- a/extensions/positron-python/src/client/linters/pylama.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-import { CancellationToken, TextDocument } from 'vscode';
-import '../common/extensions';
-import { Product } from '../common/types';
-import { IServiceContainer } from '../ioc/types';
-import { BaseLinter } from './baseLinter';
-import { ILintMessage, LintMessageSeverity } from './types';
-
-/**
- * Example messages to parse from PyLama
- * 1. Linter: pycodestyle - recent version removed an extra colon (:) after line:col, hence made it optional in the regex (to be backward compatibile)
- * `src/test_py.py:23:60 [E] E226 missing whitespace around arithmetic operator [pycodestyle]`
- * 2. Linter: mypy - output is missing the error code, something like `E226` - hence made it optional in the regex
- * `src/test_py.py:7:4 [E] Argument 1 to "fn" has incompatible type "str"; expected "int" [mypy]`
- */
-
-const REGEX =
- '(?.py):(?\\d+):(?\\d+):? \\[(?\\w+)\\]( (?\\w\\d+)?:?)? (?.*)\\r?(\\n|$)';
-const COLUMN_OFF_SET = 1;
-
-export class PyLama extends BaseLinter {
- constructor(serviceContainer: IServiceContainer) {
- super(Product.pylama, serviceContainer, COLUMN_OFF_SET);
- }
-
- protected async runLinter(document: TextDocument, cancellation: CancellationToken): Promise {
- const messages = await this.run([document.uri.fsPath], document, cancellation, REGEX);
- // All messages in pylama are treated as warnings for now.
- messages.forEach((msg) => {
- msg.severity = LintMessageSeverity.Warning;
- });
-
- return messages;
- }
-}
diff --git a/extensions/positron-python/src/client/linters/pylint.ts b/extensions/positron-python/src/client/linters/pylint.ts
deleted file mode 100644
index 0b635417f90..00000000000
--- a/extensions/positron-python/src/client/linters/pylint.ts
+++ /dev/null
@@ -1,96 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-import { CancellationToken, TextDocument } from 'vscode';
-import '../common/extensions';
-import { Product } from '../common/types';
-import { IServiceContainer } from '../ioc/types';
-import { traceError, traceLog } from '../logging';
-import { BaseLinter } from './baseLinter';
-import { isExtensionEnabled } from './prompts/common';
-import { PYLINT_EXTENSION } from './prompts/pylintPrompt';
-import { IToolsExtensionPrompt } from './prompts/types';
-import { ILintMessage } from './types';
-
-interface IJsonMessage {
- column: number | null;
- line: number;
- message: string;
- symbol: string;
- type: string;
- endLine?: number | null;
- endColumn?: number | null;
-}
-
-export class Pylint extends BaseLinter {
- constructor(serviceContainer: IServiceContainer, private readonly prompt: IToolsExtensionPrompt) {
- super(Product.pylint, serviceContainer);
- }
-
- protected async runLinter(document: TextDocument, cancellation: CancellationToken): Promise {
- await this.prompt.showPrompt();
-
- if (isExtensionEnabled(this.serviceContainer, PYLINT_EXTENSION)) {
- traceLog(
- 'LINTING: Skipping linting from Python extension, since Pylint extension is installed and enabled.',
- );
- return [];
- }
-
- const { uri } = document;
- const settings = this.configService.getSettings(uri);
- const args = [uri.fsPath];
- const messages = await this.run(args, document, cancellation);
- messages.forEach((msg) => {
- msg.severity = this.parseMessagesSeverity(msg.type, settings.linting.pylintCategorySeverity);
- });
- return messages;
- }
-
- private parseOutputMessage(outputMsg: IJsonMessage, colOffset = 0): ILintMessage | undefined {
- // Both 'endLine' and 'endColumn' are only present on pylint 2.12.2+
- // If present, both can still be 'null' if AST node didn't have endLine and / or endColumn information.
- // If 'endColumn' is 'null' or not preset, set it to 'undefined' to
- // prevent the lintingEngine from inferring an error range.
- if (outputMsg.endColumn) {
- outputMsg.endColumn = outputMsg.endColumn <= 0 ? 0 : outputMsg.endColumn - colOffset;
- } else {
- outputMsg.endColumn = undefined;
- }
-
- return {
- code: outputMsg.symbol,
- message: outputMsg.message,
- column: outputMsg.column === null || outputMsg.column <= 0 ? 0 : outputMsg.column - colOffset,
- line: outputMsg.line,
- type: outputMsg.type,
- provider: this.info.id,
- endLine: outputMsg.endLine === null ? undefined : outputMsg.endLine,
- endColumn: outputMsg.endColumn,
- };
- }
-
- protected async parseMessages(
- output: string,
- _document: TextDocument,
- _token: CancellationToken,
- _: string,
- ): Promise {
- const messages: ILintMessage[] = [];
- try {
- const parsedOutput: IJsonMessage[] = JSON.parse(output);
- for (const outputMsg of parsedOutput) {
- const msg = this.parseOutputMessage(outputMsg, this.columnOffset);
- if (msg) {
- messages.push(msg);
- if (messages.length >= this.pythonSettings.linting.maxNumberOfProblems) {
- break;
- }
- }
- }
- } catch (ex) {
- traceError(`Linter '${this.info.id}' failed to parse the output '${output}.`, ex);
- }
- return messages;
- }
-}
diff --git a/extensions/positron-python/src/client/linters/serviceRegistry.ts b/extensions/positron-python/src/client/linters/serviceRegistry.ts
deleted file mode 100644
index 26ada4d0cc8..00000000000
--- a/extensions/positron-python/src/client/linters/serviceRegistry.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-'use strict';
-
-import { IExtensionActivationService } from '../activation/types';
-import { IServiceManager } from '../ioc/types';
-import { LinterProvider } from '../providers/linterProvider';
-import { LinterManager } from './linterManager';
-import { LintingEngine } from './lintingEngine';
-import { ILinterManager, ILintingEngine } from './types';
-
-export function registerTypes(serviceManager: IServiceManager): void {
- serviceManager.addSingleton(ILintingEngine, LintingEngine);
- serviceManager.addSingleton(ILinterManager, LinterManager);
- serviceManager.addSingleton(IExtensionActivationService, LinterProvider);
-}
diff --git a/extensions/positron-python/src/client/linters/types.ts b/extensions/positron-python/src/client/linters/types.ts
deleted file mode 100644
index b24fe508ea1..00000000000
--- a/extensions/positron-python/src/client/linters/types.ts
+++ /dev/null
@@ -1,80 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-'use strict';
-
-import * as vscode from 'vscode';
-import { ExecutionInfo, Product } from '../common/types';
-import { IServiceContainer } from '../ioc/types';
-import { LinterTrigger } from '../telemetry/types';
-
-export interface IErrorHandler {
- handleError(error: Error, resource: vscode.Uri, execInfo: ExecutionInfo): Promise;
-}
-
-export enum LinterId {
- Flake8 = 'flake8',
- MyPy = 'mypy',
- PyCodeStyle = 'pycodestyle',
- Prospector = 'prospector',
- PyDocStyle = 'pydocstyle',
- PyLama = 'pylama',
- PyLint = 'pylint',
- Bandit = 'bandit',
-}
-
-export interface ILinterInfo {
- readonly id: LinterId;
- readonly product: Product;
- readonly pathSettingName: string;
- readonly argsSettingName: string;
- readonly enabledSettingName: string;
- readonly configFileNames: string[];
- enableAsync(enabled: boolean, resource?: vscode.Uri): Promise;
- isEnabled(resource?: vscode.Uri): boolean;
- pathName(resource?: vscode.Uri): string;
- linterArgs(resource?: vscode.Uri): string[];
- getExecutionInfo(customArgs: string[], resource?: vscode.Uri): ExecutionInfo;
-}
-
-export interface ILinter {
- readonly info: ILinterInfo;
- lint(document: vscode.TextDocument, cancellation: vscode.CancellationToken): Promise;
-}
-
-export const ILinterManager = Symbol('ILinterManager');
-export interface ILinterManager {
- getAllLinterInfos(): ILinterInfo[];
- getLinterInfo(product: Product): ILinterInfo;
- getActiveLinters(resource?: vscode.Uri): Promise;
- isLintingEnabled(resource?: vscode.Uri): Promise;
- enableLintingAsync(enable: boolean, resource?: vscode.Uri): Promise;
- setActiveLintersAsync(products: Product[], resource?: vscode.Uri): Promise;
- createLinter(product: Product, serviceContainer: IServiceContainer, resource?: vscode.Uri): Promise;
-}
-
-export interface ILintMessage {
- line: number;
- column: number;
- endLine?: number;
- endColumn?: number;
- code: string | undefined;
- message: string;
- type: string;
- severity?: LintMessageSeverity;
- provider: string;
-}
-export enum LintMessageSeverity {
- Hint,
- Error,
- Warning,
- Information,
-}
-
-export const ILintingEngine = Symbol('ILintingEngine');
-export interface ILintingEngine {
- readonly diagnostics: vscode.DiagnosticCollection;
- lintOpenPythonFiles(trigger?: LinterTrigger): Promise;
- lintDocument(document: vscode.TextDocument, trigger: LinterTrigger): Promise;
- clearDiagnostics(document: vscode.TextDocument): void;
-}
diff --git a/extensions/positron-python/src/client/logging/settingLogs.ts b/extensions/positron-python/src/client/logging/settingLogs.ts
new file mode 100644
index 00000000000..257e204e151
--- /dev/null
+++ b/extensions/positron-python/src/client/logging/settingLogs.ts
@@ -0,0 +1,118 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+import { l10n } from 'vscode';
+import { traceError, traceInfo } from '.';
+import { Commands, PVSC_EXTENSION_ID } from '../common/constants';
+import { showWarningMessage } from '../common/vscodeApis/windowApis';
+import { getConfiguration, getWorkspaceFolders } from '../common/vscodeApis/workspaceApis';
+import { Common } from '../common/utils/localize';
+import { executeCommand } from '../common/vscodeApis/commandApis';
+
+function logOnLegacyFormatterSetting(): boolean {
+ let usesLegacyFormatter = false;
+ getWorkspaceFolders()?.forEach(async (workspace) => {
+ let config = getConfiguration('editor', { uri: workspace.uri, languageId: 'python' });
+ if (!config) {
+ config = getConfiguration('editor', workspace.uri);
+ if (!config) {
+ traceError('Unable to get editor configuration');
+ }
+ }
+ const formatter = config.get('defaultFormatter', '');
+ traceInfo(`Default formatter is set to ${formatter} for workspace ${workspace.uri.fsPath}`);
+ if (formatter === PVSC_EXTENSION_ID) {
+ usesLegacyFormatter = true;
+ traceError(
+ 'The setting "editor.defaultFormatter" for Python is set to "ms-python.python" which is deprecated.',
+ );
+ traceError('Formatting features have been moved to separate formatter extensions.');
+ traceError('See here for more information: https://code.visualstudio.com/docs/python/formatting');
+ traceError('Please install the formatter extension you prefer and set it as the default formatter.');
+ traceError('For `autopep8` use: https://marketplace.visualstudio.com/items?itemName=ms-python.autopep8');
+ traceError(
+ 'For `black` use: https://marketplace.visualstudio.com/items?itemName=ms-python.black-formatter',
+ );
+ traceError('For `yapf` use: https://marketplace.visualstudio.com/items?itemName=eeyore.yapf');
+ }
+ });
+ return usesLegacyFormatter;
+}
+
+function logOnLegacyLinterSetting(): boolean {
+ let usesLegacyLinter = false;
+ getWorkspaceFolders()?.forEach(async (workspace) => {
+ let config = getConfiguration('python', { uri: workspace.uri, languageId: 'python' });
+ if (!config) {
+ config = getConfiguration('python', workspace.uri);
+ if (!config) {
+ traceError('Unable to get editor configuration');
+ }
+ }
+
+ const linters: string[] = [
+ 'pylint',
+ 'flake8',
+ 'mypy',
+ 'pydocstyle',
+ 'pylama',
+ 'pycodestyle',
+ 'bandit',
+ 'prospector',
+ ];
+
+ linters.forEach((linter) => {
+ const linterEnabled = config.get(`linting.${linter}Enabled`, false);
+ if (linterEnabled) {
+ usesLegacyLinter = true;
+ traceError(`Following setting is deprecated: "python.linting.${linter}Enabled"`);
+ traceError(
+ `All settings starting with "python.linting." are deprecated and can be removed from settings.`,
+ );
+ traceError('Linting features have been moved to separate linter extensions.');
+ traceError('See here for more information: https://code.visualstudio.com/docs/python/linting');
+ if (linter === 'pylint' || linter === 'flake8') {
+ traceError(
+ `Please install "${linter}" extension: https://marketplace.visualstudio.com/items?itemName=ms-python.${linter}`,
+ );
+ } else if (linter === 'mypy') {
+ traceError(
+ `Please install "${linter}" extension: https://marketplace.visualstudio.com/items?itemName=ms-python.mypy-type-checker`,
+ );
+ } else if (['pydocstyle', 'pylama', 'pycodestyle', 'bandit'].includes(linter)) {
+ traceError(
+ `Selected linter "${linter}" may be supported by extensions like "Ruff", which include several linter rules: https://marketplace.visualstudio.com/items?itemName=charliermarsh.ruff`,
+ );
+ }
+ }
+ });
+ });
+
+ return usesLegacyLinter;
+}
+
+let _isShown = false;
+async function notifyLegacySettings(): Promise {
+ if (_isShown) {
+ return;
+ }
+ _isShown = true;
+ const response = await showWarningMessage(
+ l10n.t(
+ `You have deprecated linting or formatting settings for Python. Please see the [logs](command:${Commands.ViewOutput}) for more details.`,
+ ),
+ Common.learnMore,
+ );
+ if (response === Common.learnMore) {
+ executeCommand('vscode.open', 'https://aka.ms/AAlgvkb');
+ }
+}
+
+export function logAndNotifyOnLegacySettings(): void {
+ const usesLegacyFormatter = logOnLegacyFormatterSetting();
+ const usesLegacyLinter = logOnLegacyLinterSetting();
+
+ if (usesLegacyFormatter || usesLegacyLinter) {
+ setImmediate(() => notifyLegacySettings().ignoreErrors());
+ }
+}
diff --git a/extensions/positron-python/src/client/providers/codeActionProvider/isortPrompt.ts b/extensions/positron-python/src/client/providers/codeActionProvider/isortPrompt.ts
deleted file mode 100644
index ffef481b498..00000000000
--- a/extensions/positron-python/src/client/providers/codeActionProvider/isortPrompt.ts
+++ /dev/null
@@ -1,89 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-import { IApplicationEnvironment } from '../../common/application/types';
-import { IPersistentState, IPersistentStateFactory } from '../../common/types';
-import { Common, ToolsExtensions } from '../../common/utils/localize';
-import { executeCommand } from '../../common/vscodeApis/commandApis';
-import { isExtensionDisabled, isExtensionEnabled } from '../../common/vscodeApis/extensionsApi';
-import { showInformationMessage } from '../../common/vscodeApis/windowApis';
-import { IServiceContainer } from '../../ioc/types';
-import { sendTelemetryEvent } from '../../telemetry';
-import { EventName } from '../../telemetry/constants';
-
-export const ISORT_EXTENSION = 'ms-python.isort';
-const ISORT_PROMPT_DONOTSHOW_KEY = 'showISortExtensionPrompt';
-
-function doNotShowPromptState(serviceContainer: IServiceContainer, promptKey: string): IPersistentState {
- const persistFactory: IPersistentStateFactory = serviceContainer.get(
- IPersistentStateFactory,
- );
- return persistFactory.createWorkspacePersistentState(promptKey, false);
-}
-
-export class ISortExtensionPrompt {
- private shownThisSession = false;
-
- public constructor(private readonly serviceContainer: IServiceContainer) {}
-
- public async showPrompt(): Promise {
- const isEnabled = isExtensionEnabled(ISORT_EXTENSION);
- if (isEnabled || isExtensionDisabled(ISORT_EXTENSION)) {
- sendTelemetryEvent(EventName.TOOLS_EXTENSIONS_ALREADY_INSTALLED, undefined, {
- extensionId: ISORT_EXTENSION,
- isEnabled,
- });
- return true;
- }
-
- const doNotShow = doNotShowPromptState(this.serviceContainer, ISORT_PROMPT_DONOTSHOW_KEY);
- if (this.shownThisSession || doNotShow.value) {
- return false;
- }
-
- sendTelemetryEvent(EventName.TOOLS_EXTENSIONS_PROMPT_SHOWN, undefined, { extensionId: ISORT_EXTENSION });
- this.shownThisSession = true;
- const response = await showInformationMessage(
- ToolsExtensions.isortPromptMessage,
- ToolsExtensions.installISortExtension,
- Common.doNotShowAgain,
- );
-
- if (response === Common.doNotShowAgain) {
- await doNotShow.updateValue(true);
- sendTelemetryEvent(EventName.TOOLS_EXTENSIONS_PROMPT_DISMISSED, undefined, {
- extensionId: ISORT_EXTENSION,
- dismissType: 'doNotShow',
- });
- return false;
- }
-
- if (response === ToolsExtensions.installISortExtension) {
- sendTelemetryEvent(EventName.TOOLS_EXTENSIONS_INSTALL_SELECTED, undefined, {
- extensionId: ISORT_EXTENSION,
- });
- const appEnv: IApplicationEnvironment = this.serviceContainer.get(
- IApplicationEnvironment,
- );
- await executeCommand('workbench.extensions.installExtension', ISORT_EXTENSION, {
- installPreReleaseVersion: appEnv.extensionChannel === 'insiders',
- });
- return true;
- }
-
- sendTelemetryEvent(EventName.TOOLS_EXTENSIONS_PROMPT_DISMISSED, undefined, {
- extensionId: ISORT_EXTENSION,
- dismissType: 'close',
- });
-
- return false;
- }
-}
-
-let _prompt: ISortExtensionPrompt | undefined;
-export function getOrCreateISortPrompt(serviceContainer: IServiceContainer): ISortExtensionPrompt {
- if (!_prompt) {
- _prompt = new ISortExtensionPrompt(serviceContainer);
- }
- return _prompt;
-}
diff --git a/extensions/positron-python/src/client/providers/codeActionProvider/main.ts b/extensions/positron-python/src/client/providers/codeActionProvider/main.ts
index 40afd4dbb2b..259f4284860 100644
--- a/extensions/positron-python/src/client/providers/codeActionProvider/main.ts
+++ b/extensions/positron-python/src/client/providers/codeActionProvider/main.ts
@@ -4,23 +4,14 @@
import { inject, injectable } from 'inversify';
import * as vscodeTypes from 'vscode';
import { IExtensionSingleActivationService } from '../../activation/types';
-import { Commands } from '../../common/constants';
import { IDisposableRegistry } from '../../common/types';
-import { executeCommand, registerCommand } from '../../common/vscodeApis/commandApis';
-import { isExtensionEnabled } from '../../common/vscodeApis/extensionsApi';
-import { IServiceContainer } from '../../ioc/types';
-import { traceLog } from '../../logging';
-import { getOrCreateISortPrompt, ISORT_EXTENSION } from './isortPrompt';
import { LaunchJsonCodeActionProvider } from './launchJsonCodeActionProvider';
@injectable()
export class CodeActionProviderService implements IExtensionSingleActivationService {
public readonly supportedWorkspaceTypes = { untrustedWorkspace: false, virtualWorkspace: false };
- constructor(
- @inject(IDisposableRegistry) private disposableRegistry: IDisposableRegistry,
- @inject(IServiceContainer) private serviceContainer: IServiceContainer,
- ) {}
+ constructor(@inject(IDisposableRegistry) private disposableRegistry: IDisposableRegistry) {}
public async activate(): Promise {
// eslint-disable-next-line global-require
@@ -35,19 +26,5 @@ export class CodeActionProviderService implements IExtensionSingleActivationServ
providedCodeActionKinds: [vscode.CodeActionKind.QuickFix],
}),
);
- this.disposableRegistry.push(
- registerCommand(Commands.Sort_Imports, async () => {
- const prompt = getOrCreateISortPrompt(this.serviceContainer);
- await prompt.showPrompt();
- if (!isExtensionEnabled(ISORT_EXTENSION)) {
- traceLog(
- 'Sort Imports: Please install and enable `ms-python.isort` extension to use this feature.',
- );
- return;
- }
-
- executeCommand('editor.action.organizeImports');
- }),
- );
}
}
diff --git a/extensions/positron-python/src/client/providers/formatProvider.ts b/extensions/positron-python/src/client/providers/formatProvider.ts
deleted file mode 100644
index 1ea239c03be..00000000000
--- a/extensions/positron-python/src/client/providers/formatProvider.ts
+++ /dev/null
@@ -1,135 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-import * as vscode from 'vscode';
-import { ICommandManager, IDocumentManager, IWorkspaceService } from '../common/application/types';
-import { PYTHON_LANGUAGE } from '../common/constants';
-import { IConfigurationService } from '../common/types';
-import { IInterpreterService } from '../interpreter/contracts';
-import { IServiceContainer } from '../ioc/types';
-import { AutoPep8Formatter } from '../formatters/autoPep8Formatter';
-import { BaseFormatter } from '../formatters/baseFormatter';
-import { BlackFormatter } from '../formatters/blackFormatter';
-import { DummyFormatter } from '../formatters/dummyFormatter';
-import { YapfFormatter } from '../formatters/yapfFormatter';
-
-export class PythonFormattingEditProvider
- implements vscode.DocumentFormattingEditProvider, vscode.DocumentRangeFormattingEditProvider, vscode.Disposable {
- private readonly config: IConfigurationService;
-
- private readonly workspace: IWorkspaceService;
-
- private readonly documentManager: IDocumentManager;
-
- private readonly commands: ICommandManager;
-
- private formatters = new Map();
-
- private disposables: vscode.Disposable[] = [];
-
- // Workaround for https://github.com/Microsoft/vscode/issues/41194
- private documentVersionBeforeFormatting = -1;
-
- private formatterMadeChanges = false;
-
- private saving = false;
-
- public constructor(_context: vscode.ExtensionContext, serviceContainer: IServiceContainer) {
- const yapfFormatter = new YapfFormatter(serviceContainer);
- const autoPep8 = new AutoPep8Formatter(serviceContainer);
- const black = new BlackFormatter(serviceContainer);
- const dummy = new DummyFormatter(serviceContainer);
- this.formatters.set(yapfFormatter.Id, yapfFormatter);
- this.formatters.set(black.Id, black);
- this.formatters.set(autoPep8.Id, autoPep8);
- this.formatters.set(dummy.Id, dummy);
-
- this.commands = serviceContainer.get(ICommandManager);
- this.workspace = serviceContainer.get(IWorkspaceService);
- this.documentManager = serviceContainer.get(IDocumentManager);
- this.config = serviceContainer.get(IConfigurationService);
- const interpreterService = serviceContainer.get(IInterpreterService);
- this.disposables.push(
- this.documentManager.onDidSaveTextDocument(async (document) => this.onSaveDocument(document)),
- );
- this.disposables.push(
- interpreterService.onDidChangeInterpreter(async () => {
- if (this.documentManager.activeTextEditor) {
- return this.onSaveDocument(this.documentManager.activeTextEditor.document);
- }
-
- return undefined;
- }),
- );
- }
-
- public dispose(): void {
- this.disposables.forEach((d) => d.dispose());
- }
-
- public provideDocumentFormattingEdits(
- document: vscode.TextDocument,
- options: vscode.FormattingOptions,
- token: vscode.CancellationToken,
- ): Promise {
- return this.provideDocumentRangeFormattingEdits(document, undefined, options, token);
- }
-
- public async provideDocumentRangeFormattingEdits(
- document: vscode.TextDocument,
- range: vscode.Range | undefined,
- options: vscode.FormattingOptions,
- token: vscode.CancellationToken,
- ): Promise {
- // Workaround for https://github.com/Microsoft/vscode/issues/41194
- // VSC rejects 'format on save' promise in 750 ms. Python formatting may take quite a bit longer.
- // Workaround is to resolve promise to nothing here, then execute format document and force new save.
- // However, we need to know if this is 'format document' or formatting on save.
-
- if (this.saving || document.languageId !== PYTHON_LANGUAGE) {
- // We are saving after formatting (see onSaveDocument below)
- // so we do not want to format again.
- return [];
- }
-
- // Remember content before formatting so we can detect if
- // formatting edits have been really applied
- const editorConfig = this.workspace.getConfiguration('editor', document.uri);
- if (editorConfig.get('formatOnSave') === true) {
- this.documentVersionBeforeFormatting = document.version;
- }
-
- const settings = this.config.getSettings(document.uri);
- const formatter = this.formatters.get(settings.formatting.provider)!;
- const edits = await formatter.formatDocument(document, options, token, range);
-
- this.formatterMadeChanges = edits.length > 0;
- return edits;
- }
-
- private async onSaveDocument(document: vscode.TextDocument): Promise {
- // Promise was rejected = formatting took too long.
- // Don't format inside the event handler, do it on timeout
- setTimeout(() => {
- try {
- if (
- this.formatterMadeChanges &&
- !document.isDirty &&
- document.version === this.documentVersionBeforeFormatting
- ) {
- // Formatter changes were not actually applied due to the timeout on save.
- // Force formatting now and then save the document.
- this.commands.executeCommand('editor.action.formatDocument').then(async () => {
- this.saving = true;
- await document.save();
- this.saving = false;
- });
- }
- } finally {
- this.documentVersionBeforeFormatting = -1;
- this.saving = false;
- this.formatterMadeChanges = false;
- }
- }, 50);
- }
-}
diff --git a/extensions/positron-python/src/client/providers/linterProvider.ts b/extensions/positron-python/src/client/providers/linterProvider.ts
deleted file mode 100644
index 7821eaeccd5..00000000000
--- a/extensions/positron-python/src/client/providers/linterProvider.ts
+++ /dev/null
@@ -1,123 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-'use strict';
-
-import { inject, injectable } from 'inversify';
-import * as path from 'path';
-import { ConfigurationChangeEvent, Disposable, TextDocument, Uri, workspace } from 'vscode';
-import { IExtensionActivationService } from '../activation/types';
-import { IDocumentManager, IWorkspaceService } from '../common/application/types';
-import { isTestExecution } from '../common/constants';
-import '../common/extensions';
-import { IFileSystem } from '../common/platform/types';
-import { IConfigurationService, IDisposable } from '../common/types';
-import { IInterpreterService } from '../interpreter/contracts';
-import { IServiceContainer } from '../ioc/types';
-import { ILinterManager, ILintingEngine } from '../linters/types';
-
-@injectable()
-export class LinterProvider implements IExtensionActivationService, Disposable {
- public readonly supportedWorkspaceTypes = { untrustedWorkspace: false, virtualWorkspace: false };
-
- private interpreterService: IInterpreterService;
-
- private documents: IDocumentManager;
-
- private configuration: IConfigurationService;
-
- private linterManager: ILinterManager;
-
- private engine: ILintingEngine;
-
- private fs: IFileSystem;
-
- private readonly disposables: IDisposable[] = [];
-
- private workspaceService: IWorkspaceService;
-
- private activatedOnce = false;
-
- constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer) {
- this.serviceContainer = serviceContainer;
- this.fs = this.serviceContainer.get(IFileSystem);
- this.engine = this.serviceContainer.get(ILintingEngine);
- this.linterManager = this.serviceContainer.get(ILinterManager);
- this.interpreterService = this.serviceContainer.get(IInterpreterService);
- this.documents = this.serviceContainer.get(IDocumentManager);
- this.configuration = this.serviceContainer.get(IConfigurationService);
- this.workspaceService = this.serviceContainer.get(IWorkspaceService);
- }
-
- public async activate(): Promise {
- if (this.activatedOnce) {
- return;
- }
- this.activatedOnce = true;
- this.disposables.push(this.interpreterService.onDidChangeInterpreter(() => this.engine.lintOpenPythonFiles()));
-
- this.documents.onDidOpenTextDocument((e) => this.onDocumentOpened(e), this.disposables);
- this.documents.onDidCloseTextDocument((e) => this.onDocumentClosed(e), this.disposables);
- this.documents.onDidSaveTextDocument((e) => this.onDocumentSaved(e), this.disposables);
-
- const disposable = this.workspaceService.onDidChangeConfiguration(this.lintSettingsChangedHandler.bind(this));
- this.disposables.push(disposable);
-
- // On workspace reopen we don't get `onDocumentOpened` since it is first opened
- // and then the extension is activated. So schedule linting pass now.
- if (!isTestExecution()) {
- const timer = setTimeout(() => this.engine.lintOpenPythonFiles().ignoreErrors(), 1200);
- this.disposables.push({ dispose: () => clearTimeout(timer) });
- }
- }
-
- public dispose(): void {
- this.disposables.forEach((d) => d.dispose());
- }
-
- private isDocumentOpen(uri: Uri): boolean {
- return this.documents.textDocuments.some((document) => this.fs.arePathsSame(document.uri.fsPath, uri.fsPath));
- }
-
- private lintSettingsChangedHandler(e: ConfigurationChangeEvent) {
- // Look for python files that belong to the specified workspace folder.
- workspace.textDocuments.forEach((document) => {
- if (e.affectsConfiguration('python.linting', document.uri)) {
- this.engine.lintDocument(document, 'auto').ignoreErrors();
- }
- });
- }
-
- private onDocumentOpened(document: TextDocument): void {
- this.engine.lintDocument(document, 'auto').ignoreErrors();
- }
-
- private onDocumentSaved(document: TextDocument): void {
- const settings = this.configuration.getSettings(document.uri);
- if (document.languageId === 'python' && settings.linting.enabled && settings.linting.lintOnSave) {
- this.engine.lintDocument(document, 'save').ignoreErrors();
- return;
- }
-
- this.linterManager
- .getActiveLinters(document.uri)
- .then((linters) => {
- const fileName = path.basename(document.uri.fsPath).toLowerCase();
- const watchers = linters.filter((info) => info.configFileNames.indexOf(fileName) >= 0);
- if (watchers.length > 0) {
- setTimeout(() => this.engine.lintOpenPythonFiles(), 1000);
- }
- })
- .ignoreErrors();
- }
-
- private onDocumentClosed(document: TextDocument) {
- if (!document || !document.fileName || !document.uri) {
- return;
- }
- // Check if this document is still open as a duplicate editor.
- if (!this.isDocumentOpen(document.uri)) {
- this.engine.clearDiagnostics(document);
- }
- }
-}
diff --git a/extensions/positron-python/src/client/providers/prompts/installFormatterPrompt.ts b/extensions/positron-python/src/client/providers/prompts/installFormatterPrompt.ts
deleted file mode 100644
index 5743f840205..00000000000
--- a/extensions/positron-python/src/client/providers/prompts/installFormatterPrompt.ts
+++ /dev/null
@@ -1,145 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-import { Uri } from 'vscode';
-import { inject, injectable } from 'inversify';
-import { IDisposableRegistry } from '../../common/types';
-import { Common, ToolsExtensions } from '../../common/utils/localize';
-import { isExtensionEnabled } from '../../common/vscodeApis/extensionsApi';
-import { showInformationMessage } from '../../common/vscodeApis/windowApis';
-import { getConfiguration, onDidSaveTextDocument } from '../../common/vscodeApis/workspaceApis';
-import { IServiceContainer } from '../../ioc/types';
-import {
- doNotShowPromptState,
- inFormatterExtensionExperiment,
- installFormatterExtension,
- updateDefaultFormatter,
-} from './promptUtils';
-import { AUTOPEP8_EXTENSION, BLACK_EXTENSION, IInstallFormatterPrompt } from './types';
-
-const SHOW_FORMATTER_INSTALL_PROMPT_DONOTSHOW_KEY = 'showFormatterExtensionInstallPrompt';
-
-@injectable()
-export class InstallFormatterPrompt implements IInstallFormatterPrompt {
- private currentlyShown = false;
-
- constructor(@inject(IServiceContainer) private readonly serviceContainer: IServiceContainer) {}
-
- /*
- * This method is called when the user saves a python file or a cell.
- * Returns true if an extension was selected. Otherwise returns false.
- */
- public async showInstallFormatterPrompt(resource?: Uri): Promise {
- if (!inFormatterExtensionExperiment(this.serviceContainer)) {
- return false;
- }
-
- const promptState = doNotShowPromptState(SHOW_FORMATTER_INSTALL_PROMPT_DONOTSHOW_KEY, this.serviceContainer);
- if (this.currentlyShown || promptState.value) {
- return false;
- }
-
- const config = getConfiguration('python', resource);
- const formatter = config.get('formatting.provider', 'none');
- if (!['autopep8', 'black'].includes(formatter)) {
- return false;
- }
-
- const editorConfig = getConfiguration('editor', { uri: resource, languageId: 'python' });
- const defaultFormatter = editorConfig.get('defaultFormatter', '');
- if ([BLACK_EXTENSION, AUTOPEP8_EXTENSION].includes(defaultFormatter)) {
- return false;
- }
-
- const black = isExtensionEnabled(BLACK_EXTENSION);
- const autopep8 = isExtensionEnabled(AUTOPEP8_EXTENSION);
-
- let selection: string | undefined;
-
- if (black || autopep8) {
- this.currentlyShown = true;
- if (black && autopep8) {
- selection = await showInformationMessage(
- ToolsExtensions.selectMultipleFormattersPrompt,
- 'Black',
- 'Autopep8',
- Common.doNotShowAgain,
- );
- } else if (black) {
- selection = await showInformationMessage(
- ToolsExtensions.selectBlackFormatterPrompt,
- Common.bannerLabelYes,
- Common.doNotShowAgain,
- );
- if (selection === Common.bannerLabelYes) {
- selection = 'Black';
- }
- } else if (autopep8) {
- selection = await showInformationMessage(
- ToolsExtensions.selectAutopep8FormatterPrompt,
- Common.bannerLabelYes,
- Common.doNotShowAgain,
- );
- if (selection === Common.bannerLabelYes) {
- selection = 'Autopep8';
- }
- }
- } else if (formatter === 'black' && !black) {
- this.currentlyShown = true;
- selection = await showInformationMessage(
- ToolsExtensions.installBlackFormatterPrompt,
- 'Black',
- 'Autopep8',
- Common.doNotShowAgain,
- );
- } else if (formatter === 'autopep8' && !autopep8) {
- this.currentlyShown = true;
- selection = await showInformationMessage(
- ToolsExtensions.installAutopep8FormatterPrompt,
- 'Black',
- 'Autopep8',
- Common.doNotShowAgain,
- );
- }
-
- let userSelectedAnExtension = false;
- if (selection === 'Black') {
- if (black) {
- userSelectedAnExtension = true;
- await updateDefaultFormatter(BLACK_EXTENSION, resource);
- } else {
- userSelectedAnExtension = true;
- await installFormatterExtension(BLACK_EXTENSION, resource);
- }
- } else if (selection === 'Autopep8') {
- if (autopep8) {
- userSelectedAnExtension = true;
- await updateDefaultFormatter(AUTOPEP8_EXTENSION, resource);
- } else {
- userSelectedAnExtension = true;
- await installFormatterExtension(AUTOPEP8_EXTENSION, resource);
- }
- } else if (selection === Common.doNotShowAgain) {
- userSelectedAnExtension = false;
- await promptState.updateValue(true);
- } else {
- userSelectedAnExtension = false;
- }
-
- this.currentlyShown = false;
- return userSelectedAnExtension;
- }
-}
-
-export function registerInstallFormatterPrompt(serviceContainer: IServiceContainer): void {
- const disposables = serviceContainer.get(IDisposableRegistry);
- const installFormatterPrompt = serviceContainer.get(IInstallFormatterPrompt);
- disposables.push(
- onDidSaveTextDocument(async (e) => {
- const editorConfig = getConfiguration('editor', { uri: e.uri, languageId: 'python' });
- if (e.languageId === 'python' && editorConfig.get('formatOnSave')) {
- await installFormatterPrompt.showInstallFormatterPrompt(e.uri);
- }
- }),
- );
-}
diff --git a/extensions/positron-python/src/client/providers/prompts/promptUtils.ts b/extensions/positron-python/src/client/providers/prompts/promptUtils.ts
deleted file mode 100644
index 05b1b28f061..00000000000
--- a/extensions/positron-python/src/client/providers/prompts/promptUtils.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-import { ConfigurationTarget, Uri } from 'vscode';
-import { ShowFormatterExtensionPrompt } from '../../common/experiments/groups';
-import { IExperimentService, IPersistentState, IPersistentStateFactory } from '../../common/types';
-import { executeCommand } from '../../common/vscodeApis/commandApis';
-import { isInsider } from '../../common/vscodeApis/extensionsApi';
-import { getConfiguration, getWorkspaceFolder } from '../../common/vscodeApis/workspaceApis';
-import { IServiceContainer } from '../../ioc/types';
-
-export function inFormatterExtensionExperiment(serviceContainer: IServiceContainer): boolean {
- const experiment = serviceContainer.get(IExperimentService);
- return experiment.inExperimentSync(ShowFormatterExtensionPrompt.experiment);
-}
-
-export function doNotShowPromptState(key: string, serviceContainer: IServiceContainer): IPersistentState {
- const persistFactory = serviceContainer.get(IPersistentStateFactory);
- const promptState = persistFactory.createWorkspacePersistentState(key, false);
- return promptState;
-}
-
-export async function updateDefaultFormatter(extensionId: string, resource?: Uri): Promise {
- const scope = getWorkspaceFolder(resource) ? ConfigurationTarget.Workspace : ConfigurationTarget.Global;
-
- const config = getConfiguration('python', resource);
- const editorConfig = getConfiguration('editor', { uri: resource, languageId: 'python' });
- await editorConfig.update('defaultFormatter', extensionId, scope, true);
- await config.update('formatting.provider', 'none', scope);
-}
-
-export async function installFormatterExtension(extensionId: string, resource?: Uri): Promise {
- await executeCommand('workbench.extensions.installExtension', extensionId, {
- installPreReleaseVersion: isInsider(),
- });
-
- await updateDefaultFormatter(extensionId, resource);
-}
diff --git a/extensions/positron-python/src/client/providers/prompts/types.ts b/extensions/positron-python/src/client/providers/prompts/types.ts
deleted file mode 100644
index 4edaadb46b4..00000000000
--- a/extensions/positron-python/src/client/providers/prompts/types.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-import { Uri } from 'vscode';
-
-export const BLACK_EXTENSION = 'ms-python.black-formatter';
-export const AUTOPEP8_EXTENSION = 'ms-python.autopep8';
-
-export const IInstallFormatterPrompt = Symbol('IInstallFormatterPrompt');
-export interface IInstallFormatterPrompt {
- showInstallFormatterPrompt(resource?: Uri): Promise;
-}
diff --git a/extensions/positron-python/src/client/providers/serviceRegistry.ts b/extensions/positron-python/src/client/providers/serviceRegistry.ts
index 70fc6dc3413..a96ec14ff5e 100644
--- a/extensions/positron-python/src/client/providers/serviceRegistry.ts
+++ b/extensions/positron-python/src/client/providers/serviceRegistry.ts
@@ -6,13 +6,10 @@
import { IExtensionSingleActivationService } from '../activation/types';
import { IServiceManager } from '../ioc/types';
import { CodeActionProviderService } from './codeActionProvider/main';
-import { InstallFormatterPrompt } from './prompts/installFormatterPrompt';
-import { IInstallFormatterPrompt } from './prompts/types';
export function registerTypes(serviceManager: IServiceManager): void {
serviceManager.addSingleton(
IExtensionSingleActivationService,
CodeActionProviderService,
);
- serviceManager.addSingleton(IInstallFormatterPrompt, InstallFormatterPrompt);
}
diff --git a/extensions/positron-python/src/client/pythonEnvironments/base/locators/lowLevel/customVirtualEnvLocator.ts b/extensions/positron-python/src/client/pythonEnvironments/base/locators/lowLevel/customVirtualEnvLocator.ts
index 57ae9187cdc..e4daeee640c 100644
--- a/extensions/positron-python/src/client/pythonEnvironments/base/locators/lowLevel/customVirtualEnvLocator.ts
+++ b/extensions/positron-python/src/client/pythonEnvironments/base/locators/lowLevel/customVirtualEnvLocator.ts
@@ -92,8 +92,8 @@ export class CustomVirtualEnvironmentLocator extends FSWatchingLocator {
}
protected async initResources(): Promise {
- this.disposables.push(onDidChangePythonSetting(VENVPATH_SETTING_KEY, () => this.emitter.fire({})));
- this.disposables.push(onDidChangePythonSetting(VENVFOLDERS_SETTING_KEY, () => this.emitter.fire({})));
+ this.disposables.push(onDidChangePythonSetting(VENVPATH_SETTING_KEY, () => this.fire()));
+ this.disposables.push(onDidChangePythonSetting(VENVFOLDERS_SETTING_KEY, () => this.fire()));
}
// eslint-disable-next-line class-methods-use-this
diff --git a/extensions/positron-python/src/client/pythonEnvironments/base/locators/lowLevel/customWorkspaceLocator.ts b/extensions/positron-python/src/client/pythonEnvironments/base/locators/lowLevel/customWorkspaceLocator.ts
new file mode 100644
index 00000000000..8a2b857d496
--- /dev/null
+++ b/extensions/positron-python/src/client/pythonEnvironments/base/locators/lowLevel/customWorkspaceLocator.ts
@@ -0,0 +1,47 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+import { PythonEnvKind } from '../../info';
+import { BasicEnvInfo, IPythonEnvsIterator } from '../../locator';
+import { FSWatchingLocator } from './fsWatchingLocator';
+import { getPythonSetting, onDidChangePythonSetting } from '../../../common/externalDependencies';
+import '../../../../common/extensions';
+import { traceVerbose } from '../../../../logging';
+import { DEFAULT_INTERPRETER_SETTING } from '../../../../common/constants';
+
+export const DEFAULT_INTERPRETER_PATH_SETTING_KEY = 'defaultInterpreterPath';
+
+/**
+ * Finds and resolves custom virtual environments that users have provided.
+ */
+export class CustomWorkspaceLocator extends FSWatchingLocator {
+ public readonly providerId: string = 'custom-workspace-locator';
+
+ constructor(private readonly root: string) {
+ super(
+ () => [],
+ async () => PythonEnvKind.Unknown,
+ );
+ }
+
+ protected async initResources(): Promise {
+ this.disposables.push(
+ onDidChangePythonSetting(DEFAULT_INTERPRETER_PATH_SETTING_KEY, () => this.fire(), this.root),
+ );
+ }
+
+ // eslint-disable-next-line class-methods-use-this
+ protected doIterEnvs(): IPythonEnvsIterator {
+ const iterator = async function* (root: string) {
+ traceVerbose('Searching for custom workspace envs');
+ const filename = getPythonSetting(DEFAULT_INTERPRETER_PATH_SETTING_KEY, root);
+ if (!filename || filename === DEFAULT_INTERPRETER_SETTING) {
+ // If the user has not set a custom interpreter, our job is done.
+ return;
+ }
+ yield { kind: PythonEnvKind.Unknown, executablePath: filename };
+ traceVerbose(`Finished searching for custom workspace envs`);
+ };
+ return iterator(this.root);
+ }
+}
diff --git a/extensions/positron-python/src/client/pythonEnvironments/base/locators/lowLevel/fsWatchingLocator.ts b/extensions/positron-python/src/client/pythonEnvironments/base/locators/lowLevel/fsWatchingLocator.ts
index 0eb1d125200..7565913f0a7 100644
--- a/extensions/positron-python/src/client/pythonEnvironments/base/locators/lowLevel/fsWatchingLocator.ts
+++ b/extensions/positron-python/src/client/pythonEnvironments/base/locators/lowLevel/fsWatchingLocator.ts
@@ -128,6 +128,10 @@ export abstract class FSWatchingLocator extends LazyResourceBasedLocator {
watchableRoots.forEach((root) => this.startWatchers(root));
}
+ protected fire(args = {}): void {
+ this.emitter.fire({ ...args, providerId: this.providerId });
+ }
+
private startWatchers(root: string): void {
const opts = this.creationOptions;
if (isWatchingAFile(opts)) {
diff --git a/extensions/positron-python/src/client/pythonEnvironments/common/externalDependencies.ts b/extensions/positron-python/src/client/pythonEnvironments/common/externalDependencies.ts
index ecb6f2212ab..b64d47f4226 100644
--- a/extensions/positron-python/src/client/pythonEnvironments/common/externalDependencies.ts
+++ b/extensions/positron-python/src/client/pythonEnvironments/common/externalDependencies.ts
@@ -175,8 +175,9 @@ export async function* getSubDirs(
* Returns the value for setting `python.`.
* @param name The name of the setting.
*/
-export function getPythonSetting(name: string): T | undefined {
- const settings = internalServiceContainer.get