diff --git a/.github/workflows/positron-python-ci.yml b/.github/workflows/positron-python-ci.yml
index cb86336b1c8..a39a4cd617a 100644
--- a/.github/workflows/positron-python-ci.yml
+++ b/.github/workflows/positron-python-ci.yml
@@ -23,7 +23,7 @@ env:
NODE_VERSION: '18.17.1'
PYTHON_VERSION: '3.10'
PROJECT_DIR: 'extensions/positron-python'
- PYTHON_SRC_DIR: 'extensions/positron-python/pythonFiles'
+ PYTHON_SRC_DIR: 'extensions/positron-python/python_files'
# Force a path with spaces and to test extension works in these scenarios
# Unicode characters are causing 2.7 failures so skip that for now.
special-working-directory: './path with spaces'
@@ -67,16 +67,11 @@ jobs:
- name: Check TypeScript format
run: yarn format-check
- - name: Check Python format
- run: |
- python -m pip install -U black
- python -m black . --check
- working-directory: ${{ env.PYTHON_SRC_DIR }}
-
- - name: Run Ruff
+ - name: Lint and Check Formatting with Ruff
run: |
python -m pip install -U ruff
python -m ruff check .
+ python -m ruff format --check
working-directory: ${{ env.PYTHON_SRC_DIR }}
check-types:
@@ -93,16 +88,16 @@ jobs:
cache: 'pip'
- name: Install base Python requirements
- run: 'python -m pip install --no-deps --require-hashes --only-binary :all: -t ./pythonFiles/lib/python --no-cache-dir --implementation py -r requirements.txt'
+ run: 'python -m pip install --no-deps --require-hashes --only-binary :all: -t ./python_files/lib/python --no-cache-dir --implementation py -r requirements.txt'
- name: Install Positron IPyKernel requirements
run: python scripts/vendor.py
- name: Install other Python requirements
run: |
- python -m pip --disable-pip-version-check install -t ./pythonFiles/lib/python --no-cache-dir --implementation py --no-deps --upgrade --pre debugpy
+ python -m pip --disable-pip-version-check install -t ./python_files/lib/python --no-cache-dir --implementation py --no-deps --upgrade --pre debugpy
python -m pip install --upgrade -r build/test-requirements.txt
- python -m pip install --upgrade -r ./pythonFiles/positron/pinned-test-requirements.txt
+ python -m pip install --upgrade -r ./python_files/positron/pinned-test-requirements.txt
- name: Run Pyright
uses: jakebailey/pyright-action@v2
@@ -169,13 +164,13 @@ jobs:
run: python -m pytest --version
- name: Install base Python requirements
- run: 'python -m pip install --no-deps --require-hashes --only-binary :all: -t ./pythonFiles/lib/python --no-cache-dir --implementation py -r requirements.txt'
+ run: 'python -m pip install --no-deps --require-hashes --only-binary :all: -t ./python_files/lib/python --no-cache-dir --implementation py -r requirements.txt'
- name: Install test requirements
run: python -m pip install -r build/test-requirements.txt
- name: Run Python unit tests
- run: python pythonFiles/tests/run_all.py
+ run: python python_files/tests/run_all.py
positron-ipykernel-tests:
name: Test Positron IPyKernel
@@ -185,31 +180,14 @@ jobs:
include:
- os: 'ubuntu-latest'
python: '3.8'
- time-elapsed: ''
- os: 'macos-latest'
python: '3.9'
- time-elapsed: ''
- os: 'windows-latest'
python: '3.10'
- time-elapsed: ''
- os: 'ubuntu-latest'
python: '3.11'
- time-elapsed: ''
- os: 'ubuntu-latest'
python: '3.12'
- time-elapsed: ''
- # - os: 'ubuntu-latest'
- # python: '3.10'
- # time-elapsed: '3 months'
- # - os: 'ubuntu-latest'
- # python: '3.10'
- # time-elapsed: '6 months'
- # - os: 'ubuntu-latest'
- # python: '3.10'
- # time-elapsed: '9 months'
- # - os: 'ubuntu-latest'
- # python: '3.10'
- # time-elapsed: '1 year'
steps:
- name: Checkout
@@ -225,22 +203,37 @@ jobs:
run: python scripts/vendor.py
- name: Install Positron IPyKernel test requirements
- run: python -m pip install --prefer-binary --upgrade -r pythonFiles/positron/pinned-test-requirements.txt
+ run: python -m pip install --prefer-binary --upgrade -r python_files/positron/pinned-test-requirements.txt
- - name: Get date for older dependencies
- if: ${{ matrix.time-elapsed != '' }}
- run: |
- echo "SNAPSHOT_DATE=$(date -d '-${{ matrix.time-elapsed }}' --iso-8601)" >> $GITHUB_ENV
+ - name: Run Positron IPyKernel unit tests
+ run: pytest python_files/positron
- - name: Get older PyPI snapshot
- if: ${{ matrix.time-elapsed != '' }}
+ python-minimum-dependencies:
+ name: Test Minimum Positron IPyKernel Dependencies
+ runs-on: ${{ matrix.os }}
+ strategy:
+ matrix:
+ include:
+ # currently only running on oldest supported Python version
+ - os: 'ubuntu-latest'
+ python: '3.8'
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Use Python ${{ matrix.python }}
+ uses: actions/setup-python@v5
+ with:
+ python-version: ${{ matrix.python }}
+ cache: 'pip'
+
+ - name: Install testing requirements
run: |
- python -m pip config set global.index-url https://packagemanager.posit.co/pypi/${{ env.SNAPSHOT_DATE }}/simple
- python -m pip config set global.trusted-host packagemanager.posit.co
- python -m pip install --prefer-binary --force-reinstall -r pythonFiles/positron/data-science-requirements.txt
+ python scripts/vendor.py
+ python -m pip install nox
- - name: Run Positron IPyKernel unit tests
- run: pytest pythonFiles/positron
+ - name: Run tests
+ run: yarn positron:testMinimumPythonReqs
typescript-tests:
name: Test TypeScript
diff --git a/ThirdPartyNotices.txt b/ThirdPartyNotices.txt
index 735eefdf610..efbc726a2cd 100644
--- a/ThirdPartyNotices.txt
+++ b/ThirdPartyNotices.txt
@@ -2806,4 +2806,478 @@ Apache License
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.
----------------------------------------------------------
\ No newline at end of file
+---------------------------------------------------------
+
+---------------------------------------------------------
+
+Jupyter logo from jupyter/notebook - BSD 3-Clause License
+https://github.com/jupyter/notebook
+
+BSD 3-Clause License
+
+- Copyright (c) 2001-2015, IPython Development Team
+- Copyright (c) 2015-, Jupyter Development Team
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+---------------------------------------------------------
+
+---------------------------------------------------------
+
+The R logo is © 2016 The R Foundation - CC-BY-SA 4.0
+https://www.r-project.org/logo/
+
+=======================================================================
+
+Attribution-ShareAlike 4.0 International
+
+=======================================================================
+
+Creative Commons Corporation ("Creative Commons") is not a law firm and
+does not provide legal services or legal advice. Distribution of
+Creative Commons public licenses does not create a lawyer-client or
+other relationship. Creative Commons makes its licenses and related
+information available on an "as-is" basis. Creative Commons gives no
+warranties regarding its licenses, any material licensed under their
+terms and conditions, or any related information. Creative Commons
+disclaims all liability for damages resulting from their use to the
+fullest extent possible.
+
+Using Creative Commons Public Licenses
+
+Creative Commons public licenses provide a standard set of terms and
+conditions that creators and other rights holders may use to share
+original works of authorship and other material subject to copyright
+and certain other rights specified in the public license below. The
+following considerations are for informational purposes only, are not
+exhaustive, and do not form part of our licenses.
+
+ Considerations for licensors: Our public licenses are
+ intended for use by those authorized to give the public
+ permission to use material in ways otherwise restricted by
+ copyright and certain other rights. Our licenses are
+ irrevocable. Licensors should read and understand the terms
+ and conditions of the license they choose before applying it.
+ Licensors should also secure all rights necessary before
+ applying our licenses so that the public can reuse the
+ material as expected. Licensors should clearly mark any
+ material not subject to the license. This includes other CC-
+ licensed material, or material used under an exception or
+ limitation to copyright. More considerations for licensors:
+ wiki.creativecommons.org/Considerations_for_licensors
+
+ Considerations for the public: By using one of our public
+ licenses, a licensor grants the public permission to use the
+ licensed material under specified terms and conditions. If
+ the licensor's permission is not necessary for any reason--for
+ example, because of any applicable exception or limitation to
+ copyright--then that use is not regulated by the license. Our
+ licenses grant only permissions under copyright and certain
+ other rights that a licensor has authority to grant. Use of
+ the licensed material may still be restricted for other
+ reasons, including because others have copyright or other
+ rights in the material. A licensor may make special requests,
+ such as asking that all changes be marked or described.
+ Although not required by our licenses, you are encouraged to
+ respect those requests where reasonable. More considerations
+ for the public:
+ wiki.creativecommons.org/Considerations_for_licensees
+
+=======================================================================
+
+Creative Commons Attribution-ShareAlike 4.0 International Public
+License
+
+By exercising the Licensed Rights (defined below), You accept and agree
+to be bound by the terms and conditions of this Creative Commons
+Attribution-ShareAlike 4.0 International Public License ("Public
+License"). To the extent this Public License may be interpreted as a
+contract, You are granted the Licensed Rights in consideration of Your
+acceptance of these terms and conditions, and the Licensor grants You
+such rights in consideration of benefits the Licensor receives from
+making the Licensed Material available under these terms and
+conditions.
+
+
+Section 1 -- Definitions.
+
+ a. Adapted Material means material subject to Copyright and Similar
+ Rights that is derived from or based upon the Licensed Material
+ and in which the Licensed Material is translated, altered,
+ arranged, transformed, or otherwise modified in a manner requiring
+ permission under the Copyright and Similar Rights held by the
+ Licensor. For purposes of this Public License, where the Licensed
+ Material is a musical work, performance, or sound recording,
+ Adapted Material is always produced where the Licensed Material is
+ synched in timed relation with a moving image.
+
+ b. Adapter's License means the license You apply to Your Copyright
+ and Similar Rights in Your contributions to Adapted Material in
+ accordance with the terms and conditions of this Public License.
+
+ c. BY-SA Compatible License means a license listed at
+ creativecommons.org/compatiblelicenses, approved by Creative
+ Commons as essentially the equivalent of this Public License.
+
+ d. Copyright and Similar Rights means copyright and/or similar rights
+ closely related to copyright including, without limitation,
+ performance, broadcast, sound recording, and Sui Generis Database
+ Rights, without regard to how the rights are labeled or
+ categorized. For purposes of this Public License, the rights
+ specified in Section 2(b)(1)-(2) are not Copyright and Similar
+ Rights.
+
+ e. Effective Technological Measures means those measures that, in the
+ absence of proper authority, may not be circumvented under laws
+ fulfilling obligations under Article 11 of the WIPO Copyright
+ Treaty adopted on December 20, 1996, and/or similar international
+ agreements.
+
+ f. Exceptions and Limitations means fair use, fair dealing, and/or
+ any other exception or limitation to Copyright and Similar Rights
+ that applies to Your use of the Licensed Material.
+
+ g. License Elements means the license attributes listed in the name
+ of a Creative Commons Public License. The License Elements of this
+ Public License are Attribution and ShareAlike.
+
+ h. Licensed Material means the artistic or literary work, database,
+ or other material to which the Licensor applied this Public
+ License.
+
+ i. Licensed Rights means the rights granted to You subject to the
+ terms and conditions of this Public License, which are limited to
+ all Copyright and Similar Rights that apply to Your use of the
+ Licensed Material and that the Licensor has authority to license.
+
+ j. Licensor means the individual(s) or entity(ies) granting rights
+ under this Public License.
+
+ k. Share means to provide material to the public by any means or
+ process that requires permission under the Licensed Rights, such
+ as reproduction, public display, public performance, distribution,
+ dissemination, communication, or importation, and to make material
+ available to the public including in ways that members of the
+ public may access the material from a place and at a time
+ individually chosen by them.
+
+ l. Sui Generis Database Rights means rights other than copyright
+ resulting from Directive 96/9/EC of the European Parliament and of
+ the Council of 11 March 1996 on the legal protection of databases,
+ as amended and/or succeeded, as well as other essentially
+ equivalent rights anywhere in the world.
+
+ m. You means the individual or entity exercising the Licensed Rights
+ under this Public License. Your has a corresponding meaning.
+
+
+Section 2 -- Scope.
+
+ a. License grant.
+
+ 1. Subject to the terms and conditions of this Public License,
+ the Licensor hereby grants You a worldwide, royalty-free,
+ non-sublicensable, non-exclusive, irrevocable license to
+ exercise the Licensed Rights in the Licensed Material to:
+
+ a. reproduce and Share the Licensed Material, in whole or
+ in part; and
+
+ b. produce, reproduce, and Share Adapted Material.
+
+ 2. Exceptions and Limitations. For the avoidance of doubt, where
+ Exceptions and Limitations apply to Your use, this Public
+ License does not apply, and You do not need to comply with
+ its terms and conditions.
+
+ 3. Term. The term of this Public License is specified in Section
+ 6(a).
+
+ 4. Media and formats; technical modifications allowed. The
+ Licensor authorizes You to exercise the Licensed Rights in
+ all media and formats whether now known or hereafter created,
+ and to make technical modifications necessary to do so. The
+ Licensor waives and/or agrees not to assert any right or
+ authority to forbid You from making technical modifications
+ necessary to exercise the Licensed Rights, including
+ technical modifications necessary to circumvent Effective
+ Technological Measures. For purposes of this Public License,
+ simply making modifications authorized by this Section 2(a)
+ (4) never produces Adapted Material.
+
+ 5. Downstream recipients.
+
+ a. Offer from the Licensor -- Licensed Material. Every
+ recipient of the Licensed Material automatically
+ receives an offer from the Licensor to exercise the
+ Licensed Rights under the terms and conditions of this
+ Public License.
+
+ b. Additional offer from the Licensor -- Adapted Material.
+ Every recipient of Adapted Material from You
+ automatically receives an offer from the Licensor to
+ exercise the Licensed Rights in the Adapted Material
+ under the conditions of the Adapter's License You apply.
+
+ c. No downstream restrictions. You may not offer or impose
+ any additional or different terms or conditions on, or
+ apply any Effective Technological Measures to, the
+ Licensed Material if doing so restricts exercise of the
+ Licensed Rights by any recipient of the Licensed
+ Material.
+
+ 6. No endorsement. Nothing in this Public License constitutes or
+ may be construed as permission to assert or imply that You
+ are, or that Your use of the Licensed Material is, connected
+ with, or sponsored, endorsed, or granted official status by,
+ the Licensor or others designated to receive attribution as
+ provided in Section 3(a)(1)(A)(i).
+
+ b. Other rights.
+
+ 1. Moral rights, such as the right of integrity, are not
+ licensed under this Public License, nor are publicity,
+ privacy, and/or other similar personality rights; however, to
+ the extent possible, the Licensor waives and/or agrees not to
+ assert any such rights held by the Licensor to the limited
+ extent necessary to allow You to exercise the Licensed
+ Rights, but not otherwise.
+
+ 2. Patent and trademark rights are not licensed under this
+ Public License.
+
+ 3. To the extent possible, the Licensor waives any right to
+ collect royalties from You for the exercise of the Licensed
+ Rights, whether directly or through a collecting society
+ under any voluntary or waivable statutory or compulsory
+ licensing scheme. In all other cases the Licensor expressly
+ reserves any right to collect such royalties.
+
+
+Section 3 -- License Conditions.
+
+Your exercise of the Licensed Rights is expressly made subject to the
+following conditions.
+
+ a. Attribution.
+
+ 1. If You Share the Licensed Material (including in modified
+ form), You must:
+
+ a. retain the following if it is supplied by the Licensor
+ with the Licensed Material:
+
+ i. identification of the creator(s) of the Licensed
+ Material and any others designated to receive
+ attribution, in any reasonable manner requested by
+ the Licensor (including by pseudonym if
+ designated);
+
+ ii. a copyright notice;
+
+ iii. a notice that refers to this Public License;
+
+ iv. a notice that refers to the disclaimer of
+ warranties;
+
+ v. a URI or hyperlink to the Licensed Material to the
+ extent reasonably practicable;
+
+ b. indicate if You modified the Licensed Material and
+ retain an indication of any previous modifications; and
+
+ c. indicate the Licensed Material is licensed under this
+ Public License, and include the text of, or the URI or
+ hyperlink to, this Public License.
+
+ 2. You may satisfy the conditions in Section 3(a)(1) in any
+ reasonable manner based on the medium, means, and context in
+ which You Share the Licensed Material. For example, it may be
+ reasonable to satisfy the conditions by providing a URI or
+ hyperlink to a resource that includes the required
+ information.
+
+ 3. If requested by the Licensor, You must remove any of the
+ information required by Section 3(a)(1)(A) to the extent
+ reasonably practicable.
+
+ b. ShareAlike.
+
+ In addition to the conditions in Section 3(a), if You Share
+ Adapted Material You produce, the following conditions also apply.
+
+ 1. The Adapter's License You apply must be a Creative Commons
+ license with the same License Elements, this version or
+ later, or a BY-SA Compatible License.
+
+ 2. You must include the text of, or the URI or hyperlink to, the
+ Adapter's License You apply. You may satisfy this condition
+ in any reasonable manner based on the medium, means, and
+ context in which You Share Adapted Material.
+
+ 3. You may not offer or impose any additional or different terms
+ or conditions on, or apply any Effective Technological
+ Measures to, Adapted Material that restrict exercise of the
+ rights granted under the Adapter's License You apply.
+
+
+Section 4 -- Sui Generis Database Rights.
+
+Where the Licensed Rights include Sui Generis Database Rights that
+apply to Your use of the Licensed Material:
+
+ a. for the avoidance of doubt, Section 2(a)(1) grants You the right
+ to extract, reuse, reproduce, and Share all or a substantial
+ portion of the contents of the database;
+
+ b. if You include all or a substantial portion of the database
+ contents in a database in which You have Sui Generis Database
+ Rights, then the database in which You have Sui Generis Database
+ Rights (but not its individual contents) is Adapted Material,
+ including for purposes of Section 3(b); and
+
+ c. You must comply with the conditions in Section 3(a) if You Share
+ all or a substantial portion of the contents of the database.
+
+For the avoidance of doubt, this Section 4 supplements and does not
+replace Your obligations under this Public License where the Licensed
+Rights include other Copyright and Similar Rights.
+
+
+Section 5 -- Disclaimer of Warranties and Limitation of Liability.
+
+ a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
+ EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
+ AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
+ ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
+ IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
+ WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
+ ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
+ KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
+ ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
+
+ b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
+ TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
+ NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
+ INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
+ COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
+ USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
+ ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
+ DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
+ IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
+
+ c. The disclaimer of warranties and limitation of liability provided
+ above shall be interpreted in a manner that, to the extent
+ possible, most closely approximates an absolute disclaimer and
+ waiver of all liability.
+
+
+Section 6 -- Term and Termination.
+
+ a. This Public License applies for the term of the Copyright and
+ Similar Rights licensed here. However, if You fail to comply with
+ this Public License, then Your rights under this Public License
+ terminate automatically.
+
+ b. Where Your right to use the Licensed Material has terminated under
+ Section 6(a), it reinstates:
+
+ 1. automatically as of the date the violation is cured, provided
+ it is cured within 30 days of Your discovery of the
+ violation; or
+
+ 2. upon express reinstatement by the Licensor.
+
+ For the avoidance of doubt, this Section 6(b) does not affect any
+ right the Licensor may have to seek remedies for Your violations
+ of this Public License.
+
+ c. For the avoidance of doubt, the Licensor may also offer the
+ Licensed Material under separate terms or conditions or stop
+ distributing the Licensed Material at any time; however, doing so
+ will not terminate this Public License.
+
+ d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
+ License.
+
+
+Section 7 -- Other Terms and Conditions.
+
+ a. The Licensor shall not be bound by any additional or different
+ terms or conditions communicated by You unless expressly agreed.
+
+ b. Any arrangements, understandings, or agreements regarding the
+ Licensed Material not stated herein are separate from and
+ independent of the terms and conditions of this Public License.
+
+
+Section 8 -- Interpretation.
+
+ a. For the avoidance of doubt, this Public License does not, and
+ shall not be interpreted to, reduce, limit, restrict, or impose
+ conditions on any use of the Licensed Material that could lawfully
+ be made without permission under this Public License.
+
+ b. To the extent possible, if any provision of this Public License is
+ deemed unenforceable, it shall be automatically reformed to the
+ minimum extent necessary to make it enforceable. If the provision
+ cannot be reformed, it shall be severed from this Public License
+ without affecting the enforceability of the remaining terms and
+ conditions.
+
+ c. No term or condition of this Public License will be waived and no
+ failure to comply consented to unless expressly agreed to by the
+ Licensor.
+
+ d. Nothing in this Public License constitutes or may be interpreted
+ as a limitation upon, or waiver of, any privileges and immunities
+ that apply to the Licensor or You, including from the legal
+ processes of any jurisdiction or authority.
+
+
+=======================================================================
+
+Creative Commons is not a party to its public
+licenses. Notwithstanding, Creative Commons may elect to apply one of
+its public licenses to material it publishes and in those instances
+will be considered the “Licensor.” The text of the Creative Commons
+public licenses is dedicated to the public domain under the CC0 Public
+Domain Dedication. Except for the limited purpose of indicating that
+material is shared under a Creative Commons public license or as
+otherwise permitted by the Creative Commons policies published at
+creativecommons.org/policies, Creative Commons does not authorize the
+use of the trademark "Creative Commons" or any other trademark or logo
+of Creative Commons without its prior written consent including,
+without limitation, in connection with any unauthorized modifications
+to any of its public licenses or any other arrangements,
+understandings, or agreements concerning use of licensed material. For
+the avoidance of doubt, this paragraph does not form part of the
+public licenses.
+
+Creative Commons may be contacted at creativecommons.org.
+=======================================================================
diff --git a/build/lib/stylelint/vscode-known-variables.json b/build/lib/stylelint/vscode-known-variables.json
index 737c3b42ed6..6f8bcbccadf 100644
--- a/build/lib/stylelint/vscode-known-variables.json
+++ b/build/lib/stylelint/vscode-known-variables.json
@@ -569,6 +569,9 @@
"--vscode-positronModalDialog-buttonActiveBackground",
"--vscode-positronModalDialog-buttonBackground",
"--vscode-positronModalDialog-buttonBorder",
+ "--vscode-positronModalDialog-buttonDisabledBackground",
+ "--vscode-positronModalDialog-buttonDisabledBorder",
+ "--vscode-positronModalDialog-buttonDisabledForeground",
"--vscode-positronModalDialog-buttonForeground",
"--vscode-positronModalDialog-buttonHoverBackground",
"--vscode-positronModalDialog-checkboxBackground",
@@ -579,6 +582,15 @@
"--vscode-positronModalDialog-defaultButtonForeground",
"--vscode-positronModalDialog-defaultButtonHoverBackground",
"--vscode-positronModalDialog-foreground",
+ "--vscode-positronModalDialog-projectTypeBackground",
+ "--vscode-positronModalDialog-projectTypeBackgroundHover",
+ "--vscode-positronModalDialog-projectTypeBackgroundSelected",
+ "--vscode-positronModalDialog-projectTypeBorder",
+ "--vscode-positronModalDialog-projectTypeBorderHover",
+ "--vscode-positronModalDialog-projectTypeBorderSelected",
+ "--vscode-positronModalDialog-projectTypeForeground",
+ "--vscode-positronModalDialog-projectTypeForegroundHover",
+ "--vscode-positronModalDialog-projectTypeForegroundSelected",
"--vscode-positronModalDialog-radioButtonBackground",
"--vscode-positronModalDialog-radioButtonBorder",
"--vscode-positronModalDialog-radioButtonForeground",
diff --git a/extensions/jupyter-adapter/scripts/install.ts b/extensions/jupyter-adapter/scripts/install.ts
index 1617f9928fc..6b2849ee448 100644
--- a/extensions/jupyter-adapter/scripts/install.ts
+++ b/extensions/jupyter-adapter/scripts/install.ts
@@ -1,5 +1,5 @@
/*---------------------------------------------------------------------------------------------
- * Copyright (C) 2023 Posit Software, PBC. All rights reserved.
+ * Copyright (C) 2023-2024 Posit Software, PBC. All rights reserved.
*--------------------------------------------------------------------------------------------*/
import { spawnSync } from 'child_process';
@@ -32,7 +32,7 @@ if (platform() === 'darwin') {
// on every invocation in that case, which is no fun.
let zeromqVersion: string;
if (platform() === 'darwin') {
- zeromqVersion = 'kevinushey/zeromq.js#fa6b52f85293d9fe14958d18f031d65520afd272';
+ zeromqVersion = 'jmcphers/zeromq.js#e260089d6ede978aeba635a5e552f46410609b83';
} else {
zeromqVersion = 'zeromq@6.0.0-beta.16';
}
diff --git a/extensions/jupyter-adapter/src/JupyterKernel.ts b/extensions/jupyter-adapter/src/JupyterKernel.ts
index 59ae8d31ea7..ca1a373b7f4 100644
--- a/extensions/jupyter-adapter/src/JupyterKernel.ts
+++ b/extensions/jupyter-adapter/src/JupyterKernel.ts
@@ -344,12 +344,12 @@ export class JupyterKernel extends EventEmitter implements vscode.Disposable {
this.log(`Connecting to kernel sockets defined in ${session.state.connectionFile}...`);
// Wait for the sockets to connect or the timeout to expire. Note that
- // each socket has 10 second timeout for connecting, so this is just an
+ // each socket has 20 second timeout for connecting, so this is just an
// additional safeguard.
await withTimeout(
this.connect(session.state.connectionFile),
- 15000,
- `Timed out waiting 15 seconds for kernel to connect to sockets`);
+ 25000,
+ `Timed out waiting 25 seconds for kernel to connect to sockets`);
// We're connected! Establish the socket listeners
return this.establishSocketListeners();
@@ -1577,11 +1577,11 @@ export class JupyterKernel extends EventEmitter implements vscode.Disposable {
* Creates a detailed error object to emit to the client when the kernel fails
* to start.
*
- * @param message The error message
+ * @param error The source error message or object
* @returns A StartupFailure object containing the error message and the
* contents of the kernel's log file, if it exists
*/
- private createStartupFailure(message: string): StartupFailure {
+ private createStartupFailure(err: any): StartupFailure {
// Read the content of the log file, if it exists; this may contain more detail
// about why the kernel exited.
let logFileContent = '';
@@ -1599,6 +1599,6 @@ export class JupyterKernel extends EventEmitter implements vscode.Disposable {
.join('\n');
// Create a startup failure message
- return new StartupFailure(message, logFileContent);
+ return new StartupFailure(err.toString(), logFileContent);
}
}
diff --git a/extensions/jupyter-adapter/src/JupyterSocket.ts b/extensions/jupyter-adapter/src/JupyterSocket.ts
index 80924575b40..f26879fce62 100644
--- a/extensions/jupyter-adapter/src/JupyterSocket.ts
+++ b/extensions/jupyter-adapter/src/JupyterSocket.ts
@@ -253,10 +253,10 @@ export class JupyterSocket implements vscode.Disposable {
}
// Compute how long we've been waiting
const waitTime = Date.now() - startTime;
- if (waitTime >= 10000) {
- // If we've been waiting for more than 10 seconds, reject the promise
- this._logger(`${this._title} socket connect timed out after 10 seconds`);
- this._connectPromise.reject(new Error('Socket connect timed out after 10 seconds'));
+ if (waitTime >= 20000) {
+ // If we've been waiting for more than 20 seconds, reject the promise
+ this._logger(`${this._title} socket connect timed out after 20 seconds`);
+ this._connectPromise.reject(new Error('Socket connect timed out after 20 seconds'));
this._connectPromise = undefined;
// Return to the uninitialized state so a new connection can be attempted if
diff --git a/extensions/positron-notebook-controllers/package.json b/extensions/positron-notebook-controllers/package.json
index fac9d865e02..ca70a0bca0f 100644
--- a/extensions/positron-notebook-controllers/package.json
+++ b/extensions/positron-notebook-controllers/package.json
@@ -14,6 +14,16 @@
"onStartupFinished"
],
"contributes": {
+ "commands": [
+ {
+ "command": "positron.restartKernel",
+ "category": "Notebook",
+ "title": "%positron.command.restartKernel.title%",
+ "shortTitle": "%positron.command.restartKernel.shortTitle%",
+ "icon": "$(debug-restart)",
+ "enablement": "positron.hasRunningNotebookSession"
+ }
+ ],
"configuration": [
{
"title": "Positron Notebook Controllers",
@@ -25,7 +35,16 @@
}
}
}
- ]
+ ],
+ "menus": {
+ "notebook/toolbar": [
+ {
+ "command": "positron.restartKernel",
+ "group": "navigation/execute@5",
+ "when": "notebookKernel =~ /^vscode.positron-notebook-controllers\\//"
+ }
+ ]
+ }
},
"main": "./out/extension.js",
"scripts": {
diff --git a/extensions/positron-notebook-controllers/package.nls.json b/extensions/positron-notebook-controllers/package.nls.json
index f4bc03ec484..26d050ffcd3 100644
--- a/extensions/positron-notebook-controllers/package.nls.json
+++ b/extensions/positron-notebook-controllers/package.nls.json
@@ -1,3 +1,5 @@
{
+ "positron.command.restartKernel.title": "Restart Kernel",
+ "positron.command.restartKernel.shortTitle": "Restart",
"python.languageServer.description": "This setting is included as a temporary workaround to disable the Jupyter extension's language server. The value should remain 'Default'."
}
diff --git a/extensions/positron-notebook-controllers/src/commands.ts b/extensions/positron-notebook-controllers/src/commands.ts
new file mode 100644
index 00000000000..f17c9ac2470
--- /dev/null
+++ b/extensions/positron-notebook-controllers/src/commands.ts
@@ -0,0 +1,34 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (C) 2024 Posit Software, PBC. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+import * as vscode from 'vscode';
+import { NotebookSessionService } from './notebookSessionService';
+
+export function registerCommands(context: vscode.ExtensionContext, notebookSessionService: NotebookSessionService): void {
+ context.subscriptions.push(vscode.commands.registerCommand('positron.restartKernel', async () => {
+ // Get the active notebook.
+ const notebook = vscode.window.activeNotebookEditor?.notebook;
+ if (!notebook) {
+ throw new Error('No active notebook. This command should only be available when a notebook is active.');
+ }
+
+ // Get the session for the active notebook.
+ const session = notebookSessionService.getNotebookSession(notebook.uri);
+ if (!session) {
+ throw new Error('No session found for active notebook. This command should only be available when a session is running.');
+ }
+
+ // Restart the session with a progress bar.
+ try {
+ await vscode.window.withProgress({
+ location: vscode.ProgressLocation.Notification,
+ title: vscode.l10n.t("Restarting {0} interpreter for '{1}'", session.runtimeMetadata.languageName, notebook.uri.path),
+ }, () => notebookSessionService.restartRuntimeSession(notebook.uri));
+ } catch (error) {
+ vscode.window.showErrorMessage(
+ vscode.l10n.t("Restarting {0} interpreter for '{1}' failed. Reason: {2}",
+ session.runtimeMetadata.languageName, notebook.uri.path, error.message));
+ }
+ }));
+}
diff --git a/extensions/positron-notebook-controllers/src/extension.ts b/extensions/positron-notebook-controllers/src/extension.ts
index 9fbfa8e5d87..87227e930bd 100644
--- a/extensions/positron-notebook-controllers/src/extension.ts
+++ b/extensions/positron-notebook-controllers/src/extension.ts
@@ -6,14 +6,17 @@ import * as positron from 'positron';
import * as vscode from 'vscode';
import { NotebookControllerManager } from './notebookControllerManager';
import { NotebookSessionService } from './notebookSessionService';
+import { registerCommands } from './commands';
export const log = vscode.window.createOutputChannel('Positron Notebook Controllers', { log: true });
export async function activate(context: vscode.ExtensionContext): Promise {
const notebookSessionService = new NotebookSessionService();
+ context.subscriptions.push(notebookSessionService);
// Shutdown any running sessions when a notebook is closed.
context.subscriptions.push(vscode.workspace.onDidCloseNotebookDocument(async (notebook) => {
+ log.debug(`Notebook closed: ${notebook.uri.path}`);
if (notebookSessionService.hasStartingOrRunningNotebookSession(notebook.uri)) {
await notebookSessionService.shutdownRuntimeSession(notebook.uri);
}
@@ -45,4 +48,28 @@ export async function activate(context: vscode.ExtensionContext): Promise
for (const notebook of vscode.workspace.notebookDocuments) {
manager.updateNotebookAffinity(notebook);
}
+
+ // Set the hasRunningNotebookSession context when the active notebook editor changes.
+ vscode.window.onDidChangeActiveNotebookEditor((editor) => {
+ const value = notebookSessionService.hasRunningNotebookSession(editor?.notebook.uri);
+ setHasRunningNotebookSessionContext(value);
+ });
+
+ // Set the hasRunningNotebookSession context when a session is started/shutdown for the active notebook.
+ context.subscriptions.push(notebookSessionService.onDidChangeNotebookSession((e) => {
+ if (e.notebookUri === vscode.window.activeNotebookEditor?.notebook.uri) {
+ setHasRunningNotebookSessionContext(!!e.session);
+ }
+ }));
+
+ registerCommands(context, notebookSessionService);
+}
+
+function setHasRunningNotebookSessionContext(value: boolean): void {
+ log.debug(`Setting 'positron.hasRunningNotebookSession' context to: ${value}`);
+ vscode.commands.executeCommand(
+ 'setContext',
+ 'positron.hasRunningNotebookSession',
+ value,
+ );
}
diff --git a/extensions/positron-notebook-controllers/src/notebookController.ts b/extensions/positron-notebook-controllers/src/notebookController.ts
index 3794c359323..9d68d02b14d 100644
--- a/extensions/positron-notebook-controllers/src/notebookController.ts
+++ b/extensions/positron-notebook-controllers/src/notebookController.ts
@@ -67,6 +67,13 @@ export class NotebookController implements vscode.Disposable {
await this._notebookSessionService.shutdownRuntimeSession(e.notebook.uri);
}
}));
+
+ // Restart the execution counter when a notebook session is started.
+ this._disposables.push(this._notebookSessionService.onDidChangeNotebookSession(async (e) => {
+ if (e.session) {
+ this._executionOrderBySessionId.delete(e.session.metadata.sessionId);
+ }
+ }));
}
/** The human-readable label of the controller. */
@@ -80,7 +87,7 @@ export class NotebookController implements vscode.Disposable {
* @param notebook The notebook to start a runtime for.
* @returns Promise that resolves when the runtime has started.
*/
- public async startRuntimeSession(notebook: vscode.NotebookDocument): Promise {
+ private async startRuntimeSession(notebook: vscode.NotebookDocument): Promise {
try {
return await this._notebookSessionService.startRuntimeSession(notebook.uri, this._languageId);
} catch (err) {
diff --git a/extensions/positron-notebook-controllers/src/notebookSessionService.ts b/extensions/positron-notebook-controllers/src/notebookSessionService.ts
index 58a7bc352ee..552113186f0 100644
--- a/extensions/positron-notebook-controllers/src/notebookSessionService.ts
+++ b/extensions/positron-notebook-controllers/src/notebookSessionService.ts
@@ -3,10 +3,17 @@
*--------------------------------------------------------------------------------------------*/
import * as positron from 'positron';
-import { Uri } from 'vscode';
+import * as vscode from 'vscode';
import { log } from './extension';
import { ResourceMap } from './map';
-import { DeferredPromise } from './util';
+
+export interface INotebookSessionDidChangeEvent {
+ /** The URI of the notebook corresponding to the session. */
+ readonly notebookUri: vscode.Uri;
+
+ /** The session that was set for the notebook, or undefined if it was deleted. */
+ readonly session?: positron.LanguageRuntimeSession;
+}
/**
* The notebook session service is the main interface for interacting with
@@ -17,31 +24,64 @@ import { DeferredPromise } from './util';
* required into the runtime session service and expose what's needed via the Positron Extensions
* API.
*/
-export class NotebookSessionService {
+export class NotebookSessionService implements vscode.Disposable {
+ private readonly _disposables = new Array();
/**
* A map of sessions currently starting, keyed by notebook URI. Values are promises that resolve
* when the session has started and is ready to execute code.
*/
- private readonly _startingSessionsByNotebookUri = new ResourceMap>();
+ private readonly _startingSessionsByNotebookUri = new ResourceMap>();
/**
* A map of sessions currently shutting down, keyed by notebook URI. Values are promises that resolve
* when the session has completed the shutdown sequence.
*/
- private readonly _shuttingDownSessionsByNotebookUri = new ResourceMap>();
+ private readonly _shuttingDownSessionsByNotebookUri = new ResourceMap>();
+
+ /**
+ * A map of sessions currently restarting, keyed by notebook URI. Values are promises that resolve
+ * when the session has completed the restart sequence.
+ */
+ private readonly _restartingSessionsByNotebookUri = new ResourceMap>();
/** A map of the currently active notebook sessions, keyed by notebook URI. */
private readonly _notebookSessionsByNotebookUri = new ResourceMap();
+ /** The event emitter for the onDidChangeNotebookSession event. */
+ private readonly _onDidChangeNotebookSession = this._register(new vscode.EventEmitter);
+
+ /** An event that fires when a session is set/unset for a notebook. */
+ readonly onDidChangeNotebookSession = this._onDidChangeNotebookSession.event;
+
+ private _register(disposable: T): T {
+ this._disposables.push(disposable);
+ return disposable;
+ }
+
/**
* Checks for a starting or running notebook for the given notebook URI.
*
* @param notebookUri The notebook URI to check for.
* @returns True if a starting or running notebook session exists for the given notebook URI.
*/
- hasStartingOrRunningNotebookSession(notebookUri: Uri): boolean {
- return this._startingSessionsByNotebookUri.has(notebookUri) || this._notebookSessionsByNotebookUri.has(notebookUri);
+ hasStartingOrRunningNotebookSession(notebookUri: vscode.Uri): boolean {
+ return this._startingSessionsByNotebookUri.has(notebookUri) ||
+ this._restartingSessionsByNotebookUri.has(notebookUri) ||
+ this._notebookSessionsByNotebookUri.has(notebookUri);
+ }
+
+ /**
+ * Checks for a running notebook for the given notebook URI.
+ *
+ * @param notebookUri The notebook URI to check for.
+ * @returns True if a running notebook session exists for the given notebook URI.
+ */
+ hasRunningNotebookSession(notebookUri: vscode.Uri | undefined): boolean {
+ if (!notebookUri) {
+ return false;
+ }
+ return this._notebookSessionsByNotebookUri.has(notebookUri);
}
/**
@@ -50,103 +90,120 @@ export class NotebookSessionService {
* @param notebookUri The notebook URI of the session to retrieve.
* @returns The running notebook session for the given notebook URI, if one exists.
*/
- getNotebookSession(notebookUri: Uri): positron.LanguageRuntimeSession | undefined {
+ getNotebookSession(notebookUri: vscode.Uri): positron.LanguageRuntimeSession | undefined {
return this._notebookSessionsByNotebookUri.get(notebookUri);
}
+ /**
+ * Set a notebook session for a notebook URI.
+ *
+ * @param notebookUri The notebook URI of the session to set.
+ * @param session The session to set for the notebook URI, or undefined to delete the session.
+ */
+ setNotebookSession(notebookUri: vscode.Uri, session: positron.LanguageRuntimeSession | undefined): void {
+ if (session) {
+ this._notebookSessionsByNotebookUri.set(notebookUri, session);
+ } else {
+ this._notebookSessionsByNotebookUri.delete(notebookUri);
+ }
+ this._onDidChangeNotebookSession.fire({ notebookUri, session });
+ }
+
/**
* Start a new runtime session for a notebook.
*
* @param notebookUri The notebook URI to start a runtime for.
* @returns Promise that resolves when the runtime startup sequence has been started.
*/
- async startRuntimeSession(notebookUri: Uri, languageId: string): Promise {
+ async startRuntimeSession(notebookUri: vscode.Uri, languageId: string): Promise {
// Return the existing promise, if there is one.
- const startingSessionPromise = this._startingSessionsByNotebookUri.get(notebookUri);
- if (startingSessionPromise && !startingSessionPromise.isSettled) {
- return startingSessionPromise.p;
+ const startingSessionPromise = this._startingSessionsByNotebookUri.get(notebookUri) ||
+ this._restartingSessionsByNotebookUri.get(notebookUri);
+ if (startingSessionPromise) {
+ return startingSessionPromise;
}
- // Update the starting sessions map. This needs to be set before any awaits in case another
- // caller tries to start a runtime or access the start promise concurrently.
- const startPromise = new DeferredPromise();
+ // Construct a wrapping promise that resolves/rejects after the session maps have been updated.
+ const startPromise = (async () => {
+ try {
+ const session = await this.doStartRuntimeSession(notebookUri, languageId);
+ this._startingSessionsByNotebookUri.delete(notebookUri);
+ this.setNotebookSession(notebookUri, session);
+ log.info(`Session ${session.metadata.sessionId} is started`);
+ return session;
+ } catch (err) {
+ this._startingSessionsByNotebookUri.delete(notebookUri);
+ throw err;
+ }
+ })();
+
this._startingSessionsByNotebookUri.set(notebookUri, startPromise);
- // If the notebook has a session that is still shutting down, wait for it to finish.
+ return startPromise;
+ }
+
+ async doStartRuntimeSession(notebookUri: vscode.Uri, languageId: string): Promise {
+ // If the session is still shutting down, wait for it to finish.
const shuttingDownSessionPromise = this._shuttingDownSessionsByNotebookUri.get(notebookUri);
- if (shuttingDownSessionPromise && !shuttingDownSessionPromise.isSettled) {
- await shuttingDownSessionPromise.p;
+ if (shuttingDownSessionPromise) {
+ try {
+ await shuttingDownSessionPromise;
+ } catch (err) {
+ log.error(`Waiting for notebook runtime to shutdown before starting failed. Reason ${err}`);
+ throw err;
+ }
}
// Ensure that we don't start a runtime for a notebook that already has one.
if (this._notebookSessionsByNotebookUri.has(notebookUri)) {
- const err = new Error(`Tried to start a runtime for a notebook that already has one: ${notebookUri.path}`);
- startPromise.error(err);
- this._startingSessionsByNotebookUri.delete(notebookUri);
- throw err;
+ throw new Error(`Tried to start a runtime for a notebook that already has one: ${notebookUri.path}`);
}
// If there's already a session for this runtime e.g. one restored after a window reload, use it.
- let session: positron.LanguageRuntimeSession | undefined;
try {
- session = await positron.runtime.getNotebookSession(notebookUri);
+ const session = await positron.runtime.getNotebookSession(notebookUri);
if (session) {
+ // TODO: If it isn't running, log an error and start a new one.
+ // TODO: If it doesn't match the runtime ID, log an error, shut it down, and start a new one.
log.info(
`Restored session for language runtime ${session.metadata.sessionId} `
+ `(language: ${session.runtimeMetadata.languageName}, name: ${session.runtimeMetadata.runtimeName}, `
+ `version: ${session.runtimeMetadata.runtimeVersion}, notebook: ${notebookUri.path})`
);
+ return session;
}
} catch (err) {
log.error(
`Getting existing session for notebook ${notebookUri.path}' failed. Reason: ${err}`
);
- startPromise.error(err);
- this._startingSessionsByNotebookUri.delete(notebookUri);
throw err;
}
- // TODO: If it isn't running, log an error and start a new one.
- // TODO: If it doesn't match the runtime ID, log an error, shut it down, and start a new one.
-
// If we couldn't restore a session, start a new one.
- if (!session) {
- // Get the preferred runtime for this language.
- let preferredRuntime: positron.LanguageRuntimeMetadata;
- try {
- preferredRuntime = await positron.runtime.getPreferredRuntime(languageId);
- } catch (err) {
- log.error(`Getting preferred runtime for language '${languageId}' failed. Reason: ${err}`);
- startPromise.error(err);
- this._startingSessionsByNotebookUri.delete(notebookUri);
- throw err;
- }
-
- try {
- session = await positron.runtime.startLanguageRuntime(
- preferredRuntime.runtimeId,
- notebookUri.path, // Use the notebook's path as the session name.
- notebookUri);
- log.info(
- `Starting session for language runtime ${session.metadata.sessionId} `
- + `(language: ${session.runtimeMetadata.languageName}, name: ${session.runtimeMetadata.runtimeName}, `
- + `version: ${session.runtimeMetadata.runtimeVersion}, notebook: ${notebookUri.path})`
- );
- } catch (err) {
- log.error(`Starting session for language runtime ${preferredRuntime.runtimeName} failed. Reason: ${err}`);
- startPromise.error(err);
- this._startingSessionsByNotebookUri.delete(notebookUri);
- throw err;
- }
+ // Get the preferred runtime for this language.
+ let preferredRuntime: positron.LanguageRuntimeMetadata;
+ try {
+ preferredRuntime = await positron.runtime.getPreferredRuntime(languageId);
+ } catch (err) {
+ log.error(`Getting preferred runtime for language '${languageId}' failed. Reason: ${err}`);
+ throw err;
}
- // Complete the promise and update the session maps.
- this._notebookSessionsByNotebookUri.set(notebookUri, session);
- this._startingSessionsByNotebookUri.delete(notebookUri);
- startPromise.complete(session);
- log.info(`Session ${session.metadata.sessionId} is ready`);
-
- return session;
+ try {
+ const session = await positron.runtime.startLanguageRuntime(
+ preferredRuntime.runtimeId,
+ notebookUri.path, // Use the notebook's path as the session name.
+ notebookUri);
+ log.info(
+ `Starting session for language runtime ${session.metadata.sessionId} `
+ + `(language: ${session.runtimeMetadata.languageName}, name: ${session.runtimeMetadata.runtimeName}, `
+ + `version: ${session.runtimeMetadata.runtimeVersion}, notebook: ${notebookUri.path})`
+ );
+ return session;
+ } catch (err) {
+ log.error(`Starting session for language runtime ${preferredRuntime.runtimeName} failed. Reason: ${err}`);
+ throw err;
+ }
}
/**
@@ -155,77 +212,173 @@ export class NotebookSessionService {
* @param notebookUri The notebook URI whose runtime to shutdown.
* @returns Promise that resolves when the runtime shutdown sequence has been started.
*/
- async shutdownRuntimeSession(notebookUri: Uri): Promise {
+ async shutdownRuntimeSession(notebookUri: vscode.Uri): Promise {
// Return the existing promise, if there is one.
const shuttingDownSessionPromise = this._shuttingDownSessionsByNotebookUri.get(notebookUri);
- if (shuttingDownSessionPromise && !shuttingDownSessionPromise.isSettled) {
- return shuttingDownSessionPromise.p;
+ if (shuttingDownSessionPromise) {
+ return shuttingDownSessionPromise;
}
- // Update the shutting down sessions map. This needs to be set before any awaits in case
- // another caller tries to shutdown a runtime or access the shutdown promise concurrently.
- const shutDownPromise = new DeferredPromise();
- this._shuttingDownSessionsByNotebookUri.set(notebookUri, shutDownPromise);
+ // Construct a wrapping promise that resolves/rejects after the session maps have been updated.
+ const shutdownPromise = (async () => {
+ try {
+ const session = await this.doShutdownRuntimeSession(notebookUri);
+ this._shuttingDownSessionsByNotebookUri.delete(notebookUri);
+ this.setNotebookSession(notebookUri, undefined);
+ log.info(`Session ${session.metadata.sessionId} is shutdown`);
+ } catch (err) {
+ this._startingSessionsByNotebookUri.delete(notebookUri);
+ throw err;
+ }
+ })();
+
+ this._shuttingDownSessionsByNotebookUri.set(notebookUri, shutdownPromise);
+
+ return shutdownPromise;
+ }
+ async doShutdownRuntimeSession(notebookUri: vscode.Uri): Promise {
// Get the notebook's session.
let session = this._notebookSessionsByNotebookUri.get(notebookUri);
- // If the runtime is still starting, wait for it to be ready.
if (!session) {
- const startingSessionPromise = this._startingSessionsByNotebookUri.get(notebookUri);
- if (startingSessionPromise && !startingSessionPromise.isSettled) {
- session = await startingSessionPromise.p;
+ // If the notebook's session is still starting, wait for it to finish.
+ const startingSessionPromise = this._startingSessionsByNotebookUri.get(notebookUri) ||
+ this._restartingSessionsByNotebookUri.get(notebookUri);
+ if (startingSessionPromise) {
+ try {
+ session = await startingSessionPromise;
+ } catch (err) {
+ log.error(`Waiting for notebook runtime to start before shutting down failed. Reason ${err}`);
+ throw err;
+ }
}
}
// Ensure that we have a session.
if (!session) {
- const err = new Error(`Tried to shutdown runtime for notebook without a running runtime: ${notebookUri.path}`);
- this._shuttingDownSessionsByNotebookUri.delete(notebookUri);
- shutDownPromise.error(err);
- throw err;
+ throw new Error(`Tried to shutdown runtime for notebook without a running runtime: ${notebookUri.path}`);
}
// Start the shutdown sequence.
try {
- await session.shutdown(positron.RuntimeExitReason.Shutdown);
log.info(`Shutting down runtime ${session.runtimeMetadata.runtimeName} for notebook ${notebookUri.path}`);
+ await session.shutdown(positron.RuntimeExitReason.Shutdown);
} catch (err) {
log.error(`Shutting down runtime ${session.runtimeMetadata.runtimeName} for notebook ${notebookUri.path} failed. Reason: ${err}`);
- this._shuttingDownSessionsByNotebookUri.delete(notebookUri);
- this._notebookSessionsByNotebookUri.delete(notebookUri);
- shutDownPromise.error(err);
throw err;
}
// Wait for the session to end. This is necessary so that we know when to start the next
// session for the notebook, since at most one session can exist per notebook.
- try {
- const timeout = new Promise((_, reject) => {
- setTimeout(() => {
- reject(new Error(`Shutting down runtime ${session.runtimeMetadata.runtimeName} for notebook ${notebookUri.path} timed out`));
- }, 5000);
+ const timeout = new Promise((_, reject) => {
+ setTimeout(() => {
+ reject(new Error(`Shutting down runtime ${session.runtimeMetadata.runtimeName} for notebook ${notebookUri.path} timed out`));
+ }, 5000);
+ });
+ const promise = new Promise(resolve => {
+ const disposable = session.onDidEndSession(() => {
+ disposable.dispose();
+ resolve();
});
- const promise = new Promise(resolve => {
- const disposable = session.onDidEndSession(() => {
+ });
+ try {
+ await Promise.race([promise, timeout]);
+ } catch (err) {
+ log.error(err);
+ throw err;
+ }
+
+ return session;
+ }
+
+ /**
+ * Restart a runtime session for a notebook.
+ *
+ * @param notebookUri The notebook URI to restart a runtime for.
+ * @returns Promise that resolves when the runtime restart sequence has completed and the
+ * session is enters the ready state.
+ */
+ async restartRuntimeSession(notebookUri: vscode.Uri): Promise {
+ // Return the existing promise, if there is one.
+ const startingSessionPromise = this._startingSessionsByNotebookUri.get(notebookUri) ||
+ this._restartingSessionsByNotebookUri.get(notebookUri);
+ if (startingSessionPromise) {
+ return startingSessionPromise;
+ }
+
+ // Construct a wrapping promise that resolves/rejects after the session maps have been updated.
+ const restartPromise = (async () => {
+ try {
+ const session = await this.doRestartRuntimeSession(notebookUri);
+ this._restartingSessionsByNotebookUri.delete(notebookUri);
+ this.setNotebookSession(notebookUri, session);
+ log.info(`Session ${session.metadata.sessionId} is restarted`);
+ return session;
+ } catch (err) {
+ this._restartingSessionsByNotebookUri.delete(notebookUri);
+ throw err;
+ }
+ })();
+
+ this._restartingSessionsByNotebookUri.set(notebookUri, restartPromise);
+
+ return restartPromise;
+ }
+
+ async doRestartRuntimeSession(notebookUri: vscode.Uri): Promise {
+ // Get the notebook's session.
+ const session = this._notebookSessionsByNotebookUri.get(notebookUri);
+ if (!session) {
+ throw new Error(`Tried to restart runtime for notebook without a running runtime: ${notebookUri.path}`);
+ }
+
+ // Remove the session from the map of active notebooks in case it's accessed while we're
+ // restarting.
+ this.setNotebookSession(notebookUri, undefined);
+
+ // If the notebook's session is still shutting down, wait for it to finish.
+ const shuttingDownSessionPromise = this._shuttingDownSessionsByNotebookUri.get(notebookUri);
+ if (shuttingDownSessionPromise) {
+ try {
+ await shuttingDownSessionPromise;
+ } catch (err) {
+ log.error(`Waiting for notebook runtime to shutdown before starting failed. Reason ${err}`);
+ throw err;
+ }
+ }
+
+ // Start the restart sequence.
+ try {
+ log.info(`Restarting session ${session.metadata.sessionId} for notebook ${notebookUri.path}`);
+ await positron.runtime.restartSession(session.metadata.sessionId);
+ } catch (err) {
+ log.error(`Restarting session ${session.metadata.sessionId} for notebook ${notebookUri.path} failed. Reason: ${err}`);
+ throw err;
+ }
+
+ // Wait for the session to be ready, or for a timeout.
+ const timeout = new Promise((_, reject) =>
+ setTimeout(() => reject(new Error('Timeout waiting for runtime to restart')), 5000));
+ const promise = new Promise((resolve) => {
+ const disposable = session.onDidChangeRuntimeState((state) => {
+ if (state === positron.RuntimeState.Ready) {
disposable.dispose();
resolve();
- });
+ }
});
+ });
+ try {
await Promise.race([promise, timeout]);
} catch (err) {
log.error(err);
- this._shuttingDownSessionsByNotebookUri.delete(notebookUri);
- this._notebookSessionsByNotebookUri.delete(notebookUri);
- shutDownPromise.error(err);
throw err;
}
- // Complete the promise and update the session maps.
- this._shuttingDownSessionsByNotebookUri.delete(notebookUri);
- this._notebookSessionsByNotebookUri.delete(notebookUri);
- shutDownPromise.complete();
- log.info(`Session ${session.metadata.sessionId} shutdown completed`);
+ return session;
}
+ dispose() {
+ this._disposables.forEach(d => d.dispose());
+ }
}
diff --git a/extensions/positron-notebook-controllers/src/util.ts b/extensions/positron-notebook-controllers/src/util.ts
deleted file mode 100644
index e6816e1bb62..00000000000
--- a/extensions/positron-notebook-controllers/src/util.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-/*---------------------------------------------------------------------------------------------
- * Copyright (C) 2023 Posit Software, PBC. All rights reserved.
- *--------------------------------------------------------------------------------------------*/
-
-/**
- * A promise with exposed and imperatively callable resolve and reject methods.
- */
-export class DeferredPromise {
- complete!: (value: T) => void;
- error!: (error: unknown) => void;
- isSettled: boolean = false;
- p: Promise;
- value: T | undefined;
-
- constructor() {
- this.p = new Promise((resolve, reject) => {
- this.complete = (val) => {
- this.isSettled = true;
- this.value = val;
- resolve(val);
- };
- this.error = (reason) => {
- this.isSettled = true;
- reject(reason);
- };
- });
- }
-}
-
-export function delay(ms: number) {
- return new Promise(resolve => setTimeout(resolve, ms));
-}
diff --git a/extensions/positron-python/.devcontainer/devcontainer.json b/extensions/positron-python/.devcontainer/devcontainer.json
index fe15f35764e..67a8833d30c 100644
--- a/extensions/positron-python/.devcontainer/devcontainer.json
+++ b/extensions/positron-python/.devcontainer/devcontainer.json
@@ -9,13 +9,13 @@
"customizations": {
"vscode": {
"extensions": [
+ "charliermarsh.ruff",
"editorconfig.editorconfig",
"esbenp.prettier-vscode",
"dbaeumer.vscode-eslint",
"ms-python.python",
- "ms-python.black-formatter",
"ms-python.vscode-pylance",
- "charliermarsh.ruff"
+ "ms-python.debugpy"
]
}
},
diff --git a/extensions/positron-python/.eslintignore b/extensions/positron-python/.eslintignore
index 7f6bb48d6c8..9399ff461dc 100644
--- a/extensions/positron-python/.eslintignore
+++ b/extensions/positron-python/.eslintignore
@@ -105,7 +105,7 @@ src/test/common/process/proc.unit.test.ts
src/test/common/interpreterPathService.unit.test.ts
-src/test/pythonFiles/formatting/dummy.ts
+src/test/python_files/formatting/dummy.ts
src/test/debugger/extension/adapter/adapter.test.ts
src/test/debugger/extension/adapter/outdatedDebuggerPrompt.unit.test.ts
diff --git a/extensions/positron-python/.git-blame-ignore-revs b/extensions/positron-python/.git-blame-ignore-revs
new file mode 100644
index 00000000000..e2c2a50781b
--- /dev/null
+++ b/extensions/positron-python/.git-blame-ignore-revs
@@ -0,0 +1,15 @@
+# Prettier
+2b6a8f2d439fe9d5e66665ea46d8b690ac9b2c39
+649156a09ccdc51c0d20f7cd44540f1918f9347b
+4f774d94bf4fbf87bb417b2b2b8e79e334eb3536
+61b179b2092050709e3c373a6738abad8ce581c4
+c33617b0b98daeb4d72040b48c5850b476d6256c
+db8e1e2460e9754ec0672d958789382b6d15c5aa
+08bc9ad3bee5b19f02fa756fbc53ab32f1b39920
+# Black
+a58eeffd1b64498e2afe5f11597888dfd1c8699c
+5cd8f539f4d2086b718c8f11f823c0ac12fc2c49
+9ec9e9eaebb25adc6d942ac19d4d6c128abb987f
+c4af91e090057d20d7a633b3afa45eaa13ece76f
+# Ruff
+e931bed3efbede7b05113316506958ecd7506777
diff --git a/extensions/positron-python/.github/actions/build-vsix/action.yml b/extensions/positron-python/.github/actions/build-vsix/action.yml
index 52d6d1cdbdd..5b8a569889a 100644
--- a/extensions/positron-python/.github/actions/build-vsix/action.yml
+++ b/extensions/positron-python/.github/actions/build-vsix/action.yml
@@ -30,7 +30,7 @@ runs:
cache-dependency-path: |
requirements.txt
build/build-install-requirements.txt
- pythonFiles/jedilsp_requirements/requirements.txt
+ python_files/jedilsp_requirements/requirements.txt
- name: Upgrade Pip
run: python -m pip install -U pip
@@ -44,20 +44,20 @@ runs:
- name: Install Python dependencies
uses: brettcannon/pip-secure-install@v1
with:
- options: '-t ./pythonFiles/lib/python --implementation py'
+ options: '-t ./python_files/lib/python --implementation py'
- name: Install debugpy and get-pip
run: |
python -m pip --disable-pip-version-check install packaging
- python ./pythonFiles/install_debugpy.py
- python ./pythonFiles/download_get_pip.py
+ python ./python_files/install_debugpy.py
+ python ./python_files/download_get_pip.py
shell: bash
- name: Install Jedi LSP
uses: brettcannon/pip-secure-install@v1
with:
- requirements-file: './pythonFiles/jedilsp_requirements/requirements.txt'
- options: '-t ./pythonFiles/lib/jedilsp --implementation py --platform any --abi none'
+ requirements-file: './python_files/jedilsp_requirements/requirements.txt'
+ options: '-t ./python_files/lib/jedilsp --implementation py --platform any --abi none'
- name: Run npm ci
run: npm ci --prefer-offline
diff --git a/extensions/positron-python/.github/actions/lint/action.yml b/extensions/positron-python/.github/actions/lint/action.yml
index 47924c10815..444f331a3a9 100644
--- a/extensions/positron-python/.github/actions/lint/action.yml
+++ b/extensions/positron-python/.github/actions/lint/action.yml
@@ -41,16 +41,10 @@ runs:
python-version: '3.x'
cache: 'pip'
- - name: Check Python format
- run: |
- python -m pip install -U black
- python -m black . --check
- working-directory: pythonFiles
- shell: bash
-
- name: Run Ruff
run: |
python -m pip install -U ruff
python -m ruff check .
- working-directory: pythonFiles
+ python -m ruff format --check
+ working-directory: python_files
shell: bash
diff --git a/extensions/positron-python/.github/actions/smoke-tests/action.yml b/extensions/positron-python/.github/actions/smoke-tests/action.yml
index b2d00205043..7eaa2483942 100644
--- a/extensions/positron-python/.github/actions/smoke-tests/action.yml
+++ b/extensions/positron-python/.github/actions/smoke-tests/action.yml
@@ -34,12 +34,12 @@ runs:
- name: Install Python requirements
uses: brettcannon/pip-secure-install@v1
with:
- options: '-t ./pythonFiles/lib/python --implementation py'
+ options: '-t ./python_files/lib/python --implementation py'
- name: pip install system test requirements
run: |
python -m pip install --upgrade -r build/test-requirements.txt
- python -m pip --disable-pip-version-check install -t ./pythonFiles/lib/python --implementation py --no-deps --upgrade --pre debugpy
+ python -m pip --disable-pip-version-check install -t ./python_files/lib/python --implementation py --no-deps --upgrade --pre debugpy
shell: bash
# Bits from the VSIX are reused by smokeTest.ts to speed things up.
diff --git a/extensions/positron-python/.github/release_plan.md b/extensions/positron-python/.github/release_plan.md
index 71f8d8aa095..4d57726b4d0 100644
--- a/extensions/positron-python/.github/release_plan.md
+++ b/extensions/positron-python/.github/release_plan.md
@@ -1,14 +1,34 @@
+### General Notes
All dates should align with VS Code's [iteration](https://github.com/microsoft/vscode/labels/iteration-plan) and [endgame](https://github.com/microsoft/vscode/labels/endgame-plan) plans.
Feature freeze is Monday @ 17:00 America/Vancouver, XXX XX. At that point, commits to `main` should only be in response to bugs found during endgame testing until the release candidate is ready.
+
+ Release Primary and Secondary Assignments for the 2024 Calendar Year
-NOTE: the number of this release is in the issue title and can be substituted in wherever you see [YYYY.minor].
+| Month | Primary | Secondary |
+|:----------|:----------|:------------|
+✅ | ~~January~~ | ~~Eleanor~~ | ~~Karthik~~ |
+✅ | February | Kartik | Anthony |
+| March | Karthik | Eleanor |
+| April | Paula | Kartik |
+| May | Anthony | Karthik |
+| June | Eleanor | Paula |
+| July | Anthony | Karthik |
+| August | Paula | Kartik |
+| September | Anthony | Eleanor |
+| October | Paula | Kartik |
+| November | Kartik | Eleanor |
+| December | Karthik | Anthony |
+
+
# Release candidate (Monday, XXX XX)
NOTE: Third Party Notices are automatically added by our build pipelines using https://tools.opensource.microsoft.com/notice.
+NOTE: the number of this release is in the issue title and can be substituted in wherever you see [YYYY.minor].
+
### Step 1:
##### Bump the version of `main` to be a release candidate (also updating debugpy dependences, third party notices, and package-lock.json).❄️ (steps with ❄️ will dictate this step happens while main is frozen 🥶)
@@ -17,7 +37,7 @@ NOTE: Third Party Notices are automatically added by our build pipelines using
- [ ] Create a new branch called **`bump-release-[YYYY.minor]`**.
- [ ] Change the version in `package.json` to the next **even** number and switch the `-dev` to `-rc`. (🤖)
- [ ] Run `npm install` to make sure `package-lock.json` is up-to-date _(you should now see changes to the `package.json` and `package-lock.json` at this point which update the version number **only**)_. (🤖)
-- [ ] Check [debugpy on PyPI](https://pypi.org/project/debugpy/) for a new release and update the version of debugpy in [`install_debugpy.py`](https://github.com/microsoft/vscode-python/blob/main/pythonFiles/install_debugpy.py) if necessary.
+- [ ] Check [debugpy on PyPI](https://pypi.org/project/debugpy/) for a new release and update the version of debugpy in [`install_debugpy.py`](https://github.com/microsoft/vscode-python/blob/main/python_files/install_debugpy.py) if necessary.
- [ ] Update `ThirdPartyNotices-Repository.txt` as appropriate. You can check by looking at the [commit history](https://github.com/microsoft/vscode-python/commits/main) and scrolling through to see if there's anything listed there which might have pulled in some code directly into the repository from somewhere else. If you are still unsure you can check with the team.
- [ ] Create a PR from your branch **`bump-release-[YYYY.minor]`** to `main`. Add the `"no change-log"` tag to the PR so it does not show up on the release notes before merging it.
diff --git a/extensions/positron-python/.github/workflows/build.yml b/extensions/positron-python/.github/workflows/build.yml
index 586fe619d5d..544d07a6574 100644
--- a/extensions/positron-python/.github/workflows/build.yml
+++ b/extensions/positron-python/.github/workflows/build.yml
@@ -87,21 +87,21 @@ jobs:
- name: Install core Python requirements
uses: brettcannon/pip-secure-install@v1
with:
- options: '-t ./pythonFiles/lib/python --no-cache-dir --implementation py'
+ options: '-t ./python_files/lib/python --no-cache-dir --implementation py'
- name: Install Jedi requirements
run: python scripts/vendor.py
- name: Install other Python requirements
run: |
- python -m pip --disable-pip-version-check install -t ./pythonFiles/lib/python --no-cache-dir --implementation py --no-deps --upgrade --pre debugpy
+ python -m pip --disable-pip-version-check install -t ./python_files/lib/python --no-cache-dir --implementation py --no-deps --upgrade --pre debugpy
python -m pip install --upgrade -r build/test-requirements.txt
- name: Run Pyright
uses: jakebailey/pyright-action@v2
with:
version: 1.1.308
- working-directory: 'pythonFiles'
+ working-directory: 'python_files'
python-tests:
name: Python Tests
@@ -134,13 +134,13 @@ jobs:
uses: brettcannon/pip-secure-install@v1
with:
requirements-file: '"${{ env.special-working-directory-relative }}/requirements.txt"'
- options: '-t "${{ env.special-working-directory-relative }}/pythonFiles/lib/python" --no-cache-dir --implementation py'
+ options: '-t "${{ env.special-working-directory-relative }}/python_files/lib/python" --no-cache-dir --implementation py'
- name: Install test requirements
run: python -m pip install --upgrade -r build/test-requirements.txt
- name: Run Python unit tests
- run: python pythonFiles/tests/run_all.py
+ run: python python_files/tests/run_all.py
tests:
name: Tests
@@ -189,19 +189,19 @@ jobs:
run: |
python -m pip install wheel
python -m pip install -r build/build-install-requirements.txt
- python ./pythonFiles/download_get_pip.py
+ python ./python_files/download_get_pip.py
shell: bash
- name: Install debugpy
run: |
# We need to have debugpy so that tests relying on it keep passing, but we don't need install_debugpy's logic in the test phase.
- python -m pip --disable-pip-version-check install -t ./pythonFiles/lib/python --no-cache-dir --implementation py --no-deps --upgrade --pre debugpy
+ python -m pip --disable-pip-version-check install -t ./python_files/lib/python --no-cache-dir --implementation py --no-deps --upgrade --pre debugpy
- name: Install core Python requirements
uses: brettcannon/pip-secure-install@v1
with:
requirements-file: '"${{ env.special-working-directory-relative }}/requirements.txt"'
- options: '-t "${{ env.special-working-directory-relative }}/pythonFiles/lib/python" --no-cache-dir --implementation py'
+ options: '-t "${{ env.special-working-directory-relative }}/python_files/lib/python" --no-cache-dir --implementation py'
if: startsWith(matrix.python, 3.)
- name: Install Jedi requirements
@@ -215,7 +215,7 @@ jobs:
run: |
python -m pip install wheel
python -m pip install -r build/build-install-requirements.txt
- python ./pythonFiles/install_debugpy.py
+ python ./python_files/install_debugpy.py
shell: bash
if: matrix.test-suite == 'debugger'
diff --git a/extensions/positron-python/.github/workflows/pr-check.yml b/extensions/positron-python/.github/workflows/pr-check.yml
index 9b2aaa09974..427649ecd62 100644
--- a/extensions/positron-python/.github/workflows/pr-check.yml
+++ b/extensions/positron-python/.github/workflows/pr-check.yml
@@ -61,21 +61,21 @@ jobs:
- name: Install base Python requirements
uses: brettcannon/pip-secure-install@v1
with:
- options: '-t ./pythonFiles/lib/python --no-cache-dir --implementation py'
+ options: '-t ./python_files/lib/python --no-cache-dir --implementation py'
- name: Install Jedi requirements
run: python scripts/vendor.py
- name: Install other Python requirements
run: |
- python -m pip --disable-pip-version-check install -t ./pythonFiles/lib/python --no-cache-dir --implementation py --no-deps --upgrade --pre debugpy
+ python -m pip --disable-pip-version-check install -t ./python_files/lib/python --no-cache-dir --implementation py --no-deps --upgrade --pre debugpy
python -m pip install --upgrade -r build/test-requirements.txt
- name: Run Pyright
uses: jakebailey/pyright-action@v2
with:
version: 1.1.308
- working-directory: 'pythonFiles'
+ working-directory: 'python_files'
python-tests:
name: Python Tests
@@ -121,13 +121,13 @@ jobs:
uses: brettcannon/pip-secure-install@v1
with:
requirements-file: '"${{ env.special-working-directory-relative }}/requirements.txt"'
- options: '-t "${{ env.special-working-directory-relative }}/pythonFiles/lib/python" --no-cache-dir --implementation py'
+ options: '-t "${{ env.special-working-directory-relative }}/python_files/lib/python" --no-cache-dir --implementation py'
- name: Install test requirements
run: python -m pip install --upgrade -r build/test-requirements.txt
- name: Run Python unit tests
- run: python pythonFiles/tests/run_all.py
+ run: python python_files/tests/run_all.py
tests:
name: Tests
@@ -176,20 +176,20 @@ jobs:
- name: Install debugpy
run: |
# We need to have debugpy so that tests relying on it keep passing, but we don't need install_debugpy's logic in the test phase.
- python -m pip --disable-pip-version-check install -t ./pythonFiles/lib/python --no-cache-dir --implementation py --no-deps --upgrade --pre debugpy
+ python -m pip --disable-pip-version-check install -t ./python_files/lib/python --no-cache-dir --implementation py --no-deps --upgrade --pre debugpy
- name: Download get-pip.py
run: |
python -m pip install wheel
python -m pip install -r build/build-install-requirements.txt
- python ./pythonFiles/download_get_pip.py
+ python ./python_files/download_get_pip.py
shell: bash
- name: Install base Python requirements
uses: brettcannon/pip-secure-install@v1
with:
requirements-file: '"${{ env.special-working-directory-relative }}/requirements.txt"'
- options: '-t "${{ env.special-working-directory-relative }}/pythonFiles/lib/python" --no-cache-dir --implementation py'
+ options: '-t "${{ env.special-working-directory-relative }}/python_files/lib/python" --no-cache-dir --implementation py'
- name: Install Jedi requirements
run: python scripts/vendor.py
@@ -201,7 +201,7 @@ jobs:
run: |
python -m pip install wheel
python -m pip --disable-pip-version-check install -r build/build-install-requirements.txt
- python ./pythonFiles/install_debugpy.py
+ python ./python_files/install_debugpy.py
shell: bash
if: matrix.test-suite == 'debugger'
@@ -387,14 +387,14 @@ jobs:
cache: 'pip'
cache-dependency-path: |
requirements.txt
- pythonFiles/jedilsp_requirements/requirements.txt
+ python_files/jedilsp_requirements/requirements.txt
build/test-requirements.txt
build/functional-test-requirements.txt
- name: Install base Python requirements
uses: brettcannon/pip-secure-install@v1
with:
- options: '-t ./pythonFiles/lib/python --implementation py'
+ options: '-t ./python_files/lib/python --implementation py'
- name: Install Jedi requirements
run: python scripts/vendor.py
@@ -402,7 +402,7 @@ jobs:
- name: Install debugpy
run: |
# We need to have debugpy so that tests relying on it keep passing, but we don't need install_debugpy's logic in the test phase.
- python -m pip --disable-pip-version-check install -t ./pythonFiles/lib/python --implementation py --no-deps --upgrade --pre debugpy
+ python -m pip --disable-pip-version-check install -t ./python_files/lib/python --implementation py --no-deps --upgrade --pre debugpy
- name: Install test requirements
run: python -m pip install --upgrade -r build/test-requirements.txt
@@ -478,7 +478,7 @@ jobs:
- name: Run Python unit tests
run: |
- python pythonFiles/tests/run_all.py
+ python python_files/tests/run_all.py
# The virtual environment based tests use the `testSingleWorkspace` set of tests
# with the environment variable `TEST_FILES_SUFFIX` set to `testvirtualenvs`,
diff --git a/extensions/positron-python/.gitignore b/extensions/positron-python/.gitignore
index dceec679bc2..b38aca49bd6 100644
--- a/extensions/positron-python/.gitignore
+++ b/extensions/positron-python/.gitignore
@@ -23,9 +23,8 @@ cucumber-report.json
**/.venv*/
port.txt
precommit.hook
-pythonFiles/lib/**
-pythonFiles/get-pip.py
-pythonFiles/get_pip.py
+python_files/lib/**
+python_files/get-pip.py
debug_coverage*/**
languageServer/**
languageServer.*/**
@@ -52,6 +51,6 @@ dist/**
l10n/
tags
# --- Start Positron ---
-pythonFiles/positron/positron_ipykernel/tests/images
-pythonFiles/positron/positron_ipykernel/_vendor/**
+python_files/positron/positron_ipykernel/tests/images
+python_files/positron/positron_ipykernel/_vendor/**
# --- End Positron ---
diff --git a/extensions/positron-python/.pre-commit-config.yaml b/extensions/positron-python/.pre-commit-config.yaml
index 25e2507b3ad..21de8f8f8da 100644
--- a/extensions/positron-python/.pre-commit-config.yaml
+++ b/extensions/positron-python/.pre-commit-config.yaml
@@ -1,4 +1,4 @@
-exclude: "(.*\\.csv)|(^build/)|(^src/)|(^resources/)|(^pythonFiles/tests/unittestadapter)|(^pythonFiles/tests/testing_tools)"
+exclude: "(.*\\.csv)|(^build/)|(^src/)|(^resources/)|(^python_files/tests/unittestadapter)|(^python_files/tests/testing_tools)"
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v2.4.0
diff --git a/extensions/positron-python/.vscode/extensions.json b/extensions/positron-python/.vscode/extensions.json
index 93a73827e7a..15e6aada1d5 100644
--- a/extensions/positron-python/.vscode/extensions.json
+++ b/extensions/positron-python/.vscode/extensions.json
@@ -2,13 +2,11 @@
// See https://go.microsoft.com/fwlink/?LinkId=827846
// for the documentation about the extensions.json format
"recommendations": [
+ "charliermarsh.ruff",
"editorconfig.editorconfig",
"esbenp.prettier-vscode",
"dbaeumer.vscode-eslint",
"ms-python.python",
- "ms-python.black-formatter",
- "ms-python.vscode-pylance",
- "ms-python.isort",
- "ms-python.flake8"
+ "ms-python.vscode-pylance"
]
}
diff --git a/extensions/positron-python/.vscode/launch.json b/extensions/positron-python/.vscode/launch.json
index bfb336d767b..ce67e4f77d6 100644
--- a/extensions/positron-python/.vscode/launch.json
+++ b/extensions/positron-python/.vscode/launch.json
@@ -268,7 +268,7 @@
},
{
"name": "Listen",
- "type": "python",
+ "type": "debugpy",
"request": "attach",
"listen": { "host": "localhost", "port": 5678 },
"justMyCode": true
@@ -276,10 +276,10 @@
{
"name": "Debug pytest plugin tests",
- "type": "python",
+ "type": "debugpy",
"request": "launch",
"module": "pytest",
- "args": ["${workspaceFolder}/pythonFiles/tests/pytestadapter"],
+ "args": ["${workspaceFolder}/python_files/tests/pytestadapter"],
"justMyCode": true
}
],
diff --git a/extensions/positron-python/.vscode/settings.json b/extensions/positron-python/.vscode/settings.json
index 72f4133a4a9..0a2f64ec534 100644
--- a/extensions/positron-python/.vscode/settings.json
+++ b/extensions/positron-python/.vscode/settings.json
@@ -27,7 +27,7 @@
"editor.codeActionsOnSave": {
"source.organizeImports": "explicit"
},
- "editor.defaultFormatter": "ms-python.black-formatter"
+ "editor.defaultFormatter": "charliermarsh.ruff",
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
@@ -68,7 +68,7 @@
// Open merge editor for resolving conflicts.
"git.mergeEditor": true,
"python.testing.pytestArgs": [
- "pythonFiles/tests"
+ "python_files/tests"
],
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true
diff --git a/extensions/positron-python/.vscodeignore b/extensions/positron-python/.vscodeignore
index 6788f9b6d8e..9d1934545bf 100644
--- a/extensions/positron-python/.vscodeignore
+++ b/extensions/positron-python/.vscodeignore
@@ -51,16 +51,16 @@ obj/**
out/**/*.stats.json
out/client/**/*.analyzer.html
out/coverconfig.json
-out/pythonFiles/**
+out/python_files/**
out/src/**
out/test/**
out/testMultiRootWkspc/**
precommit.hook
-pythonFiles/**/*.pyc
-pythonFiles/lib/**/*.egg-info/**
-pythonFiles/lib/python/bin/**
-pythonFiles/jedilsp_requirements/**
-pythonFiles/tests/**
+python_files/**/*.pyc
+python_files/lib/**/*.egg-info/**
+python_files/lib/python/bin/**
+python_files/jedilsp_requirements/**
+python_files/tests/**
scripts/**
src/**
test/**
diff --git a/extensions/positron-python/README.md b/extensions/positron-python/README.md
index 718f4b48857..3fc04e10197 100644
--- a/extensions/positron-python/README.md
+++ b/extensions/positron-python/README.md
@@ -6,23 +6,23 @@ You can read more about Positron IDE development on the [Positron Wiki](https://
## About
-The extension is a fork of [Microsoft's Python VSCode extension](https://github.com/microsoft/vscode-python). The main TypeScript functionality (mostly UI) is implemented in [`src`](src) and calls out to Python scripts in [`pythonFiles`](pythonFiles).
+The extension is a fork of [Microsoft's Python VSCode extension](https://github.com/microsoft/vscode-python). The main TypeScript functionality (mostly UI) is implemented in [`src`](src) and calls out to Python scripts in [`python_files`](python_files).
We provide a custom Positron Python Kernel based on the following open-source Python projects:
- [**IPyKernel**](https://github.com/ipython/ipykernel), a Jupyter kernel for the Python programming language written in Python
- [**Jedi Language Server**](https://github.com/pappasam/jedi-language-server), a language server built on the [pygls](https://github.com/openlawlibrary/pygls) (Python Generic Language Server Framework) using the [Jedi](https://github.com/davidhalter/jedi) library for autocompletion, static analysis, and refactoring
-The entrypoint to our kernel is the [`positron_language_server.py`](pythonFiles/positron/positron_language_server.py) script. The core functionality of the kernel can be found in the [`positron_ipykernel`](pythonFiles/positron/positron/positron_ipykernel/) package, which consists of these services:
+The entrypoint to our kernel is the [`positron_language_server.py`](python_files/positron/positron_language_server.py) script. The core functionality of the kernel can be found in the [`positron_ipykernel`](python_files/positron/positron/positron_ipykernel/) package, which consists of these services:
-- [`positron_ipkernel`](pythonFiles/positron/positron_ipykernel/positron_ipkernel.py), the Positron Python Kernel
-- [`positron_jedilsp`](pythonFiles/positron/positron_ipykernel/positron_jedilsp.py), the Positron Python Language Server
-- [`variables`](pythonFiles/positron/positron_ipykernel/variables.py), manages Positron's Variables pane
-- [`ui`](pythonFiles/positron/positron_ipykernel/ui.py), manages Positron's Frontend comm channel (a global channel for communication unscoped to any particular view)
-- [`help`](pythonFiles/positron/positron_ipykernel/help.py), manages Positron's Help pane
-- [`lsp`](pythonFiles/positron/positron_ipykernel/lsp.py), manages the language server
-- [`plots`](pythonFiles/positron/positron_ipykernel/plots.py), a custom [IPython display publisher](https://github.com/ipython/ipython/blob/main/IPython/core/displaypub.py) that displays to Positron's Plots pane
-- [`data_explorer`](pythonFiles/positron/positron_ipykernel/data_explorer.py), manages Positron's Data Viewer
+- [`positron_ipkernel`](python_files/positron/positron_ipykernel/positron_ipkernel.py), the Positron Python Kernel
+- [`positron_jedilsp`](python_files/positron/positron_ipykernel/positron_jedilsp.py), the Positron Python Language Server
+- [`variables`](python_files/positron/positron_ipykernel/variables.py), manages Positron's Variables pane
+- [`ui`](python_files/positron/positron_ipykernel/ui.py), manages Positron's Frontend comm channel (a global channel for communication unscoped to any particular view)
+- [`help`](python_files/positron/positron_ipykernel/help.py), manages Positron's Help pane
+- [`lsp`](python_files/positron/positron_ipykernel/lsp.py), manages the language server
+- [`plots`](python_files/positron/positron_ipykernel/plots.py), a custom [IPython display publisher](https://github.com/ipython/ipython/blob/main/IPython/core/displaypub.py) that displays to Positron's Plots pane
+- [`data_explorer`](python_files/positron/positron_ipykernel/data_explorer.py), manages Positron's Data Viewer
The various Positron services communicate with the front end via Jupyter's [comms](https://connect.rstudioservices.com/content/59a1f153-dcd8-44ac-849b-3371829b7002/positron-architecture.html#comms-and-ui-bindings) messaging protocol.
@@ -30,7 +30,7 @@ The various Positron services communicate with the front end via Jupyter's [comm
When editing the Python source, **open a new workspace at the root `positron-python` folder** to use the settings for the various tools (linters, testers, etc) to match the CI workflows.
-From the `positron-python/pythonFiles` folder, you can run the following commands.
+From the `positron-python/python_files` folder, you can run the following commands.
Format source files with [Black](https://github.com/psf/black):
@@ -53,5 +53,5 @@ pip install -r ../build/pinned-test-requirements.txt
Run Positron's unit tests with [pytest](https://docs.pytest.org/en/8.0.x/):
```sh
-pytest pythonFiles/positron/
+pytest python_files/positron/
```
diff --git a/extensions/positron-python/build/azure-pipeline.pre-release.yml b/extensions/positron-python/build/azure-pipeline.pre-release.yml
index bb52f983d02..bee373845e2 100644
--- a/extensions/positron-python/build/azure-pipeline.pre-release.yml
+++ b/extensions/positron-python/build/azure-pipeline.pre-release.yml
@@ -54,16 +54,16 @@ extends:
- script: |
python -m pip --disable-pip-version-check install -r build/build-install-requirements.txt
- python ./pythonFiles/install_debugpy.py
- python ./pythonFiles/download_get_pip.py
+ python ./python_files/install_debugpy.py
+ python ./python_files/download_get_pip.py
displayName: Install debugpy and get-pip.py
- script: |
- python -m pip install --no-deps --require-hashes --only-binary :all: -t ./pythonFiles/lib/python --implementation py -r ./requirements.txt
+ python -m pip install --no-deps --require-hashes --only-binary :all: -t ./python_files/lib/python --implementation py -r ./requirements.txt
displayName: Install Python dependencies
- script: |
- python -m pip install --no-deps --require-hashes --only-binary :all: -t ./pythonFiles/lib/jedilsp --implementation py --platform any --abi none -r ./pythonFiles/jedilsp_requirements/requirements.txt
+ python -m pip install --no-deps --require-hashes --only-binary :all: -t ./python_files/lib/jedilsp --implementation py --platform any --abi none -r ./python_files/jedilsp_requirements/requirements.txt
displayName: Install Jedi Language Server
- script: |
@@ -79,3 +79,8 @@ extends:
- script: gulp prePublishBundle
displayName: Build
+ tsa:
+ config:
+ areaPath: 'Visual Studio Code Python Extensions'
+ serviceTreeID: '6e6194bc-7baa-4486-86d0-9f5419626d46'
+ enabled: true
diff --git a/extensions/positron-python/build/azure-pipeline.stable.yml b/extensions/positron-python/build/azure-pipeline.stable.yml
index 02f8bd38cf8..754a820e36e 100644
--- a/extensions/positron-python/build/azure-pipeline.stable.yml
+++ b/extensions/positron-python/build/azure-pipeline.stable.yml
@@ -49,16 +49,16 @@ extends:
- script: |
python -m pip --disable-pip-version-check install -r build/build-install-requirements.txt
- python ./pythonFiles/install_debugpy.py
- python ./pythonFiles/download_get_pip.py
+ python ./python_files/install_debugpy.py
+ python ./python_files/download_get_pip.py
displayName: Install debugpy and get-pip.py
- script: |
- python -m pip install --no-deps --require-hashes --only-binary :all: -t ./pythonFiles/lib/python --implementation py -r ./requirements.txt
+ python -m pip install --no-deps --require-hashes --only-binary :all: -t ./python_files/lib/python --implementation py -r ./requirements.txt
displayName: Install Python dependencies
- script: |
- python -m pip install --no-deps --require-hashes --only-binary :all: -t ./pythonFiles/lib/jedilsp --implementation py --platform any --abi none -r ./pythonFiles/jedilsp_requirements/requirements.txt
+ python -m pip install --no-deps --require-hashes --only-binary :all: -t ./python_files/lib/jedilsp --implementation py --platform any --abi none -r ./python_files/jedilsp_requirements/requirements.txt
displayName: Install Jedi Language Server
- script: |
@@ -75,15 +75,8 @@ extends:
- script: gulp prePublishBundle
displayName: Build
tsa:
- enabled: true
- options:
- codebaseName: 'devdiv_$(Build.Repository.Name)'
+ config:
+ areaPath: 'Visual Studio Code Python Extensions'
serviceTreeID: '6e6194bc-7baa-4486-86d0-9f5419626d46'
- instanceUrl: 'https://devdiv.visualstudio.com/defaultcollection'
- projectName: 'DevDiv'
- areaPath: "DevDiv\\VS Code (compliance tracking only)\\Visual Studio Code Python Extensions"
- notificationAliases:
- - 'stbatt@microsoft.com'
- - 'lszomoru@microsoft.com'
- - 'brcan@microsoft.com'
- - 'kanadig@microsoft.com'
+ enabled: true
+ apiScanSoftwareVersion: '2024'
diff --git a/extensions/positron-python/build/existingFiles.json b/extensions/positron-python/build/existingFiles.json
index 1f5acc727d8..48ab84ff565 100644
--- a/extensions/positron-python/build/existingFiles.json
+++ b/extensions/positron-python/build/existingFiles.json
@@ -500,7 +500,7 @@
"src/test/providers/shebangCodeLenseProvider.test.ts",
"src/test/providers/symbolProvider.unit.test.ts",
"src/test/providers/terminal.unit.test.ts",
- "src/test/pythonFiles/formatting/dummy.ts",
+ "src/test/python_files/formatting/dummy.ts",
"src/test/refactor/extension.refactor.extract.method.test.ts",
"src/test/refactor/extension.refactor.extract.var.test.ts",
"src/test/refactor/rename.test.ts",
diff --git a/extensions/positron-python/gulpfile.js b/extensions/positron-python/gulpfile.js
index 4950eeb0af9..50772926088 100644
--- a/extensions/positron-python/gulpfile.js
+++ b/extensions/positron-python/gulpfile.js
@@ -262,7 +262,7 @@ gulp.task('installPythonRequirements', async (done) => {
'install',
'--no-user',
'-t',
- './pythonFiles/lib/python',
+ './python_files/lib/python',
'--no-cache-dir',
'--implementation',
'py',
@@ -298,7 +298,7 @@ gulp.task('installDebugpy', async (done) => {
'--no-user',
'--upgrade',
'-t',
- './pythonFiles/lib/temp',
+ './python_files/lib/temp',
'-r',
'./build/build-install-requirements.txt',
];
@@ -311,8 +311,8 @@ gulp.task('installDebugpy', async (done) => {
});
// Install new DEBUGPY with wheels for python
- const wheelsArgs = ['./pythonFiles/install_debugpy.py'];
- const wheelsEnv = { PYTHONPATH: './pythonFiles/lib/temp' };
+ const wheelsArgs = ['./python_files/install_debugpy.py'];
+ const wheelsEnv = { PYTHONPATH: './python_files/lib/temp' };
await spawnAsync(pythonCommand, wheelsArgs, wheelsEnv)
.then(() => true)
.catch((ex) => {
@@ -322,8 +322,8 @@ gulp.task('installDebugpy', async (done) => {
});
// Download get-pip.py
- const getPipArgs = ['./pythonFiles/download_get_pip.py'];
- const getPipEnv = { PYTHONPATH: './pythonFiles/lib/temp' };
+ const getPipArgs = ['./python_files/download_get_pip.py'];
+ const getPipEnv = { PYTHONPATH: './python_files/lib/temp' };
await spawnAsync(pythonCommand, getPipArgs, getPipEnv)
.then(() => true)
.catch((ex) => {
@@ -332,7 +332,7 @@ gulp.task('installDebugpy', async (done) => {
done(new Error(msg));
});
- rmrf.sync('./pythonFiles/lib/temp');
+ rmrf.sync('./python_files/lib/temp');
});
gulp.task('installPythonLibs', gulp.series('installPythonRequirements', 'installDebugpy'));
diff --git a/extensions/positron-python/noxfile-positron.py b/extensions/positron-python/noxfile-positron.py
new file mode 100644
index 00000000000..02fc1bf09cb
--- /dev/null
+++ b/extensions/positron-python/noxfile-positron.py
@@ -0,0 +1,22 @@
+#
+# Copyright (C) 2024 Posit Software, PBC. All rights reserved.
+#
+
+
+import nox
+
+
+@nox.session()
+@nox.parametrize('pandas', ['1.5.3'])
+@nox.parametrize('torch', ['1.12.1'])
+def test_minimum_reqs(session, pandas, torch):
+ session.install("-r", "python_files/positron/pinned-test-requirements.txt")
+ session.install('--force-reinstall', f'pandas=={pandas}')
+ session.install('--force-reinstall', f'torch=={torch}')
+
+ if session.posargs:
+ test_args = session.posargs
+ else:
+ test_args = []
+
+ session.run('pytest', *test_args)
diff --git a/extensions/positron-python/noxfile.py b/extensions/positron-python/noxfile.py
index b9ebba64544..aa6d0253c66 100644
--- a/extensions/positron-python/noxfile.py
+++ b/extensions/positron-python/noxfile.py
@@ -9,10 +9,10 @@
@nox.session()
def install_python_libs(session: nox.Session):
requirements = [
- ("./pythonFiles/lib/python", "./requirements.txt"),
+ ("./python_files/lib/python", "./requirements.txt"),
(
- "./pythonFiles/lib/jedilsp",
- "./pythonFiles/jedilsp_requirements/requirements.txt",
+ "./python_files/lib/jedilsp",
+ "./python_files/jedilsp_requirements/requirements.txt",
),
]
for target, file in requirements:
@@ -35,16 +35,16 @@ def install_python_libs(session: nox.Session):
# Install debugger
session.run(
"python",
- "./pythonFiles/install_debugpy.py",
- env={"PYTHONPATH": "./pythonFiles/lib/temp"},
+ "./python_files/install_debugpy.py",
+ env={"PYTHONPATH": "./python_files/lib/temp"},
)
# Download get-pip script
session.run(
"python",
- "./pythonFiles/download_get_pip.py",
- env={"PYTHONPATH": "./pythonFiles/lib/temp"},
+ "./python_files/download_get_pip.py",
+ env={"PYTHONPATH": "./python_files/lib/temp"},
)
- if pathlib.Path("./pythonFiles/lib/temp").exists():
- shutil.rmtree("./pythonFiles/lib/temp")
+ if pathlib.Path("./python_files/lib/temp").exists():
+ shutil.rmtree("./python_files/lib/temp")
diff --git a/extensions/positron-python/package.json b/extensions/positron-python/package.json
index 83845e7920e..a2da4e1a71e 100644
--- a/extensions/positron-python/package.json
+++ b/extensions/positron-python/package.json
@@ -23,7 +23,8 @@
"testObserver",
"quickPickItemTooltip",
"terminalDataWriteEvent",
- "terminalExecuteCommandEvent"
+ "terminalExecuteCommandEvent",
+ "contribIssueReporter"
],
"author": {
"name": "Posit Software, PBC",
@@ -50,7 +51,7 @@
"theme": "dark"
},
"engines": {
- "vscode": "^1.82.0"
+ "vscode": "^1.86.0"
},
"enableTelemetry": false,
"keywords": [
@@ -62,8 +63,6 @@
"categories": [
"Programming Languages",
"Debuggers",
- "Linters",
- "Formatters",
"Other",
"Data Science",
"Machine Learning"
@@ -169,16 +168,6 @@
"description": "%walkthrough.pythonWelcome.description%",
"when": "false",
"steps": [
- {
- "id": "python.createPythonFolder",
- "title": "%walkthrough.step.python.createPythonFolder.title%",
- "description": "%walkthrough.step.python.createPythonFolder.description%",
- "media": {
- "svg": "resources/walkthrough/open-folder.svg",
- "altText": "%walkthrough.step.python.createPythonFile.altText%"
- },
- "when": "workspaceFolderCount = 0"
- },
{
"id": "python.createPythonFile",
"title": "%walkthrough.step.python.createPythonFile.title%",
@@ -223,14 +212,15 @@
"title": "%walkthrough.step.python.createEnvironment.title2%",
"description": "%walkthrough.step.python.createEnvironment.description2%",
"media": {
- "markdown": "resources/walkthrough/environments-info.md"
+ "svg": "resources/walkthrough/create-environment.svg",
+ "altText": "%walkthrough.step.python.createEnvironment.altText%"
},
"when": ""
},
{
- "id": "python.runAndDebug",
- "title": "%walkthrough.step.python.runAndDebug.title%",
- "description": "%walkthrough.step.python.runAndDebug.description%",
+ "id": "python.runAndDebug2",
+ "title": "%walkthrough.step.python.runAndDebug2.title%",
+ "description": "%walkthrough.step.python.runAndDebug2.description%",
"media": {
"svg": "resources/walkthrough/rundebug2.svg",
"altText": "%walkthrough.step.python.runAndDebug.altText%"
@@ -239,7 +229,7 @@
},
{
"id": "python.learnMoreWithDS2",
- "title": "%walkthrough.step.python.learnMoreWithDS.title%",
+ "title": "%walkthrough.step.python.learnMoreWithDS2.title%",
"description": "%walkthrough.step.python.learnMoreWithDS.description2%",
"media": {
"altText": "%walkthrough.step.python.learnMoreWithDS.altText%",
@@ -475,7 +465,7 @@
"uniqueItems": true
},
"python.createEnvironment.contentButton": {
- "default": "show",
+ "default": "hide",
"markdownDescription": "%python.createEnvironment.contentButton.description%",
"scope": "machine-overridable",
"type": "string",
@@ -485,7 +475,7 @@
]
},
"python.createEnvironment.trigger": {
- "default": "off",
+ "default": "prompt",
"markdownDescription": "%python.createEnvironment.trigger.description%",
"scope": "machine-overridable",
"type": "string",
@@ -1274,6 +1264,11 @@
}
],
"menus": {
+ "issue/reporter": [
+ {
+ "command": "python.reportIssue"
+ }
+ ],
"commandPalette": [
{
"category": "Python",
@@ -1618,7 +1613,8 @@
"addExtensionPackDependencies": "gulp addExtensionPackDependencies",
"updateBuildNumber": "gulp updateBuildNumber",
"verifyBundle": "gulp verifyBundle",
- "webpack": "webpack"
+ "webpack": "webpack",
+ "positron:testMinimumPythonReqs": "nox --noxfile noxfile-positron.py -- python_files/positron"
},
"dependencies": {
"@iarna/toml": "^2.2.5",
@@ -1651,9 +1647,9 @@
"unicode": "^14.0.0",
"untildify": "^4.0.0",
"vscode-debugprotocol": "^1.28.0",
- "vscode-jsonrpc": "^8.2.0",
- "vscode-languageclient": "^9.0.1",
- "vscode-languageserver-protocol": "^3.17.5",
+ "vscode-jsonrpc": "8.2.0",
+ "vscode-languageclient": "9.0.1",
+ "vscode-languageserver-protocol": "3.17.5",
"vscode-tas-client": "^0.1.84",
"which": "^2.0.2",
"winreg": "^1.2.4",
@@ -1737,4 +1733,4 @@
"extensionDependencies": [
"vscode.jupyter-adapter"
]
-}
\ No newline at end of file
+}
diff --git a/extensions/positron-python/package.nls.json b/extensions/positron-python/package.nls.json
index 127c2b08ac4..891d56bced3 100644
--- a/extensions/positron-python/package.nls.json
+++ b/extensions/positron-python/package.nls.json
@@ -94,6 +94,7 @@
"walkthrough.step.python.createPythonFile.description": {
"message": "[Open](command:toSide:workbench.action.files.openFile) or [create](command:toSide:workbench.action.files.newUntitledFile?%7B%22languageId%22%3A%22python%22%7D) a Python file - make sure to save it as \".py\".\n[Create Python File](command:toSide:workbench.action.files.newUntitledFile?%7B%22languageId%22%3A%22python%22%7D)",
"comment": [
+ "{Locked='](command:toSide:workbench.action.files.openFile'}",
"{Locked='](command:toSide:workbench.action.files.newUntitledFile?%7B%22languageId%22%3A%22python%22%7D'}",
"Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code",
"Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links"
@@ -131,14 +132,14 @@
"walkthrough.step.python.selectInterpreter.description": {
"message": "Choose which Python interpreter/environment you want to use for your Python project.\n[Select Python Interpreter](command:python.setInterpreter)\n**Tip**: Run the ``Python: Select Interpreter`` command in the [Command Palette](command:workbench.action.showCommands).",
"comment": [
- "{Locked='](command:python.setInterpreter'}",
- "{Locked='](command:workbench.action.showCommands'}",
- "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code",
- "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links"
+ "{Locked='](command:python.setInterpreter'}",
+ "{Locked='](command:workbench.action.showCommands'}",
+ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code",
+ "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links"
]
},
- "walkthrough.step.python.createEnvironment.title": "Create a Python Environment ",
- "walkthrough.step.python.createEnvironment.title2": "Create or select a Python Environment ",
+ "walkthrough.step.python.createEnvironment.title": "Create a Python Environment",
+ "walkthrough.step.python.createEnvironment.title2": "Select a Python environment",
"walkthrough.step.python.createEnvironment.description": {
"message": "Create an environment for your Python project.\n[Create Environment](command:python.createEnvironment)\n**Tip**: Run the ``Python: Create Environment`` command in the [Command Palette](command:workbench.action.showCommands).\n 🔍 Check out our [docs](https://aka.ms/pythonenvs) to learn more.",
"comment": [
@@ -148,12 +149,32 @@
"Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links"
]
},
- "walkthrough.step.python.createEnvironment.description2": "Create an environment for your Python project or use [Select Python Interpreter](command:python.setInterpreter) to select an existing one.\n[Create Environment](command:python.createEnvironment)\n**Tip**: Run the ``Python: Create Environment`` command in the [Command Palette](command:workbench.action.showCommands).",
+ "walkthrough.step.python.createEnvironment.description2": {
+ "message": "[Select a Python interpreter](command:python.setInterpreter) or [create an environment](command:python.createEnvironment) for your Python project.\n[Select Interpreter](command:python.setInterpreter)",
+ "comment": [
+ "{Locked='](command:python.createEnvironment'}",
+ "{Locked='](command:python.setInterpreter'}",
+ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code",
+ "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links"
+ ]
+ },
"walkthrough.step.python.runAndDebug.title": "Run and debug your Python file",
- "walkthrough.step.python.runAndDebug.description": "Open your Python file and click on the play button on the top right of the editor, or press F5 when on the file and select \"Python File\" to run with the debugger. \n \n[Learn more](https://code.visualstudio.com/docs/python/python-tutorial#_run-hello-world)",
+ "walkthrough.step.python.runAndDebug2.title": "Run and debug",
+ "walkthrough.step.python.runAndDebug2.description": "Click the play button on the top right of the editor, or press F5 when on the file and select \"Python File\" to run with the debugger.",
+ "walkthrough.step.python.runAndDebug.description": "Open your Python file and click on the play button on the top right of the editor, or press F5 when on the file and select \"Python File\" to run with the debugger. \n \n[Learn more](https://code.visualstudio.com/docs/python/python-tutorial#_run-hello-world)",
"walkthrough.step.python.learnMoreWithDS.title": "Explore more resources",
+ "walkthrough.step.python.learnMoreWithDS2.title": "Keep exploring!",
"walkthrough.step.python.learnMoreWithDS.description": {
- "message": "🎨 Explore all the features the Python extension has to offer by looking for \"Python\" in the [Command Palette](command:workbench.action.showCommands). \n 📈 Learn more about getting started with [data science](command:workbench.action.openWalkthrough?%7B%22category%22%3A%22ms-python.python%23pythonDataScienceWelcome%22%2C%22step%22%3A%22ms-python.python%23python.createNewNotebook%22%7D) in Python. \n ✨ Take a look at our [Release Notes](https://aka.ms/AA8dxtb) to learn more about the latest features. \n \n[Learn More](https://aka.ms/AA8dqti)",
+ "message": "🎨 Find all the features the Python extension has to offer by looking for \"Python\" in the [Command Palette](command:workbench.action.showCommands). \n 📈 Learn more about getting started with [data science](command:workbench.action.openWalkthrough?%7B%22category%22%3A%22ms-python.python%23pythonDataScienceWelcome%22%2C%22step%22%3A%22ms-python.python%23python.createNewNotebook%22%7D) in Python. \n ✨ Take a look at our [Release Notes](https://aka.ms/AA8dxtb) to learn more about the latest features. \n \n[Learn More](https://aka.ms/AA8dqti)",
+ "comment": [
+ "{Locked='](command:workbench.action.showCommands'}",
+ "{Locked='](command:workbench.action.openWalkthrough?%7B%22category%22%3A%22ms-python.python%23pythonDataScienceWelcome%22%2C%22step%22%3A%22ms-python.python%23python.createNewNotebook%22%7D'}",
+ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code",
+ "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links"
+ ]
+ },
+ "walkthrough.step.python.learnMoreWithDS.description2": {
+ "message": "🎨 Explore all the features the Python extension has to offer by searching for \"Python\" in the [Command Palette](command:workbench.action.showCommands). \n 📈 Learn more about getting started with [data science](command:workbench.action.openWalkthrough?%7B%22category%22%3A%22ms-python.python%23pythonDataScienceWelcome%22%2C%22step%22%3A%22ms-python.python%23python.createNewNotebook%22%7D) in Python. \n ✨ Take a look at our [Release Notes](https://aka.ms/AA8dxtb) to learn more about the latest features. \n \n[Follow along with the Python Quick Start](https://code.visualstudio.com/docs/python/python-quick-start)",
"comment": [
"{Locked='](command:workbench.action.showCommands'}",
"{Locked='](command:workbench.action.openWalkthrough?%7B%22category%22%3A%22ms-python.python%23pythonDataScienceWelcome%22%2C%22step%22%3A%22ms-python.python%23python.createNewNotebook%22%7D'}",
@@ -161,7 +182,6 @@
"Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links"
]
},
- "walkthrough.step.python.learnMoreWithDS.description2": "🎨 Explore all the features the Python extension has to offer by looking for \"Python\" in the [Command Palette](command:workbench.action.showCommands). \n 📈 Learn more about getting started with [data science](command:workbench.action.openWalkthrough?%7B%22category%22%3A%22ms-python.python%23pythonDataScienceWelcome%22%2C%22step%22%3A%22ms-python.python%23python.createNewNotebook%22%7D) in Python. \n ✨ Take a look at our [Release Notes](https://aka.ms/AA8dxtb) to learn more about the latest features. \n \n[Follow along with the Python Tutorial](https://aka.ms/AA8dqti)",
"walkthrough.pythonDataScienceWelcome.title": "Get Started with Python for Data Science",
"walkthrough.pythonDataScienceWelcome.description": "Your first steps to getting started with a Data Science project with Python!",
"walkthrough.step.python.installJupyterExt.title": "Install Jupyter extension",
diff --git a/extensions/positron-python/pythonExtensionApi/src/main.ts b/extensions/positron-python/pythonExtensionApi/src/main.ts
index 4de554bf5a2..dccbd78f6f0 100644
--- a/extensions/positron-python/pythonExtensionApi/src/main.ts
+++ b/extensions/positron-python/pythonExtensionApi/src/main.ts
@@ -16,7 +16,7 @@ export interface PythonExtension {
/**
* Generate an array of strings for commands to pass to the Python executable to launch the debugger for remote debugging.
* Users can append another array of strings of what they want to execute along with relevant arguments to Python.
- * E.g `['/Users/..../pythonVSCode/pythonFiles/lib/python/debugpy', '--listen', 'localhost:57039', '--wait-for-client']`
+ * E.g `['/Users/..../pythonVSCode/python_files/lib/python/debugpy', '--listen', 'localhost:57039', '--wait-for-client']`
* @param host
* @param port
* @param waitUntilDebuggerAttaches Defaults to `true`.
diff --git a/extensions/positron-python/pythonFiles/.vscode/launch.json b/extensions/positron-python/pythonFiles/.vscode/launch.json
deleted file mode 100644
index c525016b309..00000000000
--- a/extensions/positron-python/pythonFiles/.vscode/launch.json
+++ /dev/null
@@ -1,15 +0,0 @@
-{
- // Use IntelliSense to learn about possible attributes.
- // Hover to view descriptions of existing attributes.
- // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
- "version": "0.2.0",
- "configurations": [
- {
- "name": "Debug unit tests",
- "type": "python",
- "request": "test",
- "console": "integratedTerminal",
- "justMyCode": false
- }
- ]
-}
diff --git a/extensions/positron-python/pythonFiles/positron/data-science-requirements.txt b/extensions/positron-python/pythonFiles/positron/data-science-requirements.txt
deleted file mode 100644
index cad010f85b6..00000000000
--- a/extensions/positron-python/pythonFiles/positron/data-science-requirements.txt
+++ /dev/null
@@ -1,7 +0,0 @@
-pandas
-polars
-matplotlib
-numpy
-torch
-fastapi
-sqlalchemy
diff --git a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/plots.py b/extensions/positron-python/pythonFiles/positron/positron_ipykernel/plots.py
deleted file mode 100644
index e8375e1fa8d..00000000000
--- a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/plots.py
+++ /dev/null
@@ -1,224 +0,0 @@
-#
-# Copyright (C) 2023-2024 Posit Software, PBC. All rights reserved.
-#
-
-import base64
-import codecs
-import io
-import logging
-import pickle
-import uuid
-from typing import Dict, List, Optional
-
-import comm
-
-from .plot_comm import PlotBackendMessageContent, PlotResult, RenderRequest
-from .positron_comm import CommMessage, JsonRpcErrorCode, PositronComm
-from .utils import JsonRecord
-from .widget import _WIDGET_MIME_TYPE
-
-logger = logging.getLogger(__name__)
-
-
-# Matplotlib Default Figure Size
-DEFAULT_WIDTH_IN = 6.4
-DEFAULT_HEIGHT_IN = 4.8
-BASE_DPI = 100
-
-
-class PositronDisplayPublisherHook:
- def __init__(self, target_name: str):
- self.comms: Dict[str, PositronComm] = {}
- self.figures: Dict[str, str] = {}
- self.target_name = target_name
- self.fignums: List[int] = []
-
- def __call__(self, msg, *args, **kwargs) -> Optional[dict]:
- if msg["msg_type"] == "display_data":
- # If there is no image for our display, don't create a
- # positron.plot comm and let the parent deal with the msg.
- data = msg["content"]["data"]
- if _WIDGET_MIME_TYPE in data:
- # This is a widget, let the widget hook handle it
- return msg
- if "image/png" not in data:
- return msg
-
- # Otherwise, try to pickle the current figure so that we
- # can restore the context for future renderings. We construct
- # a new plot comm to advise the client of the new figure.
- pickled = self._pickle_current_figure()
- if pickled is not None:
- id = str(uuid.uuid4())
- self.figures[id] = pickled
-
- # Creating a comm per plot figure allows the client
- # to request new renderings of each plot at a later time,
- # e.g. on resizing the plots view
- self._create_comm(id)
-
- # Returning None implies our hook has processed the message
- # and it stops the parent from sending the display_data via
- # the standard iopub channel
- return None
-
- return msg
-
- def _create_comm(self, comm_id: str) -> None:
- """
- Create a new plot comm with the given id.
- """
- plot_comm = PositronComm(comm.create_comm(target_name=self.target_name, comm_id=comm_id))
- self.comms[comm_id] = plot_comm
- plot_comm.on_msg(self.handle_msg, PlotBackendMessageContent)
-
- def handle_msg(self, msg: CommMessage[PlotBackendMessageContent], raw_msg: JsonRecord) -> None:
- """
- Handle client messages to render a plot figure.
- """
- comm_id = msg.content.comm_id
- request = msg.content.data
-
- figure_comm = self.comms.get(comm_id, None)
- if figure_comm is None:
- logger.warning(f"Plot figure comm {comm_id} not found")
- return
-
- if isinstance(request, RenderRequest):
- pickled = self.figures.get(comm_id, None)
- if pickled is None:
- figure_comm.send_error(
- code=JsonRpcErrorCode.INVALID_PARAMS, message=f"Figure {comm_id} not found"
- )
- return
-
- width_px = request.params.width or 0
- height_px = request.params.height or 0
- pixel_ratio = request.params.pixel_ratio or 1.0
-
- if width_px != 0 and height_px != 0:
- format_dict = self._resize_pickled_figure(pickled, width_px, height_px, pixel_ratio)
- data = format_dict["image/png"]
- output = PlotResult(data=data, mime_type="image/png").dict()
- figure_comm.send_result(data=output, metadata={"mime_type": "image/png"})
-
- else:
- logger.warning(f"Unhandled request: {request}")
-
- def shutdown(self) -> None:
- """
- Shutdown plot comms and release any resources.
- """
- for figure_comm in self.comms.values():
- try:
- figure_comm.close()
- except Exception:
- pass
- self.comms.clear()
- self.figures.clear()
-
- # -- Private Methods --
-
- def _pickle_current_figure(self) -> Optional[str]:
- pickled = None
- figure = None
-
- # Delay importing matplotlib until the kernel and shell has been initialized
- # otherwise the graphics backend will be reset to the gui
- import matplotlib.pyplot as plt
-
- # We turn off interactive mode before accessing the plot context
- was_interactive = plt.isinteractive()
- plt.ioff()
-
- # Check to see if there are any figures left in stack to display
- # If not, get the number of figures to display from matplotlib
- if len(self.fignums) == 0:
- self.fignums = plt.get_fignums()
-
- # Get the current figure, remove from it from being called next hook
- if len(self.fignums) > 0:
- figure = plt.figure(self.fignums.pop(0))
-
- # Pickle the current figure
- if figure is not None and not self._is_figure_empty(figure):
- pickled = codecs.encode(pickle.dumps(figure), "base64").decode()
-
- if was_interactive:
- plt.ion()
-
- return pickled
-
- def _resize_pickled_figure(
- self,
- pickled: str,
- new_width_px: int = 614,
- new_height_px: int = 460,
- pixel_ratio: float = 1.0,
- formats: list = ["image/png"],
- ) -> dict:
- # Delay importing matplotlib until the kernel and shell has been
- # initialized otherwise the graphics backend will be reset to the gui
- import matplotlib.pyplot as plt
-
- # Turn off interactive mode before, including before unpickling a
- # figures (otherwise it will cause and endless loop of plot changes)
- was_interactive = plt.isinteractive()
- plt.ioff()
-
- figure = pickle.loads(codecs.decode(pickled.encode(), "base64"))
- figure_buffer = io.BytesIO()
-
- # Adjust the DPI based on pixel_ratio to accommodate high
- # resolution displays...
- dpi = BASE_DPI * pixel_ratio
- figure.set_dpi(dpi)
- figure.set_layout_engine("tight") # eliminates whitespace around the figure
-
- # ... but use base DPI to convert to inch based dimensions.
- width_in, height_in = figure.get_size_inches()
- new_width_in = new_width_px / BASE_DPI
- new_height_in = new_height_px / BASE_DPI
-
- # Try to determine if the figure had an explicit width or height set.
- if width_in == DEFAULT_WIDTH_IN and height_in == DEFAULT_HEIGHT_IN:
- # If default values are still set, apply new size, even if this
- # resets the aspect ratio
- width_in = new_width_in
- height_in = new_height_in
- else:
- # Preserve the existing aspect ratio, constraining the scale
- # based on the shorter dimension
- if width_in < height_in:
- height_in = height_in * (new_width_in / width_in)
- width_in = new_width_in
- else:
- width_in = width_in * (new_height_in / height_in)
- height_in = new_height_in
-
- figure.set_size_inches(width_in, height_in)
-
- # Render the figure to a buffer
- # using format_display_data() crops the figure to smaller than requested size
- figure.savefig(figure_buffer, format="png")
- figure_buffer.seek(0)
- image_data = base64.b64encode(figure_buffer.read()).decode()
-
- format_dict = {"image/png": image_data}
-
- plt.close(figure)
-
- if was_interactive:
- plt.ion()
- return format_dict
-
- def _is_figure_empty(self, figure):
- children = figure.get_children()
- if len(children) < 1:
- return True
-
- for child in children:
- if child.get_visible():
- return False
-
- return True
diff --git a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/tests/test_plots.py b/extensions/positron-python/pythonFiles/positron/positron_ipykernel/tests/test_plots.py
deleted file mode 100644
index 5e100974a2d..00000000000
--- a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/tests/test_plots.py
+++ /dev/null
@@ -1,242 +0,0 @@
-#
-# Copyright (C) 2023-2024 Posit Software, PBC. All rights reserved.
-#
-
-import base64
-import codecs
-import io
-import pickle
-from pathlib import Path
-from typing import Iterable, cast
-
-import matplotlib
-import matplotlib.pyplot as plt
-import pytest
-from IPython.core.formatters import DisplayFormatter
-from matplotlib.axes import Axes
-from matplotlib.figure import Figure
-from matplotlib.testing.compare import compare_images
-from matplotlib_inline.backend_inline import configure_inline_support
-from positron_ipykernel.plots import BASE_DPI, PositronDisplayPublisherHook
-from positron_ipykernel.positron_comm import JsonRpcErrorCode
-
-from .conftest import DummyComm, PositronShell
-from .utils import comm_request, json_rpc_error, json_rpc_request
-
-PLOT_DATA = [1, 2]
-
-
-@pytest.fixture(autouse=True)
-def setup_matplotlib(shell: PositronShell) -> Iterable[None]:
- # Use IPython's `matplotlib_inline` backend
- backend = "module://matplotlib_inline.backend_inline"
- matplotlib.use(backend)
-
- # Enable all IPython mimetype formatters
- display_formatter = cast(DisplayFormatter, shell.display_formatter)
- active_types = display_formatter.active_types
- display_formatter.active_types = display_formatter.format_types
-
- # Enable matplotlib IPython formatters
- configure_inline_support(shell, backend)
-
- yield
-
- # Restore the original active formatters
- display_formatter.active_types = active_types
-
-
-@pytest.fixture(scope="session")
-def images_path() -> Path:
- images_path = Path(__file__).parent / "images"
- images_path.mkdir(exist_ok=True)
- return images_path
-
-
-@pytest.fixture
-def hook() -> PositronDisplayPublisherHook:
- return PositronDisplayPublisherHook("positron.plot")
-
-
-@pytest.fixture
-def figure_comm(hook: PositronDisplayPublisherHook) -> DummyComm:
- """
- A comm corresponding to a test figure belonging to the Positron display publisher hook.
- """
- # Initialize the hook by calling it on a figure created with the test plot data
- plt.plot(PLOT_DATA)
- msg = comm_request({"image/png": None}, msg_type="display_data")
- hook(msg)
- plt.close()
-
- # Return the comm corresponding to the first figure
- id = next(iter(hook.comms))
- figure_comm = cast(DummyComm, hook.comms[id].comm)
-
- # Clear messages due to the comm_open
- figure_comm.messages.clear()
-
- return figure_comm
-
-
-def test_hook_call_noop_on_non_display_data(hook: PositronDisplayPublisherHook) -> None:
- msg = comm_request({"image/png": None}, msg_type="not_display_data")
- assert hook(msg) == msg
- assert hook.figures == {}
- assert hook.comms == {}
-
-
-def test_hook_call_noop_on_no_image_png(hook: PositronDisplayPublisherHook) -> None:
- msg = comm_request({}, msg_type="display_data")
- assert hook(msg) == msg
- assert hook.figures == {}
- assert hook.comms == {}
-
-
-def test_hook_call(hook: PositronDisplayPublisherHook, images_path: Path) -> None:
- # It returns `None` to indicate that it's consumed the message
- plt.plot(PLOT_DATA)
- msg = comm_request({"image/png": None}, msg_type="display_data")
- assert hook(msg) is None
-
- # It creates a new figure and comm
- assert len(hook.figures) == 1
- id = next(iter(hook.figures))
- assert id in hook.comms
-
- # Check the comm's properties
- comm = hook.comms[id].comm
- assert comm.target_name == hook.target_name
- assert comm.comm_id == id
-
- # Check that the figure is a pickled base64-encoded string by decoding it and comparing it
- # with a reference figure.
- # First, save the hook's figure
- fig_encoded = hook.figures[id]
- fig: Figure = pickle.loads(codecs.decode(fig_encoded.encode(), "base64"))
- actual = images_path / "test-hook-call-actual.png"
- fig.savefig(str(actual))
-
- # Create the reference figure
- fig_ref = cast(Figure, plt.figure())
- fig_axes = cast(Axes, fig_ref.subplots())
- fig_axes.plot(PLOT_DATA)
- expected = images_path / "test-hook-call-expected.png"
- fig_ref.savefig(str(expected))
-
- # Compare actual versus expected figures
- err = compare_images(str(actual), str(expected), tol=0)
- assert not err
-
-
-def test_hook_handle_msg_noop_on_unknown_method(figure_comm: DummyComm) -> None:
- # Handle a message with an invalid msg_type
- msg = json_rpc_request("not_render", {})
- figure_comm.handle_msg(msg)
-
- assert figure_comm.messages == [
- json_rpc_error(JsonRpcErrorCode.METHOD_NOT_FOUND, "Unknown method 'not_render'")
- ]
-
-
-def render_request(comm_id: str, width_px: int = 500, height_px: int = 500, pixel_ratio: int = 1):
- return json_rpc_request(
- "render",
- {"width": width_px, "height": height_px, "pixel_ratio": pixel_ratio},
- comm_id=comm_id,
- )
-
-
-def test_hook_render_noop_on_unknown_comm(figure_comm: DummyComm) -> None:
- # Handle a valid message but invalid comm_id
- msg = render_request("unknown_comm_id")
- figure_comm.handle_msg(msg)
-
- # No messages sent
- assert figure_comm.messages == []
-
-
-def test_hook_render_error_on_unknown_figure(
- hook: PositronDisplayPublisherHook, figure_comm: DummyComm
-) -> None:
- # Clear the hook's figures to simulate a missing figure
- hook.figures.clear()
-
- # Handle a message with a valid msg_type and valid comm_id, but the hook now has a missing figure
- msg = render_request(figure_comm.comm_id)
- figure_comm.handle_msg(msg)
-
- # Check that we receive an error reply
- assert figure_comm.messages == [
- json_rpc_error(JsonRpcErrorCode.INVALID_PARAMS, f"Figure {figure_comm.comm_id} not found")
- ]
-
-
-def _save_base64_image(encoded: str, filename: Path) -> None:
- image = codecs.decode(encoded.encode(), "base64")
- with open(filename, "wb") as f:
- f.write(image)
-
-
-def test_hook_render(figure_comm: DummyComm, images_path: Path) -> None:
- # Send a valid render message with a custom width and height
- width_px = height_px = 100
- pixel_ratio = 1
- msg = render_request(figure_comm.comm_id, width_px, height_px, pixel_ratio)
- figure_comm.handle_msg(msg)
-
- # Check that the reply is a comm_msg
- reply = figure_comm.messages[0]
- assert reply["msg_type"] == "comm_msg"
- assert reply["buffers"] is None
- assert reply["metadata"] == {"mime_type": "image/png"}
-
- # Check that the reply data is an `image` message
- image_msg = reply["data"]
- assert image_msg["result"]["mime_type"] == "image/png"
-
- # Check that the reply data includes the expected base64-encoded resized image
-
- # Save the reply's image
- actual = images_path / "test-hook-render-actual.png"
- _save_base64_image(image_msg["result"]["data"], actual)
-
- # Create the reference figure
- dpi = BASE_DPI * pixel_ratio
- width_in = width_px / BASE_DPI
- height_in = height_px / BASE_DPI
-
- fig_buffer = io.BytesIO()
- fig_ref = cast(Figure, plt.figure())
- fig_axes = cast(Axes, fig_ref.subplots())
- fig_axes.plot([1, 2])
- fig_ref.set_dpi(dpi)
- fig_ref.set_size_inches(width_in, height_in)
- fig_ref.set_layout_engine("tight")
-
- # Serialize the reference figure as a base64-encoded image
- fig_ref.savefig(fig_buffer, format="png")
- fig_buffer.seek(0)
- expected = images_path / "test-hook-render-expected.png"
- _save_base64_image(base64.b64encode(fig_buffer.read()).decode(), expected)
-
- # Compare the actual vs expected figures
- err = compare_images(str(actual), str(expected), tol=0)
- assert not err
-
-
-# It's important that we depend on the figure_comm fixture too, so that the hook is initialized
-def test_shutdown(hook: PositronDisplayPublisherHook, figure_comm: DummyComm) -> None:
- # Double-check that it still has figures and comms
- assert len(hook.figures) == 1
- assert len(hook.comms) == 1
-
- # Double-check that the comm is not yet closed
- assert not figure_comm._closed
-
- hook.shutdown()
-
- # Figures and comms are closed and cleared
- assert not hook.figures
- assert not hook.comms
- assert figure_comm._closed
diff --git a/extensions/positron-python/pythonFiles/.env b/extensions/positron-python/python_files/.env
similarity index 100%
rename from extensions/positron-python/pythonFiles/.env
rename to extensions/positron-python/python_files/.env
diff --git a/extensions/positron-python/pythonFiles/.vscode/settings.json b/extensions/positron-python/python_files/.vscode/settings.json
similarity index 86%
rename from extensions/positron-python/pythonFiles/.vscode/settings.json
rename to extensions/positron-python/python_files/.vscode/settings.json
index 480631710e8..165fbc8b9fc 100644
--- a/extensions/positron-python/pythonFiles/.vscode/settings.json
+++ b/extensions/positron-python/python_files/.vscode/settings.json
@@ -9,7 +9,8 @@
"editor.rulers": [100],
},
"python.testing.pytestArgs": [
- "tests"
+ "tests",
+ "positron/positron_ipykernel/tests"
],
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true,
diff --git a/extensions/positron-python/pythonFiles/Notebooks intro.ipynb b/extensions/positron-python/python_files/Notebooks intro.ipynb
similarity index 100%
rename from extensions/positron-python/pythonFiles/Notebooks intro.ipynb
rename to extensions/positron-python/python_files/Notebooks intro.ipynb
diff --git a/extensions/positron-python/pythonFiles/create_conda.py b/extensions/positron-python/python_files/create_conda.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/create_conda.py
rename to extensions/positron-python/python_files/create_conda.py
diff --git a/extensions/positron-python/pythonFiles/create_microvenv.py b/extensions/positron-python/python_files/create_microvenv.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/create_microvenv.py
rename to extensions/positron-python/python_files/create_microvenv.py
diff --git a/extensions/positron-python/pythonFiles/create_venv.py b/extensions/positron-python/python_files/create_venv.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/create_venv.py
rename to extensions/positron-python/python_files/create_venv.py
diff --git a/extensions/positron-python/pythonFiles/deactivate/bash/deactivate b/extensions/positron-python/python_files/deactivate/bash/deactivate
old mode 100755
new mode 100644
similarity index 100%
rename from extensions/positron-python/pythonFiles/deactivate/bash/deactivate
rename to extensions/positron-python/python_files/deactivate/bash/deactivate
diff --git a/extensions/positron-python/pythonFiles/deactivate/fish/deactivate b/extensions/positron-python/python_files/deactivate/fish/deactivate
old mode 100755
new mode 100644
similarity index 100%
rename from extensions/positron-python/pythonFiles/deactivate/fish/deactivate
rename to extensions/positron-python/python_files/deactivate/fish/deactivate
diff --git a/extensions/positron-python/pythonFiles/deactivate/powershell/deactivate.ps1 b/extensions/positron-python/python_files/deactivate/powershell/deactivate.ps1
similarity index 100%
rename from extensions/positron-python/pythonFiles/deactivate/powershell/deactivate.ps1
rename to extensions/positron-python/python_files/deactivate/powershell/deactivate.ps1
diff --git a/extensions/positron-python/pythonFiles/deactivate/zsh/deactivate b/extensions/positron-python/python_files/deactivate/zsh/deactivate
old mode 100755
new mode 100644
similarity index 100%
rename from extensions/positron-python/pythonFiles/deactivate/zsh/deactivate
rename to extensions/positron-python/python_files/deactivate/zsh/deactivate
diff --git a/extensions/positron-python/pythonFiles/download_get_pip.py b/extensions/positron-python/python_files/download_get_pip.py
similarity index 96%
rename from extensions/positron-python/pythonFiles/download_get_pip.py
rename to extensions/positron-python/python_files/download_get_pip.py
index b8238d60f26..d87d1cf032f 100644
--- a/extensions/positron-python/pythonFiles/download_get_pip.py
+++ b/extensions/positron-python/python_files/download_get_pip.py
@@ -8,7 +8,7 @@
from packaging.version import parse as version_parser
EXTENSION_ROOT = pathlib.Path(__file__).parent.parent
-GET_PIP_DEST = EXTENSION_ROOT / "pythonFiles"
+GET_PIP_DEST = EXTENSION_ROOT / "python_files"
PIP_PACKAGE = "pip"
PIP_VERSION = "latest" # Can be "latest", or specific version "23.1.2"
diff --git a/extensions/positron-python/pythonFiles/get_output_via_markers.py b/extensions/positron-python/python_files/get_output_via_markers.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/get_output_via_markers.py
rename to extensions/positron-python/python_files/get_output_via_markers.py
diff --git a/extensions/positron-python/pythonFiles/install_debugpy.py b/extensions/positron-python/python_files/install_debugpy.py
similarity index 96%
rename from extensions/positron-python/pythonFiles/install_debugpy.py
rename to extensions/positron-python/python_files/install_debugpy.py
index c217e81fd21..e38ca82230c 100644
--- a/extensions/positron-python/pythonFiles/install_debugpy.py
+++ b/extensions/positron-python/python_files/install_debugpy.py
@@ -10,7 +10,7 @@
from packaging.version import parse as version_parser
EXTENSION_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
-DEBUGGER_DEST = os.path.join(EXTENSION_ROOT, "pythonFiles", "lib", "python")
+DEBUGGER_DEST = os.path.join(EXTENSION_ROOT, "python_files", "lib", "python")
DEBUGGER_PACKAGE = "debugpy"
DEBUGGER_PYTHON_ABI_VERSIONS = ("cp310",)
DEBUGGER_VERSION = "1.8.1" # can also be "latest"
diff --git a/extensions/positron-python/pythonFiles/installed_check.py b/extensions/positron-python/python_files/installed_check.py
similarity index 96%
rename from extensions/positron-python/pythonFiles/installed_check.py
rename to extensions/positron-python/python_files/installed_check.py
index 4a43a8bc8b3..6dafe23b512 100644
--- a/extensions/positron-python/pythonFiles/installed_check.py
+++ b/extensions/positron-python/python_files/installed_check.py
@@ -11,9 +11,9 @@
LIB_ROOT = pathlib.Path(__file__).parent / "lib" / "python"
sys.path.insert(0, os.fspath(LIB_ROOT))
-import tomli
-from importlib_metadata import metadata
-from packaging.requirements import Requirement
+import tomli # noqa: E402
+from importlib_metadata import metadata # noqa: E402
+from packaging.requirements import Requirement # noqa: E402
DEFAULT_SEVERITY = "3" # 'Hint'
try:
diff --git a/extensions/positron-python/pythonFiles/interpreterInfo.py b/extensions/positron-python/python_files/interpreterInfo.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/interpreterInfo.py
rename to extensions/positron-python/python_files/interpreterInfo.py
diff --git a/extensions/positron-python/pythonFiles/jedilsp_requirements/requirements.in b/extensions/positron-python/python_files/jedilsp_requirements/requirements.in
similarity index 87%
rename from extensions/positron-python/pythonFiles/jedilsp_requirements/requirements.in
rename to extensions/positron-python/python_files/jedilsp_requirements/requirements.in
index 826bcf1e158..b0471a1e765 100644
--- a/extensions/positron-python/pythonFiles/jedilsp_requirements/requirements.in
+++ b/extensions/positron-python/python_files/jedilsp_requirements/requirements.in
@@ -2,7 +2,7 @@
# To update requirements.txt, run the following commands.
# Use Python 3.8 when creating the environment or using pip-tools
# 1) pip install pip-tools
-# 2) pip-compile --generate-hashes --upgrade pythonFiles\jedilsp_requirements\requirements.in
+# 2) pip-compile --generate-hashes --upgrade python_files\jedilsp_requirements\requirements.in
jedi-language-server>=0.34.3
pygls>=0.10.3
diff --git a/extensions/positron-python/pythonFiles/jedilsp_requirements/requirements.txt b/extensions/positron-python/python_files/jedilsp_requirements/requirements.txt
similarity index 92%
rename from extensions/positron-python/pythonFiles/jedilsp_requirements/requirements.txt
rename to extensions/positron-python/python_files/jedilsp_requirements/requirements.txt
index fdaa547f714..cd8945d8a52 100644
--- a/extensions/positron-python/pythonFiles/jedilsp_requirements/requirements.txt
+++ b/extensions/positron-python/python_files/jedilsp_requirements/requirements.txt
@@ -2,7 +2,7 @@
# This file is autogenerated by pip-compile with Python 3.8
# by the following command:
#
-# pip-compile --generate-hashes pythonFiles/jedilsp_requirements/requirements.in
+# pip-compile --generate-hashes 'python_files/jedilsp_requirements/requirements.in'
#
attrs==23.2.0 \
--hash=sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30 \
@@ -20,7 +20,7 @@ docstring-to-markdown==0.13 \
--hash=sha256:3025c428638ececae920d6d26054546a20335af3504a145327e657e7ad7ce1ce \
--hash=sha256:aa487059d0883e70e54da25c7b230e918d9e4d40f23d6dfaa2b73e4225b2d7dd
# via
- # -r pythonFiles/jedilsp_requirements/requirements.in
+ # -r python_files/jedilsp_requirements/requirements.in
# jedi-language-server
exceptiongroup==1.2.0 \
--hash=sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14 \
@@ -33,7 +33,7 @@ jedi==0.19.1 \
jedi-language-server==0.41.2 \
--hash=sha256:865a93546b3711713eaca1a76b3a5d4aaacd4594c87f777aa8d54da37ae5aeca \
--hash=sha256:bfa112d88c0bd21872522b96c64e8054854f767449bbb3d858be04fb2cda0606
- # via -r pythonFiles/jedilsp_requirements/requirements.in
+ # via -r python_files/jedilsp_requirements/requirements.in
lsprotocol==2023.0.0 \
--hash=sha256:c9d92e12a3f4ed9317d3068226592860aab5357d93cf5b2451dc244eee8f35f2 \
--hash=sha256:e85fc87ee26c816adca9eb497bb3db1a7c79c477a11563626e712eaccf926a05
@@ -43,7 +43,7 @@ lsprotocol==2023.0.0 \
markdown-it-py==3.0.0 \
--hash=sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1 \
--hash=sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb
- # via -r pythonFiles/jedilsp_requirements/requirements.in
+ # via -r python_files/jedilsp_requirements/requirements.in
mdurl==0.1.2 \
--hash=sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8 \
--hash=sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba
@@ -89,17 +89,17 @@ pydantic==1.10.14 \
--hash=sha256:e897c9f35281f7889873a3e6d6b69aa1447ceb024e8495a5f0d02ecd17742a7f \
--hash=sha256:f9f674b5c3bebc2eba401de64f29948ae1e646ba2735f884d1594c5f675d6f2a \
--hash=sha256:fa7790e94c60f809c95602a26d906eba01a0abee9cc24150e4ce2189352deb1b
- # via -r pythonFiles/jedilsp_requirements/requirements.in
+ # via -r python_files/jedilsp_requirements/requirements.in
pygls==1.2.1 \
--hash=sha256:04f9b9c115b622dcc346fb390289066565343d60245a424eca77cb429b911ed8 \
--hash=sha256:7dcfcf12b6f15beb606afa46de2ed348b65a279c340ef2242a9a35c22eeafe94
# via
- # -r pythonFiles/jedilsp_requirements/requirements.in
+ # -r python_files\jedilsp_requirements\requirements.in
# jedi-language-server
pygments==2.17.2 \
--hash=sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c \
--hash=sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367
- # via -r pythonFiles/jedilsp_requirements/requirements.in
+ # via -r python_files/jedilsp_requirements/requirements.in
typing-extensions==4.9.0 \
--hash=sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783 \
--hash=sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd
diff --git a/extensions/positron-python/pythonFiles/linter.py b/extensions/positron-python/python_files/linter.py
similarity index 89%
rename from extensions/positron-python/pythonFiles/linter.py
rename to extensions/positron-python/python_files/linter.py
index 58ad9397f58..af9634f83f4 100644
--- a/extensions/positron-python/pythonFiles/linter.py
+++ b/extensions/positron-python/python_files/linter.py
@@ -37,11 +37,7 @@ def main():
invoke = sys.argv[1]
if invoke == "-m":
linter = sys.argv[2]
- args = (
- [sys.executable, "-m", linter]
- + linter_settings[linter]["args"]
- + sys.argv[3:]
- )
+ args = [sys.executable, "-m", linter] + linter_settings[linter]["args"] + sys.argv[3:]
else:
linter = sys.argv[2]
args = [sys.argv[3]] + linter_settings[linter]["args"] + sys.argv[4:]
diff --git a/extensions/positron-python/pythonFiles/normalizeSelection.py b/extensions/positron-python/python_files/normalizeSelection.py
similarity index 98%
rename from extensions/positron-python/pythonFiles/normalizeSelection.py
rename to extensions/positron-python/python_files/normalizeSelection.py
index f0397b7db3a..523db0727e5 100644
--- a/extensions/positron-python/pythonFiles/normalizeSelection.py
+++ b/extensions/positron-python/python_files/normalizeSelection.py
@@ -297,11 +297,7 @@ def get_next_block_lineno(which_line_next):
data = None
which_line_next = 0
- if (
- empty_Highlight
- and contents.get("smartSendExperimentEnabled")
- and contents.get("smartSendSettingsEnabled")
- ):
+ if empty_Highlight and contents.get("smartSendSettingsEnabled"):
result = traverse_file(
contents["wholeFileContent"],
vscode_start_line,
diff --git a/extensions/positron-python/pythonFiles/positron/pinned-test-requirements.txt b/extensions/positron-python/python_files/positron/pinned-test-requirements.txt
similarity index 100%
rename from extensions/positron-python/pythonFiles/positron/pinned-test-requirements.txt
rename to extensions/positron-python/python_files/positron/pinned-test-requirements.txt
diff --git a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/__init__.py b/extensions/positron-python/python_files/positron/positron_ipykernel/__init__.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/positron/positron_ipykernel/__init__.py
rename to extensions/positron-python/python_files/positron/positron_ipykernel/__init__.py
diff --git a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/_pydoc.css b/extensions/positron-python/python_files/positron/positron_ipykernel/_pydoc.css
similarity index 100%
rename from extensions/positron-python/pythonFiles/positron/positron_ipykernel/_pydoc.css
rename to extensions/positron-python/python_files/positron/positron_ipykernel/_pydoc.css
diff --git a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/access_keys.py b/extensions/positron-python/python_files/positron/positron_ipykernel/access_keys.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/positron/positron_ipykernel/access_keys.py
rename to extensions/positron-python/python_files/positron/positron_ipykernel/access_keys.py
diff --git a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/connections.py b/extensions/positron-python/python_files/positron/positron_ipykernel/connections.py
similarity index 99%
rename from extensions/positron-python/pythonFiles/positron/positron_ipykernel/connections.py
rename to extensions/positron-python/python_files/positron/positron_ipykernel/connections.py
index 8bf793dfd34..c2bae01cd42 100644
--- a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/connections.py
+++ b/extensions/positron-python/python_files/positron/positron_ipykernel/connections.py
@@ -184,7 +184,6 @@ def _wrap_connection(self, obj: Any) -> Connection:
raise ValueError(f"Unsupported connection type {type_name}")
def _close_connection(self, comm_id: str):
-
try:
# calling disconnect can fail if the connection has already been closed or
# if it's called from a different thread.
@@ -396,7 +395,6 @@ def disconnect(self):
self.conn.close()
def preview_object(self, path: List[ObjectSchema]):
-
if pd_ is None:
raise ModuleNotFoundError("Pandas is required for previewing SQLite tables.")
@@ -429,14 +427,12 @@ class SQLAlchemyConnection(Connection):
"""
def __init__(self, conn):
-
self.conn: sqlalchemy.Engine = conn
self.display_name = f"SQLAlchemy ({conn.name})"
self.host = conn.url
self.type = "SQLAlchemy"
def list_objects(self, path: List[ObjectSchema]):
-
if sqlalchemy_ is None:
raise ModuleNotFoundError(
"SQLAlchemy is required for listing objects in SQLAlchemy connections."
@@ -466,7 +462,6 @@ def list_objects(self, path: List[ObjectSchema]):
raise ValueError(f"Path length must be at most 1, but got {len(path)}. Path: {path}")
def list_fields(self, path: List[ObjectSchema]):
-
if sqlalchemy_ is None:
raise ModuleNotFoundError(
"SQLAlchemy is required for listing fields in SQLAlchemy connections."
@@ -492,7 +487,6 @@ def list_object_types(self):
}
def preview_object(self, path: List[ObjectSchema]):
-
if sqlalchemy_ is None:
raise ModuleNotFoundError(
"SQLAlchemy is required for previewing objects in SQLAlchemy connections."
diff --git a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/connections_comm.py b/extensions/positron-python/python_files/positron/positron_ipykernel/connections_comm.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/positron/positron_ipykernel/connections_comm.py
rename to extensions/positron-python/python_files/positron/positron_ipykernel/connections_comm.py
diff --git a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/data_explorer.py b/extensions/positron-python/python_files/positron/positron_ipykernel/data_explorer.py
similarity index 90%
rename from extensions/positron-python/pythonFiles/positron/positron_ipykernel/data_explorer.py
rename to extensions/positron-python/python_files/positron/positron_ipykernel/data_explorer.py
index 29efcad3f68..773c26ca0c5 100644
--- a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/data_explorer.py
+++ b/extensions/positron-python/python_files/positron/positron_ipykernel/data_explorer.py
@@ -8,7 +8,6 @@
import abc
import logging
import operator
-import uuid
from typing import (
TYPE_CHECKING,
Callable,
@@ -24,6 +23,7 @@
from .access_keys import decode_access_key
from .data_explorer_comm import (
+ BackendState,
ColumnFrequencyTable,
ColumnHistogram,
ColumnSummaryStats,
@@ -41,8 +41,8 @@
GetDataValuesRequest,
GetSchemaRequest,
GetStateRequest,
- GetSupportedFeaturesRequest,
RowFilter,
+ RowFilterCondition,
RowFilterType,
SchemaUpdateParams,
SearchFilterType,
@@ -59,10 +59,11 @@
TableData,
TableSchema,
TableShape,
- TableState,
)
from .positron_comm import CommMessage, PositronComm
from .third_party import pd_
+from .utils import guid
+
if TYPE_CHECKING:
import pandas as pd
@@ -87,10 +88,13 @@ class DataExplorerTableView(abc.ABC):
def __init__(
self,
+ display_name: str,
table,
filters: Optional[List[RowFilter]],
sort_keys: Optional[List[ColumnSortKey]],
):
+ self.display_name = display_name
+
# Note: we must not ever modify the user's data
self.table = table
@@ -130,7 +134,7 @@ def get_data_values(self, request: GetDataValuesRequest):
).dict()
def set_row_filters(self, request: SetRowFiltersRequest):
- return self._set_row_filters(request.params.filters)
+ return self._set_row_filters(request.params.filters).dict()
def set_sort_columns(self, request: SetSortColumnsRequest):
self.sort_keys = request.params.sort_keys
@@ -166,9 +170,6 @@ def get_column_profiles(self, request: GetColumnProfilesRequest):
def get_state(self, request: GetStateRequest):
return self._get_state().dict()
- def get_supported_features(self, request: GetSupportedFeaturesRequest):
- return self._get_supported_features().dict()
-
@abc.abstractmethod
def invalidate_computations(self):
pass
@@ -225,11 +226,7 @@ def _prof_histogram(self, column_index: int) -> ColumnHistogram:
pass
@abc.abstractmethod
- def _get_state(self) -> TableState:
- pass
-
- @abc.abstractmethod
- def _get_supported_features(self) -> SupportedFeatures:
+ def _get_state(self) -> BackendState:
pass
@@ -276,11 +273,12 @@ class PandasView(DataExplorerTableView):
def __init__(
self,
+ display_name: str,
table,
filters: Optional[List[RowFilter]],
sort_keys: Optional[List[ColumnSortKey]],
):
- super().__init__(table, filters, sort_keys)
+ super().__init__(display_name, table, filters, sort_keys)
self._dtypes = None
@@ -482,7 +480,7 @@ def _update_view_indices(self):
# reflect the filtered_indices that have just been updated
self._sort_data()
- def _set_row_filters(self, filters) -> FilterResult:
+ def _set_row_filters(self, filters: List[RowFilter]) -> FilterResult:
self.filters = filters
if len(filters) == 0:
@@ -491,20 +489,32 @@ def _set_row_filters(self, filters) -> FilterResult:
self._update_view_indices()
return FilterResult(selected_num_rows=len(self.table))
- # Evaluate all the filters and AND them together
+ # Evaluate all the filters and combine them using the
+ # indicated conditions
combined_mask = None
for filt in filters:
+ if filt.is_valid is False:
+ # If filter is invalid, do not evaluate it
+ continue
+
single_mask = self._eval_filter(filt)
if combined_mask is None:
combined_mask = single_mask
- else:
+ elif filt.condition == RowFilterCondition.And:
combined_mask &= single_mask
+ elif filt.condition == RowFilterCondition.Or:
+ combined_mask |= single_mask
- self.filtered_indices = combined_mask.nonzero()[0]
+ if combined_mask is None:
+ self.filtered_indices = None
+ selected_num_rows = len(self.table)
+ else:
+ self.filtered_indices = combined_mask.nonzero()[0]
+ selected_num_rows = len(self.filtered_indices)
# Update the view indices, re-sorting if needed
self._update_view_indices()
- return FilterResult(selected_num_rows=len(self.filtered_indices))
+ return FilterResult(selected_num_rows=selected_num_rows)
def _eval_filter(self, filt: RowFilter):
col = self.table.iloc[:, filt.column_index]
@@ -531,8 +541,12 @@ def _eval_filter(self, filt: RowFilter):
op = COMPARE_OPS[params.op]
# pandas comparison filters return False for null values
mask = op(col, _coerce_value_param(params.value, col.dtype))
+ elif filt.filter_type == RowFilterType.IsEmpty:
+ mask = col.str.len() == 0
elif filt.filter_type == RowFilterType.IsNull:
mask = col.isnull()
+ elif filt.filter_type == RowFilterType.NotEmpty:
+ mask = col.str.len() != 0
elif filt.filter_type == RowFilterType.NotNull:
mask = col.notnull()
elif filt.filter_type == RowFilterType.SetMembership:
@@ -687,40 +701,46 @@ def _prof_freq_table(self, column_index: int):
def _prof_histogram(self, column_index: int):
raise NotImplementedError
- def _get_state(self) -> TableState:
- return TableState(
- table_shape=TableShape(num_rows=self.table.shape[0], num_columns=self.table.shape[1]),
+ _row_filter_features = SetRowFiltersFeatures(
+ supported=True,
+ supports_conditions=False,
+ supported_types=[
+ RowFilterType.Between,
+ RowFilterType.Compare,
+ RowFilterType.IsNull,
+ RowFilterType.NotNull,
+ RowFilterType.NotBetween,
+ RowFilterType.Search,
+ RowFilterType.SetMembership,
+ ],
+ )
+
+ _column_profile_features = GetColumnProfilesFeatures(
+ supported=True,
+ supported_types=[
+ ColumnProfileType.NullCount,
+ ColumnProfileType.SummaryStats,
+ ],
+ )
+
+ FEATURES = SupportedFeatures(
+ search_schema=SearchSchemaFeatures(supported=True),
+ set_row_filters=_row_filter_features,
+ get_column_profiles=_column_profile_features,
+ )
+
+ def _get_state(self) -> BackendState:
+ if self.view_indices is not None:
+ num_rows = len(self.view_indices)
+ else:
+ num_rows = self.table.shape[0]
+
+ return BackendState(
+ display_name=self.display_name,
+ table_shape=TableShape(num_rows=num_rows, num_columns=self.table.shape[1]),
row_filters=self.filters,
sort_keys=self.sort_keys,
- )
-
- def _get_supported_features(self) -> SupportedFeatures:
- row_filter_features = SetRowFiltersFeatures(
- supported=True,
- supports_conditions=False,
- supported_types=[
- RowFilterType.Between,
- RowFilterType.Compare,
- RowFilterType.IsNull,
- RowFilterType.NotNull,
- RowFilterType.NotBetween,
- RowFilterType.Search,
- RowFilterType.SetMembership,
- ],
- )
-
- column_profile_features = GetColumnProfilesFeatures(
- supported=True,
- supported_types=[
- ColumnProfileType.NullCount,
- ColumnProfileType.SummaryStats,
- ],
- )
-
- return SupportedFeatures(
- search_schema=SearchSchemaFeatures(supported=True),
- set_row_filters=row_filter_features,
- get_column_profiles=column_profile_features,
+ supported_features=self.FEATURES,
)
@@ -748,8 +768,9 @@ class PyArrowView(DataExplorerTableView):
pass
-def _get_table_view(table, filters=None, sort_keys=None):
- return PandasView(table, filters, sort_keys)
+def _get_table_view(table, filters=None, sort_keys=None, name=None):
+ name = name or guid()
+ return PandasView(name, table, filters, sort_keys)
def _value_type_is_supported(value):
@@ -814,9 +835,14 @@ def register_table(
raise TypeError(type(table))
if comm_id is None:
- comm_id = str(uuid.uuid4())
+ comm_id = guid()
- self.table_views[comm_id] = _get_table_view(table)
+ if variable_path is not None:
+ full_title = ", ".join([str(decode_access_key(k)) for k in variable_path])
+ else:
+ full_title = title
+
+ self.table_views[comm_id] = _get_table_view(table, name=full_title)
base_comm = comm.create_comm(
target_name=self.comm_target,
@@ -934,6 +960,8 @@ def _update_explorer_for_comm(self, comm_id: str, path: PathKey, new_variable):
comm = self.comms[comm_id]
table_view = self.table_views[comm_id]
+ full_title = ", ".join([str(decode_access_key(k)) for k in path])
+
# When detecting namespace assignments or changes, the first
# level of the path has already been resolved. If there is a
# data explorer open for a nested value, then we need to use
@@ -941,7 +969,7 @@ def _update_explorer_for_comm(self, comm_id: str, path: PathKey, new_variable):
if len(path) > 1:
is_found, new_table = _resolve_value_from_path(new_variable, path[1:])
if not is_found:
- raise KeyError(f"Path {', '.join(path)} not found in value")
+ raise KeyError(f"Path {full_title} not found in value")
else:
new_table = new_variable
@@ -966,7 +994,7 @@ def _fire_schema_update(discard_state=False):
# start over. At some point we can return here and
# selectively preserve state if we feel it is safe enough
# to do so.
- self.table_views[comm_id] = _get_table_view(new_table)
+ self.table_views[comm_id] = _get_table_view(new_table, name=full_title)
return _fire_schema_update(discard_state=True)
# New value for data explorer is the same. For now, we just
@@ -990,12 +1018,13 @@ def _fire_schema_update(discard_state=False):
) = table_view.ui_should_update_schema(new_table)
if should_discard_state:
- self.table_views[comm_id] = _get_table_view(new_table)
+ self.table_views[comm_id] = _get_table_view(new_table, name=full_title)
else:
self.table_views[comm_id] = _get_table_view(
new_table,
filters=table_view.filters,
sort_keys=table_view.sort_keys,
+ name=full_title,
)
if should_update_schema:
@@ -1015,4 +1044,13 @@ def handle_msg(self, msg: CommMessage[DataExplorerBackendMessageContent], raw_ms
table = self.table_views[comm_id]
result = getattr(table, request.method.value)(request)
+
+ # To help remember to convert pydantic types to dicts
+ if result is not None:
+ if isinstance(result, list):
+ for x in result:
+ assert isinstance(x, dict)
+ else:
+ assert isinstance(result, dict)
+
comm.send_result(result)
diff --git a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/data_explorer_comm.py b/extensions/positron-python/python_files/positron/positron_ipykernel/data_explorer_comm.py
similarity index 96%
rename from extensions/positron-python/pythonFiles/positron/positron_ipykernel/data_explorer_comm.py
rename to extensions/positron-python/python_files/positron/positron_ipykernel/data_explorer_comm.py
index 07c143074fa..1cc8132ee97 100644
--- a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/data_explorer_comm.py
+++ b/extensions/positron-python/python_files/positron/positron_ipykernel/data_explorer_comm.py
@@ -42,6 +42,17 @@ class ColumnDisplayType(str, enum.Enum):
Unknown = "unknown"
+@enum.unique
+class RowFilterCondition(str, enum.Enum):
+ """
+ Possible values for Condition in RowFilter
+ """
+
+ And = "and"
+
+ Or = "or"
+
+
@enum.unique
class RowFilterType(str, enum.Enum):
"""
@@ -52,10 +63,14 @@ class RowFilterType(str, enum.Enum):
Compare = "compare"
+ IsEmpty = "is_empty"
+
IsNull = "is_null"
NotBetween = "not_between"
+ NotEmpty = "not_empty"
+
NotNull = "not_null"
Search = "search"
@@ -152,11 +167,15 @@ class FilterResult(BaseModel):
)
-class TableState(BaseModel):
+class BackendState(BaseModel):
"""
- The current backend table state
+ The current backend state for the data explorer
"""
+ display_name: str = Field(
+ description="Variable name or other string to display for tab name in UI",
+ )
+
table_shape: TableShape = Field(
description="Provides number of rows and columns in table",
)
@@ -169,6 +188,10 @@ class TableState(BaseModel):
description="The set of currently applied sorts",
)
+ supported_features: SupportedFeatures = Field(
+ description="The features currently supported by the backend instance",
+ )
+
class TableShape(BaseModel):
"""
@@ -184,24 +207,6 @@ class TableShape(BaseModel):
)
-class SupportedFeatures(BaseModel):
- """
- For each field, returns flags indicating supported features
- """
-
- search_schema: SearchSchemaFeatures = Field(
- description="Support for 'search_schema' RPC and its features",
- )
-
- set_row_filters: SetRowFiltersFeatures = Field(
- description="Support for 'set_row_filters' RPC and its features",
- )
-
- get_column_profiles: GetColumnProfilesFeatures = Field(
- description="Support for 'get_column_profiles' RPC and its features",
- )
-
-
class ColumnSchema(BaseModel):
"""
Schema for a column in a table
@@ -281,6 +286,15 @@ class RowFilter(BaseModel):
description="Column index to apply filter to",
)
+ condition: RowFilterCondition = Field(
+ description="The binary condition to use to combine with preceding row filters",
+ )
+
+ is_valid: Optional[bool] = Field(
+ default=None,
+ description="Whether the filter is valid and supported by the backend, if undefined then true",
+ )
+
between_params: Optional[BetweenFilterParams] = Field(
default=None,
description="Parameters for the 'between' and 'not_between' filter types",
@@ -556,6 +570,24 @@ class ColumnSortKey(BaseModel):
)
+class SupportedFeatures(BaseModel):
+ """
+ For each field, returns flags indicating supported features
+ """
+
+ search_schema: SearchSchemaFeatures = Field(
+ description="Support for 'search_schema' RPC and its features",
+ )
+
+ set_row_filters: SetRowFiltersFeatures = Field(
+ description="Support for 'set_row_filters' RPC and its features",
+ )
+
+ get_column_profiles: GetColumnProfilesFeatures = Field(
+ description="Support for 'get_column_profiles' RPC and its features",
+ )
+
+
class SearchSchemaFeatures(BaseModel):
"""
Feature flags for 'search_schema' RPC
@@ -625,9 +657,6 @@ class DataExplorerBackendRequest(str, enum.Enum):
# Get the state
GetState = "get_state"
- # Query the backend to determine supported features
- GetSupportedFeatures = "get_supported_features"
-
class GetSchemaParams(BaseModel):
"""
@@ -840,22 +869,6 @@ class GetStateRequest(BaseModel):
)
-class GetSupportedFeaturesRequest(BaseModel):
- """
- Query the backend to determine supported features, to enable feature
- toggling
- """
-
- method: Literal[DataExplorerBackendRequest.GetSupportedFeatures] = Field(
- description="The JSON-RPC method name (get_supported_features)",
- )
-
- jsonrpc: str = Field(
- default="2.0",
- description="The JSON-RPC version specifier",
- )
-
-
class DataExplorerBackendMessageContent(BaseModel):
comm_id: str
data: Union[
@@ -866,7 +879,6 @@ class DataExplorerBackendMessageContent(BaseModel):
SetSortColumnsRequest,
GetColumnProfilesRequest,
GetStateRequest,
- GetSupportedFeaturesRequest,
] = Field(..., discriminator="method")
@@ -899,12 +911,10 @@ class SchemaUpdateParams(BaseModel):
FilterResult.update_forward_refs()
-TableState.update_forward_refs()
+BackendState.update_forward_refs()
TableShape.update_forward_refs()
-SupportedFeatures.update_forward_refs()
-
ColumnSchema.update_forward_refs()
TableSchema.update_forward_refs()
@@ -941,6 +951,8 @@ class SchemaUpdateParams(BaseModel):
ColumnSortKey.update_forward_refs()
+SupportedFeatures.update_forward_refs()
+
SearchSchemaFeatures.update_forward_refs()
SetRowFiltersFeatures.update_forward_refs()
@@ -973,6 +985,4 @@ class SchemaUpdateParams(BaseModel):
GetStateRequest.update_forward_refs()
-GetSupportedFeaturesRequest.update_forward_refs()
-
SchemaUpdateParams.update_forward_refs()
diff --git a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/docstrings/__init__.py b/extensions/positron-python/python_files/positron/positron_ipykernel/docstrings/__init__.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/positron/positron_ipykernel/docstrings/__init__.py
rename to extensions/positron-python/python_files/positron/positron_ipykernel/docstrings/__init__.py
diff --git a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/docstrings/epytext.py b/extensions/positron-python/python_files/positron/positron_ipykernel/docstrings/epytext.py
similarity index 98%
rename from extensions/positron-python/pythonFiles/positron/positron_ipykernel/docstrings/epytext.py
rename to extensions/positron-python/python_files/positron/positron_ipykernel/docstrings/epytext.py
index 044d0510966..9cae9a7884e 100644
--- a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/docstrings/epytext.py
+++ b/extensions/positron-python/python_files/positron/positron_ipykernel/docstrings/epytext.py
@@ -202,9 +202,9 @@ def combine_sections(self):
if name == "Type:":
type_sections[section.arg_name] = content.split(f"`{section.arg_name}`: ", 1)[1]
elif name == "Rtype:":
- unique_sections["Return:"].content = (
- f"({content.rstrip()}) {unique_sections['Return:'].content}"
- )
+ unique_sections[
+ "Return:"
+ ].content = f"({content.rstrip()}) {unique_sections['Return:'].content}"
else:
matching_type = type_sections.get(str(section.arg_name))
diff --git a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/help.py b/extensions/positron-python/python_files/positron/positron_ipykernel/help.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/positron/positron_ipykernel/help.py
rename to extensions/positron-python/python_files/positron/positron_ipykernel/help.py
diff --git a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/help_comm.py b/extensions/positron-python/python_files/positron/positron_ipykernel/help_comm.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/positron/positron_ipykernel/help_comm.py
rename to extensions/positron-python/python_files/positron/positron_ipykernel/help_comm.py
diff --git a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/inspectors.py b/extensions/positron-python/python_files/positron/positron_ipykernel/inspectors.py
similarity index 99%
rename from extensions/positron-python/pythonFiles/positron/positron_ipykernel/inspectors.py
rename to extensions/positron-python/python_files/positron/positron_ipykernel/inspectors.py
index 62ec7d57a27..51a13890871 100644
--- a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/inspectors.py
+++ b/extensions/positron-python/python_files/positron/positron_ipykernel/inspectors.py
@@ -232,7 +232,13 @@ def get_length(self) -> int:
return len([p for p in dir(self.value) if not (p.startswith("_"))])
def get_child(self, key: str) -> Any:
- return getattr(self.value, key)
+ if isinstance(self.value, property):
+ pass
+ try:
+ return getattr(self.value, key)
+ except Exception as e:
+ logger.warning(msg=f"{type(e).__name__}: {e}")
+ return "Unable to show value."
def get_items(self) -> Iterable[Tuple[str, Any]]:
for key in dir(self.value):
diff --git a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/jedi.py b/extensions/positron-python/python_files/positron/positron_ipykernel/jedi.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/positron/positron_ipykernel/jedi.py
rename to extensions/positron-python/python_files/positron/positron_ipykernel/jedi.py
diff --git a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/lsp.py b/extensions/positron-python/python_files/positron/positron_ipykernel/lsp.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/positron/positron_ipykernel/lsp.py
rename to extensions/positron-python/python_files/positron/positron_ipykernel/lsp.py
diff --git a/extensions/positron-python/python_files/positron/positron_ipykernel/matplotlib_backend.py b/extensions/positron-python/python_files/positron/positron_ipykernel/matplotlib_backend.py
new file mode 100644
index 00000000000..b1501dea304
--- /dev/null
+++ b/extensions/positron-python/python_files/positron/positron_ipykernel/matplotlib_backend.py
@@ -0,0 +1,195 @@
+#
+# Copyright (C) 2024 Posit Software, PBC. All rights reserved.
+#
+"""
+The matplotlib backend for Positron.
+"""
+
+from __future__ import annotations
+
+import hashlib
+import io
+import logging
+from typing import Optional, Union, cast
+
+import matplotlib
+from matplotlib._pylab_helpers import Gcf
+from matplotlib.backend_bases import FigureManagerBase
+from matplotlib.backends.backend_agg import FigureCanvasAgg
+from matplotlib.figure import Figure
+
+from .plots import Plot
+
+logger = logging.getLogger(__name__)
+
+
+class FigureManagerPositron(FigureManagerBase):
+ """
+ Interface for the matplotlib backend to interact with the Positron frontend.
+
+ Parameters:
+ -----------
+ canvas
+ The canvas for this figure.
+ num
+ The figure number.
+
+ Attributes:
+ -----------
+ canvas
+ The canvas for this figure.
+ """
+
+ canvas: FigureCanvasPositron
+
+ def __init__(self, canvas: FigureCanvasPositron, num: Union[int, str]):
+ from .positron_ipkernel import PositronIPyKernel
+
+ super().__init__(canvas, num)
+
+ self._plot: Optional[Plot] = None
+ self._plots_service = cast(PositronIPyKernel, PositronIPyKernel.instance()).plots_service
+
+ @property
+ def is_visible(self) -> bool:
+ """
+ Whether the figure is visible to the frontend.
+ """
+ return self._plot is not None
+
+ def show(self) -> None:
+ """
+ Called by matplotlib when a figure is shown via `plt.show()` or `figure.show()`.
+ """
+ if self._plot is None:
+ # The frontend should respond with a render request, so there's no need for the explicit
+ # show call in this case.
+ self._plot = self._plots_service.create_plot(self.canvas._render, self._handle_close)
+ else:
+ self._plot.show()
+
+ def destroy(self) -> None:
+ """
+ Called by matplotlib when a figure is closed via `plt.close()`.
+ """
+ if self._plot is not None:
+ self._plots_service.close_plot(self._plot)
+
+ def update(self) -> None:
+ """
+ Notify the frontend that the plot needs to be rerendered.
+
+ Called by the canvas when a figure is drawn and its contents have changed.
+ """
+ if self._plot is None:
+ logger.warning("Cannot update a plot that is not visible")
+ else:
+ self._plot.update()
+
+ def _handle_close(self) -> None:
+ """
+ Called by the plots service after the plot is closed in the frontend.
+ """
+ # Notify matplotlib to close the figure (and its manager and canvas).
+ Gcf.destroy(self)
+
+
+class FigureCanvasPositron(FigureCanvasAgg):
+ """
+ The canvas for a figure in the Positron backend.
+
+ Parameters:
+ -----------
+ figure
+ The figure to draw on this canvas.
+
+ Attributes:
+ -----------
+ manager
+ The manager for this canvas.
+ """
+
+ manager: FigureManagerPositron
+
+ manager_class = FigureManagerPositron # type: ignore
+
+ def __init__(self, figure: Optional[Figure] = None) -> None:
+ super().__init__(figure)
+
+ # Track the hash of the canvas contents for change detection.
+ self._last_hash = ""
+
+ def draw(self, is_rendering=False) -> None:
+ """
+ Draw the canvas; send an update event if the canvas has changed.
+
+ Parameters:
+ -----------
+ is_rendering
+ Whether the canvas is being rendered, to avoid recursively requesting an update from the
+ frontend.
+ """
+ logger.debug("Drawing to canvas")
+ try:
+ super().draw()
+ finally:
+ if self.manager.is_visible and not is_rendering:
+ current_hash = self._hash_buffer_rgba()
+ logger.debug(f"Canvas: last hash: {self._last_hash[:6]}")
+ logger.debug(f"Canvas: current hash: {current_hash[:6]}")
+ if current_hash == self._last_hash:
+ logger.debug("Canvas: hash is the same, no need to render")
+ else:
+ logger.debug("Canvas: hash changed, requesting a render")
+ self.manager.update()
+
+ def _render(self, width_px: int, height_px: int, pixel_ratio: float, format: str) -> bytes:
+ # Set the device pixel ratio to the requested value.
+ self._set_device_pixel_ratio(pixel_ratio) # type: ignore
+
+ # This must be set before setting the size and can't be passed via print_figure else the
+ # resulting size won't match the request size.
+ self.figure.set_layout_engine("tight")
+
+ # Resize the figure to the requested size in pixels.
+ width_in = width_px * self.device_pixel_ratio / self.figure.dpi
+ height_in = height_px * self.device_pixel_ratio / self.figure.dpi
+ self.figure.set_size_inches(width_in, height_in, forward=False)
+
+ # Render the canvas.
+ figure_buffer = io.BytesIO()
+ with io.BytesIO() as figure_buffer:
+ self.print_figure(
+ figure_buffer,
+ format=format,
+ dpi=self.figure.dpi,
+ )
+ rendered = figure_buffer.getvalue()
+
+ # NOTE: For some reason, setting the layout engine earlier then calling print_figure
+ # requires this redraw before calculating the hash else the next draw() call will
+ # spuriously detect a change.
+ self.draw(is_rendering=True)
+ self._last_hash = self._hash_buffer_rgba()
+
+ return rendered
+
+ def _hash_buffer_rgba(self) -> str:
+ """Hash the canvas contents for change detection."""
+ return hashlib.sha1(self.buffer_rgba()).hexdigest()
+
+
+def enable_positron_matplotlib_backend() -> None:
+ """
+ Enable this backend.
+ """
+ # Enable interactive mode to allow for redraws after each cell execution.
+ matplotlib.interactive(True)
+
+ # Set the backend.
+ matplotlib.use("module://positron_ipykernel.matplotlib_backend")
+
+
+# Fulfill the matplotlib backend API.
+FigureCanvas = FigureCanvasPositron
+FigureManager = FigureManagerPositron
diff --git a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/plot_comm.py b/extensions/positron-python/python_files/positron/positron_ipykernel/plot_comm.py
similarity index 94%
rename from extensions/positron-python/pythonFiles/positron/positron_ipykernel/plot_comm.py
rename to extensions/positron-python/python_files/positron/positron_ipykernel/plot_comm.py
index fdeaa8dcd67..6d8e245777c 100644
--- a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/plot_comm.py
+++ b/extensions/positron-python/python_files/positron/positron_ipykernel/plot_comm.py
@@ -59,6 +59,10 @@ class RenderParams(BaseModel):
description="The pixel ratio of the display device",
)
+ format: str = Field(
+ description="The requested plot format",
+ )
+
class RenderRequest(BaseModel):
"""
@@ -94,6 +98,9 @@ class PlotFrontendEvent(str, enum.Enum):
# Notification that a plot has been updated on the backend.
Update = "update"
+ # Show a plot.
+ Show = "show"
+
PlotResult.update_forward_refs()
diff --git a/extensions/positron-python/python_files/positron/positron_ipykernel/plots.py b/extensions/positron-python/python_files/positron/positron_ipykernel/plots.py
new file mode 100644
index 00000000000..d967f3db397
--- /dev/null
+++ b/extensions/positron-python/python_files/positron/positron_ipykernel/plots.py
@@ -0,0 +1,172 @@
+#
+# Copyright (C) 2023-2024 Posit Software, PBC. All rights reserved.
+#
+
+from __future__ import annotations
+
+import base64
+import logging
+import uuid
+from typing import Callable, List, Optional, Protocol
+
+from .plot_comm import PlotBackendMessageContent, PlotFrontendEvent, PlotResult, RenderRequest
+from .positron_comm import CommMessage, PositronComm
+from .session_mode import SessionMode
+from .utils import JsonRecord
+
+logger = logging.getLogger(__name__)
+
+
+MIME_TYPE = {
+ "png": "image/png",
+ "svg": "image/svg+xml",
+ "pdf": "application/pdf",
+ "jpeg": "image/jpeg",
+}
+
+
+class Plot:
+ """
+ The backend representation of a frontend plot instance.
+
+ Paramaters:
+ -----------
+ comm
+ The communication channel to the frontend plot instance.
+ render
+ A callable that renders the plot. See `plot_comm.RenderRequest` for parameter details.
+ close_callback
+ A callback that is called after the plot comm is closed.
+ """
+
+ def __init__(
+ self,
+ comm: PositronComm,
+ render: Renderer,
+ close_callback: Optional[Callable[[], None]] = None,
+ ) -> None:
+ self._comm = comm
+ self._render = render
+ self._close_callback = close_callback
+
+ self._closed = False
+
+ self._comm.on_msg(self._handle_msg, PlotBackendMessageContent)
+
+ @property
+ def closed(self) -> bool:
+ """
+ Whether the plot is closed.
+ """
+ return self._closed
+
+ def close(self) -> None:
+ """
+ Close the plot.
+ """
+ if self._closed:
+ return
+
+ self._closed = True
+ self._comm.close()
+ if self._close_callback:
+ self._close_callback()
+
+ def show(self) -> None:
+ """
+ Show the plot.
+ """
+ self._comm.send_event(PlotFrontendEvent.Show, {})
+
+ def update(self) -> None:
+ """
+ Notify the frontend that the plot needs to be rerendered.
+ """
+ self._comm.send_event(PlotFrontendEvent.Update, {})
+
+ def _handle_msg(self, msg: CommMessage[PlotBackendMessageContent], raw_msg: JsonRecord) -> None:
+ request = msg.content.data
+ if isinstance(request, RenderRequest):
+ self._handle_render(
+ request.params.width,
+ request.params.height,
+ request.params.pixel_ratio,
+ request.params.format,
+ )
+ else:
+ logger.warning(f"Unhandled request: {request}")
+
+ def _handle_render(
+ self,
+ width_px: int,
+ height_px: int,
+ pixel_ratio: float,
+ format: str,
+ ) -> None:
+ rendered = self._render(width_px, height_px, pixel_ratio, format)
+ data = base64.b64encode(rendered).decode()
+ result = PlotResult(data=data, mime_type=MIME_TYPE[format]).dict()
+ self._comm.send_result(data=result)
+
+
+class Renderer(Protocol):
+ """
+ A callable that renders a plot. See `plot_comm.RenderRequest` for parameter details.
+ """
+
+ def __call__(self, width_px: int, height_px: int, pixel_ratio: float, format: str) -> bytes: ...
+
+
+class PlotsService:
+ """
+ The plots service is responsible for managing `Plot` instances.
+
+ Paramaters:
+ -----------
+ target_name
+ The name of the target for plot comms, as defined in the frontend.
+ session_mode
+ The session mode that the kernel was started in.
+ """
+
+ def __init__(self, target_name: str, session_mode: SessionMode):
+ self._target_name = target_name
+ self._session_mode = session_mode
+
+ self._plots: List[Plot] = []
+
+ def create_plot(self, render: Renderer, close_callback: Callable[[], None]) -> Plot:
+ """
+ Create a plot.
+
+ See Also:
+ ---------
+ Plot
+ """
+ comm_id = str(uuid.uuid4())
+ logger.info(f"Creating plot with comm {comm_id}")
+ plot_comm = PositronComm.create(self._target_name, comm_id)
+ plot = Plot(plot_comm, render, close_callback)
+ self._plots.append(plot)
+ return plot
+
+ def close_plot(self, plot: Plot) -> None:
+ """
+ Close a plot.
+
+ Parameters:
+ -----------
+ plot
+ The plot to close.
+ """
+ if plot.closed:
+ return
+ plot.close()
+ self._plots.remove(plot)
+
+ def shutdown(self) -> None:
+ """
+ Shutdown the plots service.
+ """
+ for plot in list(self._plots):
+ self.close_plot(plot)
diff --git a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/positron_comm.py b/extensions/positron-python/python_files/positron/positron_ipykernel/positron_comm.py
similarity index 58%
rename from extensions/positron-python/pythonFiles/positron/positron_ipykernel/positron_comm.py
rename to extensions/positron-python/python_files/positron/positron_ipykernel/positron_comm.py
index 8cedb73df5f..4c8cfb06370 100644
--- a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/positron_comm.py
+++ b/extensions/positron-python/python_files/positron/positron_ipykernel/positron_comm.py
@@ -10,7 +10,14 @@
import comm
-from . import connections_comm, data_explorer_comm, help_comm, plot_comm, ui_comm, variables_comm
+from . import (
+ connections_comm,
+ data_explorer_comm,
+ help_comm,
+ plot_comm,
+ ui_comm,
+ variables_comm,
+)
from ._vendor.pydantic import ValidationError
from ._vendor.pydantic.generics import GenericModel
from .utils import JsonData, JsonRecord
@@ -21,10 +28,23 @@
## Create an enum of JSON-RPC error codes
@enum.unique
class JsonRpcErrorCode(enum.IntEnum):
+ # Documentation below is taken directly from https://www.jsonrpc.org/specification#error_object
+ # for convenience.
+
+ # Invalid JSON was received by the server.
+ # An error occurred on the server while parsing the JSON text.
PARSE_ERROR = -32700
+
+ # The JSON sent is not a valid Request object.
INVALID_REQUEST = -32600
+
+ # The method does not exist / is not available.
METHOD_NOT_FOUND = -32601
+
+ # Invalid method parameter(s).
INVALID_PARAMS = -32602
+
+ # Internal JSON-RPC error.
INTERNAL_ERROR = -32603
@@ -40,15 +60,70 @@ class JsonRpcErrorCode(enum.IntEnum):
class CommMessage(GenericModel, Generic[T_content]):
+ """
+ A generic message received from the frontend-side of a comm.
+ """
+
content: T_content
class PositronComm:
- """A wrapper around a base IPython comm that provides a JSON-RPC interface"""
+ """
+ A wrapper around a base IPython comm that provides a JSON-RPC interface.
+
+ Paramaters:
+ -----------
+ comm
+ The wrapped IPython comm.
+
+ Attributes:
+ -----------
+ comm
+ The wrapped IPython comm.
+ comm_id
+ """
def __init__(self, comm: comm.base_comm.BaseComm) -> None:
self.comm = comm
+ @classmethod
+ def create(cls, target_name: str, comm_id: str) -> PositronComm:
+ """
+ Create a Positron comm.
+
+ Parameters:
+ -----------
+ target_name
+ The name of the target for the comm, as defined in the frontend.
+ comm_id
+ The unique identifier for the comm.
+
+ Returns:
+ --------
+ PositronComm
+ The new PositronComm instance.
+ """
+ base_comm = comm.create_comm(target_name=target_name, comm_id=comm_id)
+ return cls(base_comm)
+
+ @property
+ def comm_id(self) -> str:
+ """
+ The unique identifier of this comm.
+ """
+ return self.comm.comm_id
+
+ def on_close(self, callback: Callable[[JsonRecord], None]):
+ """
+ Register a callback for when the frontend-side version of this comm is closed.
+
+ Paramaters:
+ -----------
+ callback
+ Called when the comm is closed, with the raw close message.
+ """
+ self.comm.on_close(callback)
+
def on_msg(
self,
callback: Callable[[CommMessage[T_content], JsonRecord], None],
@@ -57,9 +132,14 @@ def on_msg(
"""
Register a callback for an RPC request from the frontend.
- Will be called with both the parsed `msg: CommMessage` and the original `raw_msg`.
-
- If the `raw_msg` could not be parsed, a JSON-RPC error will be sent to the frontend.
+ Parameters:
+ -----------
+ callback
+ Called when a message is received, with both the parsed message `msg: CommMessage` and
+ original `raw_msg`. Not called if the `raw_msg` could not be parsed; instead, a JSON-RPC
+ error will be sent to the frontend.
+ content_cls
+ The Pydantic model to parse the message with.
"""
def handle_msg(
@@ -108,7 +188,16 @@ def handle_msg(
self.comm.on_msg(handle_msg)
def send_result(self, data: JsonData = None, metadata: Optional[JsonRecord] = None) -> None:
- """Send a JSON-RPC result to the frontend-side version of this comm"""
+ """
+ Send a JSON-RPC result to the frontend-side version of this comm.
+
+ Parameters:
+ -----------
+ data
+ The result data to send.
+ metadata
+ The metadata to send with the result.
+ """
result = dict(
jsonrpc="2.0",
result=data,
@@ -120,7 +209,16 @@ def send_result(self, data: JsonData = None, metadata: Optional[JsonRecord] = No
)
def send_event(self, name: str, payload: JsonRecord) -> None:
- """Send a JSON-RPC notification (event) to the frontend-side version of this comm"""
+ """
+ Send a JSON-RPC notification (event) to the frontend-side version of this comm.
+
+ Parameters:
+ -----------
+ name
+ The name of the event.
+ payload
+ The payload of the event.
+ """
event = dict(
jsonrpc="2.0",
method=name,
@@ -129,7 +227,16 @@ def send_event(self, name: str, payload: JsonRecord) -> None:
self.comm.send(data=event)
def send_error(self, code: JsonRpcErrorCode, message: Optional[str] = None) -> None:
- """Send a JSON-RPC result to the frontend-side version of this comm"""
+ """
+ Send a JSON-RPC result to the frontend-side version of this comm.
+
+ Parameters:
+ -----------
+ code
+ The error code to send.
+ message
+ The error message to send.
+ """
error = dict(
jsonrpc="2.0",
error=dict(
@@ -144,5 +251,7 @@ def send_error(self, code: JsonRpcErrorCode, message: Optional[str] = None) -> N
)
def close(self) -> None:
- """Close the underlying comm."""
+ """
+ Close the frontend-side version of this comm.
+ """
self.comm.close()
diff --git a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/positron_ipkernel.py b/extensions/positron-python/python_files/positron/positron_ipykernel/positron_ipkernel.py
similarity index 87%
rename from extensions/positron-python/pythonFiles/positron/positron_ipykernel/positron_ipkernel.py
rename to extensions/positron-python/python_files/positron/positron_ipykernel/positron_ipkernel.py
index 8bc9fd8c293..c73e756d108 100644
--- a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/positron_ipkernel.py
+++ b/extensions/positron-python/python_files/positron/positron_ipykernel/positron_ipkernel.py
@@ -2,7 +2,8 @@
# Copyright (C) 2023-2024 Posit Software, PBC. All rights reserved.
#
-""" Positron extensions to the iPython Kernel."""
+"""Positron extensions to the iPython Kernel."""
+
from __future__ import annotations
import enum
@@ -10,7 +11,7 @@
import re
import warnings
from pathlib import Path
-from typing import Any, Callable, Container, Dict, List, Optional, Type
+from typing import Any, Callable, Container, Dict, List, Optional, Type, cast
import traitlets
from ipykernel.comm.manager import CommManager
@@ -26,7 +27,9 @@
from .data_explorer import DataExplorerService
from .help import HelpService, help
from .lsp import LSPService
-from .plots import PositronDisplayPublisherHook
+from .matplotlib_backend import enable_positron_matplotlib_backend
+from .plots import PlotsService
+from .session_mode import SessionMode
from .ui import UiService
from .utils import JsonRecord
from .variables import VariablesService
@@ -137,6 +140,18 @@ class PositronShell(ZMQInteractiveShell):
help="Class to use to instantiate the shell inspector",
).tag(config=True)
+ # Positron-specific attributes:
+ session_mode: SessionMode = SessionMode.trait() # type: ignore
+
+ def __init__(self, *args, **kwargs):
+ # Set custom attributes from the parent object.
+ # It would be better to pass these as explicit arguments, but there's no easy way
+ # to override the parent to do that.
+ parent = cast(PositronIPyKernel, kwargs["parent"])
+ self.session_mode = parent.session_mode
+
+ super().__init__(*args, **kwargs)
+
def init_events(self) -> None:
super().init_events()
@@ -204,6 +219,7 @@ def _handle_post_run_cell(self, info: ExecutionInfo) -> None:
After execution, sends an update message to the client to summarize
the changes observed to variables in the user's environment.
"""
+ # TODO: Split these to separate callbacks?
# Check for changes to the working directory
try:
self.kernel.ui_service.poll_working_directory()
@@ -249,6 +265,11 @@ def _showtraceback(self, etype, evalue: Exception, stb: List[str]): # type: ign
"""
Enhance tracebacks for the Positron frontend.
"""
+ if self.session_mode == SessionMode.NOTEBOOK:
+ # Don't modify the traceback in a notebook. The frontend assumes that it's unformatted
+ # and applies its own formatting.
+ return super()._showtraceback(etype, evalue, stb) # type: ignore IPython type annotation is wrong
+
# Remove the first two lines of the traceback, which are the "---" header and the repeated
# exception name and "Traceback (most recent call last)".
# Also remove the last line of the traceback, which repeats f"{etype}: {evalue}".
@@ -304,12 +325,21 @@ class PositronIPyKernel(IPythonKernel):
klass=InteractiveShell,
)
+ # Positron-specific attributes:
+ session_mode: SessionMode = SessionMode.trait() # type: ignore
+
def __init__(self, **kwargs) -> None:
+ # Set custom attributes from the parent object.
+ # It would be better to pass these as explicit arguments, but there's no easy way
+ # to override the parent to do that.
+ parent = cast(PositronIPKernelApp, kwargs["parent"])
+ self.session_mode = parent.session_mode
+
super().__init__(**kwargs)
# Create Positron services
self.data_explorer_service = DataExplorerService(_CommTarget.DataExplorer)
- self.display_pub_hook = PositronDisplayPublisherHook(_CommTarget.Plot)
+ self.plots_service = PlotsService(_CommTarget.Plot, self.session_mode)
self.ui_service = UiService()
self.help_service = HelpService()
self.lsp_service = LSPService(self)
@@ -325,7 +355,6 @@ def __init__(self, **kwargs) -> None:
_CommTarget.Variables, self.variables_service.on_comm_open
)
# Register display publisher hooks
- self.shell.display_pub.register_hook(self.display_pub_hook)
self.shell.display_pub.register_hook(self.widget_hook)
# Ignore warnings that the user can't do anything about
@@ -364,10 +393,10 @@ async def do_shutdown(self, restart: bool) -> JsonRecord: # type: ignore Report
# Shutdown Positron services
self.data_explorer_service.shutdown()
- self.display_pub_hook.shutdown()
self.ui_service.shutdown()
self.help_service.shutdown()
self.lsp_service.shutdown()
+ self.plots_service.shutdown()
self.widget_hook.shutdown()
await self.variables_service.shutdown()
self.connections_service.shutdown()
@@ -379,9 +408,25 @@ async def do_shutdown(self, restart: bool) -> JsonRecord: # type: ignore Report
class PositronIPKernelApp(IPKernelApp):
+ kernel: PositronIPyKernel
+
# Use the PositronIPyKernel class.
kernel_class: Type[PositronIPyKernel] = traitlets.Type(PositronIPyKernel) # type: ignore
+ # Positron-specific attributes:
+ session_mode: SessionMode = SessionMode.trait() # type: ignore
+
+ def init_gui_pylab(self):
+ try:
+ # Enable the Positron matplotlib backend if we're not in a notebook.
+ # If we're in a notebook, use IPython's default backend via the super() call below.
+ if self.session_mode != SessionMode.NOTEBOOK:
+ enable_positron_matplotlib_backend()
+ except Exception:
+ logger.error("Error setting matplotlib backend")
+
+ return super().init_gui_pylab()
+
#
# OSC8 functionality
diff --git a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/positron_jedilsp.py b/extensions/positron-python/python_files/positron/positron_ipykernel/positron_jedilsp.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/positron/positron_ipykernel/positron_jedilsp.py
rename to extensions/positron-python/python_files/positron/positron_ipykernel/positron_jedilsp.py
diff --git a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/pydoc.py b/extensions/positron-python/python_files/positron/positron_ipykernel/pydoc.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/positron/positron_ipykernel/pydoc.py
rename to extensions/positron-python/python_files/positron/positron_ipykernel/pydoc.py
diff --git a/extensions/positron-python/python_files/positron/positron_ipykernel/session_mode.py b/extensions/positron-python/python_files/positron/positron_ipykernel/session_mode.py
new file mode 100644
index 00000000000..48e1213d426
--- /dev/null
+++ b/extensions/positron-python/python_files/positron/positron_ipykernel/session_mode.py
@@ -0,0 +1,27 @@
+#
+# Copyright (C) 2023-2024 Posit Software, PBC. All rights reserved.
+#
+
+import enum
+
+import traitlets
+
+
+class SessionMode(str, enum.Enum):
+ """
+ The mode that the kernel application was started in.
+ """
+
+ CONSOLE = "console"
+ NOTEBOOK = "notebook"
+ BACKGROUND = "background"
+
+ DEFAULT = CONSOLE
+
+ def __str__(self) -> str:
+ # Override for better display in argparse help.
+ return self.value
+
+ @classmethod
+ def trait(cls) -> traitlets.Enum:
+ return traitlets.Enum(sorted(cls), help=cls.__doc__)
diff --git a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/tests/__init__.py b/extensions/positron-python/python_files/positron/positron_ipykernel/tests/__init__.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/positron/positron_ipykernel/tests/__init__.py
rename to extensions/positron-python/python_files/positron/positron_ipykernel/tests/__init__.py
diff --git a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/tests/conftest.py b/extensions/positron-python/python_files/positron/positron_ipykernel/tests/conftest.py
similarity index 83%
rename from extensions/positron-python/pythonFiles/positron/positron_ipykernel/tests/conftest.py
rename to extensions/positron-python/python_files/positron/positron_ipykernel/tests/conftest.py
index 8bac479aaec..a3c016f60d9 100644
--- a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/tests/conftest.py
+++ b/extensions/positron-python/python_files/positron/positron_ipykernel/tests/conftest.py
@@ -3,13 +3,20 @@
#
from typing import Iterable
-from unittest.mock import Mock
+from unittest.mock import MagicMock, Mock
import comm
import pytest
+from traitlets.config import Config
+
from positron_ipykernel.connections import ConnectionsService
from positron_ipykernel.data_explorer import DataExplorerService
-from positron_ipykernel.positron_ipkernel import PositronIPyKernel, PositronShell
+from positron_ipykernel.positron_ipkernel import (
+ PositronIPKernelApp,
+ PositronIPyKernel,
+ PositronShell,
+)
+from positron_ipykernel.session_mode import SessionMode
from positron_ipykernel.variables import VariablesService
@@ -26,6 +33,11 @@ def publish_msg(self, msg_type, **msg): # type: ignore ReportIncompatibleMethod
msg["msg_type"] = msg_type
self.messages.append(msg)
+ def pop_messages(self):
+ messages = list(self.messages)
+ self.messages.clear()
+ return messages
+
# Enable autouse so that all comms are created as DummyComms.
@pytest.fixture(autouse=True)
@@ -43,10 +55,15 @@ def kernel() -> PositronIPyKernel:
"""
The Positron kernel, configured for testing purposes.
"""
- # Create a Positron kernel. The kernel calls shell_class.instance() to get the globally
- # registered shell instance, and IPython registers a TerminalInteractiveShell instead of a
- # PositronShell. This causes a traitlets validation error unless we pass the shell_class explicitly.
- kernel = PositronIPyKernel.instance(shell_class=PositronShell)
+ # Mock the application object. We haven't needed to use it in tests yet, but we do need it to
+ # pass our custom attributes down to the shell.
+ app = MagicMock(PositronIPKernelApp)
+ app.config = Config() # Needed to avoid traitlets errors
+
+ # Positron-specific attributes:
+ app.session_mode = SessionMode.CONSOLE
+
+ kernel = PositronIPyKernel.instance(parent=app)
return kernel
diff --git a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/tests/data.py b/extensions/positron-python/python_files/positron/positron_ipykernel/tests/data.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/positron/positron_ipykernel/tests/data.py
rename to extensions/positron-python/python_files/positron/positron_ipykernel/tests/data.py
diff --git a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/tests/test_access_keys.py b/extensions/positron-python/python_files/positron/positron_ipykernel/tests/test_access_keys.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/positron/positron_ipykernel/tests/test_access_keys.py
rename to extensions/positron-python/python_files/positron/positron_ipykernel/tests/test_access_keys.py
diff --git a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/tests/test_connections.py b/extensions/positron-python/python_files/positron/positron_ipykernel/tests/test_connections.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/positron/positron_ipykernel/tests/test_connections.py
rename to extensions/positron-python/python_files/positron/positron_ipykernel/tests/test_connections.py
diff --git a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/tests/test_data_explorer.py b/extensions/positron-python/python_files/positron/positron_ipykernel/tests/test_data_explorer.py
similarity index 91%
rename from extensions/positron-python/pythonFiles/positron/positron_ipykernel/tests/test_data_explorer.py
rename to extensions/positron-python/python_files/positron/positron_ipykernel/tests/test_data_explorer.py
index efff8e6e771..53deba30512 100644
--- a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/tests/test_data_explorer.py
+++ b/extensions/positron-python/python_files/positron/positron_ipykernel/tests/test_data_explorer.py
@@ -2,7 +2,6 @@
# Copyright (C) 2023-2024 Posit Software, PBC. All rights reserved.
#
-import uuid
from typing import Any, Dict, List, Optional, Type, cast
import numpy as np
@@ -22,6 +21,7 @@
)
from .conftest import DummyComm, PositronShell
from .test_variables import BIG_ARRAY_LENGTH
+from ..utils import guid
from .utils import json_rpc_notification, json_rpc_request, json_rpc_response
TARGET_NAME = "positron.dataExplorer"
@@ -30,10 +30,6 @@
# pytest fixtures
-def guid():
- return str(uuid.uuid4())
-
-
def get_new_comm(
de_service: DataExplorerService,
table: Any,
@@ -261,6 +257,7 @@ def test_explorer_variable_updates(
dxf = DataExplorerFixture(de_service)
new_state = dxf.get_state("x")
+ assert new_state["display_name"] == "x"
assert new_state["table_shape"]["num_rows"] == 5
assert new_state["table_shape"]["num_columns"] == 1
assert new_state["sort_keys"] == [ColumnSortKey(**k) for k in x_sort_keys]
@@ -373,9 +370,6 @@ def search_schema(self, table_name, search_term, start_index, max_results):
def get_state(self, table_name):
return self.do_json_rpc(table_name, "get_state")
- def get_supported_features(self, table_name):
- return self.do_json_rpc(table_name, "get_supported_features")
-
def get_data_values(self, table_name, **params):
return self.do_json_rpc(table_name, "get_data_values", **params)
@@ -395,7 +389,11 @@ def check_filter_case(self, table, filter_set, expected_table):
self.register_table(ex_id, expected_table)
response = self.set_row_filters(table_id, filters=filter_set)
- assert response == FilterResult(selected_num_rows=len(expected_table))
+
+ ex_num_rows = len(expected_table)
+ assert response == FilterResult(selected_num_rows=ex_num_rows)
+
+ assert self.get_state(table_id)["table_shape"]["num_rows"] == ex_num_rows
self.compare_tables(table_id, ex_id, table.shape)
def check_sort_case(self, table, sort_keys, expected_table, filters=None):
@@ -440,6 +438,7 @@ def _wrap_json(model: Type[BaseModel], data: JsonRecords):
def test_pandas_get_state(dxf: DataExplorerFixture):
result = dxf.get_state("simple")
+ assert result["display_name"] == "simple"
assert result["table_shape"]["num_rows"] == 5
assert result["table_shape"]["num_columns"] == 6
@@ -456,9 +455,9 @@ def test_pandas_get_state(dxf: DataExplorerFixture):
assert result["row_filters"] == [RowFilter(**f) for f in filters]
-def test_pandas_get_supported_features(dxf: DataExplorerFixture):
+def test_pandas_supported_features(dxf: DataExplorerFixture):
dxf.register_table("example", SIMPLE_PANDAS_DF)
- features = dxf.get_supported_features("example")
+ features = dxf.get_state("example")["supported_features"]
search_schema = features["search_schema"]
row_filters = features["set_row_filters"]
@@ -668,37 +667,59 @@ def test_pandas_get_data_values(dxf: DataExplorerFixture):
# )
-def _filter(filter_type, column_index, **kwargs):
+def _filter(filter_type, column_index, condition="and", is_valid=None, **kwargs):
kwargs.update(
{
"filter_id": guid(),
"filter_type": filter_type,
"column_index": column_index,
+ "condition": condition,
+ "is_valid": is_valid,
}
)
return kwargs
-def _compare_filter(column_index, op, value):
- return _filter("compare", column_index, compare_params={"op": op, "value": value})
+def _compare_filter(column_index, op, value, condition="and", is_valid=None):
+ return _filter(
+ "compare",
+ column_index,
+ condition=condition,
+ is_valid=is_valid,
+ compare_params={"op": op, "value": value},
+ )
-def _between_filter(column_index, left_value, right_value, op="between"):
+def _between_filter(column_index, left_value, right_value, op="between", condition="and"):
return _filter(
op,
column_index,
+ condition=condition,
between_params={"left_value": left_value, "right_value": right_value},
)
-def _not_between_filter(column_index, left_value, right_value):
- return _between_filter(column_index, left_value, right_value, op="not_between")
+def _not_between_filter(column_index, left_value, right_value, condition="and"):
+ return _between_filter(
+ column_index,
+ left_value,
+ right_value,
+ op="not_between",
+ condition=condition,
+ )
-def _search_filter(column_index, term, case_sensitive=False, search_type="contains"):
+def _search_filter(
+ column_index,
+ term,
+ case_sensitive=False,
+ search_type="contains",
+ condition="and",
+):
return _filter(
"search",
column_index,
+ condition=condition,
search_params={
"search_type": search_type,
"term": term,
@@ -707,10 +728,16 @@ def _search_filter(column_index, term, case_sensitive=False, search_type="contai
)
-def _set_member_filter(column_index, values, inclusive=True):
+def _set_member_filter(
+ column_index,
+ values,
+ inclusive=True,
+ condition="and",
+):
return _filter(
"set_membership",
column_index,
+ condition=condition,
set_membership_params={"values": values, "inclusive": inclusive},
)
@@ -743,6 +770,26 @@ def test_pandas_filter_between(dxf: DataExplorerFixture):
)
+def test_pandas_filter_conditions(dxf: DataExplorerFixture):
+ # Test AND/OR conditions when filtering
+ df = SIMPLE_PANDAS_DF
+ filters = [
+ _compare_filter(0, ">=", 3, condition="or"),
+ _compare_filter(3, "<=", -4.5, condition="or"),
+ # Delbierately duplicated
+ _compare_filter(3, "<=", -4.5, condition="or"),
+ ]
+
+ expected_df = df[(df["a"] >= 3) | (df["d"] <= -4.5)]
+ dxf.check_filter_case(df, filters, expected_df)
+
+ # Test a single condition with or set
+ filters = [
+ _compare_filter(0, ">=", 3, condition="or"),
+ ]
+ dxf.check_filter_case(df, filters, df[df["a"] >= 3])
+
+
def test_pandas_filter_compare(dxf: DataExplorerFixture):
# Just use the 'a' column to smoke test comparison filters on
# integers
@@ -771,6 +818,40 @@ def test_pandas_filter_compare(dxf: DataExplorerFixture):
dxf.compare_tables(table_name, ex_id, df.shape)
+def test_pandas_filter_is_valid(dxf: DataExplorerFixture):
+ # Test AND/OR conditions when filtering
+ df = SIMPLE_PANDAS_DF
+ filters = [
+ _compare_filter(0, ">=", 3),
+ _compare_filter(0, "<", 3, is_valid=False),
+ ]
+
+ expected_df = df[df["a"] >= 3]
+ dxf.check_filter_case(df, filters, expected_df)
+
+ # No filter is valid
+ filters = [
+ _compare_filter(0, ">=", 3, is_valid=False),
+ _compare_filter(0, "<", 3, is_valid=False),
+ ]
+
+ dxf.check_filter_case(df, filters, df)
+
+
+def test_pandas_filter_empty(dxf: DataExplorerFixture):
+ df = pd.DataFrame(
+ {
+ "a": ["foo", "bar", "", "", "", None, "baz", ""],
+ "b": [b"foo", b"bar", b"", b"", None, b"", b"baz", b""],
+ }
+ )
+
+ dxf.check_filter_case(df, [_filter("is_empty", 0)], df[df["a"].str.len() == 0])
+ dxf.check_filter_case(df, [_filter("not_empty", 0)], df[df["a"].str.len() != 0])
+ dxf.check_filter_case(df, [_filter("is_empty", 1)], df[df["b"].str.len() == 0])
+ dxf.check_filter_case(df, [_filter("not_empty", 1)], df[df["b"].str.len() != 0])
+
+
def test_pandas_filter_is_null_not_null(dxf: DataExplorerFixture):
df = SIMPLE_PANDAS_DF
b_is_null = _filter("is_null", 1)
diff --git a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/tests/test_docstrings_epytext.py b/extensions/positron-python/python_files/positron/positron_ipykernel/tests/test_docstrings_epytext.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/positron/positron_ipykernel/tests/test_docstrings_epytext.py
rename to extensions/positron-python/python_files/positron/positron_ipykernel/tests/test_docstrings_epytext.py
diff --git a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/tests/test_help.py b/extensions/positron-python/python_files/positron/positron_ipykernel/tests/test_help.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/positron/positron_ipykernel/tests/test_help.py
rename to extensions/positron-python/python_files/positron/positron_ipykernel/tests/test_help.py
diff --git a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/tests/test_inspectors.py b/extensions/positron-python/python_files/positron/positron_ipykernel/tests/test_inspectors.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/positron/positron_ipykernel/tests/test_inspectors.py
rename to extensions/positron-python/python_files/positron/positron_ipykernel/tests/test_inspectors.py
diff --git a/extensions/positron-python/python_files/positron/positron_ipykernel/tests/test_plots.py b/extensions/positron-python/python_files/positron/positron_ipykernel/tests/test_plots.py
new file mode 100644
index 00000000000..e0253f8fa2e
--- /dev/null
+++ b/extensions/positron-python/python_files/positron/positron_ipykernel/tests/test_plots.py
@@ -0,0 +1,238 @@
+#
+# Copyright (C) 2023-2024 Posit Software, PBC. All rights reserved.
+#
+
+import base64
+import io
+from pathlib import Path
+from typing import Iterable, List, cast
+
+import matplotlib
+import matplotlib.pyplot as plt
+import pytest
+from PIL import Image
+
+from positron_ipykernel.matplotlib_backend import enable_positron_matplotlib_backend
+from positron_ipykernel.plots import PlotsService
+from positron_ipykernel.positron_ipkernel import PositronIPyKernel, _CommTarget
+
+from .conftest import DummyComm, PositronShell
+from .utils import (
+ comm_close_message,
+ comm_open_message,
+ json_rpc_notification,
+ json_rpc_request,
+ json_rpc_response,
+)
+
+#
+# Matplotlib backend + shell + plots service integration tests.
+#
+
+TARGET_NAME = "target_name"
+
+
+@pytest.fixture(autouse=True)
+def setup_positron_matplotlib_backend() -> None:
+ enable_positron_matplotlib_backend()
+
+ assert matplotlib.get_backend() == "module://positron_ipykernel.matplotlib_backend"
+ assert matplotlib.is_interactive()
+
+
+@pytest.fixture(autouse=True)
+def import_pyplot(shell: PositronShell) -> None:
+ # Import pyplot for convenience.
+ shell.run_cell("import matplotlib.pyplot as plt")
+
+
+@pytest.fixture
+def plots_service(kernel: PositronIPyKernel) -> Iterable[PlotsService]:
+ """
+ The Positron plots service.
+ """
+ plots_service = kernel.plots_service
+
+ assert not plots_service._plots
+
+ yield plots_service
+
+ plt.close("all")
+
+
+@pytest.fixture(scope="session")
+def images_path() -> Path:
+ images_path = Path(__file__).parent / "images"
+ images_path.mkdir(exist_ok=True)
+ return images_path
+
+
+def test_mpl_dont_create_plot_on_new_figure(
+ shell: PositronShell, plots_service: PlotsService
+) -> None:
+ # Creating a figure should not yet create a plot with the plots service.
+ shell.run_cell("plt.figure()")
+ assert not plots_service._plots
+
+
+def _get_plot_comms(plots_service: PlotsService) -> List[DummyComm]:
+ return [cast(DummyComm, plot._comm.comm) for plot in plots_service._plots]
+
+
+def _get_single_plot_comm(plots_service: PlotsService) -> DummyComm:
+ plot_comms = _get_plot_comms(plots_service)
+ assert len(plot_comms) == 1
+ return plot_comms[0]
+
+
+@pytest.mark.parametrize("code", ["plt.figure(); plt.show()", "plt.figure().show()"])
+def test_mpl_send_open_comm_on_plt_show(
+ code: str, shell: PositronShell, plots_service: PlotsService
+) -> None:
+ # Showing a figure should create a plot with the plots service and open a corresponding comm.
+ shell.run_cell(code)
+ plot_comm = _get_single_plot_comm(plots_service)
+ assert plot_comm.pop_messages() == [comm_open_message(_CommTarget.Plot)]
+
+
+def _create_mpl_plot(shell: PositronShell, plots_service: PlotsService) -> DummyComm:
+ shell.run_cell("plt.figure().show()")
+ plot_comm = _get_single_plot_comm(plots_service)
+ plot_comm.messages.clear()
+ return plot_comm
+
+
+def test_mpl_send_show_on_successive_plt_show(
+ shell: PositronShell, plots_service: PlotsService
+) -> None:
+ plot_comm = _create_mpl_plot(shell, plots_service)
+
+ # Show the figure again.
+ shell.run_cell("plt.show()")
+ assert plot_comm.pop_messages() == [json_rpc_notification("show", {})]
+
+ # It should also work with Figure.show().
+ shell.run_cell("plt.gcf().show()")
+ assert plot_comm.pop_messages() == [json_rpc_notification("show", {})]
+
+
+def test_mpl_send_update_on_draw(shell: PositronShell, plots_service: PlotsService) -> None:
+ plot_comm = _create_mpl_plot(shell, plots_service)
+
+ # Drawing to an active plot should trigger an update.
+ shell.run_cell("plt.plot([1, 2])")
+ assert plot_comm.pop_messages() == [json_rpc_notification("update", {})]
+
+
+def test_mpl_dont_send_update_on_execution(
+ shell: PositronShell, plots_service: PlotsService
+) -> None:
+ plot_comm = _create_mpl_plot(shell, plots_service)
+
+ # Executing code that doesn't draw to the active plot should not trigger an update.
+ shell.run_cell("1")
+ assert plot_comm.pop_messages() == []
+
+
+def test_mpl_send_close_comm_on_plt_close(
+ shell: PositronShell, plots_service: PlotsService
+) -> None:
+ plot_comm = _create_mpl_plot(shell, plots_service)
+
+ # Closing the figure should close the plot and send a comm close message.
+ shell.run_cell("plt.close()")
+ assert plot_comm.pop_messages() == [comm_close_message()]
+ assert not plots_service._plots
+
+
+def test_mpl_multiple_figures(shell: PositronShell, plots_service: PlotsService) -> None:
+ # Create two figures, and show them both.
+ shell.run_cell("f1 = plt.figure(); f2 = plt.figure(); plt.show()")
+
+ plot_comms = _get_plot_comms(plots_service)
+ assert len(plot_comms) == 2
+ for plot_comm in plot_comms:
+ assert plot_comm.pop_messages() == [comm_open_message(_CommTarget.Plot)]
+
+ # Draw to the first figure.
+ shell.run_cell("plt.figure(f1); plt.plot([1, 2])")
+
+ assert plot_comms[0].pop_messages() == [json_rpc_notification("update", {})]
+ assert plot_comms[1].pop_messages() == []
+
+ # Draw to the second figure.
+ shell.run_cell("plt.figure(f2); plt.plot([1, 2])")
+
+ assert plot_comms[0].pop_messages() == []
+ assert plot_comms[1].pop_messages() == [json_rpc_notification("update", {})]
+
+ # Show the first figure.
+ shell.run_cell("f1.show()")
+
+ assert plot_comms[0].pop_messages() == [json_rpc_notification("show", {})]
+ assert plot_comms[1].pop_messages() == []
+
+ # Show the second figure.
+ shell.run_cell("f2.show()")
+
+ assert plot_comms[0].pop_messages() == []
+ assert plot_comms[1].pop_messages() == [json_rpc_notification("show", {})]
+
+
+def test_mpl_render(shell: PositronShell, plots_service: PlotsService, images_path: Path) -> None:
+ # First show the figure and get the plot comm.
+ shell.run_cell("plt.plot([1, 2])\nplt.show()")
+ plot_comm = _get_single_plot_comm(plots_service)
+ assert plot_comm.pop_messages() == [
+ comm_open_message(_CommTarget.Plot),
+ # NOTE: The update here is unnecessary since when the frontend receives a comm open, it
+ # responds with a render request. It is probably harmless though since the frontend
+ # debounces render requests. It happens because all figures are redrawn post cell execution,
+ # when matplotlib interactive mode is enabled.
+ json_rpc_notification("update", {}),
+ ]
+
+ # Send a render request to the plot comm.
+ width = 400
+ height = 300
+ pixel_ratio = 2
+ format = "png"
+ msg = json_rpc_request(
+ "render",
+ {"width": width, "height": height, "pixel_ratio": pixel_ratio, "format": format},
+ comm_id="dummy_comm_id",
+ )
+ plot_comm.handle_msg(msg)
+
+ responses = plot_comm.pop_messages()
+ assert len(responses) == 1
+ response = responses[0]
+
+ # Check that the response includes the expected base64-encoded resized image.
+ image_bytes = response["data"]["result"].pop("data")
+ image = Image.open(io.BytesIO(base64.b64decode(image_bytes)))
+ assert image.format == format.upper()
+ assert image.size == (width * pixel_ratio, height * pixel_ratio)
+ # Save it to disk for manual inspection.
+ image.save(images_path / "test-mpl-render.png")
+
+ # Check the rest of the response.
+ assert response == json_rpc_response({"mime_type": "image/png"})
+
+
+def test_mpl_shutdown(shell: PositronShell, plots_service: PlotsService) -> None:
+ # Create a figure and show it.
+ shell.run_cell("plt.figure(); plt.figure(); plt.show()")
+ plot_comms = _get_plot_comms(plots_service)
+
+ # Double-check that it still has plots.
+ assert len(plots_service._plots) == 2
+
+ # Double-check that all comms are still open.
+ assert not any(comm._closed for comm in plot_comms)
+
+ plots_service.shutdown()
+
+ # Plots are closed and cleared.
+ assert not plots_service._plots
+ assert all(comm._closed for comm in plot_comms)
diff --git a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/tests/test_positron_ipkernel.py b/extensions/positron-python/python_files/positron/positron_ipykernel/tests/test_positron_ipkernel.py
similarity index 81%
rename from extensions/positron-python/pythonFiles/positron/positron_ipykernel/tests/test_positron_ipkernel.py
rename to extensions/positron-python/python_files/positron/positron_ipykernel/tests/test_positron_ipkernel.py
index 966027c8599..b5b3e89617c 100644
--- a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/tests/test_positron_ipkernel.py
+++ b/extensions/positron-python/python_files/positron/positron_ipykernel/tests/test_positron_ipkernel.py
@@ -10,7 +10,9 @@
import pandas as pd
import pytest
from IPython.utils.syspathcontext import prepended_to_syspath
+
from positron_ipykernel.help import help
+from positron_ipykernel.session_mode import SessionMode
from positron_ipykernel.utils import alias_home
from .conftest import PositronShell
@@ -81,7 +83,12 @@ def g():
"""
-def test_traceback(shell: PositronShell, tmp_path: Path, mock_displayhook: Mock) -> None:
+def test_console_traceback(
+ shell: PositronShell, tmp_path: Path, mock_displayhook: Mock, monkeypatch
+) -> None:
+ # Ensure that we're in console mode.
+ monkeypatch.setattr(shell, "session_mode", SessionMode.CONSOLE)
+
# We follow the approach of IPython's test_ultratb.py, which is to create a temporary module,
# prepend its parent directory to sys.path, import it, then run a cell that calls a function
# from it.
@@ -167,6 +174,44 @@ def test_traceback(shell: PositronShell, tmp_path: Path, mock_displayhook: Mock)
)
+def test_notebook_traceback(
+ shell: PositronShell, tmp_path: Path, mock_displayhook: Mock, monkeypatch
+) -> None:
+ # Ensure that we're in notebook mode.
+ monkeypatch.setattr(shell, "session_mode", SessionMode.NOTEBOOK)
+
+ # We follow the approach of IPython's test_ultratb.py, which is to create a temporary module,
+ # prepend its parent directory to sys.path, import it, then run a cell that calls a function
+ # from it.
+
+ # Create a temporary module.
+ file = tmp_path / "test_traceback.py"
+ file.write_text(code)
+
+ # Temporarily add the module to sys.path and call a function from it, which should error.
+ with prepended_to_syspath(str(tmp_path)):
+ shell.run_cell("import test_traceback; test_traceback.g()")
+
+ # Check that a single message was sent to the frontend.
+ call_args_list = mock_displayhook.session.send.call_args_list
+ assert len(call_args_list) == 1
+
+ call_args = call_args_list[0]
+
+ # Check that the message was sent over the "error" stream.
+ assert call_args.args[1] == "error"
+
+ exc_content = call_args.args[2]
+
+ # Check that we haven't removed any frames.
+ # We don't check the traceback contents in this case since that's tested in IPython.
+ assert len(exc_content["traceback"]) == 6
+
+ # Check that we haven't modified any other contents.
+ assert exc_content["ename"] == "Exception"
+ assert exc_content["evalue"] == "This is an error!"
+
+
def assert_ansi_string_startswith(actual: str, expected: str) -> None:
"""
Assert that an ansi-formatted string starts with an expected string, in a way that gets pytest
diff --git a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/tests/test_positron_jedilsp.py b/extensions/positron-python/python_files/positron/positron_ipykernel/tests/test_positron_jedilsp.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/positron/positron_ipykernel/tests/test_positron_jedilsp.py
rename to extensions/positron-python/python_files/positron/positron_ipykernel/tests/test_positron_jedilsp.py
diff --git a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/tests/test_pydoc.py b/extensions/positron-python/python_files/positron/positron_ipykernel/tests/test_pydoc.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/positron/positron_ipykernel/tests/test_pydoc.py
rename to extensions/positron-python/python_files/positron/positron_ipykernel/tests/test_pydoc.py
diff --git a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/tests/test_ui.py b/extensions/positron-python/python_files/positron/positron_ipykernel/tests/test_ui.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/positron/positron_ipykernel/tests/test_ui.py
rename to extensions/positron-python/python_files/positron/positron_ipykernel/tests/test_ui.py
diff --git a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/tests/test_variables.py b/extensions/positron-python/python_files/positron/positron_ipykernel/tests/test_variables.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/positron/positron_ipykernel/tests/test_variables.py
rename to extensions/positron-python/python_files/positron/positron_ipykernel/tests/test_variables.py
diff --git a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/tests/test_widget.py b/extensions/positron-python/python_files/positron/positron_ipykernel/tests/test_widget.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/positron/positron_ipykernel/tests/test_widget.py
rename to extensions/positron-python/python_files/positron/positron_ipykernel/tests/test_widget.py
diff --git a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/tests/utils.py b/extensions/positron-python/python_files/positron/positron_ipykernel/tests/utils.py
similarity index 96%
rename from extensions/positron-python/pythonFiles/positron/positron_ipykernel/tests/utils.py
rename to extensions/positron-python/python_files/positron/positron_ipykernel/tests/utils.py
index c24a8457e58..703db0d6afc 100644
--- a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/tests/utils.py
+++ b/extensions/positron-python/python_files/positron/positron_ipykernel/tests/utils.py
@@ -71,6 +71,13 @@ def comm_open_message(target_name: str, data: Optional[JsonRecord] = None) -> Js
}
+def comm_close_message() -> JsonRecord:
+ return {
+ **comm_message(),
+ "msg_type": "comm_close",
+ }
+
+
def json_rpc_error(code: int, message: str) -> JsonRecord:
return comm_message(
{
diff --git a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/third_party.py b/extensions/positron-python/python_files/positron/positron_ipykernel/third_party.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/positron/positron_ipykernel/third_party.py
rename to extensions/positron-python/python_files/positron/positron_ipykernel/third_party.py
diff --git a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/ui.py b/extensions/positron-python/python_files/positron/positron_ipykernel/ui.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/positron/positron_ipykernel/ui.py
rename to extensions/positron-python/python_files/positron/positron_ipykernel/ui.py
diff --git a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/ui_comm.py b/extensions/positron-python/python_files/positron/positron_ipykernel/ui_comm.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/positron/positron_ipykernel/ui_comm.py
rename to extensions/positron-python/python_files/positron/positron_ipykernel/ui_comm.py
diff --git a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/utils.py b/extensions/positron-python/python_files/positron/positron_ipykernel/utils.py
similarity index 98%
rename from extensions/positron-python/pythonFiles/positron/positron_ipykernel/utils.py
rename to extensions/positron-python/python_files/positron/positron_ipykernel/utils.py
index 710bbe39d26..6cd031f5041 100644
--- a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/utils.py
+++ b/extensions/positron-python/python_files/positron/positron_ipykernel/utils.py
@@ -8,10 +8,22 @@
import pprint
import sys
import types
+import uuid
from binascii import b2a_base64
from datetime import datetime
from pathlib import Path
-from typing import Any, Coroutine, Dict, List, Optional, Set, Tuple, TypeVar, Union, cast
+from typing import (
+ Any,
+ Coroutine,
+ Dict,
+ List,
+ Optional,
+ Set,
+ Tuple,
+ TypeVar,
+ Union,
+ cast,
+)
JsonData = Union[Dict[str, "JsonData"], List["JsonData"], str, int, float, bool, None]
JsonRecord = Dict[str, JsonData]
@@ -260,6 +272,10 @@ def alias_home(path: Path) -> Path:
return path
+def guid():
+ return str(uuid.uuid4())
+
+
def positron_ipykernel_usage():
"""
diff --git a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/variables.py b/extensions/positron-python/python_files/positron/positron_ipykernel/variables.py
similarity index 99%
rename from extensions/positron-python/pythonFiles/positron/positron_ipykernel/variables.py
rename to extensions/positron-python/python_files/positron/positron_ipykernel/variables.py
index a9d2b514436..503bc5e35a5 100644
--- a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/variables.py
+++ b/extensions/positron-python/python_files/positron/positron_ipykernel/variables.py
@@ -15,7 +15,13 @@
from .access_keys import decode_access_key, encode_access_key
from .inspectors import get_inspector
from .positron_comm import CommMessage, JsonRpcErrorCode, PositronComm
-from .utils import JsonData, JsonRecord, cancel_tasks, create_task, get_qualname
+from .utils import (
+ JsonData,
+ JsonRecord,
+ cancel_tasks,
+ create_task,
+ get_qualname,
+)
from .variables_comm import (
ClearRequest,
ClipboardFormatFormat,
diff --git a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/variables_comm.py b/extensions/positron-python/python_files/positron/positron_ipykernel/variables_comm.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/positron/positron_ipykernel/variables_comm.py
rename to extensions/positron-python/python_files/positron/positron_ipykernel/variables_comm.py
diff --git a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/widget.py b/extensions/positron-python/python_files/positron/positron_ipykernel/widget.py
similarity index 93%
rename from extensions/positron-python/pythonFiles/positron/positron_ipykernel/widget.py
rename to extensions/positron-python/python_files/positron/positron_ipykernel/widget.py
index f9682e1cde9..4e6879c24b8 100644
--- a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/widget.py
+++ b/extensions/positron-python/python_files/positron/positron_ipykernel/widget.py
@@ -12,7 +12,7 @@
logger = logging.getLogger(__name__)
-_WIDGET_MIME_TYPE = "application/vnd.jupyter.widget-view+json"
+WIDGET_MIME_TYPE = "application/vnd.jupyter.widget-view+json"
@enum.unique
@@ -48,11 +48,11 @@ def __call__(self, msg, *args, **kwargs) -> Optional[dict]:
if msg["msg_type"] == "display_data":
# If there is no widget, let the parent deal with the msg.
data = msg["content"]["data"]
- if _WIDGET_MIME_TYPE not in data:
+ if WIDGET_MIME_TYPE not in data:
logger.warning("No widget MIME type found.")
return msg
- comm_id = data[_WIDGET_MIME_TYPE].get("model_id")
+ comm_id = data[WIDGET_MIME_TYPE].get("model_id")
if comm_id is None:
logger.warning("No comm associated with widget.")
diff --git a/extensions/positron-python/pythonFiles/positron/positron_language_server.py b/extensions/positron-python/python_files/positron/positron_language_server.py
similarity index 91%
rename from extensions/positron-python/pythonFiles/positron/positron_language_server.py
rename to extensions/positron-python/python_files/positron/positron_language_server.py
index 77da0b54b0c..4891d8cd36d 100644
--- a/extensions/positron-python/pythonFiles/positron/positron_language_server.py
+++ b/extensions/positron-python/python_files/positron/positron_language_server.py
@@ -11,7 +11,7 @@
from positron_ipykernel.positron_ipkernel import PositronIPKernelApp
from positron_ipykernel.positron_jedilsp import POSITRON
-from traitlets.config import Config
+from positron_ipykernel.session_mode import SessionMode
logger = logging.getLogger(__name__)
@@ -57,9 +57,9 @@ def parse_args() -> argparse.Namespace:
parser.add_argument(
"--session-mode",
help="session mode in which the kernel is to be started",
- type=str,
- default="console",
- choices=["console", "notebook", "background"],
+ type=SessionMode,
+ default=SessionMode.DEFAULT,
+ choices=sorted(SessionMode),
)
args = parser.parse_args()
args.loglevel = args.loglevel.upper()
@@ -108,20 +108,17 @@ def parse_args() -> argparse.Namespace:
}
}
- # Start Positron's IPyKernel as the interpreter for our console.
# IPKernelApp expects an empty string if no connection_file is provided.
if args.connection_file is None:
args.connection_file = ""
- config = Config(
- IPKernelApp={
- "connection_file": args.connection_file,
- "log_level": args.loglevel,
- "logging_config": logging_config,
- },
+ # Start Positron's IPyKernel as the interpreter for our console.
+ app: PositronIPKernelApp = PositronIPKernelApp.instance(
+ connection_file=args.connection_file,
+ log_level=args.loglevel,
+ logging_config=logging_config,
+ session_mode=args.session_mode,
)
-
- app: PositronIPKernelApp = PositronIPKernelApp.instance(config=config)
# Initialize with empty argv, otherwise BaseIPythonApplication.initialize reuses our
# command-line arguments in unexpected ways (e.g. logfile instructs it to log executed code).
app.initialize(argv=[])
diff --git a/extensions/positron-python/pythonFiles/positron/test-requirements.txt b/extensions/positron-python/python_files/positron/test-requirements.txt
similarity index 100%
rename from extensions/positron-python/pythonFiles/positron/test-requirements.txt
rename to extensions/positron-python/python_files/positron/test-requirements.txt
diff --git a/extensions/positron-python/pythonFiles/printEnvVariables.py b/extensions/positron-python/python_files/printEnvVariables.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/printEnvVariables.py
rename to extensions/positron-python/python_files/printEnvVariables.py
diff --git a/extensions/positron-python/pythonFiles/printEnvVariablesToFile.py b/extensions/positron-python/python_files/printEnvVariablesToFile.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/printEnvVariablesToFile.py
rename to extensions/positron-python/python_files/printEnvVariablesToFile.py
diff --git a/extensions/positron-python/pythonFiles/pyproject.toml b/extensions/positron-python/python_files/pyproject.toml
similarity index 61%
rename from extensions/positron-python/pythonFiles/pyproject.toml
rename to extensions/positron-python/python_files/pyproject.toml
index 878c4a8b6b6..fe9368dab5e 100644
--- a/extensions/positron-python/pythonFiles/pyproject.toml
+++ b/extensions/positron-python/python_files/pyproject.toml
@@ -54,35 +54,18 @@ ignore = [
[tool.ruff]
line-length = 100
-lint.ignore = ["E402"]
exclude = [
- # Ignore testing_tools files same as Pyright way
- 'get-pip.py',
- 'install_debugpy.py',
- 'tensorboard_launcher.py',
- 'testlauncher.py',
- 'visualstudio_py_testlauncher.py',
- 'testing_tools/unittest_discovery.py',
- 'testing_tools/adapter/util.py',
- 'testing_tools/adapter/pytest/_discovery.py',
- 'testing_tools/adapter/pytest/_pytest_item.py',
- 'tests/debug_adapter/test_install_debugpy.py',
- 'tests/testing_tools/adapter/.data',
- 'tests/testing_tools/adapter/test___main__.py',
- 'tests/testing_tools/adapter/test_discovery.py',
- 'tests/testing_tools/adapter/test_functional.py',
- 'tests/testing_tools/adapter/test_report.py',
- 'tests/testing_tools/adapter/test_util.py',
- 'tests/testing_tools/adapter/pytest/test_cli.py',
- 'tests/testing_tools/adapter/pytest/test_discovery.py',
- 'pythonFiles/testing_tools/*',
- 'pythonFiles/testing_tools/adapter/pytest/__init__.py',
- 'pythonFiles/tests/pytestadapter/expected_execution_test_output.py',
- 'pythonFiles/tests/unittestadapter/.data/discovery_error/file_one.py',
- 'pythonFiles/tests/unittestadapter/test_utils.py',
+ "tests/testing_tools/adapter/.data",
+ "tests/unittestadapter/.data",
# --- Start Positron ---
# Ignore vendored dependencies
'lib/',
'positron/positron_ipykernel/_vendor/',
# --- End Positron ---
]
+
+[tool.ruff.format]
+docstring-code-format = true
+
+[tool.ruff.lint.pydocstyle]
+convention = "pep257"
diff --git a/extensions/positron-python/pythonFiles/pythonrc.py b/extensions/positron-python/python_files/pythonrc.py
similarity index 95%
rename from extensions/positron-python/pythonFiles/pythonrc.py
rename to extensions/positron-python/python_files/pythonrc.py
index 374888ddada..2edd8887467 100644
--- a/extensions/positron-python/pythonFiles/pythonrc.py
+++ b/extensions/positron-python/python_files/pythonrc.py
@@ -54,13 +54,13 @@ def __str__(self):
result = ""
# For non-windows allow recent_command history.
if sys.platform != "win32":
- result = "{command_finished}{prompt_started}{prompt}{command_start}{command_executed}{command_line}".format(
+ result = "{command_line}{command_finished}{prompt_started}{prompt}{command_start}{command_executed}".format(
+ command_line="\x1b]633;E;" + str(get_last_command()) + "\x07",
command_finished="\x1b]633;D;" + str(exit_code) + "\x07",
prompt_started="\x1b]633;A\x07",
prompt=original_ps1,
command_start="\x1b]633;B\x07",
command_executed="\x1b]633;C\x07",
- command_line="\x1b]633;E;" + str(get_last_command()) + "\x07",
)
else:
result = "{command_finished}{prompt_started}{prompt}{command_start}{command_executed}".format(
diff --git a/extensions/positron-python/pythonFiles/run-jedi-language-server.py b/extensions/positron-python/python_files/run-jedi-language-server.py
similarity index 59%
rename from extensions/positron-python/pythonFiles/run-jedi-language-server.py
rename to extensions/positron-python/python_files/run-jedi-language-server.py
index 31095121409..5a972799bc3 100644
--- a/extensions/positron-python/pythonFiles/run-jedi-language-server.py
+++ b/extensions/positron-python/python_files/run-jedi-language-server.py
@@ -1,11 +1,11 @@
-import sys
import os
+import sys
# Add the lib path to our sys path so jedi_language_server can find its references
EXTENSION_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
-sys.path.insert(0, os.path.join(EXTENSION_ROOT, "pythonFiles", "lib", "jedilsp"))
+sys.path.insert(0, os.path.join(EXTENSION_ROOT, "python_files", "lib", "jedilsp"))
-from jedi_language_server.cli import cli
+from jedi_language_server.cli import cli # noqa: E402
sys.exit(cli())
diff --git a/extensions/positron-python/pythonFiles/shell_exec.py b/extensions/positron-python/python_files/shell_exec.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/shell_exec.py
rename to extensions/positron-python/python_files/shell_exec.py
diff --git a/extensions/positron-python/pythonFiles/tensorboard_launcher.py b/extensions/positron-python/python_files/tensorboard_launcher.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tensorboard_launcher.py
rename to extensions/positron-python/python_files/tensorboard_launcher.py
diff --git a/extensions/positron-python/pythonFiles/testing_tools/__init__.py b/extensions/positron-python/python_files/testing_tools/__init__.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/testing_tools/__init__.py
rename to extensions/positron-python/python_files/testing_tools/__init__.py
diff --git a/extensions/positron-python/pythonFiles/testing_tools/adapter/__init__.py b/extensions/positron-python/python_files/testing_tools/adapter/__init__.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/testing_tools/adapter/__init__.py
rename to extensions/positron-python/python_files/testing_tools/adapter/__init__.py
diff --git a/extensions/positron-python/pythonFiles/testing_tools/adapter/__main__.py b/extensions/positron-python/python_files/testing_tools/adapter/__main__.py
similarity index 95%
rename from extensions/positron-python/pythonFiles/testing_tools/adapter/__main__.py
rename to extensions/positron-python/python_files/testing_tools/adapter/__main__.py
index 218456897df..cc7084eb943 100644
--- a/extensions/positron-python/pythonFiles/testing_tools/adapter/__main__.py
+++ b/extensions/positron-python/python_files/testing_tools/adapter/__main__.py
@@ -50,9 +50,7 @@ def parse_args(
subsub = add_subparser(cmdname, toolname, subsubs)
if cmdname == "discover":
subsub.add_argument("--simple", action="store_true")
- subsub.add_argument(
- "--no-hide-stdio", dest="hidestdio", action="store_false"
- )
+ subsub.add_argument("--no-hide-stdio", dest="hidestdio", action="store_false")
subsub.add_argument("--pretty", action="store_true")
# Parse the args!
diff --git a/extensions/positron-python/pythonFiles/testing_tools/adapter/discovery.py b/extensions/positron-python/python_files/testing_tools/adapter/discovery.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/testing_tools/adapter/discovery.py
rename to extensions/positron-python/python_files/testing_tools/adapter/discovery.py
diff --git a/extensions/positron-python/pythonFiles/testing_tools/adapter/errors.py b/extensions/positron-python/python_files/testing_tools/adapter/errors.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/testing_tools/adapter/errors.py
rename to extensions/positron-python/python_files/testing_tools/adapter/errors.py
diff --git a/extensions/positron-python/pythonFiles/testing_tools/adapter/info.py b/extensions/positron-python/python_files/testing_tools/adapter/info.py
similarity index 97%
rename from extensions/positron-python/pythonFiles/testing_tools/adapter/info.py
rename to extensions/positron-python/python_files/testing_tools/adapter/info.py
index d518a29dd97..8e5d0442ce1 100644
--- a/extensions/positron-python/pythonFiles/testing_tools/adapter/info.py
+++ b/extensions/positron-python/python_files/testing_tools/adapter/info.py
@@ -61,9 +61,7 @@ def __init__(self, *args, **kwargs):
raise TypeError("missing relpath")
-class SingleTestInfo(
- namedtuple("TestInfo", "id name path source markers parentid kind")
-):
+class SingleTestInfo(namedtuple("TestInfo", "id name path source markers parentid kind")):
"""Info for a single test."""
MARKERS = ("skip", "skip-if", "expected-failure")
diff --git a/extensions/positron-python/pythonFiles/testing_tools/adapter/pytest/__init__.py b/extensions/positron-python/python_files/testing_tools/adapter/pytest/__init__.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/testing_tools/adapter/pytest/__init__.py
rename to extensions/positron-python/python_files/testing_tools/adapter/pytest/__init__.py
diff --git a/extensions/positron-python/pythonFiles/testing_tools/adapter/pytest/_cli.py b/extensions/positron-python/python_files/testing_tools/adapter/pytest/_cli.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/testing_tools/adapter/pytest/_cli.py
rename to extensions/positron-python/python_files/testing_tools/adapter/pytest/_cli.py
diff --git a/extensions/positron-python/pythonFiles/testing_tools/adapter/pytest/_discovery.py b/extensions/positron-python/python_files/testing_tools/adapter/pytest/_discovery.py
similarity index 99%
rename from extensions/positron-python/pythonFiles/testing_tools/adapter/pytest/_discovery.py
rename to extensions/positron-python/python_files/testing_tools/adapter/pytest/_discovery.py
index 4b852ecf81c..bbe5ae9856c 100644
--- a/extensions/positron-python/pythonFiles/testing_tools/adapter/pytest/_discovery.py
+++ b/extensions/positron-python/python_files/testing_tools/adapter/pytest/_discovery.py
@@ -17,7 +17,7 @@ def discover(
# *,
_pytest_main=pytest.main,
_plugin=None,
- **_ignored
+ **_ignored,
):
"""Return the results of test discovery."""
if _plugin is None:
diff --git a/extensions/positron-python/pythonFiles/testing_tools/adapter/pytest/_pytest_item.py b/extensions/positron-python/python_files/testing_tools/adapter/pytest/_pytest_item.py
similarity index 99%
rename from extensions/positron-python/pythonFiles/testing_tools/adapter/pytest/_pytest_item.py
rename to extensions/positron-python/python_files/testing_tools/adapter/pytest/_pytest_item.py
index ccfe1412231..724b71a1ac4 100644
--- a/extensions/positron-python/pythonFiles/testing_tools/adapter/pytest/_pytest_item.py
+++ b/extensions/positron-python/python_files/testing_tools/adapter/pytest/_pytest_item.py
@@ -170,9 +170,7 @@ def parse_item(
parents = [(parentid, item.originalname, kind)] + parents
name = parameterized[1:-1] or ""
else:
- (nodeid, parents, fileid, testfunc, parameterized) = _parse_node_id(
- item.nodeid, kind
- )
+ (nodeid, parents, fileid, testfunc, parameterized) = _parse_node_id(item.nodeid, kind)
name = item.name
# Note: testfunc does not necessarily match item.function.__name__.
diff --git a/extensions/positron-python/pythonFiles/testing_tools/adapter/report.py b/extensions/positron-python/python_files/testing_tools/adapter/report.py
similarity index 99%
rename from extensions/positron-python/pythonFiles/testing_tools/adapter/report.py
rename to extensions/positron-python/python_files/testing_tools/adapter/report.py
index bacdef7b9a0..1ad02fe7bcd 100644
--- a/extensions/positron-python/pythonFiles/testing_tools/adapter/report.py
+++ b/extensions/positron-python/python_files/testing_tools/adapter/report.py
@@ -13,7 +13,7 @@ def report_discovered(
pretty=False,
simple=False,
_send=print,
- **_ignored
+ **_ignored,
):
"""Serialize the discovered tests and write to stdout."""
if simple:
diff --git a/extensions/positron-python/pythonFiles/testing_tools/adapter/util.py b/extensions/positron-python/python_files/testing_tools/adapter/util.py
similarity index 94%
rename from extensions/positron-python/pythonFiles/testing_tools/adapter/util.py
rename to extensions/positron-python/python_files/testing_tools/adapter/util.py
index c7a178311b8..9f3089fb29d 100644
--- a/extensions/positron-python/pythonFiles/testing_tools/adapter/util.py
+++ b/extensions/positron-python/python_files/testing_tools/adapter/util.py
@@ -46,12 +46,6 @@ def group_attr_names(attrnames):
return grouped
-if sys.version_info < (3,):
- _str_to_lower = lambda val: val.decode().lower()
-else:
- _str_to_lower = str.lower
-
-
#############################
# file paths
@@ -134,7 +128,7 @@ def fix_fileid(
normalize=False,
strictpathsep=None,
_pathsep=PATH_SEP,
- **kwargs
+ **kwargs,
):
"""Return a pathsep-separated file ID ("./"-prefixed) for the given value.
@@ -156,7 +150,7 @@ def fix_fileid(
rootdir,
_pathsep=_pathsep,
# ...
- **kwargs
+ **kwargs,
)
if relpath: # Note that we treat "" here as an absolute path.
_fileid = "./" + relpath
@@ -164,7 +158,7 @@ def fix_fileid(
if normalize:
if strictpathsep:
raise ValueError("cannot normalize *and* keep strict path separator")
- _fileid = _str_to_lower(_fileid)
+ _fileid = _fileid.lower()
elif strictpathsep:
# We do not use _normcase since we want to preserve capitalization.
_fileid = _fileid.replace("/", _pathsep)
@@ -224,12 +218,6 @@ def _replace_stderr(target):
sys.stderr = orig
-if sys.version_info < (3,):
- _coerce_unicode = lambda s: unicode(s)
-else:
- _coerce_unicode = lambda s: s
-
-
@contextlib.contextmanager
def _temp_io():
sio = StringIO()
@@ -239,7 +227,7 @@ def _temp_io():
finally:
tmp.seek(0)
buff = tmp.read()
- sio.write(_coerce_unicode(buff))
+ sio.write(buff)
@contextlib.contextmanager
diff --git a/extensions/positron-python/pythonFiles/testing_tools/process_json_util.py b/extensions/positron-python/python_files/testing_tools/process_json_util.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/testing_tools/process_json_util.py
rename to extensions/positron-python/python_files/testing_tools/process_json_util.py
diff --git a/extensions/positron-python/pythonFiles/testing_tools/run_adapter.py b/extensions/positron-python/python_files/testing_tools/run_adapter.py
similarity index 81%
rename from extensions/positron-python/pythonFiles/testing_tools/run_adapter.py
rename to extensions/positron-python/python_files/testing_tools/run_adapter.py
index 1eeef194f8f..8af4e49dd31 100644
--- a/extensions/positron-python/pythonFiles/testing_tools/run_adapter.py
+++ b/extensions/positron-python/python_files/testing_tools/run_adapter.py
@@ -7,8 +7,8 @@
sys.path.insert(
1,
- os.path.dirname( # pythonFiles
- os.path.dirname( # pythonFiles/testing_tools
+ os.path.dirname( # python_files
+ os.path.dirname( # python_files/testing_tools
os.path.abspath(__file__) # this file
)
),
diff --git a/extensions/positron-python/pythonFiles/testing_tools/socket_manager.py b/extensions/positron-python/python_files/testing_tools/socket_manager.py
similarity index 90%
rename from extensions/positron-python/pythonFiles/testing_tools/socket_manager.py
rename to extensions/positron-python/python_files/testing_tools/socket_manager.py
index b2afbf0e5a1..3392a4d54e0 100644
--- a/extensions/positron-python/pythonFiles/testing_tools/socket_manager.py
+++ b/extensions/positron-python/python_files/testing_tools/socket_manager.py
@@ -29,9 +29,7 @@ def __exit__(self, *_):
self.close()
def connect(self):
- self.socket = socket.socket(
- socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP
- )
+ self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP)
if sys.platform == "win32":
addr_use = socket.SO_EXCLUSIVEADDRUSE
else:
diff --git a/extensions/positron-python/pythonFiles/testing_tools/unittest_discovery.py b/extensions/positron-python/python_files/testing_tools/unittest_discovery.py
similarity index 90%
rename from extensions/positron-python/pythonFiles/testing_tools/unittest_discovery.py
rename to extensions/positron-python/python_files/testing_tools/unittest_discovery.py
index 2988092c387..5d5e9bcc660 100644
--- a/extensions/positron-python/pythonFiles/testing_tools/unittest_discovery.py
+++ b/extensions/positron-python/python_files/testing_tools/unittest_discovery.py
@@ -1,3 +1,4 @@
+import contextlib
import inspect
import os
import sys
@@ -13,13 +14,13 @@
def get_sourceline(obj):
try:
s, n = inspect.getsourcelines(obj)
- except:
+ except Exception:
try:
# this handles `tornado` case we need a better
# way to get to the wrapped function.
- # This is a temporary solution
+ # XXX This is a temporary solution
s, n = inspect.getsourcelines(obj.orig_method)
- except:
+ except Exception:
return "*"
for i, v in enumerate(s):
@@ -50,16 +51,14 @@ def generate_test_cases(suite):
loader_errors.append(s._exception)
else:
print(testId.replace(".", ":") + ":" + get_sourceline(tm))
-except:
+except Exception:
print("=== exception start ===")
traceback.print_exc()
print("=== exception end ===")
for error in loader_errors:
- try:
+ with contextlib.suppress(Exception):
print("=== exception start ===")
print(error.msg)
print("=== exception end ===")
- except:
- pass
diff --git a/extensions/positron-python/pythonFiles/testlauncher.py b/extensions/positron-python/python_files/testlauncher.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/testlauncher.py
rename to extensions/positron-python/python_files/testlauncher.py
diff --git a/extensions/positron-python/pythonFiles/tests/__init__.py b/extensions/positron-python/python_files/tests/__init__.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/__init__.py
rename to extensions/positron-python/python_files/tests/__init__.py
diff --git a/extensions/positron-python/pythonFiles/tests/__main__.py b/extensions/positron-python/python_files/tests/__main__.py
similarity index 92%
rename from extensions/positron-python/pythonFiles/tests/__main__.py
rename to extensions/positron-python/python_files/tests/__main__.py
index 901385d41d8..347222bd85d 100644
--- a/extensions/positron-python/pythonFiles/tests/__main__.py
+++ b/extensions/positron-python/python_files/tests/__main__.py
@@ -12,9 +12,7 @@
def parse_args():
parser = argparse.ArgumentParser()
# To mark a test as functional: (decorator) @pytest.mark.functional
- parser.add_argument(
- "--functional", dest="markers", action="append_const", const="functional"
- )
+ parser.add_argument("--functional", dest="markers", action="append_const", const="functional")
parser.add_argument(
"--no-functional", dest="markers", action="append_const", const="not functional"
)
diff --git a/extensions/positron-python/pythonFiles/tests/debug_adapter/__init__.py b/extensions/positron-python/python_files/tests/debug_adapter/__init__.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/debug_adapter/__init__.py
rename to extensions/positron-python/python_files/tests/debug_adapter/__init__.py
diff --git a/extensions/positron-python/pythonFiles/tests/debug_adapter/test_install_debugpy.py b/extensions/positron-python/python_files/tests/debug_adapter/test_install_debugpy.py
similarity index 64%
rename from extensions/positron-python/pythonFiles/tests/debug_adapter/test_install_debugpy.py
rename to extensions/positron-python/python_files/tests/debug_adapter/test_install_debugpy.py
index 8e2ed33a1da..f72e1089aaa 100644
--- a/extensions/positron-python/pythonFiles/tests/debug_adapter/test_install_debugpy.py
+++ b/extensions/positron-python/python_files/tests/debug_adapter/test_install_debugpy.py
@@ -1,7 +1,4 @@
import os
-import pytest
-import subprocess
-import sys
def _check_binaries(dir_path):
@@ -21,12 +18,8 @@ def test_install_debugpy(tmpdir):
import install_debugpy
install_debugpy.main(str(tmpdir))
- dir_path = os.path.join(
- str(tmpdir), "debugpy", "_vendored", "pydevd", "_pydevd_bundle"
- )
+ dir_path = os.path.join(str(tmpdir), "debugpy", "_vendored", "pydevd", "_pydevd_bundle")
_check_binaries(dir_path)
- dir_path = os.path.join(
- str(tmpdir), "debugpy", "_vendored", "pydevd", "_pydevd_frame_eval"
- )
+ dir_path = os.path.join(str(tmpdir), "debugpy", "_vendored", "pydevd", "_pydevd_frame_eval")
_check_binaries(dir_path)
diff --git a/extensions/positron-python/pythonFiles/tests/pytestadapter/.data/dual_level_nested_folder/nested_folder_one/test_bottom_folder.py b/extensions/positron-python/python_files/tests/pytestadapter/.data/dual_level_nested_folder/nested_folder_one/test_bottom_folder.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/pytestadapter/.data/dual_level_nested_folder/nested_folder_one/test_bottom_folder.py
rename to extensions/positron-python/python_files/tests/pytestadapter/.data/dual_level_nested_folder/nested_folder_one/test_bottom_folder.py
diff --git a/extensions/positron-python/pythonFiles/tests/pytestadapter/.data/dual_level_nested_folder/test_top_folder.py b/extensions/positron-python/python_files/tests/pytestadapter/.data/dual_level_nested_folder/test_top_folder.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/pytestadapter/.data/dual_level_nested_folder/test_top_folder.py
rename to extensions/positron-python/python_files/tests/pytestadapter/.data/dual_level_nested_folder/test_top_folder.py
diff --git a/extensions/positron-python/pythonFiles/tests/pytestadapter/.data/empty_discovery.py b/extensions/positron-python/python_files/tests/pytestadapter/.data/empty_discovery.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/pytestadapter/.data/empty_discovery.py
rename to extensions/positron-python/python_files/tests/pytestadapter/.data/empty_discovery.py
diff --git a/extensions/positron-python/pythonFiles/tests/pytestadapter/.data/error_parametrize_discovery.py b/extensions/positron-python/python_files/tests/pytestadapter/.data/error_parametrize_discovery.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/pytestadapter/.data/error_parametrize_discovery.py
rename to extensions/positron-python/python_files/tests/pytestadapter/.data/error_parametrize_discovery.py
diff --git a/extensions/positron-python/pythonFiles/tests/pytestadapter/.data/error_pytest_import.txt b/extensions/positron-python/python_files/tests/pytestadapter/.data/error_pytest_import.txt
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/pytestadapter/.data/error_pytest_import.txt
rename to extensions/positron-python/python_files/tests/pytestadapter/.data/error_pytest_import.txt
diff --git a/extensions/positron-python/pythonFiles/tests/pytestadapter/.data/error_raise_exception.py b/extensions/positron-python/python_files/tests/pytestadapter/.data/error_raise_exception.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/pytestadapter/.data/error_raise_exception.py
rename to extensions/positron-python/python_files/tests/pytestadapter/.data/error_raise_exception.py
diff --git a/extensions/positron-python/pythonFiles/tests/pytestadapter/.data/error_syntax_discovery.txt b/extensions/positron-python/python_files/tests/pytestadapter/.data/error_syntax_discovery.txt
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/pytestadapter/.data/error_syntax_discovery.txt
rename to extensions/positron-python/python_files/tests/pytestadapter/.data/error_syntax_discovery.txt
diff --git a/extensions/positron-python/pythonFiles/tests/pytestadapter/.data/folder_a/folder_b/folder_a/test_nest.py b/extensions/positron-python/python_files/tests/pytestadapter/.data/folder_a/folder_b/folder_a/test_nest.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/pytestadapter/.data/folder_a/folder_b/folder_a/test_nest.py
rename to extensions/positron-python/python_files/tests/pytestadapter/.data/folder_a/folder_b/folder_a/test_nest.py
diff --git a/extensions/positron-python/pythonFiles/tests/pytestadapter/.data/param_same_name/test_param1.py b/extensions/positron-python/python_files/tests/pytestadapter/.data/param_same_name/test_param1.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/pytestadapter/.data/param_same_name/test_param1.py
rename to extensions/positron-python/python_files/tests/pytestadapter/.data/param_same_name/test_param1.py
diff --git a/extensions/positron-python/pythonFiles/tests/pytestadapter/.data/param_same_name/test_param2.py b/extensions/positron-python/python_files/tests/pytestadapter/.data/param_same_name/test_param2.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/pytestadapter/.data/param_same_name/test_param2.py
rename to extensions/positron-python/python_files/tests/pytestadapter/.data/param_same_name/test_param2.py
diff --git a/extensions/positron-python/pythonFiles/tests/pytestadapter/.data/parametrize_tests.py b/extensions/positron-python/python_files/tests/pytestadapter/.data/parametrize_tests.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/pytestadapter/.data/parametrize_tests.py
rename to extensions/positron-python/python_files/tests/pytestadapter/.data/parametrize_tests.py
diff --git a/extensions/positron-python/pythonFiles/tests/pytestadapter/.data/root/tests/pytest.ini b/extensions/positron-python/python_files/tests/pytestadapter/.data/root/tests/pytest.ini
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/pytestadapter/.data/root/tests/pytest.ini
rename to extensions/positron-python/python_files/tests/pytestadapter/.data/root/tests/pytest.ini
diff --git a/extensions/positron-python/pythonFiles/tests/pytestadapter/.data/root/tests/test_a.py b/extensions/positron-python/python_files/tests/pytestadapter/.data/root/tests/test_a.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/pytestadapter/.data/root/tests/test_a.py
rename to extensions/positron-python/python_files/tests/pytestadapter/.data/root/tests/test_a.py
diff --git a/extensions/positron-python/pythonFiles/tests/pytestadapter/.data/root/tests/test_b.py b/extensions/positron-python/python_files/tests/pytestadapter/.data/root/tests/test_b.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/pytestadapter/.data/root/tests/test_b.py
rename to extensions/positron-python/python_files/tests/pytestadapter/.data/root/tests/test_b.py
diff --git a/extensions/positron-python/pythonFiles/tests/pytestadapter/.data/simple_pytest.py b/extensions/positron-python/python_files/tests/pytestadapter/.data/simple_pytest.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/pytestadapter/.data/simple_pytest.py
rename to extensions/positron-python/python_files/tests/pytestadapter/.data/simple_pytest.py
diff --git a/extensions/positron-python/pythonFiles/tests/pytestadapter/.data/skip_tests.py b/extensions/positron-python/python_files/tests/pytestadapter/.data/skip_tests.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/pytestadapter/.data/skip_tests.py
rename to extensions/positron-python/python_files/tests/pytestadapter/.data/skip_tests.py
diff --git a/extensions/positron-python/pythonFiles/tests/pytestadapter/.data/test_env_vars.py b/extensions/positron-python/python_files/tests/pytestadapter/.data/test_env_vars.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/pytestadapter/.data/test_env_vars.py
rename to extensions/positron-python/python_files/tests/pytestadapter/.data/test_env_vars.py
diff --git a/extensions/positron-python/pythonFiles/tests/pytestadapter/.data/test_logging.py b/extensions/positron-python/python_files/tests/pytestadapter/.data/test_logging.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/pytestadapter/.data/test_logging.py
rename to extensions/positron-python/python_files/tests/pytestadapter/.data/test_logging.py
diff --git a/extensions/positron-python/pythonFiles/tests/pytestadapter/.data/test_multi_class_nest.py b/extensions/positron-python/python_files/tests/pytestadapter/.data/test_multi_class_nest.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/pytestadapter/.data/test_multi_class_nest.py
rename to extensions/positron-python/python_files/tests/pytestadapter/.data/test_multi_class_nest.py
diff --git a/extensions/positron-python/pythonFiles/tests/pytestadapter/.data/text_docstring.txt b/extensions/positron-python/python_files/tests/pytestadapter/.data/text_docstring.txt
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/pytestadapter/.data/text_docstring.txt
rename to extensions/positron-python/python_files/tests/pytestadapter/.data/text_docstring.txt
diff --git a/extensions/positron-python/pythonFiles/tests/pytestadapter/.data/unittest_folder/test_add.py b/extensions/positron-python/python_files/tests/pytestadapter/.data/unittest_folder/test_add.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/pytestadapter/.data/unittest_folder/test_add.py
rename to extensions/positron-python/python_files/tests/pytestadapter/.data/unittest_folder/test_add.py
diff --git a/extensions/positron-python/pythonFiles/tests/pytestadapter/.data/unittest_folder/test_subtract.py b/extensions/positron-python/python_files/tests/pytestadapter/.data/unittest_folder/test_subtract.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/pytestadapter/.data/unittest_folder/test_subtract.py
rename to extensions/positron-python/python_files/tests/pytestadapter/.data/unittest_folder/test_subtract.py
diff --git a/extensions/positron-python/pythonFiles/tests/pytestadapter/.data/unittest_pytest_same_file.py b/extensions/positron-python/python_files/tests/pytestadapter/.data/unittest_pytest_same_file.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/pytestadapter/.data/unittest_pytest_same_file.py
rename to extensions/positron-python/python_files/tests/pytestadapter/.data/unittest_pytest_same_file.py
diff --git a/extensions/positron-python/pythonFiles/tests/pytestadapter/.data/unittest_skiptest_file_level.py b/extensions/positron-python/python_files/tests/pytestadapter/.data/unittest_skiptest_file_level.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/pytestadapter/.data/unittest_skiptest_file_level.py
rename to extensions/positron-python/python_files/tests/pytestadapter/.data/unittest_skiptest_file_level.py
diff --git a/extensions/positron-python/pythonFiles/tests/pytestadapter/__init__.py b/extensions/positron-python/python_files/tests/pytestadapter/__init__.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/pytestadapter/__init__.py
rename to extensions/positron-python/python_files/tests/pytestadapter/__init__.py
diff --git a/extensions/positron-python/pythonFiles/tests/pytestadapter/expected_discovery_test_output.py b/extensions/positron-python/python_files/tests/pytestadapter/expected_discovery_test_output.py
similarity index 97%
rename from extensions/positron-python/pythonFiles/tests/pytestadapter/expected_discovery_test_output.py
rename to extensions/positron-python/python_files/tests/pytestadapter/expected_discovery_test_output.py
index 7fbb0c5c43e..ba42fcc68fe 100644
--- a/extensions/positron-python/pythonFiles/tests/pytestadapter/expected_discovery_test_output.py
+++ b/extensions/positron-python/python_files/tests/pytestadapter/expected_discovery_test_output.py
@@ -322,19 +322,12 @@
# └── test_bottom_function_t
# └── test_bottom_function_f
dual_level_nested_folder_path = TEST_DATA_PATH / "dual_level_nested_folder"
-test_top_folder_path = (
- TEST_DATA_PATH / "dual_level_nested_folder" / "test_top_folder.py"
-)
+test_top_folder_path = TEST_DATA_PATH / "dual_level_nested_folder" / "test_top_folder.py"
-test_nested_folder_one_path = (
- TEST_DATA_PATH / "dual_level_nested_folder" / "nested_folder_one"
-)
+test_nested_folder_one_path = TEST_DATA_PATH / "dual_level_nested_folder" / "nested_folder_one"
test_bottom_folder_path = (
- TEST_DATA_PATH
- / "dual_level_nested_folder"
- / "nested_folder_one"
- / "test_bottom_folder.py"
+ TEST_DATA_PATH / "dual_level_nested_folder" / "nested_folder_one" / "test_bottom_folder.py"
)
@@ -851,12 +844,8 @@
os.path.join(tests_path, "test_a.py"),
),
"type_": "test",
- "id_": get_absolute_test_id(
- "tests/test_a.py::test_a_function", tests_a_path
- ),
- "runID": get_absolute_test_id(
- "tests/test_a.py::test_a_function", tests_a_path
- ),
+ "id_": get_absolute_test_id("tests/test_a.py::test_a_function", tests_a_path),
+ "runID": get_absolute_test_id("tests/test_a.py::test_a_function", tests_a_path),
}
],
},
@@ -874,12 +863,8 @@
os.path.join(tests_path, "test_b.py"),
),
"type_": "test",
- "id_": get_absolute_test_id(
- "tests/test_b.py::test_b_function", tests_b_path
- ),
- "runID": get_absolute_test_id(
- "tests/test_b.py::test_b_function", tests_b_path
- ),
+ "id_": get_absolute_test_id("tests/test_b.py::test_b_function", tests_b_path),
+ "runID": get_absolute_test_id("tests/test_b.py::test_b_function", tests_b_path),
}
],
},
@@ -996,12 +981,8 @@
}
SYMLINK_FOLDER_PATH = TEST_DATA_PATH / "symlink_folder"
SYMLINK_FOLDER_PATH_TESTS = TEST_DATA_PATH / "symlink_folder" / "tests"
-SYMLINK_FOLDER_PATH_TESTS_TEST_A = (
- TEST_DATA_PATH / "symlink_folder" / "tests" / "test_a.py"
-)
-SYMLINK_FOLDER_PATH_TESTS_TEST_B = (
- TEST_DATA_PATH / "symlink_folder" / "tests" / "test_b.py"
-)
+SYMLINK_FOLDER_PATH_TESTS_TEST_A = TEST_DATA_PATH / "symlink_folder" / "tests" / "test_a.py"
+SYMLINK_FOLDER_PATH_TESTS_TEST_B = TEST_DATA_PATH / "symlink_folder" / "tests" / "test_b.py"
symlink_expected_discovery_output = {
"name": "symlink_folder",
diff --git a/extensions/positron-python/pythonFiles/tests/pytestadapter/expected_execution_test_output.py b/extensions/positron-python/python_files/tests/pytestadapter/expected_execution_test_output.py
similarity index 86%
rename from extensions/positron-python/pythonFiles/tests/pytestadapter/expected_execution_test_output.py
rename to extensions/positron-python/python_files/tests/pytestadapter/expected_execution_test_output.py
index db4e493c3da..34693d36b12 100644
--- a/extensions/positron-python/pythonFiles/tests/pytestadapter/expected_execution_test_output.py
+++ b/extensions/positron-python/python_files/tests/pytestadapter/expected_execution_test_output.py
@@ -20,9 +20,7 @@
test_add_path = TEST_DATA_PATH / "unittest_folder" / "test_add.py"
test_subtract_path = TEST_DATA_PATH / "unittest_folder" / "test_subtract.py"
uf_execution_expected_output = {
- get_absolute_test_id(
- f"{TEST_ADD_FUNCTION}test_add_negative_numbers", test_add_path
- ): {
+ get_absolute_test_id(f"{TEST_ADD_FUNCTION}test_add_negative_numbers", test_add_path): {
"test": get_absolute_test_id(
f"{TEST_ADD_FUNCTION}test_add_negative_numbers", test_add_path
),
@@ -31,9 +29,7 @@
"traceback": None,
"subtest": None,
},
- get_absolute_test_id(
- f"{TEST_ADD_FUNCTION}test_add_positive_numbers", test_add_path
- ): {
+ get_absolute_test_id(f"{TEST_ADD_FUNCTION}test_add_positive_numbers", test_add_path): {
"test": get_absolute_test_id(
f"{TEST_ADD_FUNCTION}test_add_positive_numbers", test_add_path
),
@@ -80,9 +76,7 @@
test_add_path = TEST_DATA_PATH / "unittest_folder" / "test_add.py"
uf_single_file_expected_output = {
- get_absolute_test_id(
- f"{TEST_ADD_FUNCTION}test_add_negative_numbers", test_add_path
- ): {
+ get_absolute_test_id(f"{TEST_ADD_FUNCTION}test_add_negative_numbers", test_add_path): {
"test": get_absolute_test_id(
f"{TEST_ADD_FUNCTION}test_add_negative_numbers", test_add_path
),
@@ -91,9 +85,7 @@
"traceback": None,
"subtest": None,
},
- get_absolute_test_id(
- f"{TEST_ADD_FUNCTION}test_add_positive_numbers", test_add_path
- ): {
+ get_absolute_test_id(f"{TEST_ADD_FUNCTION}test_add_positive_numbers", test_add_path): {
"test": get_absolute_test_id(
f"{TEST_ADD_FUNCTION}test_add_positive_numbers", test_add_path
),
@@ -111,9 +103,7 @@
# │ └── TestAddFunction
# │ └── test_add_positive_numbers: success
uf_single_method_execution_expected_output = {
- get_absolute_test_id(
- f"{TEST_ADD_FUNCTION}test_add_positive_numbers", test_add_path
- ): {
+ get_absolute_test_id(f"{TEST_ADD_FUNCTION}test_add_positive_numbers", test_add_path): {
"test": get_absolute_test_id(
f"{TEST_ADD_FUNCTION}test_add_positive_numbers", test_add_path
),
@@ -149,9 +139,7 @@
"traceback": None,
"subtest": None,
},
- get_absolute_test_id(
- f"{TEST_ADD_FUNCTION}test_add_positive_numbers", test_add_path
- ): {
+ get_absolute_test_id(f"{TEST_ADD_FUNCTION}test_add_positive_numbers", test_add_path): {
"test": get_absolute_test_id(
f"{TEST_ADD_FUNCTION}test_add_positive_numbers", test_add_path
),
@@ -252,35 +240,27 @@
"subtest": None,
},
get_absolute_test_id("skip_tests.py::test_another_thing", skip_tests_path): {
- "test": get_absolute_test_id(
- "skip_tests.py::test_another_thing", skip_tests_path
- ),
+ "test": get_absolute_test_id("skip_tests.py::test_another_thing", skip_tests_path),
"outcome": "skipped",
"message": None,
"traceback": None,
"subtest": None,
},
get_absolute_test_id("skip_tests.py::test_decorator_thing", skip_tests_path): {
- "test": get_absolute_test_id(
- "skip_tests.py::test_decorator_thing", skip_tests_path
- ),
+ "test": get_absolute_test_id("skip_tests.py::test_decorator_thing", skip_tests_path),
"outcome": "skipped",
"message": None,
"traceback": None,
"subtest": None,
},
get_absolute_test_id("skip_tests.py::test_decorator_thing_2", skip_tests_path): {
- "test": get_absolute_test_id(
- "skip_tests.py::test_decorator_thing_2", skip_tests_path
- ),
+ "test": get_absolute_test_id("skip_tests.py::test_decorator_thing_2", skip_tests_path),
"outcome": "skipped",
"message": None,
"traceback": None,
"subtest": None,
},
- get_absolute_test_id(
- "skip_tests.py::TestClass::test_class_function_a", skip_tests_path
- ): {
+ get_absolute_test_id("skip_tests.py::TestClass::test_class_function_a", skip_tests_path): {
"test": get_absolute_test_id(
"skip_tests.py::TestClass::test_class_function_a", skip_tests_path
),
@@ -289,9 +269,7 @@
"traceback": None,
"subtest": None,
},
- get_absolute_test_id(
- "skip_tests.py::TestClass::test_class_function_b", skip_tests_path
- ): {
+ get_absolute_test_id("skip_tests.py::TestClass::test_class_function_b", skip_tests_path): {
"test": get_absolute_test_id(
"skip_tests.py::TestClass::test_class_function_b", skip_tests_path
),
@@ -316,10 +294,7 @@
TEST_DATA_PATH / "dual_level_nested_folder" / "test_top_folder.py"
)
dual_level_nested_folder_bottom_path = (
- TEST_DATA_PATH
- / "dual_level_nested_folder"
- / "nested_folder_one"
- / "test_bottom_folder.py"
+ TEST_DATA_PATH / "dual_level_nested_folder" / "nested_folder_one" / "test_bottom_folder.py"
)
dual_level_nested_folder_execution_expected_output = {
get_absolute_test_id(
@@ -379,9 +354,7 @@
# └── test_nest.py
# └── test_function: success
-nested_folder_path = (
- TEST_DATA_PATH / "folder_a" / "folder_b" / "folder_a" / "test_nest.py"
-)
+nested_folder_path = TEST_DATA_PATH / "folder_a" / "folder_b" / "folder_a" / "test_nest.py"
double_nested_folder_expected_execution_output = {
get_absolute_test_id(
"folder_a/folder_b/folder_a/test_nest.py::test_function", nested_folder_path
@@ -403,9 +376,7 @@
parametrize_tests_path = TEST_DATA_PATH / "parametrize_tests.py"
parametrize_tests_expected_execution_output = {
- get_absolute_test_id(
- "parametrize_tests.py::test_adding[3+5-8]", parametrize_tests_path
- ): {
+ get_absolute_test_id("parametrize_tests.py::test_adding[3+5-8]", parametrize_tests_path): {
"test": get_absolute_test_id(
"parametrize_tests.py::test_adding[3+5-8]", parametrize_tests_path
),
@@ -414,9 +385,7 @@
"traceback": None,
"subtest": None,
},
- get_absolute_test_id(
- "parametrize_tests.py::test_adding[2+4-6]", parametrize_tests_path
- ): {
+ get_absolute_test_id("parametrize_tests.py::test_adding[2+4-6]", parametrize_tests_path): {
"test": get_absolute_test_id(
"parametrize_tests.py::test_adding[2+4-6]", parametrize_tests_path
),
@@ -425,9 +394,7 @@
"traceback": None,
"subtest": None,
},
- get_absolute_test_id(
- "parametrize_tests.py::test_adding[6+9-16]", parametrize_tests_path
- ): {
+ get_absolute_test_id("parametrize_tests.py::test_adding[6+9-16]", parametrize_tests_path): {
"test": get_absolute_test_id(
"parametrize_tests.py::test_adding[6+9-16]", parametrize_tests_path
),
@@ -442,9 +409,7 @@
# └── parametrize_tests.py
# └── test_adding[3+5-8]: success
single_parametrize_tests_expected_execution_output = {
- get_absolute_test_id(
- "parametrize_tests.py::test_adding[3+5-8]", parametrize_tests_path
- ): {
+ get_absolute_test_id("parametrize_tests.py::test_adding[3+5-8]", parametrize_tests_path): {
"test": get_absolute_test_id(
"parametrize_tests.py::test_adding[3+5-8]", parametrize_tests_path
),
@@ -461,9 +426,7 @@
doc_test_path = TEST_DATA_PATH / "text_docstring.txt"
doctest_pytest_expected_execution_output = {
get_absolute_test_id("text_docstring.txt::text_docstring.txt", doc_test_path): {
- "test": get_absolute_test_id(
- "text_docstring.txt::text_docstring.txt", doc_test_path
- ),
+ "test": get_absolute_test_id("text_docstring.txt::text_docstring.txt", doc_test_path),
"outcome": "success",
"message": None,
"traceback": None,
@@ -477,10 +440,7 @@
TEST_DATA_PATH / "dual_level_nested_folder" / "test_top_folder.py"
)
dual_level_nested_folder_bottom_path = (
- TEST_DATA_PATH
- / "dual_level_nested_folder"
- / "nested_folder_one"
- / "test_bottom_folder.py"
+ TEST_DATA_PATH / "dual_level_nested_folder" / "nested_folder_one" / "test_bottom_folder.py"
)
unittest_folder_add_path = TEST_DATA_PATH / "unittest_folder" / "test_add.py"
unittest_folder_subtract_path = TEST_DATA_PATH / "unittest_folder" / "test_subtract.py"
@@ -494,26 +454,20 @@
"subtest": None,
},
get_absolute_test_id("test_top_function_t", dual_level_nested_folder_top_path): {
- "test": get_absolute_test_id(
- "test_top_function_t", dual_level_nested_folder_top_path
- ),
+ "test": get_absolute_test_id("test_top_function_t", dual_level_nested_folder_top_path),
"outcome": "success",
"message": None,
"traceback": None,
"subtest": None,
},
get_absolute_test_id("test_top_function_f", dual_level_nested_folder_top_path): {
- "test": get_absolute_test_id(
- "test_top_function_f", dual_level_nested_folder_top_path
- ),
+ "test": get_absolute_test_id("test_top_function_f", dual_level_nested_folder_top_path),
"outcome": "failure",
"message": "ERROR MESSAGE",
"traceback": None,
"subtest": None,
},
- get_absolute_test_id(
- "test_bottom_function_t", dual_level_nested_folder_bottom_path
- ): {
+ get_absolute_test_id("test_bottom_function_t", dual_level_nested_folder_bottom_path): {
"test": get_absolute_test_id(
"test_bottom_function_t", dual_level_nested_folder_bottom_path
),
@@ -522,9 +476,7 @@
"traceback": None,
"subtest": None,
},
- get_absolute_test_id(
- "test_bottom_function_f", dual_level_nested_folder_bottom_path
- ): {
+ get_absolute_test_id("test_bottom_function_f", dual_level_nested_folder_bottom_path): {
"test": get_absolute_test_id(
"test_bottom_function_f", dual_level_nested_folder_bottom_path
),
@@ -533,9 +485,7 @@
"traceback": None,
"subtest": None,
},
- get_absolute_test_id(
- "TestAddFunction::test_add_negative_numbers", unittest_folder_add_path
- ): {
+ get_absolute_test_id("TestAddFunction::test_add_negative_numbers", unittest_folder_add_path): {
"test": get_absolute_test_id(
"TestAddFunction::test_add_negative_numbers", unittest_folder_add_path
),
@@ -544,9 +494,7 @@
"traceback": None,
"subtest": None,
},
- get_absolute_test_id(
- "TestAddFunction::test_add_positive_numbers", unittest_folder_add_path
- ): {
+ get_absolute_test_id("TestAddFunction::test_add_positive_numbers", unittest_folder_add_path): {
"test": get_absolute_test_id(
"TestAddFunction::test_add_positive_numbers", unittest_folder_add_path
),
@@ -606,18 +554,14 @@
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
- ),
+ "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
- ),
+ "test": get_absolute_test_id("test_logging.py::test_logging", test_logging_path),
"outcome": "success",
"message": None,
"traceback": None,
@@ -632,9 +576,7 @@
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
- ): {
+ 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
),
@@ -643,9 +585,7 @@
"traceback": None,
"subtest": None,
},
- get_absolute_test_id(
- "test_env_vars.py::test_check_env", test_safe_clear_env_vars_path
- ): {
+ 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
),
diff --git a/extensions/positron-python/pythonFiles/tests/pytestadapter/helpers.py b/extensions/positron-python/python_files/tests/pytestadapter/helpers.py
similarity index 89%
rename from extensions/positron-python/pythonFiles/tests/pytestadapter/helpers.py
rename to extensions/positron-python/python_files/tests/pytestadapter/helpers.py
index a3ed21cc553..dd69379a45b 100644
--- a/extensions/positron-python/pythonFiles/tests/pytestadapter/helpers.py
+++ b/extensions/positron-python/python_files/tests/pytestadapter/helpers.py
@@ -11,14 +11,10 @@
import sys
import threading
import uuid
-from typing import Any, Dict, List, Optional, Tuple
+from typing import Any, Dict, List, Optional, Tuple, TypedDict
-script_dir = pathlib.Path(__file__).parent.parent.parent
-sys.path.append(os.fspath(script_dir))
-sys.path.append(os.fspath(script_dir / "lib" / "python"))
TEST_DATA_PATH = pathlib.Path(__file__).parent / ".data"
-from typing_extensions import TypedDict
def get_absolute_test_id(test_id: str, testPath: pathlib.Path) -> str:
@@ -72,9 +68,7 @@ def create_server(
def _new_sock() -> socket.socket:
- sock: socket.socket = socket.socket(
- socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP
- )
+ sock: socket.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP)
options = [
("SOL_SOCKET", "SO_KEEPALIVE", 1),
("IPPROTO_TCP", "TCP_KEEPIDLE", 1),
@@ -92,9 +86,7 @@ def _new_sock() -> socket.socket:
CONTENT_LENGTH: str = "Content-Length:"
-Env_Dict = TypedDict(
- "Env_Dict", {"TEST_UUID": str, "TEST_PORT": str, "PYTHONPATH": str}
-)
+Env_Dict = TypedDict("Env_Dict", {"TEST_UUID": str, "TEST_PORT": str, "PYTHONPATH": str})
def process_rpc_message(data: str) -> Tuple[Dict[str, Any], str]:
@@ -137,9 +129,7 @@ def runner(args: List[str]) -> Optional[List[Dict[str, Any]]]:
return runner_with_cwd(args, TEST_DATA_PATH)
-def runner_with_cwd(
- args: List[str], path: pathlib.Path
-) -> Optional[List[Dict[str, Any]]]:
+def runner_with_cwd(args: List[str], path: pathlib.Path) -> Optional[List[Dict[str, Any]]]:
"""Run the pytest discovery and return the JSON data from the server."""
process_args: List[str] = [
sys.executable,
@@ -181,9 +171,7 @@ def runner_with_cwd(
return process_rpc_json(result[0]) if result else None
-def _listen_on_socket(
- listener: socket.socket, result: List[str], completed: threading.Event
-):
+def _listen_on_socket(listener: socket.socket, result: List[str], completed: threading.Event):
"""Listen on the socket for the JSON data from the server.
Created as a separate function for clarity in threading.
"""
@@ -205,9 +193,7 @@ def _listen_on_socket(
result.append("".join(all_data))
-def _run_test_code(
- proc_args: List[str], proc_env, proc_cwd: str, completed: threading.Event
-):
+def _run_test_code(proc_args: List[str], proc_env, proc_cwd: str, completed: threading.Event):
result = subprocess.run(proc_args, env=proc_env, cwd=proc_cwd)
completed.set()
return result
diff --git a/extensions/positron-python/pythonFiles/tests/pytestadapter/test_discovery.py b/extensions/positron-python/python_files/tests/pytestadapter/test_discovery.py
similarity index 79%
rename from extensions/positron-python/pythonFiles/tests/pytestadapter/test_discovery.py
rename to extensions/positron-python/python_files/tests/pytestadapter/test_discovery.py
index a1f4e4f266a..fb8a97a2df1 100644
--- a/extensions/positron-python/pythonFiles/tests/pytestadapter/test_discovery.py
+++ b/extensions/positron-python/python_files/tests/pytestadapter/test_discovery.py
@@ -2,20 +2,15 @@
# Licensed under the MIT License.
import json
import os
-import pathlib
import shutil
import sys
from typing import Any, Dict, List, Optional
import pytest
-script_dir = pathlib.Path(__file__).parent.parent
-sys.path.append(os.fspath(script_dir))
+from tests.tree_comparison_helper import is_same_tree # noqa: E402
-from tests.tree_comparison_helper import is_same_tree
-
-from . import expected_discovery_test_output
-from .helpers import TEST_DATA_PATH, runner, runner_with_cwd, create_symlink
+from . import expected_discovery_test_output, helpers # noqa: E402
@pytest.mark.skipif(
@@ -36,22 +31,20 @@ def test_import_error(tmp_path):
# Saving some files as .txt to avoid that file displaying a syntax error for
# the extension as a whole. Instead, rename it before running this test
# in order to test the error handling.
- file_path = TEST_DATA_PATH / "error_pytest_import.txt"
+ file_path = helpers.TEST_DATA_PATH / "error_pytest_import.txt"
temp_dir = tmp_path / "temp_data"
temp_dir.mkdir()
p = temp_dir / "error_pytest_import.py"
shutil.copyfile(file_path, p)
- actual: Optional[List[Dict[str, Any]]] = runner(["--collect-only", os.fspath(p)])
+ actual: Optional[List[Dict[str, Any]]] = helpers.runner(["--collect-only", os.fspath(p)])
assert actual
actual_list: List[Dict[str, Any]] = actual
if actual_list is not None:
assert actual_list.pop(-1).get("eot")
for actual_item in actual_list:
- assert all(
- item in actual_item.keys() for item in ("status", "cwd", "error")
- )
+ assert all(item in actual_item.keys() for item in ("status", "cwd", "error"))
assert actual_item.get("status") == "error"
- assert actual_item.get("cwd") == os.fspath(TEST_DATA_PATH)
+ assert actual_item.get("cwd") == os.fspath(helpers.TEST_DATA_PATH)
# Ensure that 'error' is a list and then check its length
error_content = actual_item.get("error")
@@ -81,22 +74,20 @@ def test_syntax_error(tmp_path):
# Saving some files as .txt to avoid that file displaying a syntax error for
# the extension as a whole. Instead, rename it before running this test
# in order to test the error handling.
- file_path = TEST_DATA_PATH / "error_syntax_discovery.txt"
+ file_path = helpers.TEST_DATA_PATH / "error_syntax_discovery.txt"
temp_dir = tmp_path / "temp_data"
temp_dir.mkdir()
p = temp_dir / "error_syntax_discovery.py"
shutil.copyfile(file_path, p)
- actual = runner(["--collect-only", os.fspath(p)])
+ actual = helpers.runner(["--collect-only", os.fspath(p)])
assert actual
actual_list: List[Dict[str, Any]] = actual
if actual_list is not None:
assert actual_list.pop(-1).get("eot")
for actual_item in actual_list:
- assert all(
- item in actual_item.keys() for item in ("status", "cwd", "error")
- )
+ assert all(item in actual_item.keys() for item in ("status", "cwd", "error"))
assert actual_item.get("status") == "error"
- assert actual_item.get("cwd") == os.fspath(TEST_DATA_PATH)
+ assert actual_item.get("cwd") == os.fspath(helpers.TEST_DATA_PATH)
# Ensure that 'error' is a list and then check its length
error_content = actual_item.get("error")
@@ -114,17 +105,15 @@ def test_parameterized_error_collect():
The json should still be returned but the errors list should be present.
"""
file_path_str = "error_parametrize_discovery.py"
- actual = runner(["--collect-only", file_path_str])
+ actual = helpers.runner(["--collect-only", file_path_str])
assert actual
actual_list: List[Dict[str, Any]] = actual
if actual_list is not None:
assert actual_list.pop(-1).get("eot")
for actual_item in actual_list:
- assert all(
- item in actual_item.keys() for item in ("status", "cwd", "error")
- )
+ assert all(item in actual_item.keys() for item in ("status", "cwd", "error"))
assert actual_item.get("status") == "error"
- assert actual_item.get("cwd") == os.fspath(TEST_DATA_PATH)
+ assert actual_item.get("cwd") == os.fspath(helpers.TEST_DATA_PATH)
# Ensure that 'error' is a list and then check its length
error_content = actual_item.get("error")
@@ -196,10 +185,10 @@ def test_pytest_collect(file, expected_const):
file -- a string with the file or folder to run pytest discovery on.
expected_const -- the expected output from running pytest discovery on the file.
"""
- actual = runner(
+ actual = helpers.runner(
[
"--collect-only",
- os.fspath(TEST_DATA_PATH / file),
+ os.fspath(helpers.TEST_DATA_PATH / file),
]
)
@@ -210,8 +199,10 @@ def test_pytest_collect(file, expected_const):
actual_item = actual_list.pop(0)
assert all(item in actual_item.keys() for item in ("status", "cwd", "error"))
assert actual_item.get("status") == "success"
- assert actual_item.get("cwd") == os.fspath(TEST_DATA_PATH)
- assert is_same_tree(actual_item.get("tests"), expected_const)
+ assert actual_item.get("cwd") == os.fspath(helpers.TEST_DATA_PATH)
+ assert is_same_tree(
+ actual_item.get("tests"), expected_const
+ ), f"Tests tree does not match expected value. \n Expected: {json.dumps(expected_const, indent=4)}. \n Actual: {json.dumps(actual_item.get('tests'), indent=4)}"
def test_symlink_root_dir():
@@ -219,14 +210,14 @@ def test_symlink_root_dir():
Test to test pytest discovery with the command line arg --rootdir specified as a symlink path.
Discovery should succeed and testids should be relative to the symlinked root directory.
"""
- with create_symlink(TEST_DATA_PATH, "root", "symlink_folder") as (
+ with helpers.create_symlink(helpers.TEST_DATA_PATH, "root", "symlink_folder") as (
source,
destination,
):
assert destination.is_symlink()
# Run pytest with the cwd being the resolved symlink path (as it will be when we run the subprocess from node).
- actual = runner_with_cwd(
+ actual = helpers.runner_with_cwd(
["--collect-only", f"--rootdir={os.fspath(destination)}"], source
)
expected = expected_discovery_test_output.symlink_expected_discovery_output
@@ -244,9 +235,7 @@ def test_symlink_root_dir():
assert actual_item.get("cwd") == os.fspath(
destination
), f"CWD does not match: {os.fspath(destination)}"
- assert (
- actual_item.get("tests") == expected
- ), "Tests do not match expected value"
+ assert actual_item.get("tests") == expected, "Tests do not match expected value"
except AssertionError as e:
# Print the actual_item in JSON format if an assertion fails
print(json.dumps(actual_item, indent=4))
@@ -258,13 +247,13 @@ def test_pytest_root_dir():
Test to test pytest discovery with the command line arg --rootdir specified to be a subfolder
of the workspace root. Discovery should succeed and testids should be relative to workspace root.
"""
- rd = f"--rootdir={TEST_DATA_PATH / 'root' / 'tests'}"
- actual = runner_with_cwd(
+ rd = f"--rootdir={helpers.TEST_DATA_PATH / 'root' / 'tests'}"
+ actual = helpers.runner_with_cwd(
[
"--collect-only",
rd,
],
- TEST_DATA_PATH / "root",
+ helpers.TEST_DATA_PATH / "root",
)
assert actual
actual_list: List[Dict[str, Any]] = actual
@@ -273,11 +262,11 @@ def test_pytest_root_dir():
actual_item = actual_list.pop(0)
assert all(item in actual_item.keys() for item in ("status", "cwd", "error"))
assert actual_item.get("status") == "success"
- assert actual_item.get("cwd") == os.fspath(TEST_DATA_PATH / "root")
+ assert actual_item.get("cwd") == os.fspath(helpers.TEST_DATA_PATH / "root")
assert is_same_tree(
actual_item.get("tests"),
expected_discovery_test_output.root_with_config_expected_output,
- )
+ ), f"Tests tree does not match expected value. \n Expected: {json.dumps(expected_discovery_test_output.root_with_config_expected_output, indent=4)}. \n Actual: {json.dumps(actual_item.get('tests'), indent=4)}"
def test_pytest_config_file():
@@ -285,12 +274,12 @@ def test_pytest_config_file():
Test to test pytest discovery with the command line arg -c with a specified config file which
changes the workspace root. Discovery should succeed and testids should be relative to workspace root.
"""
- actual = runner_with_cwd(
+ actual = helpers.runner_with_cwd(
[
"--collect-only",
"tests/",
],
- TEST_DATA_PATH / "root",
+ helpers.TEST_DATA_PATH / "root",
)
assert actual
actual_list: List[Dict[str, Any]] = actual
@@ -299,8 +288,8 @@ def test_pytest_config_file():
actual_item = actual_list.pop(0)
assert all(item in actual_item.keys() for item in ("status", "cwd", "error"))
assert actual_item.get("status") == "success"
- assert actual_item.get("cwd") == os.fspath(TEST_DATA_PATH / "root")
+ assert actual_item.get("cwd") == os.fspath(helpers.TEST_DATA_PATH / "root")
assert is_same_tree(
actual_item.get("tests"),
expected_discovery_test_output.root_with_config_expected_output,
- )
+ ), f"Tests tree does not match expected value. \n Expected: {json.dumps(expected_discovery_test_output.root_with_config_expected_output, indent=4)}. \n Actual: {json.dumps(actual_item.get('tests'), indent=4)}"
diff --git a/extensions/positron-python/pythonFiles/tests/pytestadapter/test_execution.py b/extensions/positron-python/python_files/tests/pytestadapter/test_execution.py
similarity index 92%
rename from extensions/positron-python/pythonFiles/tests/pytestadapter/test_execution.py
rename to extensions/positron-python/python_files/tests/pytestadapter/test_execution.py
index a8336089d0a..b4fffd6a640 100644
--- a/extensions/positron-python/pythonFiles/tests/pytestadapter/test_execution.py
+++ b/extensions/positron-python/python_files/tests/pytestadapter/test_execution.py
@@ -28,9 +28,7 @@ def test_config_file():
]
new_cwd = TEST_DATA_PATH / "root"
actual = runner_with_cwd(args, new_cwd)
- expected_const = (
- expected_execution_test_output.config_file_pytest_expected_execution_output
- )
+ expected_const = expected_execution_test_output.config_file_pytest_expected_execution_output
assert actual
actual_list: List[Dict[str, Any]] = actual
assert actual_list.pop(-1).get("eot")
@@ -38,9 +36,7 @@ def test_config_file():
actual_result_dict = dict()
if actual_list is not None:
for actual_item in actual_list:
- assert all(
- item in actual_item.keys() for item in ("status", "cwd", "result")
- )
+ assert all(item in actual_item.keys() for item in ("status", "cwd", "result"))
assert actual_item.get("status") == "success"
assert actual_item.get("cwd") == os.fspath(new_cwd)
actual_result_dict.update(actual_item["result"])
@@ -53,9 +49,7 @@ def test_rootdir_specified():
args = [rd, "tests/test_a.py::test_a_function"]
new_cwd = TEST_DATA_PATH / "root"
actual = runner_with_cwd(args, new_cwd)
- expected_const = (
- expected_execution_test_output.config_file_pytest_expected_execution_output
- )
+ expected_const = expected_execution_test_output.config_file_pytest_expected_execution_output
assert actual
actual_list: List[Dict[str, Any]] = actual
assert actual_list.pop(-1).get("eot")
@@ -63,9 +57,7 @@ def test_rootdir_specified():
actual_result_dict = dict()
if actual_list is not None:
for actual_item in actual_list:
- assert all(
- item in actual_item.keys() for item in ("status", "cwd", "result")
- )
+ assert all(item in actual_item.keys() for item in ("status", "cwd", "result"))
assert actual_item.get("status") == "success"
assert actual_item.get("cwd") == os.fspath(new_cwd)
actual_result_dict.update(actual_item["result"])
@@ -101,9 +93,7 @@ def test_syntax_error_execution(tmp_path):
assert actual_list.pop(-1).get("eot")
if actual_list is not None:
for actual_item in actual_list:
- assert all(
- item in actual_item.keys() for item in ("status", "cwd", "error")
- )
+ assert all(item in actual_item.keys() for item in ("status", "cwd", "error"))
assert actual_item.get("status") == "error"
assert actual_item.get("cwd") == os.fspath(TEST_DATA_PATH)
error_content = actual_item.get("error")
@@ -126,9 +116,7 @@ def test_bad_id_error_execution():
assert actual_list.pop(-1).get("eot")
if actual_list is not None:
for actual_item in actual_list:
- assert all(
- item in actual_item.keys() for item in ("status", "cwd", "error")
- )
+ assert all(item in actual_item.keys() for item in ("status", "cwd", "error"))
assert actual_item.get("status") == "error"
assert actual_item.get("cwd") == os.fspath(TEST_DATA_PATH)
error_content = actual_item.get("error")
@@ -273,9 +261,7 @@ def test_pytest_execution(test_ids, expected_const):
actual_result_dict = dict()
if actual_list is not None:
for actual_item in actual_list:
- assert all(
- item in actual_item.keys() for item in ("status", "cwd", "result")
- )
+ assert all(item in actual_item.keys() for item in ("status", "cwd", "result"))
assert actual_item.get("status") == "success"
assert actual_item.get("cwd") == os.fspath(TEST_DATA_PATH)
actual_result_dict.update(actual_item["result"])
@@ -307,13 +293,9 @@ def test_symlink_run():
)
# Run pytest with the cwd being the resolved symlink path (as it will be when we run the subprocess from node).
- actual = runner_with_cwd(
- [f"--rootdir={os.fspath(destination)}", test_a_id], source
- )
+ actual = runner_with_cwd([f"--rootdir={os.fspath(destination)}", test_a_id], source)
- expected_const = (
- expected_execution_test_output.symlink_run_expected_execution_output
- )
+ expected_const = expected_execution_test_output.symlink_run_expected_execution_output
assert actual
actual_list: List[Dict[str, Any]] = actual
if actual_list is not None:
diff --git a/extensions/positron-python/pythonFiles/tests/run_all.py b/extensions/positron-python/python_files/tests/run_all.py
similarity index 85%
rename from extensions/positron-python/pythonFiles/tests/run_all.py
rename to extensions/positron-python/python_files/tests/run_all.py
index ce5a6264996..7c864ba7c5c 100644
--- a/extensions/positron-python/pythonFiles/tests/run_all.py
+++ b/extensions/positron-python/python_files/tests/run_all.py
@@ -7,8 +7,7 @@
sys.path[0] = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
-from tests.__main__ import main, parse_args
-
+from tests.__main__ import main, parse_args # noqa: E402
if __name__ == "__main__":
mainkwargs, pytestargs = parse_args()
diff --git a/extensions/positron-python/pythonFiles/tests/test_create_conda.py b/extensions/positron-python/python_files/tests/test_create_conda.py
similarity index 94%
rename from extensions/positron-python/pythonFiles/tests/test_create_conda.py
rename to extensions/positron-python/python_files/tests/test_create_conda.py
index 29dc323402e..8681184ba82 100644
--- a/extensions/positron-python/pythonFiles/tests/test_create_conda.py
+++ b/extensions/positron-python/python_files/tests/test_create_conda.py
@@ -29,9 +29,7 @@ def install_packages(_name):
def run_process(args, error_message):
nonlocal run_process_called
run_process_called = True
- version = (
- "12345" if python else f"{sys.version_info.major}.{sys.version_info.minor}"
- )
+ version = "12345" if python else f"{sys.version_info.major}.{sys.version_info.minor}"
if not env_exists:
assert args == [
sys.executable,
diff --git a/extensions/positron-python/pythonFiles/tests/test_create_microvenv.py b/extensions/positron-python/python_files/tests/test_create_microvenv.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/test_create_microvenv.py
rename to extensions/positron-python/python_files/tests/test_create_microvenv.py
diff --git a/extensions/positron-python/pythonFiles/tests/test_create_venv.py b/extensions/positron-python/python_files/tests/test_create_venv.py
similarity index 95%
rename from extensions/positron-python/pythonFiles/tests/test_create_venv.py
rename to extensions/positron-python/python_files/tests/test_create_venv.py
index 57df0a7fb3c..27bd45f9c37 100644
--- a/extensions/positron-python/pythonFiles/tests/test_create_venv.py
+++ b/extensions/positron-python/python_files/tests/test_create_venv.py
@@ -14,9 +14,7 @@
import create_venv
-@pytest.mark.skipif(
- sys.platform == "win32", reason="Windows does not have micro venv fallback."
-)
+@pytest.mark.skipif(sys.platform == "win32", reason="Windows does not have micro venv fallback.")
def test_venv_not_installed_unix():
importlib.reload(create_venv)
create_venv.is_installed = lambda module: module != "venv"
@@ -43,9 +41,7 @@ def run_process(args, error_message):
assert run_process_called is True
-@pytest.mark.skipif(
- sys.platform != "win32", reason="Windows does not have microvenv fallback."
-)
+@pytest.mark.skipif(sys.platform != "win32", reason="Windows does not have microvenv fallback.")
def test_venv_not_installed_windows():
importlib.reload(create_venv)
create_venv.is_installed = lambda module: module != "venv"
@@ -106,9 +102,7 @@ def add_gitignore(_name):
assert run_process_called == (env_exists == "noEnv")
# add_gitignore is called when new venv is created and git_ignore is True
- assert add_gitignore_called == (
- (env_exists == "noEnv") and (git_ignore == "useGitIgnore")
- )
+ assert add_gitignore_called == ((env_exists == "noEnv") and (git_ignore == "useGitIgnore"))
@pytest.mark.parametrize("install_type", ["requirements", "pyproject", "both"])
@@ -238,9 +232,7 @@ def run_process(args, error_message):
if "install" in args and "pip" in args:
nonlocal run_process_called
run_process_called = True
- pip_pyz_path = os.fspath(
- create_venv.CWD / create_venv.VENV_NAME / "pip.pyz"
- )
+ pip_pyz_path = os.fspath(create_venv.CWD / create_venv.VENV_NAME / "pip.pyz")
assert args[1:] == [pip_pyz_path, "install", "pip"]
assert error_message == "CREATE_VENV.INSTALL_PIP_FAILED"
diff --git a/extensions/positron-python/pythonFiles/tests/test_data/missing-deps.data b/extensions/positron-python/python_files/tests/test_data/missing-deps.data
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/test_data/missing-deps.data
rename to extensions/positron-python/python_files/tests/test_data/missing-deps.data
diff --git a/extensions/positron-python/pythonFiles/tests/test_data/no-missing-deps.data b/extensions/positron-python/python_files/tests/test_data/no-missing-deps.data
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/test_data/no-missing-deps.data
rename to extensions/positron-python/python_files/tests/test_data/no-missing-deps.data
diff --git a/extensions/positron-python/pythonFiles/tests/test_data/pyproject-missing-deps.data b/extensions/positron-python/python_files/tests/test_data/pyproject-missing-deps.data
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/test_data/pyproject-missing-deps.data
rename to extensions/positron-python/python_files/tests/test_data/pyproject-missing-deps.data
diff --git a/extensions/positron-python/pythonFiles/tests/test_data/pyproject-no-missing-deps.data b/extensions/positron-python/python_files/tests/test_data/pyproject-no-missing-deps.data
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/test_data/pyproject-no-missing-deps.data
rename to extensions/positron-python/python_files/tests/test_data/pyproject-no-missing-deps.data
diff --git a/extensions/positron-python/pythonFiles/tests/test_dynamic_cursor.py b/extensions/positron-python/python_files/tests/test_dynamic_cursor.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/test_dynamic_cursor.py
rename to extensions/positron-python/python_files/tests/test_dynamic_cursor.py
diff --git a/extensions/positron-python/pythonFiles/tests/test_installed_check.py b/extensions/positron-python/python_files/tests/test_installed_check.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/test_installed_check.py
rename to extensions/positron-python/python_files/tests/test_installed_check.py
diff --git a/extensions/positron-python/pythonFiles/tests/test_normalize_selection.py b/extensions/positron-python/python_files/tests/test_normalize_selection.py
similarity index 98%
rename from extensions/positron-python/pythonFiles/tests/test_normalize_selection.py
rename to extensions/positron-python/python_files/tests/test_normalize_selection.py
index 5f4d6d7d4a1..60dfddb11e2 100644
--- a/extensions/positron-python/pythonFiles/tests/test_normalize_selection.py
+++ b/extensions/positron-python/python_files/tests/test_normalize_selection.py
@@ -5,7 +5,7 @@
import importlib
import textwrap
-# __file__ = "/Users/anthonykim/Desktop/vscode-python/pythonFiles/normalizeSelection.py"
+# __file__ = "/Users/anthonykim/Desktop/vscode-python/python_files/normalizeSelection.py"
# sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__))))
import normalizeSelection
diff --git a/extensions/positron-python/pythonFiles/tests/test_shell_integration.py b/extensions/positron-python/python_files/tests/test_shell_integration.py
similarity index 86%
rename from extensions/positron-python/pythonFiles/tests/test_shell_integration.py
rename to extensions/positron-python/python_files/tests/test_shell_integration.py
index 896df416ece..c5911aad2d1 100644
--- a/extensions/positron-python/pythonFiles/tests/test_shell_integration.py
+++ b/extensions/positron-python/python_files/tests/test_shell_integration.py
@@ -13,7 +13,7 @@ def test_decoration_success():
if sys.platform != "win32":
assert (
result
- == "\x1b]633;D;0\x07\x1b]633;A\x07>>> \x1b]633;B\x07\x1b]633;C\x07\x1b]633;E;None\x07"
+ == "\x1b]633;E;None\x07\x1b]633;D;0\x07\x1b]633;A\x07>>> \x1b]633;B\x07\x1b]633;C\x07"
)
else:
pass
@@ -28,7 +28,7 @@ def test_decoration_failure():
if sys.platform != "win32":
assert (
result
- == "\x1b]633;D;1\x07\x1b]633;A\x07>>> \x1b]633;B\x07\x1b]633;C\x07\x1b]633;E;None\x07"
+ == "\x1b]633;E;None\x07\x1b]633;D;1\x07\x1b]633;A\x07>>> \x1b]633;B\x07\x1b]633;C\x07"
)
else:
pass
diff --git a/extensions/positron-python/pythonFiles/tests/test_smart_selection.py b/extensions/positron-python/python_files/tests/test_smart_selection.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/test_smart_selection.py
rename to extensions/positron-python/python_files/tests/test_smart_selection.py
diff --git a/extensions/positron-python/pythonFiles/tests/testing_tools/__init__.py b/extensions/positron-python/python_files/tests/testing_tools/__init__.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/testing_tools/__init__.py
rename to extensions/positron-python/python_files/tests/testing_tools/__init__.py
diff --git a/extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/NormCase/tests/A/__init__.py b/extensions/positron-python/python_files/tests/testing_tools/adapter/.data/NormCase/tests/A/__init__.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/NormCase/tests/A/__init__.py
rename to extensions/positron-python/python_files/tests/testing_tools/adapter/.data/NormCase/tests/A/__init__.py
diff --git a/extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/NormCase/tests/A/b/C/__init__.py b/extensions/positron-python/python_files/tests/testing_tools/adapter/.data/NormCase/tests/A/b/C/__init__.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/NormCase/tests/A/b/C/__init__.py
rename to extensions/positron-python/python_files/tests/testing_tools/adapter/.data/NormCase/tests/A/b/C/__init__.py
diff --git a/extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/NormCase/tests/A/b/C/test_Spam.py b/extensions/positron-python/python_files/tests/testing_tools/adapter/.data/NormCase/tests/A/b/C/test_Spam.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/NormCase/tests/A/b/C/test_Spam.py
rename to extensions/positron-python/python_files/tests/testing_tools/adapter/.data/NormCase/tests/A/b/C/test_Spam.py
diff --git a/extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/NormCase/tests/A/b/__init__.py b/extensions/positron-python/python_files/tests/testing_tools/adapter/.data/NormCase/tests/A/b/__init__.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/NormCase/tests/A/b/__init__.py
rename to extensions/positron-python/python_files/tests/testing_tools/adapter/.data/NormCase/tests/A/b/__init__.py
diff --git a/extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/NormCase/tests/__init__.py b/extensions/positron-python/python_files/tests/testing_tools/adapter/.data/NormCase/tests/__init__.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/NormCase/tests/__init__.py
rename to extensions/positron-python/python_files/tests/testing_tools/adapter/.data/NormCase/tests/__init__.py
diff --git a/extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/complex/README.md b/extensions/positron-python/python_files/tests/testing_tools/adapter/.data/complex/README.md
similarity index 99%
rename from extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/complex/README.md
rename to extensions/positron-python/python_files/tests/testing_tools/adapter/.data/complex/README.md
index e30e96142d0..8840cda1e83 100644
--- a/extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/complex/README.md
+++ b/extensions/positron-python/python_files/tests/testing_tools/adapter/.data/complex/README.md
@@ -1,7 +1,7 @@
## Directory Structure
```
-pythonFiles/tests/testing_tools/adapter/.data/
+python_files/tests/testing_tools/adapter/.data/
tests/ # test root
test_doctest.txt
test_pytest.py
diff --git a/extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/complex/mod.py b/extensions/positron-python/python_files/tests/testing_tools/adapter/.data/complex/mod.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/complex/mod.py
rename to extensions/positron-python/python_files/tests/testing_tools/adapter/.data/complex/mod.py
diff --git a/extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/__init__.py b/extensions/positron-python/python_files/tests/testing_tools/adapter/.data/complex/tests/__init__.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/__init__.py
rename to extensions/positron-python/python_files/tests/testing_tools/adapter/.data/complex/tests/__init__.py
diff --git a/extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/spam.py b/extensions/positron-python/python_files/tests/testing_tools/adapter/.data/complex/tests/spam.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/spam.py
rename to extensions/positron-python/python_files/tests/testing_tools/adapter/.data/complex/tests/spam.py
diff --git a/extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/test_42-43.py b/extensions/positron-python/python_files/tests/testing_tools/adapter/.data/complex/tests/test_42-43.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/test_42-43.py
rename to extensions/positron-python/python_files/tests/testing_tools/adapter/.data/complex/tests/test_42-43.py
diff --git a/extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/test_42.py b/extensions/positron-python/python_files/tests/testing_tools/adapter/.data/complex/tests/test_42.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/test_42.py
rename to extensions/positron-python/python_files/tests/testing_tools/adapter/.data/complex/tests/test_42.py
diff --git a/extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/test_doctest.py b/extensions/positron-python/python_files/tests/testing_tools/adapter/.data/complex/tests/test_doctest.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/test_doctest.py
rename to extensions/positron-python/python_files/tests/testing_tools/adapter/.data/complex/tests/test_doctest.py
diff --git a/extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/test_doctest.txt b/extensions/positron-python/python_files/tests/testing_tools/adapter/.data/complex/tests/test_doctest.txt
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/test_doctest.txt
rename to extensions/positron-python/python_files/tests/testing_tools/adapter/.data/complex/tests/test_doctest.txt
diff --git a/extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/test_foo.py b/extensions/positron-python/python_files/tests/testing_tools/adapter/.data/complex/tests/test_foo.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/test_foo.py
rename to extensions/positron-python/python_files/tests/testing_tools/adapter/.data/complex/tests/test_foo.py
diff --git a/extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/test_mixed.py b/extensions/positron-python/python_files/tests/testing_tools/adapter/.data/complex/tests/test_mixed.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/test_mixed.py
rename to extensions/positron-python/python_files/tests/testing_tools/adapter/.data/complex/tests/test_mixed.py
diff --git a/extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/test_pytest.py b/extensions/positron-python/python_files/tests/testing_tools/adapter/.data/complex/tests/test_pytest.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/test_pytest.py
rename to extensions/positron-python/python_files/tests/testing_tools/adapter/.data/complex/tests/test_pytest.py
diff --git a/extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/test_pytest_param.py b/extensions/positron-python/python_files/tests/testing_tools/adapter/.data/complex/tests/test_pytest_param.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/test_pytest_param.py
rename to extensions/positron-python/python_files/tests/testing_tools/adapter/.data/complex/tests/test_pytest_param.py
diff --git a/extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/test_unittest.py b/extensions/positron-python/python_files/tests/testing_tools/adapter/.data/complex/tests/test_unittest.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/test_unittest.py
rename to extensions/positron-python/python_files/tests/testing_tools/adapter/.data/complex/tests/test_unittest.py
diff --git a/extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/testspam.py b/extensions/positron-python/python_files/tests/testing_tools/adapter/.data/complex/tests/testspam.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/testspam.py
rename to extensions/positron-python/python_files/tests/testing_tools/adapter/.data/complex/tests/testspam.py
diff --git a/extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/v/__init__.py b/extensions/positron-python/python_files/tests/testing_tools/adapter/.data/complex/tests/v/__init__.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/v/__init__.py
rename to extensions/positron-python/python_files/tests/testing_tools/adapter/.data/complex/tests/v/__init__.py
diff --git a/extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/v/spam.py b/extensions/positron-python/python_files/tests/testing_tools/adapter/.data/complex/tests/v/spam.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/v/spam.py
rename to extensions/positron-python/python_files/tests/testing_tools/adapter/.data/complex/tests/v/spam.py
diff --git a/extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/v/test_eggs.py b/extensions/positron-python/python_files/tests/testing_tools/adapter/.data/complex/tests/v/test_eggs.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/v/test_eggs.py
rename to extensions/positron-python/python_files/tests/testing_tools/adapter/.data/complex/tests/v/test_eggs.py
diff --git a/extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/v/test_ham.py b/extensions/positron-python/python_files/tests/testing_tools/adapter/.data/complex/tests/v/test_ham.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/v/test_ham.py
rename to extensions/positron-python/python_files/tests/testing_tools/adapter/.data/complex/tests/v/test_ham.py
diff --git a/extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/v/test_spam.py b/extensions/positron-python/python_files/tests/testing_tools/adapter/.data/complex/tests/v/test_spam.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/v/test_spam.py
rename to extensions/positron-python/python_files/tests/testing_tools/adapter/.data/complex/tests/v/test_spam.py
diff --git a/extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/w/test_spam.py b/extensions/positron-python/python_files/tests/testing_tools/adapter/.data/complex/tests/w/test_spam.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/w/test_spam.py
rename to extensions/positron-python/python_files/tests/testing_tools/adapter/.data/complex/tests/w/test_spam.py
diff --git a/extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/w/test_spam_ex.py b/extensions/positron-python/python_files/tests/testing_tools/adapter/.data/complex/tests/w/test_spam_ex.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/w/test_spam_ex.py
rename to extensions/positron-python/python_files/tests/testing_tools/adapter/.data/complex/tests/w/test_spam_ex.py
diff --git a/extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/x/__init__.py b/extensions/positron-python/python_files/tests/testing_tools/adapter/.data/complex/tests/x/__init__.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/x/__init__.py
rename to extensions/positron-python/python_files/tests/testing_tools/adapter/.data/complex/tests/x/__init__.py
diff --git a/extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/x/y/__init__.py b/extensions/positron-python/python_files/tests/testing_tools/adapter/.data/complex/tests/x/y/__init__.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/x/y/__init__.py
rename to extensions/positron-python/python_files/tests/testing_tools/adapter/.data/complex/tests/x/y/__init__.py
diff --git a/extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/x/y/z/__init__.py b/extensions/positron-python/python_files/tests/testing_tools/adapter/.data/complex/tests/x/y/z/__init__.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/x/y/z/__init__.py
rename to extensions/positron-python/python_files/tests/testing_tools/adapter/.data/complex/tests/x/y/z/__init__.py
diff --git a/extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/x/y/z/a/__init__.py b/extensions/positron-python/python_files/tests/testing_tools/adapter/.data/complex/tests/x/y/z/a/__init__.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/x/y/z/a/__init__.py
rename to extensions/positron-python/python_files/tests/testing_tools/adapter/.data/complex/tests/x/y/z/a/__init__.py
diff --git a/extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/x/y/z/a/test_spam.py b/extensions/positron-python/python_files/tests/testing_tools/adapter/.data/complex/tests/x/y/z/a/test_spam.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/x/y/z/a/test_spam.py
rename to extensions/positron-python/python_files/tests/testing_tools/adapter/.data/complex/tests/x/y/z/a/test_spam.py
diff --git a/extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/x/y/z/b/__init__.py b/extensions/positron-python/python_files/tests/testing_tools/adapter/.data/complex/tests/x/y/z/b/__init__.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/x/y/z/b/__init__.py
rename to extensions/positron-python/python_files/tests/testing_tools/adapter/.data/complex/tests/x/y/z/b/__init__.py
diff --git a/extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/x/y/z/b/test_spam.py b/extensions/positron-python/python_files/tests/testing_tools/adapter/.data/complex/tests/x/y/z/b/test_spam.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/x/y/z/b/test_spam.py
rename to extensions/positron-python/python_files/tests/testing_tools/adapter/.data/complex/tests/x/y/z/b/test_spam.py
diff --git a/extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/x/y/z/test_ham.py b/extensions/positron-python/python_files/tests/testing_tools/adapter/.data/complex/tests/x/y/z/test_ham.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/x/y/z/test_ham.py
rename to extensions/positron-python/python_files/tests/testing_tools/adapter/.data/complex/tests/x/y/z/test_ham.py
diff --git a/extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/notests/tests/__init__.py b/extensions/positron-python/python_files/tests/testing_tools/adapter/.data/notests/tests/__init__.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/notests/tests/__init__.py
rename to extensions/positron-python/python_files/tests/testing_tools/adapter/.data/notests/tests/__init__.py
diff --git a/extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/simple/tests/__init__.py b/extensions/positron-python/python_files/tests/testing_tools/adapter/.data/simple/tests/__init__.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/simple/tests/__init__.py
rename to extensions/positron-python/python_files/tests/testing_tools/adapter/.data/simple/tests/__init__.py
diff --git a/extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/simple/tests/test_spam.py b/extensions/positron-python/python_files/tests/testing_tools/adapter/.data/simple/tests/test_spam.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/simple/tests/test_spam.py
rename to extensions/positron-python/python_files/tests/testing_tools/adapter/.data/simple/tests/test_spam.py
diff --git a/extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/syntax-error/tests/__init__.py b/extensions/positron-python/python_files/tests/testing_tools/adapter/.data/syntax-error/tests/__init__.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/syntax-error/tests/__init__.py
rename to extensions/positron-python/python_files/tests/testing_tools/adapter/.data/syntax-error/tests/__init__.py
diff --git a/extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/syntax-error/tests/test_spam.py b/extensions/positron-python/python_files/tests/testing_tools/adapter/.data/syntax-error/tests/test_spam.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/testing_tools/adapter/.data/syntax-error/tests/test_spam.py
rename to extensions/positron-python/python_files/tests/testing_tools/adapter/.data/syntax-error/tests/test_spam.py
diff --git a/extensions/positron-python/pythonFiles/tests/testing_tools/adapter/__init__.py b/extensions/positron-python/python_files/tests/testing_tools/adapter/__init__.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/testing_tools/adapter/__init__.py
rename to extensions/positron-python/python_files/tests/testing_tools/adapter/__init__.py
diff --git a/extensions/positron-python/pythonFiles/tests/testing_tools/adapter/pytest/__init__.py b/extensions/positron-python/python_files/tests/testing_tools/adapter/pytest/__init__.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/testing_tools/adapter/pytest/__init__.py
rename to extensions/positron-python/python_files/tests/testing_tools/adapter/pytest/__init__.py
diff --git a/extensions/positron-python/pythonFiles/tests/testing_tools/adapter/pytest/test_cli.py b/extensions/positron-python/python_files/tests/testing_tools/adapter/pytest/test_cli.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/testing_tools/adapter/pytest/test_cli.py
rename to extensions/positron-python/python_files/tests/testing_tools/adapter/pytest/test_cli.py
diff --git a/extensions/positron-python/pythonFiles/tests/testing_tools/adapter/pytest/test_discovery.py b/extensions/positron-python/python_files/tests/testing_tools/adapter/pytest/test_discovery.py
similarity index 96%
rename from extensions/positron-python/pythonFiles/tests/testing_tools/adapter/pytest/test_discovery.py
rename to extensions/positron-python/python_files/tests/testing_tools/adapter/pytest/test_discovery.py
index 83eeaa1f906..55a0e65102a 100644
--- a/extensions/positron-python/pythonFiles/tests/testing_tools/adapter/pytest/test_discovery.py
+++ b/extensions/positron-python/python_files/tests/testing_tools/adapter/pytest/test_discovery.py
@@ -224,7 +224,7 @@ def _fix_fileid(*args):
# dependency injection
_normcase=normcase,
_pathsep=pathsep,
- )
+ ),
)
def _normalize_test_id(*args):
@@ -234,7 +234,7 @@ def _normalize_test_id(*args):
# dependency injection
_fix_fileid=_fix_fileid,
_pathsep=pathsep,
- )
+ ),
)
def _iter_nodes(*args):
@@ -245,7 +245,7 @@ def _iter_nodes(*args):
_normalize_test_id=_normalize_test_id,
_normcase=normcase,
_pathsep=pathsep,
- )
+ ),
)
def _parse_node_id(*args):
@@ -254,7 +254,7 @@ def _parse_node_id(*args):
**dict(
# dependency injection
_iter_nodes=_iter_nodes,
- )
+ ),
)
##########
@@ -264,7 +264,7 @@ def _split_fspath(*args):
**dict(
# dependency injection
_normcase=normcase,
- )
+ ),
)
##########
@@ -275,7 +275,7 @@ def _matches_relfile(*args):
# dependency injection
_normcase=normcase,
_pathsep=pathsep,
- )
+ ),
)
def _is_legacy_wrapper(*args):
@@ -284,7 +284,7 @@ def _is_legacy_wrapper(*args):
**dict(
# dependency injection
_pathsep=pathsep,
- )
+ ),
)
def _get_location(*args):
@@ -295,7 +295,7 @@ def _get_location(*args):
_matches_relfile=_matches_relfile,
_is_legacy_wrapper=_is_legacy_wrapper,
_pathsep=pathsep,
- )
+ ),
)
##########
@@ -307,7 +307,7 @@ def _parse_item(item):
_parse_node_id=_parse_node_id,
_split_fspath=_split_fspath,
_get_location=_get_location,
- )
+ ),
)
return _parse_item
@@ -347,9 +347,7 @@ def test_basic(self):
("discovered.__getitem__", (0,), None),
]
- parents, tests = _discovery.discover(
- [], _pytest_main=stubpytest.main, _plugin=plugin
- )
+ parents, tests = _discovery.discover([], _pytest_main=stubpytest.main, _plugin=plugin)
actual_calls = unique(stub.calls, lambda k: k[0])
expected_calls = unique(calls, lambda k: k[0])
@@ -389,9 +387,7 @@ def test_no_tests_found(self):
("discovered.__getitem__", (0,), None),
]
- parents, tests = _discovery.discover(
- [], _pytest_main=pytest.main, _plugin=plugin
- )
+ parents, tests = _discovery.discover([], _pytest_main=pytest.main, _plugin=plugin)
actual_calls = unique(stub.calls, lambda k: k[0])
expected_calls = unique(calls, lambda k: k[0])
@@ -414,9 +410,7 @@ def test_found_with_collection_error(self):
("discovered.__getitem__", (0,), None),
]
- parents, tests = _discovery.discover(
- [], _pytest_main=pytest.main, _plugin=plugin
- )
+ parents, tests = _discovery.discover([], _pytest_main=pytest.main, _plugin=plugin)
actual_calls = unique(stub.calls, lambda k: k[0])
expected_calls = unique(calls, lambda k: k[0])
@@ -845,9 +839,7 @@ def test_finish(self):
func="SpamTests.test_spam",
sub=None,
),
- source="{}:{}".format(
- adapter_util.fix_relpath(relfile), 13
- ),
+ source="{}:{}".format(adapter_util.fix_relpath(relfile), 13),
markers=None,
parentid="./x/y/z/test_eggs.py::SpamTests",
),
@@ -920,9 +912,7 @@ def test_doctest(self):
relfile=adapter_util.fix_relpath(doctestfile),
func=None,
),
- source="{}:{}".format(
- adapter_util.fix_relpath(doctestfile), 1
- ),
+ source="{}:{}".format(adapter_util.fix_relpath(doctestfile), 1),
markers=[],
parentid="./x/test_doctest.txt",
),
@@ -972,9 +962,7 @@ def test_doctest(self):
relfile=adapter_util.fix_relpath(relfile),
func=None,
),
- source="{}:{}".format(
- adapter_util.fix_relpath(relfile), 13
- ),
+ source="{}:{}".format(adapter_util.fix_relpath(relfile), 13),
markers=[],
parentid="./x/y/z/test_eggs.py",
),
@@ -999,9 +987,7 @@ def test_doctest(self):
relfile=adapter_util.fix_relpath(relfile),
func=None,
),
- source="{}:{}".format(
- adapter_util.fix_relpath(relfile), 28
- ),
+ source="{}:{}".format(adapter_util.fix_relpath(relfile), 28),
markers=[],
parentid="./x/y/z/test_eggs.py",
),
@@ -1062,9 +1048,7 @@ def test_nested_brackets(self):
func="SpamTests.test_spam",
sub=["[a-[b]-c]"],
),
- source="{}:{}".format(
- adapter_util.fix_relpath(relfile), 13
- ),
+ source="{}:{}".format(adapter_util.fix_relpath(relfile), 13),
markers=None,
parentid="./x/y/z/test_eggs.py::SpamTests::test_spam",
),
@@ -1126,9 +1110,7 @@ def test_nested_suite(self):
func="SpamTests.Ham.Eggs.test_spam",
sub=None,
),
- source="{}:{}".format(
- adapter_util.fix_relpath(relfile), 13
- ),
+ source="{}:{}".format(adapter_util.fix_relpath(relfile), 13),
markers=None,
parentid="./x/y/z/test_eggs.py::SpamTests::Ham::Eggs",
),
@@ -1479,9 +1461,7 @@ def test_mysterious_parens(self):
func="SpamTests.test_spam",
sub=[],
),
- source="{}:{}".format(
- adapter_util.fix_relpath(relfile), 13
- ),
+ source="{}:{}".format(adapter_util.fix_relpath(relfile), 13),
markers=None,
parentid="./x/y/z/test_eggs.py::SpamTests",
),
@@ -1537,9 +1517,7 @@ def test_mysterious_colons(self):
func="SpamTests.test_spam",
sub=[],
),
- source="{}:{}".format(
- adapter_util.fix_relpath(relfile), 13
- ),
+ source="{}:{}".format(adapter_util.fix_relpath(relfile), 13),
markers=None,
parentid="./x/y/z/test_eggs.py::SpamTests",
),
@@ -1607,9 +1585,7 @@ def test_imported_test(self):
func="SpamTests.test_spam",
sub=None,
),
- source="{}:{}".format(
- adapter_util.fix_relpath(srcfile), 13
- ),
+ source="{}:{}".format(adapter_util.fix_relpath(srcfile), 13),
markers=None,
parentid="./x/y/z/test_eggs.py::SpamTests",
),
diff --git a/extensions/positron-python/pythonFiles/tests/testing_tools/adapter/test___main__.py b/extensions/positron-python/python_files/tests/testing_tools/adapter/test___main__.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/testing_tools/adapter/test___main__.py
rename to extensions/positron-python/python_files/tests/testing_tools/adapter/test___main__.py
diff --git a/extensions/positron-python/pythonFiles/tests/testing_tools/adapter/test_discovery.py b/extensions/positron-python/python_files/tests/testing_tools/adapter/test_discovery.py
similarity index 99%
rename from extensions/positron-python/pythonFiles/tests/testing_tools/adapter/test_discovery.py
rename to extensions/positron-python/python_files/tests/testing_tools/adapter/test_discovery.py
index cf3b8fb3139..2fe4db7caa3 100644
--- a/extensions/positron-python/pythonFiles/tests/testing_tools/adapter/test_discovery.py
+++ b/extensions/positron-python/python_files/tests/testing_tools/adapter/test_discovery.py
@@ -5,7 +5,7 @@
import unittest
-from testing_tools.adapter.discovery import DiscoveredTests, fix_nodeid
+from testing_tools.adapter.discovery import DiscoveredTests
from testing_tools.adapter.info import ParentInfo, SingleTestInfo, SingleTestPath
from testing_tools.adapter.util import fix_path, fix_relpath
@@ -252,9 +252,7 @@ def test_add_test_simple(self):
# missing "./":
parentid=relfile,
)
- expected = test._replace(
- id=_fix_nodeid(test.id), parentid=_fix_nodeid(test.parentid)
- )
+ expected = test._replace(id=_fix_nodeid(test.id), parentid=_fix_nodeid(test.parentid))
discovered = DiscoveredTests()
before = list(discovered), discovered.parents
diff --git a/extensions/positron-python/pythonFiles/tests/testing_tools/adapter/test_functional.py b/extensions/positron-python/python_files/tests/testing_tools/adapter/test_functional.py
similarity index 99%
rename from extensions/positron-python/pythonFiles/tests/testing_tools/adapter/test_functional.py
rename to extensions/positron-python/python_files/tests/testing_tools/adapter/test_functional.py
index a78d36a5fdc..45c85ee951d 100644
--- a/extensions/positron-python/pythonFiles/tests/testing_tools/adapter/test_functional.py
+++ b/extensions/positron-python/python_files/tests/testing_tools/adapter/test_functional.py
@@ -48,9 +48,7 @@ def _run_adapter(cmd, tool, *cliargs, **kwargs):
argv.insert(4, "--no-hide-stdio")
kwds["stderr"] = subprocess.STDOUT
argv.append("--cache-clear")
- print(
- "running {!r}".format(" ".join(arg.rpartition(CWD + "/")[-1] for arg in argv))
- )
+ print("running {!r}".format(" ".join(arg.rpartition(CWD + "/")[-1] for arg in argv)))
output = subprocess.check_output(argv, universal_newlines=True, **kwds)
return output
diff --git a/extensions/positron-python/pythonFiles/tests/testing_tools/adapter/test_report.py b/extensions/positron-python/python_files/tests/testing_tools/adapter/test_report.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/testing_tools/adapter/test_report.py
rename to extensions/positron-python/python_files/tests/testing_tools/adapter/test_report.py
diff --git a/extensions/positron-python/pythonFiles/tests/testing_tools/adapter/test_util.py b/extensions/positron-python/python_files/tests/testing_tools/adapter/test_util.py
similarity index 98%
rename from extensions/positron-python/pythonFiles/tests/testing_tools/adapter/test_util.py
rename to extensions/positron-python/python_files/tests/testing_tools/adapter/test_util.py
index 822ba2ed1b2..8a7cd475a1c 100644
--- a/extensions/positron-python/pythonFiles/tests/testing_tools/adapter/test_util.py
+++ b/extensions/positron-python/python_files/tests/testing_tools/adapter/test_util.py
@@ -11,7 +11,6 @@
import sys
import unittest
-import pytest
# Pytest 3.7 and later uses pathlib/pathlib2 for path resolution.
try:
@@ -260,9 +259,7 @@ def test_fix_fileid(self):
)
for fileid, rootdir, _os_path, expected in tests:
pathsep = _os_path.sep
- with self.subTest(
- r"for {} (with rootdir {!r}): {!r}".format(pathsep, rootdir, fileid)
- ):
+ with self.subTest(r"for {} (with rootdir {!r}): {!r}".format(pathsep, rootdir, fileid)):
fixed = fix_fileid(
fileid,
rootdir,
diff --git a/extensions/positron-python/pythonFiles/tests/tree_comparison_helper.py b/extensions/positron-python/python_files/tests/tree_comparison_helper.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/tree_comparison_helper.py
rename to extensions/positron-python/python_files/tests/tree_comparison_helper.py
diff --git a/extensions/positron-python/pythonFiles/tests/unittestadapter/.data/discovery_empty.py b/extensions/positron-python/python_files/tests/unittestadapter/.data/discovery_empty.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/unittestadapter/.data/discovery_empty.py
rename to extensions/positron-python/python_files/tests/unittestadapter/.data/discovery_empty.py
diff --git a/extensions/positron-python/pythonFiles/tests/unittestadapter/.data/discovery_error/file_one.py b/extensions/positron-python/python_files/tests/unittestadapter/.data/discovery_error/file_one.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/unittestadapter/.data/discovery_error/file_one.py
rename to extensions/positron-python/python_files/tests/unittestadapter/.data/discovery_error/file_one.py
diff --git a/extensions/positron-python/pythonFiles/tests/unittestadapter/.data/discovery_error/file_two.py b/extensions/positron-python/python_files/tests/unittestadapter/.data/discovery_error/file_two.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/unittestadapter/.data/discovery_error/file_two.py
rename to extensions/positron-python/python_files/tests/unittestadapter/.data/discovery_error/file_two.py
diff --git a/extensions/positron-python/pythonFiles/tests/unittestadapter/.data/discovery_simple.py b/extensions/positron-python/python_files/tests/unittestadapter/.data/discovery_simple.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/unittestadapter/.data/discovery_simple.py
rename to extensions/positron-python/python_files/tests/unittestadapter/.data/discovery_simple.py
diff --git a/extensions/positron-python/pythonFiles/tests/unittestadapter/.data/test_fail_simple.py b/extensions/positron-python/python_files/tests/unittestadapter/.data/test_fail_simple.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/unittestadapter/.data/test_fail_simple.py
rename to extensions/positron-python/python_files/tests/unittestadapter/.data/test_fail_simple.py
diff --git a/extensions/positron-python/pythonFiles/tests/unittestadapter/.data/test_subtest.py b/extensions/positron-python/python_files/tests/unittestadapter/.data/test_subtest.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/unittestadapter/.data/test_subtest.py
rename to extensions/positron-python/python_files/tests/unittestadapter/.data/test_subtest.py
diff --git a/extensions/positron-python/pythonFiles/tests/unittestadapter/.data/test_two_classes.py b/extensions/positron-python/python_files/tests/unittestadapter/.data/test_two_classes.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/unittestadapter/.data/test_two_classes.py
rename to extensions/positron-python/python_files/tests/unittestadapter/.data/test_two_classes.py
diff --git a/extensions/positron-python/pythonFiles/tests/unittestadapter/.data/two_patterns/pattern_a_test.py b/extensions/positron-python/python_files/tests/unittestadapter/.data/two_patterns/pattern_a_test.py
similarity index 95%
rename from extensions/positron-python/pythonFiles/tests/unittestadapter/.data/two_patterns/pattern_a_test.py
rename to extensions/positron-python/python_files/tests/unittestadapter/.data/two_patterns/pattern_a_test.py
index 4f3f77e1056..52641360b52 100644
--- a/extensions/positron-python/pythonFiles/tests/unittestadapter/.data/two_patterns/pattern_a_test.py
+++ b/extensions/positron-python/python_files/tests/unittestadapter/.data/two_patterns/pattern_a_test.py
@@ -7,7 +7,6 @@
# and the two tests with their outcome as "success".
-
class DiscoveryA(unittest.TestCase):
"""Test class for the two file pattern test. It is pattern *test.py
@@ -19,4 +18,4 @@ def test_one_a(self) -> None:
self.assertGreater(2, 1)
def test_two_a(self) -> None:
- self.assertNotEqual(2, 1)
\ No newline at end of file
+ self.assertNotEqual(2, 1)
diff --git a/extensions/positron-python/pythonFiles/tests/unittestadapter/.data/two_patterns/test_pattern_b.py b/extensions/positron-python/python_files/tests/unittestadapter/.data/two_patterns/test_pattern_b.py
similarity index 93%
rename from extensions/positron-python/pythonFiles/tests/unittestadapter/.data/two_patterns/test_pattern_b.py
rename to extensions/positron-python/python_files/tests/unittestadapter/.data/two_patterns/test_pattern_b.py
index a912699383c..06b6a818537 100644
--- a/extensions/positron-python/pythonFiles/tests/unittestadapter/.data/two_patterns/test_pattern_b.py
+++ b/extensions/positron-python/python_files/tests/unittestadapter/.data/two_patterns/test_pattern_b.py
@@ -6,10 +6,10 @@
# The test_ids_multiple_runs function should return a dictionary with a "success" status,
# and the two tests with their outcome as "success".
-class DiscoveryB(unittest.TestCase):
+class DiscoveryB(unittest.TestCase):
def test_one_b(self) -> None:
self.assertGreater(2, 1)
def test_two_b(self) -> None:
- self.assertNotEqual(2, 1)
\ No newline at end of file
+ self.assertNotEqual(2, 1)
diff --git a/extensions/positron-python/pythonFiles/tests/unittestadapter/.data/unittest_folder/test_add.py b/extensions/positron-python/python_files/tests/unittestadapter/.data/unittest_folder/test_add.py
similarity index 81%
rename from extensions/positron-python/pythonFiles/tests/unittestadapter/.data/unittest_folder/test_add.py
rename to extensions/positron-python/python_files/tests/unittestadapter/.data/unittest_folder/test_add.py
index 2e616077ec4..f562474b596 100644
--- a/extensions/positron-python/pythonFiles/tests/unittestadapter/.data/unittest_folder/test_add.py
+++ b/extensions/positron-python/python_files/tests/unittestadapter/.data/unittest_folder/test_add.py
@@ -6,17 +6,16 @@
# files in the same folder. The cwd is set to the parent folder. This should return
# a dictionary with a "success" status and the two tests with their outcome as "success".
+
def add(a, b):
return a + b
class TestAddFunction(unittest.TestCase):
-
- def test_add_positive_numbers(self):
+ def test_add_positive_numbers(self):
result = add(2, 3)
self.assertEqual(result, 5)
-
- def test_add_negative_numbers(self):
+ def test_add_negative_numbers(self):
result = add(-2, -3)
- self.assertEqual(result, -5)
\ No newline at end of file
+ self.assertEqual(result, -5)
diff --git a/extensions/positron-python/pythonFiles/tests/unittestadapter/.data/unittest_folder/test_subtract.py b/extensions/positron-python/python_files/tests/unittestadapter/.data/unittest_folder/test_subtract.py
similarity index 94%
rename from extensions/positron-python/pythonFiles/tests/unittestadapter/.data/unittest_folder/test_subtract.py
rename to extensions/positron-python/python_files/tests/unittestadapter/.data/unittest_folder/test_subtract.py
index 4028e25825d..8ac3988a325 100644
--- a/extensions/positron-python/pythonFiles/tests/unittestadapter/.data/unittest_folder/test_subtract.py
+++ b/extensions/positron-python/python_files/tests/unittestadapter/.data/unittest_folder/test_subtract.py
@@ -6,6 +6,7 @@
# files in the same folder. The cwd is set to the parent folder. This should return
# a dictionary with a "success" status and the two tests with their outcome as "success".
+
def subtract(a, b):
return a - b
@@ -15,7 +16,6 @@ def test_subtract_positive_numbers(self):
result = subtract(5, 3)
self.assertEqual(result, 2)
-
def test_subtract_negative_numbers(self):
result = subtract(-2, -3)
- self.assertEqual(result, 1)
\ No newline at end of file
+ self.assertEqual(result, 1)
diff --git a/extensions/positron-python/pythonFiles/tests/unittestadapter/.data/unittest_skip/unittest_skip_file.py b/extensions/positron-python/python_files/tests/unittestadapter/.data/unittest_skip/unittest_skip_file.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/unittestadapter/.data/unittest_skip/unittest_skip_file.py
rename to extensions/positron-python/python_files/tests/unittestadapter/.data/unittest_skip/unittest_skip_file.py
diff --git a/extensions/positron-python/pythonFiles/tests/unittestadapter/.data/unittest_skip/unittest_skip_function.py b/extensions/positron-python/python_files/tests/unittestadapter/.data/unittest_skip/unittest_skip_function.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/unittestadapter/.data/unittest_skip/unittest_skip_function.py
rename to extensions/positron-python/python_files/tests/unittestadapter/.data/unittest_skip/unittest_skip_function.py
diff --git a/extensions/positron-python/pythonFiles/tests/unittestadapter/.data/utils_complex_tree/__init__.py b/extensions/positron-python/python_files/tests/unittestadapter/.data/utils_complex_tree/__init__.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/unittestadapter/.data/utils_complex_tree/__init__.py
rename to extensions/positron-python/python_files/tests/unittestadapter/.data/utils_complex_tree/__init__.py
diff --git a/extensions/positron-python/pythonFiles/tests/unittestadapter/.data/utils_complex_tree/test_outer_folder/__init__.py b/extensions/positron-python/python_files/tests/unittestadapter/.data/utils_complex_tree/test_outer_folder/__init__.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/unittestadapter/.data/utils_complex_tree/test_outer_folder/__init__.py
rename to extensions/positron-python/python_files/tests/unittestadapter/.data/utils_complex_tree/test_outer_folder/__init__.py
diff --git a/extensions/positron-python/pythonFiles/tests/unittestadapter/.data/utils_complex_tree/test_outer_folder/test_inner_folder/__init__.py b/extensions/positron-python/python_files/tests/unittestadapter/.data/utils_complex_tree/test_outer_folder/test_inner_folder/__init__.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/unittestadapter/.data/utils_complex_tree/test_outer_folder/test_inner_folder/__init__.py
rename to extensions/positron-python/python_files/tests/unittestadapter/.data/utils_complex_tree/test_outer_folder/test_inner_folder/__init__.py
diff --git a/extensions/positron-python/pythonFiles/tests/unittestadapter/.data/utils_complex_tree/test_outer_folder/test_inner_folder/test_utils_complex_tree.py b/extensions/positron-python/python_files/tests/unittestadapter/.data/utils_complex_tree/test_outer_folder/test_inner_folder/test_utils_complex_tree.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/unittestadapter/.data/utils_complex_tree/test_outer_folder/test_inner_folder/test_utils_complex_tree.py
rename to extensions/positron-python/python_files/tests/unittestadapter/.data/utils_complex_tree/test_outer_folder/test_inner_folder/test_utils_complex_tree.py
diff --git a/extensions/positron-python/pythonFiles/tests/unittestadapter/.data/utils_decorated_tree.py b/extensions/positron-python/python_files/tests/unittestadapter/.data/utils_decorated_tree.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/unittestadapter/.data/utils_decorated_tree.py
rename to extensions/positron-python/python_files/tests/unittestadapter/.data/utils_decorated_tree.py
diff --git a/extensions/positron-python/pythonFiles/tests/unittestadapter/.data/utils_nested_cases/file_one.py b/extensions/positron-python/python_files/tests/unittestadapter/.data/utils_nested_cases/file_one.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/unittestadapter/.data/utils_nested_cases/file_one.py
rename to extensions/positron-python/python_files/tests/unittestadapter/.data/utils_nested_cases/file_one.py
diff --git a/extensions/positron-python/pythonFiles/tests/unittestadapter/.data/utils_nested_cases/folder/__init__.py b/extensions/positron-python/python_files/tests/unittestadapter/.data/utils_nested_cases/folder/__init__.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/unittestadapter/.data/utils_nested_cases/folder/__init__.py
rename to extensions/positron-python/python_files/tests/unittestadapter/.data/utils_nested_cases/folder/__init__.py
diff --git a/extensions/positron-python/pythonFiles/tests/unittestadapter/.data/utils_nested_cases/folder/file_two.py b/extensions/positron-python/python_files/tests/unittestadapter/.data/utils_nested_cases/folder/file_two.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/unittestadapter/.data/utils_nested_cases/folder/file_two.py
rename to extensions/positron-python/python_files/tests/unittestadapter/.data/utils_nested_cases/folder/file_two.py
diff --git a/extensions/positron-python/pythonFiles/tests/unittestadapter/.data/utils_simple_cases.py b/extensions/positron-python/python_files/tests/unittestadapter/.data/utils_simple_cases.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/unittestadapter/.data/utils_simple_cases.py
rename to extensions/positron-python/python_files/tests/unittestadapter/.data/utils_simple_cases.py
diff --git a/extensions/positron-python/pythonFiles/tests/unittestadapter/.data/utils_simple_tree.py b/extensions/positron-python/python_files/tests/unittestadapter/.data/utils_simple_tree.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/unittestadapter/.data/utils_simple_tree.py
rename to extensions/positron-python/python_files/tests/unittestadapter/.data/utils_simple_tree.py
diff --git a/extensions/positron-python/pythonFiles/tests/unittestadapter/__init__.py b/extensions/positron-python/python_files/tests/unittestadapter/__init__.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/unittestadapter/__init__.py
rename to extensions/positron-python/python_files/tests/unittestadapter/__init__.py
diff --git a/extensions/positron-python/pythonFiles/tests/unittestadapter/conftest.py b/extensions/positron-python/python_files/tests/unittestadapter/conftest.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/unittestadapter/conftest.py
rename to extensions/positron-python/python_files/tests/unittestadapter/conftest.py
diff --git a/extensions/positron-python/pythonFiles/tests/unittestadapter/expected_discovery_test_output.py b/extensions/positron-python/python_files/tests/unittestadapter/expected_discovery_test_output.py
similarity index 79%
rename from extensions/positron-python/pythonFiles/tests/unittestadapter/expected_discovery_test_output.py
rename to extensions/positron-python/python_files/tests/unittestadapter/expected_discovery_test_output.py
index 1007a8f42df..9fca67a3a57 100644
--- a/extensions/positron-python/pythonFiles/tests/unittestadapter/expected_discovery_test_output.py
+++ b/extensions/positron-python/python_files/tests/unittestadapter/expected_discovery_test_output.py
@@ -14,20 +14,14 @@
"type_": TestNodeTypeEnum.folder,
"children": [
{
- "path": os.fspath(
- TEST_DATA_PATH / "unittest_skip" / "unittest_skip_file.py"
- ),
+ "path": os.fspath(TEST_DATA_PATH / "unittest_skip" / "unittest_skip_file.py"),
"name": "unittest_skip_file.py",
"type_": TestNodeTypeEnum.file,
"children": [],
- "id_": os.fspath(
- TEST_DATA_PATH / "unittest_skip" / "unittest_skip_file.py"
- ),
+ "id_": os.fspath(TEST_DATA_PATH / "unittest_skip" / "unittest_skip_file.py"),
},
{
- "path": os.fspath(
- TEST_DATA_PATH / "unittest_skip" / "unittest_skip_function.py"
- ),
+ "path": os.fspath(TEST_DATA_PATH / "unittest_skip" / "unittest_skip_function.py"),
"name": "unittest_skip_function.py",
"type_": TestNodeTypeEnum.file,
"children": [
@@ -41,30 +35,22 @@
{
"name": "testadd1",
"path": os.fspath(
- TEST_DATA_PATH
- / "unittest_skip"
- / "unittest_skip_function.py"
+ TEST_DATA_PATH / "unittest_skip" / "unittest_skip_function.py"
),
"lineno": "13",
"type_": TestNodeTypeEnum.test,
"id_": os.fspath(
- TEST_DATA_PATH
- / "unittest_skip"
- / "unittest_skip_function.py"
+ TEST_DATA_PATH / "unittest_skip" / "unittest_skip_function.py"
)
+ "\\SimpleTest\\testadd1",
"runID": "unittest_skip_function.SimpleTest.testadd1",
}
],
- "id_": os.fspath(
- TEST_DATA_PATH / "unittest_skip" / "unittest_skip_function.py"
- )
+ "id_": os.fspath(TEST_DATA_PATH / "unittest_skip" / "unittest_skip_function.py")
+ "\\SimpleTest",
}
],
- "id_": os.fspath(
- TEST_DATA_PATH / "unittest_skip" / "unittest_skip_function.py"
- ),
+ "id_": os.fspath(TEST_DATA_PATH / "unittest_skip" / "unittest_skip_function.py"),
},
],
"id_": os.fspath(TEST_DATA_PATH / "unittest_skip"),
@@ -88,9 +74,7 @@
"name": "test_outer_folder",
"type_": TestNodeTypeEnum.folder,
"path": os.fsdecode(
- pathlib.PurePath(
- TEST_DATA_PATH, "utils_complex_tree", "test_outer_folder"
- )
+ pathlib.PurePath(TEST_DATA_PATH, "utils_complex_tree", "test_outer_folder")
),
"children": [
{
@@ -145,9 +129,7 @@
},
],
"id_": os.fsdecode(
- pathlib.PurePath(
- TEST_DATA_PATH, "utils_complex_tree", "test_outer_folder"
- )
+ pathlib.PurePath(TEST_DATA_PATH, "utils_complex_tree", "test_outer_folder")
),
}
],
diff --git a/extensions/positron-python/pythonFiles/tests/unittestadapter/test_discovery.py b/extensions/positron-python/python_files/tests/unittestadapter/test_discovery.py
similarity index 87%
rename from extensions/positron-python/pythonFiles/tests/unittestadapter/test_discovery.py
rename to extensions/positron-python/python_files/tests/unittestadapter/test_discovery.py
index a68774d3f2d..462b9cf9b0f 100644
--- a/extensions/positron-python/pythonFiles/tests/unittestadapter/test_discovery.py
+++ b/extensions/positron-python/python_files/tests/unittestadapter/test_discovery.py
@@ -7,6 +7,7 @@
from typing import List
import pytest
+
from unittestadapter.discovery import discover_tests
from unittestadapter.pvsc_utils import TestNodeTypeEnum, parse_unittest_args
@@ -14,8 +15,9 @@
sys.path.append(os.fspath(script_dir))
-from . import expected_discovery_test_output
-from tests.tree_comparison_helper import is_same_tree
+from tests.tree_comparison_helper import is_same_tree # noqa: E402
+
+from . import expected_discovery_test_output # noqa: E402
TEST_DATA_PATH = pathlib.Path(__file__).parent / ".data"
@@ -105,22 +107,14 @@ def test_simple_discovery() -> None:
"path": file_path,
"type_": TestNodeTypeEnum.test,
"lineno": "14",
- "id_": file_path
- + "\\"
- + "DiscoverySimple"
- + "\\"
- + "test_one",
+ "id_": file_path + "\\" + "DiscoverySimple" + "\\" + "test_one",
},
{
"name": "test_two",
"path": file_path,
"type_": TestNodeTypeEnum.test,
"lineno": "17",
- "id_": file_path
- + "\\"
- + "DiscoverySimple"
- + "\\"
- + "test_two",
+ "id_": file_path + "\\" + "DiscoverySimple" + "\\" + "test_two",
},
],
"id_": file_path + "\\" + "DiscoverySimple",
@@ -168,22 +162,14 @@ def test_simple_discovery_with_top_dir_calculated() -> None:
"path": file_path,
"type_": TestNodeTypeEnum.test,
"lineno": "14",
- "id_": file_path
- + "\\"
- + "DiscoverySimple"
- + "\\"
- + "test_one",
+ "id_": file_path + "\\" + "DiscoverySimple" + "\\" + "test_one",
},
{
"name": "test_two",
"path": file_path,
"type_": TestNodeTypeEnum.test,
"lineno": "17",
- "id_": file_path
- + "\\"
- + "DiscoverySimple"
- + "\\"
- + "test_two",
+ "id_": file_path + "\\" + "DiscoverySimple" + "\\" + "test_two",
},
],
"id_": file_path + "\\" + "DiscoverySimple",
@@ -251,22 +237,14 @@ def test_error_discovery() -> None:
"path": file_path,
"type_": TestNodeTypeEnum.test,
"lineno": "14",
- "id_": file_path
- + "\\"
- + "DiscoveryErrorTwo"
- + "\\"
- + "test_one",
+ "id_": file_path + "\\" + "DiscoveryErrorTwo" + "\\" + "test_one",
},
{
"name": "test_two",
"path": file_path,
"type_": TestNodeTypeEnum.test,
"lineno": "17",
- "id_": file_path
- + "\\"
- + "DiscoveryErrorTwo"
- + "\\"
- + "test_two",
+ "id_": file_path + "\\" + "DiscoveryErrorTwo" + "\\" + "test_two",
},
],
"id_": file_path + "\\" + "DiscoveryErrorTwo",
diff --git a/extensions/positron-python/pythonFiles/tests/unittestadapter/test_execution.py b/extensions/positron-python/python_files/tests/unittestadapter/test_execution.py
similarity index 99%
rename from extensions/positron-python/pythonFiles/tests/unittestadapter/test_execution.py
rename to extensions/positron-python/python_files/tests/unittestadapter/test_execution.py
index 7d11c656b57..519c13bc2e5 100644
--- a/extensions/positron-python/pythonFiles/tests/unittestadapter/test_execution.py
+++ b/extensions/positron-python/python_files/tests/unittestadapter/test_execution.py
@@ -10,7 +10,7 @@
script_dir = pathlib.Path(__file__).parent.parent
sys.path.insert(0, os.fspath(script_dir / "lib" / "python"))
-from unittestadapter.execution import run_tests
+from unittestadapter.execution import run_tests # noqa: E402
TEST_DATA_PATH = pathlib.Path(__file__).parent / ".data"
diff --git a/extensions/positron-python/pythonFiles/tests/unittestadapter/test_utils.py b/extensions/positron-python/python_files/tests/unittestadapter/test_utils.py
similarity index 99%
rename from extensions/positron-python/pythonFiles/tests/unittestadapter/test_utils.py
rename to extensions/positron-python/python_files/tests/unittestadapter/test_utils.py
index d5f6fbbe9f1..f650f12252f 100644
--- a/extensions/positron-python/pythonFiles/tests/unittestadapter/test_utils.py
+++ b/extensions/positron-python/python_files/tests/unittestadapter/test_utils.py
@@ -19,8 +19,7 @@
script_dir = pathlib.Path(__file__).parent.parent
sys.path.append(os.fspath(script_dir))
-from tests.tree_comparison_helper import is_same_tree
-
+from tests.tree_comparison_helper import is_same_tree # noqa: E402
TEST_DATA_PATH = pathlib.Path(__file__).parent / ".data"
diff --git a/extensions/positron-python/pythonFiles/tests/util.py b/extensions/positron-python/python_files/tests/util.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/tests/util.py
rename to extensions/positron-python/python_files/tests/util.py
diff --git a/extensions/positron-python/pythonFiles/unittestadapter/__init__.py b/extensions/positron-python/python_files/unittestadapter/__init__.py
similarity index 100%
rename from extensions/positron-python/pythonFiles/unittestadapter/__init__.py
rename to extensions/positron-python/python_files/unittestadapter/__init__.py
diff --git a/extensions/positron-python/pythonFiles/unittestadapter/discovery.py b/extensions/positron-python/python_files/unittestadapter/discovery.py
similarity index 92%
rename from extensions/positron-python/pythonFiles/unittestadapter/discovery.py
rename to extensions/positron-python/python_files/unittestadapter/discovery.py
index db06004e02c..298fe027d1d 100644
--- a/extensions/positron-python/pythonFiles/unittestadapter/discovery.py
+++ b/extensions/positron-python/python_files/unittestadapter/discovery.py
@@ -13,11 +13,12 @@
sys.path.append(os.fspath(script_dir))
sys.path.insert(0, os.fspath(script_dir / "lib" / "python"))
-from testing_tools import socket_manager
-from typing_extensions import Literal, NotRequired, TypedDict
+from typing_extensions import Literal, NotRequired, TypedDict # noqa: E402
+
+from testing_tools import socket_manager # noqa: E402
# If I use from utils then there will be an import error in test_discovery.py.
-from unittestadapter.pvsc_utils import (
+from unittestadapter.pvsc_utils import ( # noqa: E402
TestNode,
build_test_tree,
parse_unittest_args,
@@ -97,9 +98,7 @@ def discover_tests(
# Get abspath of top level directory for build_test_tree.
top_level_dir = os.path.abspath(top_level_dir)
- tests, error = build_test_tree(
- suite, top_level_dir
- ) # test tree built successfully here.
+ tests, error = build_test_tree(suite, top_level_dir) # test tree built successfully here.
except Exception:
error.append(traceback.format_exc())
@@ -115,9 +114,7 @@ def discover_tests(
return payload
-def post_response(
- payload: Union[PayloadDict, EOTPayloadDict], port: int, uuid: str
-) -> None:
+def post_response(payload: Union[PayloadDict, EOTPayloadDict], port: int, uuid: str) -> None:
# Build the request data (it has to be a POST request or the Node side will not process it), and send it.
addr = ("localhost", port)
data = json.dumps(payload)
diff --git a/extensions/positron-python/pythonFiles/unittestadapter/execution.py b/extensions/positron-python/python_files/unittestadapter/execution.py
similarity index 94%
rename from extensions/positron-python/pythonFiles/unittestadapter/execution.py
rename to extensions/positron-python/python_files/unittestadapter/execution.py
index 22451c25bf1..6b925290647 100644
--- a/extensions/positron-python/pythonFiles/unittestadapter/execution.py
+++ b/extensions/positron-python/python_files/unittestadapter/execution.py
@@ -17,13 +17,12 @@
sys.path.append(os.fspath(script_dir))
sys.path.insert(0, os.fspath(script_dir / "lib" / "python"))
-from testing_tools import process_json_util, socket_manager
-from typing_extensions import Literal, NotRequired, TypeAlias, TypedDict
-from unittestadapter.pvsc_utils import parse_unittest_args
+from typing_extensions import Literal, NotRequired, TypeAlias, TypedDict # noqa: E402
-ErrorType = Union[
- Tuple[Type[BaseException], BaseException, TracebackType], Tuple[None, None, None]
-]
+from testing_tools import process_json_util, socket_manager # noqa: E402
+from unittestadapter.pvsc_utils import parse_unittest_args # noqa: E402
+
+ErrorType = Union[Tuple[Type[BaseException], BaseException, TracebackType], Tuple[None, None, None]]
testPort = 0
testUuid = 0
START_DIR = ""
@@ -172,6 +171,11 @@ def run_tests(
locals: Optional[bool] = None,
) -> PayloadDict:
cwd = os.path.abspath(start_dir)
+ if "/" in start_dir: # is a subdir
+ parent_dir = os.path.dirname(start_dir)
+ sys.path.insert(0, parent_dir)
+ else:
+ sys.path.insert(0, cwd)
status = TestExecutionStatus.error
error = None
payload: PayloadDict = {"cwd": cwd, "status": status, "result": None}
@@ -243,9 +247,7 @@ def send_run_data(raw_data, port, uuid):
post_response(payload, port, uuid)
-def post_response(
- payload: Union[PayloadDict, EOTPayloadDict], port: int, uuid: str
-) -> None:
+def post_response(payload: Union[PayloadDict, EOTPayloadDict], port: int, uuid: str) -> None:
# Build the request data (it has to be a POST request or the Node side will not process it), and send it.
addr = ("localhost", port)
global __socket
@@ -285,9 +287,7 @@ def post_response(
) = 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
- )
+ 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
@@ -308,9 +308,7 @@ def post_response(
try:
# Try to parse the buffer as JSON
- test_ids_from_buffer = process_json_util.process_rpc_json(
- buffer.decode("utf-8")
- )
+ test_ids_from_buffer = process_json_util.process_rpc_json(buffer.decode("utf-8"))
# Clear the buffer as complete JSON object is received
buffer = b""
break
diff --git a/extensions/positron-python/pythonFiles/unittestadapter/pvsc_utils.py b/extensions/positron-python/python_files/unittestadapter/pvsc_utils.py
similarity index 95%
rename from extensions/positron-python/pythonFiles/unittestadapter/pvsc_utils.py
rename to extensions/positron-python/python_files/unittestadapter/pvsc_utils.py
index 5632e69b09c..de4f23957b8 100644
--- a/extensions/positron-python/pythonFiles/unittestadapter/pvsc_utils.py
+++ b/extensions/positron-python/python_files/unittestadapter/pvsc_utils.py
@@ -14,7 +14,7 @@
sys.path.append(os.fspath(script_dir))
sys.path.append(os.fspath(script_dir / "lib" / "python"))
-from typing_extensions import TypedDict
+from typing_extensions import TypedDict # noqa: E402
# Types
@@ -91,9 +91,7 @@ def build_test_node(path: str, name: str, type_: TestNodeTypeEnum) -> TestNode:
return {"path": path, "name": name, "type_": type_, "children": [], "id_": id_gen}
-def get_child_node(
- name: str, path: str, type_: TestNodeTypeEnum, root: TestNode
-) -> TestNode:
+def get_child_node(name: str, path: str, type_: TestNodeTypeEnum, root: TestNode) -> TestNode:
"""Find a child node in a test tree given its name, type and path. If the node doesn't exist, create it.
Path is required to distinguish between nodes with the same name and type."""
try:
@@ -154,9 +152,7 @@ def build_test_tree(
"""
error = []
directory_path = pathlib.PurePath(top_level_directory)
- root = build_test_node(
- top_level_directory, directory_path.name, TestNodeTypeEnum.folder
- )
+ root = build_test_node(top_level_directory, directory_path.name, TestNodeTypeEnum.folder)
for test_case in get_test_case(suite):
test_id = test_case.id()
@@ -167,9 +163,7 @@ def build_test_tree(
class_name = f"{components[-1]}.py"
# Find/build class node.
file_path = os.fsdecode(os.path.join(directory_path, class_name))
- current_node = get_child_node(
- class_name, file_path, TestNodeTypeEnum.file, root
- )
+ current_node = get_child_node(class_name, file_path, TestNodeTypeEnum.file, root)
else:
# Get the static test path components: filename, class name and function name.
components = test_id.split(".")
diff --git a/extensions/positron-python/pythonFiles/visualstudio_py_testlauncher.py b/extensions/positron-python/python_files/visualstudio_py_testlauncher.py
similarity index 90%
rename from extensions/positron-python/pythonFiles/visualstudio_py_testlauncher.py
rename to extensions/positron-python/python_files/visualstudio_py_testlauncher.py
index 0b0ef3242f6..b085d5ce4e6 100644
--- a/extensions/positron-python/pythonFiles/visualstudio_py_testlauncher.py
+++ b/extensions/positron-python/python_files/visualstudio_py_testlauncher.py
@@ -17,6 +17,7 @@
__author__ = "Microsoft Corporation "
__version__ = "3.0.0.0"
+import contextlib
import json
import os
import signal
@@ -27,7 +28,7 @@
try:
import thread
-except:
+except ModuleNotFoundError:
import _thread as thread
@@ -115,7 +116,7 @@ def close(self):
def readSocket(self):
try:
- data = self.socket.recv(1024)
+ self.socket.recv(1024)
self.callback()
except OSError:
if not self._closed:
@@ -169,9 +170,7 @@ def addUnexpectedSuccess(self, test):
def addSubTest(self, test, subtest, err):
super(VsTestResult, self).addSubTest(test, subtest, err)
- self.sendResult(
- test, "subtest-passed" if err is None else "subtest-failed", err, subtest
- )
+ self.sendResult(test, "subtest-passed" if err is None else "subtest-failed", err, subtest)
def sendResult(self, test, outcome, trace=None, subtest=None):
if _channel is not None:
@@ -199,11 +198,8 @@ def sendResult(self, test, outcome, trace=None, subtest=None):
def stopTests():
try:
os.kill(os.getpid(), signal.SIGUSR1)
- except:
- try:
- os.kill(os.getpid(), signal.SIGTERM)
- except:
- pass
+ except Exception:
+ os.kill(os.getpid(), signal.SIGTERM)
class ExitCommand(Exception):
@@ -226,9 +222,7 @@ def main():
prog="visualstudio_py_testlauncher",
usage="Usage: %prog [] ... ",
)
- parser.add_option(
- "--debug", action="store_true", help="Whether debugging the unit tests"
- )
+ parser.add_option("--debug", action="store_true", help="Whether debugging the unit tests")
parser.add_option(
"-x",
"--mixed-mode",
@@ -243,9 +237,7 @@ def main():
action="append",
help="specifies a test to run",
)
- parser.add_option(
- "--testFile", type="str", help="Fully qualitified path to file name"
- )
+ parser.add_option("--testFile", type="str", help="Fully qualitified path to file name")
parser.add_option(
"-c", "--coverage", type="str", help="enable code coverage and specify filename"
)
@@ -271,23 +263,17 @@ def main():
help="Verbose output (0 none, 1 (no -v) simple, 2 (-v) full)",
)
parser.add_option("--uf", "--failfast", type="str", help="Stop on first failure")
- parser.add_option(
- "--uc", "--catch", type="str", help="Catch control-C and display results"
- )
+ parser.add_option("--uc", "--catch", type="str", help="Catch control-C and display results")
(opts, _) = parser.parse_args()
sys.path[0] = os.getcwd()
if opts.result_port:
try:
signal.signal(signal.SIGUSR1, signal_handler)
- except:
- try:
+ except Exception:
+ with contextlib.suppress(Exception):
signal.signal(signal.SIGTERM, signal_handler)
- except:
- pass
- _channel = _IpcChannel(
- socket.create_connection(("127.0.0.1", opts.result_port)), stopTests
- )
+ _channel = _IpcChannel(socket.create_connection(("127.0.0.1", opts.result_port)), stopTests)
sys.stdout = _TestOutput(sys.stdout, is_stdout=True)
sys.stderr = _TestOutput(sys.stderr, is_stdout=False)
@@ -314,14 +300,12 @@ def main():
cov = None
try:
if opts.coverage:
- try:
+ with contextlib.suppress(Exception):
import coverage
cov = coverage.coverage(opts.coverage)
cov.load()
cov.start()
- except:
- pass
if opts.tests is None and opts.testFile is None:
if opts.us is None:
opts.us = "."
@@ -345,7 +329,7 @@ def main():
# Run a specific test class or test method
for test_suite in suites._tests:
for cls in test_suite._tests:
- try:
+ with contextlib.suppress(Exception):
for m in cls._tests:
testId = m.id()
if testId.startswith(opts.tests[0]):
@@ -355,8 +339,6 @@ def main():
tests = unittest.TestSuite([m])
else:
tests.addTest(m)
- except Exception as err:
- errorMessage = traceback.format_exc()
if tests is None:
tests = suite
if tests is None and suite is None:
@@ -374,9 +356,7 @@ def main():
verbosity=opts.uvInt, resultclass=VsTestResult, failfast=True
)
else:
- runner = unittest.TextTestRunner(
- verbosity=opts.uvInt, resultclass=VsTestResult
- )
+ runner = unittest.TextTestRunner(verbosity=opts.uvInt, resultclass=VsTestResult)
result = runner.run(tests)
if _channel is not None:
_channel.close()
@@ -390,14 +370,10 @@ def main():
_channel.send_event(name="done")
_channel.socket.close()
# prevent generation of the error 'Error in sys.exitfunc:'
- try:
+ with contextlib.suppress(Exception):
sys.stdout.close()
- except:
- pass
- try:
+ with contextlib.suppress(Exception):
sys.stderr.close()
- except:
- pass
if __name__ == "__main__":
diff --git a/extensions/positron-python/pythonFiles/vscode_datascience_helpers/tests/logParser.py b/extensions/positron-python/python_files/vscode_datascience_helpers/tests/logParser.py
similarity index 94%
rename from extensions/positron-python/pythonFiles/vscode_datascience_helpers/tests/logParser.py
rename to extensions/positron-python/python_files/vscode_datascience_helpers/tests/logParser.py
index e021853fee7..e07a38e9d4d 100644
--- a/extensions/positron-python/pythonFiles/vscode_datascience_helpers/tests/logParser.py
+++ b/extensions/positron-python/python_files/vscode_datascience_helpers/tests/logParser.py
@@ -1,16 +1,15 @@
import argparse
import os
+import re
from io import TextIOWrapper
+from pathlib import Path
os.system("color")
-import re
-from pathlib import Path
+
parser = argparse.ArgumentParser(description="Parse a test log into its parts")
parser.add_argument("testlog", type=str, nargs=1, help="Log to parse")
-parser.add_argument(
- "--testoutput", action="store_true", help="Show all failures and passes"
-)
+parser.add_argument("--testoutput", action="store_true", help="Show all failures and passes")
parser.add_argument(
"--split",
action="store_true",
@@ -38,7 +37,7 @@ def printTestOutput(testlog):
with p.open() as f:
for line in readStripLines(f):
stripped = line.strip()
- if len(stripped) > 2 and stripped[0] == "\x1B" and stripped[1] == "[":
+ if len(stripped) > 2 and stripped[0] == "\x1b" and stripped[1] == "[":
print(line.rstrip()) # Should be a test line as it has color encoding
diff --git a/extensions/positron-python/pythonFiles/vscode_pytest/__init__.py b/extensions/positron-python/python_files/vscode_pytest/__init__.py
similarity index 96%
rename from extensions/positron-python/pythonFiles/vscode_pytest/__init__.py
rename to extensions/positron-python/python_files/vscode_pytest/__init__.py
index 05e83e5aaf2..c2efd6d456f 100644
--- a/extensions/positron-python/pythonFiles/vscode_pytest/__init__.py
+++ b/extensions/positron-python/python_files/vscode_pytest/__init__.py
@@ -10,14 +10,10 @@
import pytest
-script_dir = pathlib.Path(__file__).parent.parent
-sys.path.append(os.fspath(script_dir))
-sys.path.append(os.fspath(script_dir / "lib" / "python"))
+from typing import Any, Dict, List, Optional, Union, Literal, TypedDict # noqa: E402
-from typing import Any, Dict, List, Optional, Union
-from testing_tools import socket_manager
-from typing_extensions import Literal, TypedDict
+from testing_tools import socket_manager # noqa: E402
DEFAULT_PORT = 45454
@@ -122,13 +118,9 @@ def pytest_exception_interact(node, call, report):
if call.excinfo and call.excinfo.typename != "AssertionError":
if report.outcome == "skipped" and "SkipTest" in str(call):
return
- ERRORS.append(
- call.excinfo.exconly() + "\n Check Python Test Logs for more details."
- )
+ ERRORS.append(call.excinfo.exconly() + "\n Check Python Test Logs for more details.")
else:
- ERRORS.append(
- report.longreprtext + "\n Check Python Test Logs for more details."
- )
+ ERRORS.append(report.longreprtext + "\n Check Python Test Logs for more details.")
else:
# If during execution, send this data that the given node failed.
report_value = "error"
@@ -449,10 +441,7 @@ def build_test_tree(session: pytest.Session) -> TestNode:
test_file_node = create_file_node(parent_module)
file_nodes_dict[parent_module] = test_file_node
# Check if the class is already a child of the file node.
- if (
- test_class_node is not None
- and test_class_node not in test_file_node["children"]
- ):
+ if test_class_node is not None and test_class_node not in test_file_node["children"]:
test_file_node["children"].append(test_class_node)
elif hasattr(test_case, "callspec"): # This means it is a parameterized test.
function_name: str = ""
@@ -467,9 +456,7 @@ def build_test_tree(session: pytest.Session) -> TestNode:
ERRORS.append(
f"unable to find original name for {test_case.name} with parameterization detected."
)
- raise VSCodePytestError(
- "Unable to find original name for parameterized test case"
- )
+ raise VSCodePytestError("Unable to find original name for parameterized test case")
except KeyError:
function_test_case: TestNode = create_parameterized_function_node(
function_name, get_node_path(test_case), test_case.nodeid
@@ -526,13 +513,9 @@ def build_nested_folders(
while iterator_path != get_node_path(session):
curr_folder_name = iterator_path.name
try:
- curr_folder_node: TestNode = created_files_folders_dict[
- os.fspath(iterator_path)
- ]
+ curr_folder_node: TestNode = created_files_folders_dict[os.fspath(iterator_path)]
except KeyError:
- curr_folder_node: TestNode = create_folder_node(
- curr_folder_name, iterator_path
- )
+ curr_folder_node: TestNode = create_folder_node(curr_folder_name, iterator_path)
created_files_folders_dict[os.fspath(iterator_path)] = curr_folder_node
if prev_folder_node not in curr_folder_node["children"]:
curr_folder_node["children"].append(prev_folder_node)
diff --git a/extensions/positron-python/pythonFiles/vscode_pytest/run_pytest_script.py b/extensions/positron-python/python_files/vscode_pytest/run_pytest_script.py
similarity index 92%
rename from extensions/positron-python/pythonFiles/vscode_pytest/run_pytest_script.py
rename to extensions/positron-python/python_files/vscode_pytest/run_pytest_script.py
index d6381e9b520..db5dcb5da2e 100644
--- a/extensions/positron-python/pythonFiles/vscode_pytest/run_pytest_script.py
+++ b/extensions/positron-python/python_files/vscode_pytest/run_pytest_script.py
@@ -11,7 +11,7 @@
script_dir = pathlib.Path(__file__).parent.parent
sys.path.append(os.fspath(script_dir))
sys.path.append(os.fspath(script_dir / "lib" / "python"))
-from testing_tools import process_json_util
+from testing_tools import process_json_util # noqa: E402
# This script handles running pytest via pytest.main(). It is called via run in the
# pytest execution adapter and gets the test_ids to run via stdin and the rest of the
@@ -25,9 +25,7 @@
# Get the rest of the args to run with pytest.
args = sys.argv[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
- )
+ 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 = []
@@ -48,9 +46,7 @@
try:
# Try to parse the buffer as JSON
- test_ids_from_buffer = process_json_util.process_rpc_json(
- buffer.decode("utf-8")
- )
+ test_ids_from_buffer = process_json_util.process_rpc_json(buffer.decode("utf-8"))
# Clear the buffer as complete JSON object is received
buffer = b""
print("Received JSON data in run script")
diff --git a/extensions/positron-python/resources/report_issue_template.md b/extensions/positron-python/resources/report_issue_template.md
index a0892ecba53..a95af90ff7f 100644
--- a/extensions/positron-python/resources/report_issue_template.md
+++ b/extensions/positron-python/resources/report_issue_template.md
@@ -1,6 +1,5 @@
# Behaviour
-## Expected vs. Actual
XXX
@@ -12,13 +11,9 @@ XXX
**After** creating the issue on GitHub, you can add screenshots and GIFs of what is happening. Consider tools like https://www.cockos.com/licecap/, https://github.com/phw/peek or https://www.screentogif.com/ for GIF creation.
-->
-
+
# Diagnostic data
-- Python version (& distribution if applicable, e.g. Anaconda): {0}
-- Type of virtual environment used (e.g. conda, venv, virtualenv, etc.): {1}
-- Value of the `python.languageServer` setting: {2}
-
Output for Python
in the Output
panel (View
→Output
, change the drop-down the upper-right of the Output
panel to Python
)
@@ -32,16 +27,3 @@ XXX
-
-
-
-User Settings
-
-
-
-```
-{3}{4}
-```
-
-
-
diff --git a/extensions/positron-python/resources/report_issue_user_data_template.md b/extensions/positron-python/resources/report_issue_user_data_template.md
new file mode 100644
index 00000000000..037b844511d
--- /dev/null
+++ b/extensions/positron-python/resources/report_issue_user_data_template.md
@@ -0,0 +1,21 @@
+- Python version (& distribution if applicable, e.g. Anaconda): {0}
+- Type of virtual environment used (e.g. conda, venv, virtualenv, etc.): {1}
+- Value of the `python.languageServer` setting: {2}
+
+
+User Settings
+
+
+```
+{3}{4}
+```
+
+
+
+
+Installed Extensions
+
+|Extension Name|Extension Id|Version|
+|---|---|---|
+{5}
+
diff --git a/extensions/positron-python/scripts/patches/jedi-language-server.patch b/extensions/positron-python/scripts/patches/jedi-language-server.patch
index 948c9638341..25e7a509f87 100644
--- a/extensions/positron-python/scripts/patches/jedi-language-server.patch
+++ b/extensions/positron-python/scripts/patches/jedi-language-server.patch
@@ -1,7 +1,7 @@
-diff --git a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/_vendor/jedi_language_server/jedi_utils.py b/extensions/positron-python/pythonFiles/positron/positron_ipykernel/_vendor/jedi_language_server/jedi_utils.py
+diff --git a/extensions/positron-python/python_files/positron/positron_ipykernel/_vendor/jedi_language_server/jedi_utils.py b/extensions/positron-python/python_files/positron/positron_ipykernel/_vendor/jedi_language_server/jedi_utils.py
index 4f3ba7bd5..52524daa2 100644
---- a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/_vendor/jedi_language_server/jedi_utils.py
-+++ b/extensions/positron-python/pythonFiles/positron/positron_ipykernel/_vendor/jedi_language_server/jedi_utils.py
+--- a/extensions/positron-python/python_files/positron/positron_ipykernel/_vendor/jedi_language_server/jedi_utils.py
++++ b/extensions/positron-python/python_files/positron/positron_ipykernel/_vendor/jedi_language_server/jedi_utils.py
@@ -12,9 +12,9 @@ from inspect import Parameter
from typing import Any, Callable, Dict, Iterator, List, Optional, Tuple, Union
@@ -42,10 +42,10 @@ index 4f3ba7bd5..52524daa2 100644
"""Get LSP Diagnostic from Jedi SyntaxError."""
return Diagnostic(
range=Range(
-diff --git a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/_vendor/jedi_language_server/__init__.py b/extensions/positron-python/pythonFiles/positron/positron_ipykernel/_vendor/jedi_language_server/__init__.py
+diff --git a/extensions/positron-python/python_files/positron/positron_ipykernel/_vendor/jedi_language_server/__init__.py b/extensions/positron-python/python_files/positron/positron_ipykernel/_vendor/jedi_language_server/__init__.py
index ba6eaf9fe..28266bd95 100644
---- a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/_vendor/jedi_language_server/__init__.py
-+++ b/extensions/positron-python/pythonFiles/positron/positron_ipykernel/_vendor/jedi_language_server/__init__.py
+--- a/extensions/positron-python/python_files/positron/positron_ipykernel/_vendor/jedi_language_server/__init__.py
++++ b/extensions/positron-python/python_files/positron/positron_ipykernel/_vendor/jedi_language_server/__init__.py
@@ -1,4 +1,3 @@
"""Jedi Language Server."""
-from importlib.metadata import version
diff --git a/extensions/positron-python/scripts/patches/jedi.patch b/extensions/positron-python/scripts/patches/jedi.patch
index eabb923f86a..077a5374ce2 100644
--- a/extensions/positron-python/scripts/patches/jedi.patch
+++ b/extensions/positron-python/scripts/patches/jedi.patch
@@ -1,7 +1,7 @@
-diff --git a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/_vendor/jedi/api/replstartup.py b/extensions/positron-python/pythonFiles/positron/positron_ipykernel/_vendor/jedi/api/replstartup.py
+diff --git a/extensions/positron-python/python_files/positron/positron_ipykernel/_vendor/jedi/api/replstartup.py b/extensions/positron-python/python_files/positron/positron_ipykernel/_vendor/jedi/api/replstartup.py
index e0f23d19b..f30731476 100644
---- a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/_vendor/jedi/api/replstartup.py
-+++ b/extensions/positron-python/pythonFiles/positron/positron_ipykernel/_vendor/jedi/api/replstartup.py
+--- a/extensions/positron-python/python_files/positron/positron_ipykernel/_vendor/jedi/api/replstartup.py
++++ b/extensions/positron-python/python_files/positron/positron_ipykernel/_vendor/jedi/api/replstartup.py
@@ -17,13 +17,13 @@ Then you will be able to use Jedi completer in your Python interpreter::
..dex ..sert
@@ -19,10 +19,10 @@ index e0f23d19b..f30731476 100644
# Note: try not to do many things here, as it will contaminate global
# namespace of the interpreter.
-diff --git a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/_vendor/jedi/inference/compiled/subprocess/__main__.py b/extensions/positron-python/pythonFiles/positron/positron_ipykernel/_vendor/jedi/inference/compiled/subprocess/__main__.py
+diff --git a/extensions/positron-python/python_files/positron/positron_ipykernel/_vendor/jedi/inference/compiled/subprocess/__main__.py b/extensions/positron-python/python_files/positron/positron_ipykernel/_vendor/jedi/inference/compiled/subprocess/__main__.py
index f044e2ee1..beec3f0cf 100644
---- a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/_vendor/jedi/inference/compiled/subprocess/__main__.py
-+++ b/extensions/positron-python/pythonFiles/positron/positron_ipykernel/_vendor/jedi/inference/compiled/subprocess/__main__.py
+--- a/extensions/positron-python/python_files/positron/positron_ipykernel/_vendor/jedi/inference/compiled/subprocess/__main__.py
++++ b/extensions/positron-python/python_files/positron/positron_ipykernel/_vendor/jedi/inference/compiled/subprocess/__main__.py
@@ -9,12 +9,9 @@ del sys.path[0]
diff --git a/extensions/positron-python/scripts/patches/parso.patch b/extensions/positron-python/scripts/patches/parso.patch
index 3121436e935..52ec34819b8 100644
--- a/extensions/positron-python/scripts/patches/parso.patch
+++ b/extensions/positron-python/scripts/patches/parso.patch
@@ -2,10 +2,10 @@ We append `-Positron` to the `_VERSION_TAG` so that our cache directories don't
installation in the user's environment. The vendored parso has a different import path than the user's,
so using the same cache can cause unexpected errors.
-diff --git a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/_vendor/parso/cache.py b/extensions/positron-python/pythonFiles/positron/positron_ipykernel/_vendor/parso/cache.py
+diff --git a/extensions/positron-python/python_files/positron/positron_ipykernel/_vendor/parso/cache.py b/extensions/positron-python/python_files/positron/positron_ipykernel/_vendor/parso/cache.py
index 5592a9fdd..98b903aaa 100644
---- a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/_vendor/parso/cache.py
-+++ b/extensions/positron-python/pythonFiles/positron/positron_ipykernel/_vendor/parso/cache.py
+--- a/extensions/positron-python/python_files/positron/positron_ipykernel/_vendor/parso/cache.py
++++ b/extensions/positron-python/python_files/positron/positron_ipykernel/_vendor/parso/cache.py
@@ -49,7 +49,7 @@ are regarded as incompatible.
- A __slot__ of a class is changed.
"""
diff --git a/extensions/positron-python/scripts/patches/pydantic.patch b/extensions/positron-python/scripts/patches/pydantic.patch
index 4302de1b042..0be76d478ef 100644
--- a/extensions/positron-python/scripts/patches/pydantic.patch
+++ b/extensions/positron-python/scripts/patches/pydantic.patch
@@ -1,7 +1,7 @@
-diff --git a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/_vendor/pydantic/_hypothesis_plugin.py b/extensions/positron-python/pythonFiles/positron/positron_ipykernel/_vendor/pydantic/_hypothesis_plugin.py
+diff --git a/extensions/positron-python/python_files/positron/positron_ipykernel/_vendor/pydantic/_hypothesis_plugin.py b/extensions/positron-python/python_files/positron/positron_ipykernel/_vendor/pydantic/_hypothesis_plugin.py
index 0c529620f..ce9df7a36 100644
---- a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/_vendor/pydantic/_hypothesis_plugin.py
-+++ b/extensions/positron-python/pythonFiles/positron/positron_ipykernel/_vendor/pydantic/_hypothesis_plugin.py
+--- a/extensions/positron-python/python_files/positron/positron_ipykernel/_vendor/pydantic/_hypothesis_plugin.py
++++ b/extensions/positron-python/python_files/positron/positron_ipykernel/_vendor/pydantic/_hypothesis_plugin.py
@@ -33,7 +33,7 @@ from typing import Callable, Dict, Type, Union, cast, overload
import hypothesis.strategies as st
@@ -46,10 +46,10 @@ index 0c529620f..ce9df7a36 100644
st.tuples(
st.integers(0, 255),
st.integers(0, 255),
-diff --git a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/_vendor/pydantic/_hypothesis_plugin.py b/extensions/positron-python/pythonFiles/positron/positron_ipykernel/_vendor/pydantic/_hypothesis_plugin.py
+diff --git a/extensions/positron-python/python_files/positron/positron_ipykernel/_vendor/pydantic/_hypothesis_plugin.py b/extensions/positron-python/python_files/positron/positron_ipykernel/_vendor/pydantic/_hypothesis_plugin.py
index ce9df7a36..93ff17077 100644
---- a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/_vendor/pydantic/_hypothesis_plugin.py
-+++ b/extensions/positron-python/pythonFiles/positron/positron_ipykernel/_vendor/pydantic/_hypothesis_plugin.py
+--- a/extensions/positron-python/python_files/positron/positron_ipykernel/_vendor/pydantic/_hypothesis_plugin.py
++++ b/extensions/positron-python/python_files/positron/positron_ipykernel/_vendor/pydantic/_hypothesis_plugin.py
@@ -34,7 +34,7 @@ import hypothesis.strategies as st
import pydantic
diff --git a/extensions/positron-python/scripts/patches/pygments.patch b/extensions/positron-python/scripts/patches/pygments.patch
index 5e4968342f4..7603a9b8bb2 100644
--- a/extensions/positron-python/scripts/patches/pygments.patch
+++ b/extensions/positron-python/scripts/patches/pygments.patch
@@ -1,7 +1,7 @@
-diff --git a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/_vendor/pygments/cmdline.py b/extensions/positron-python/pythonFiles/positron/positron_ipykernel/_vendor/pygments/cmdline.py
+diff --git a/extensions/positron-python/python_files/positron/positron_ipykernel/_vendor/pygments/cmdline.py b/extensions/positron-python/python_files/positron/positron_ipykernel/_vendor/pygments/cmdline.py
index 435231e65..b75a9d7f4 100644
---- a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/_vendor/pygments/cmdline.py
-+++ b/extensions/positron-python/pythonFiles/positron/positron_ipykernel/_vendor/pygments/cmdline.py
+--- a/extensions/positron-python/python_files/positron/positron_ipykernel/_vendor/pygments/cmdline.py
++++ b/extensions/positron-python/python_files/positron/positron_ipykernel/_vendor/pygments/cmdline.py
@@ -469,11 +469,11 @@ def main_inner(parser, argns):
outfile = UnclosingTextIOWrapper(outfile, encoding=fmter.encoding)
fmter.encoding = None
@@ -16,10 +16,10 @@ index 435231e65..b75a9d7f4 100644
outfile, convert=None, strip=None, autoreset=False, wrap=True)
# When using the LaTeX formatter and the option `escapeinside` is
-diff --git a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/_vendor/pygments/__main__.py b/extensions/positron-python/pythonFiles/positron/positron_ipykernel/_vendor/pygments/__main__.py
+diff --git a/extensions/positron-python/python_files/positron/positron_ipykernel/_vendor/pygments/__main__.py b/extensions/positron-python/python_files/positron/positron_ipykernel/_vendor/pygments/__main__.py
index 5eb2c747a..b8b833ec4 100644
---- a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/_vendor/pygments/__main__.py
-+++ b/extensions/positron-python/pythonFiles/positron/positron_ipykernel/_vendor/pygments/__main__.py
+--- a/extensions/positron-python/python_files/positron/positron_ipykernel/_vendor/pygments/__main__.py
++++ b/extensions/positron-python/python_files/positron/positron_ipykernel/_vendor/pygments/__main__.py
@@ -9,9 +9,9 @@
"""
@@ -32,10 +32,10 @@ index 5eb2c747a..b8b833ec4 100644
+ sys.exit(main(sys.argv))
except KeyboardInterrupt:
sys.exit(1)
-diff --git a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/_vendor/pygments/sphinxext.py b/extensions/positron-python/pythonFiles/positron/positron_ipykernel/_vendor/pygments/sphinxext.py
+diff --git a/extensions/positron-python/python_files/positron/positron_ipykernel/_vendor/pygments/sphinxext.py b/extensions/positron-python/python_files/positron/positron_ipykernel/_vendor/pygments/sphinxext.py
index f935688f1..4c293489f 100644
---- a/extensions/positron-python/pythonFiles/positron/positron_ipykernel/_vendor/pygments/sphinxext.py
-+++ b/extensions/positron-python/pythonFiles/positron/positron_ipykernel/_vendor/pygments/sphinxext.py
+--- a/extensions/positron-python/python_files/positron/positron_ipykernel/_vendor/pygments/sphinxext.py
++++ b/extensions/positron-python/python_files/positron/positron_ipykernel/_vendor/pygments/sphinxext.py
@@ -91,7 +91,7 @@ class PygmentsDoc(Directive):
The columns are the lexer name, the extensions handled by this lexer
(or "None"), the aliases and a link to the lexer class."""
diff --git a/extensions/positron-python/scripts/vendor.py b/extensions/positron-python/scripts/vendor.py
index a57614a1b20..fbfaa1a64a8 100644
--- a/extensions/positron-python/scripts/vendor.py
+++ b/extensions/positron-python/scripts/vendor.py
@@ -25,9 +25,11 @@ def main() -> None:
cfg = Configuration(
base_directory=project_path,
- destination=Path("pythonFiles/positron/positron_ipykernel/_vendor/"),
+ destination=Path("python_files/positron/positron_ipykernel/_vendor/"),
namespace="positron_ipykernel._vendor",
- requirements=Path("pythonFiles/jedilsp_requirements/requirements.txt"),
+ requirements=Path(
+ "python_files/jedilsp_requirements/requirements.txt"
+ ),
patches_dir=Path("scripts/patches"),
substitutions=[
# Fix pygments.lexers._mapping strings, via: https://github.com/pypa/pip/blob/main/pyproject.toml
@@ -151,7 +153,9 @@ def run(args: List[str], cwd: Optional[str] = None) -> None:
if retcode is not None:
break
if retcode:
- raise VendoringError(f"Command exited with non-zero exit code: {retcode}")
+ raise VendoringError(
+ f"Command exited with non-zero exit code: {retcode}"
+ )
def detect_vendored_libs(destination: Path) -> List[str]:
diff --git a/extensions/positron-python/src/client/activation/jedi/languageClientFactory.ts b/extensions/positron-python/src/client/activation/jedi/languageClientFactory.ts
index c3ef8d9623f..70bd65da8d0 100644
--- a/extensions/positron-python/src/client/activation/jedi/languageClientFactory.ts
+++ b/extensions/positron-python/src/client/activation/jedi/languageClientFactory.ts
@@ -21,7 +21,7 @@ export class JediLanguageClientFactory implements ILanguageClientFactory {
clientOptions: LanguageClientOptions,
): Promise {
// Just run the language server using a module
- const lsScriptPath = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'run-jedi-language-server.py');
+ const lsScriptPath = path.join(EXTENSION_ROOT_DIR, 'python_files', 'run-jedi-language-server.py');
const interpreter = await this.interpreterService.getActiveInterpreter(resource);
const serverOptions: ServerOptions = {
command: interpreter ? interpreter.path : 'python',
diff --git a/extensions/positron-python/src/client/activation/jedi/manager.ts b/extensions/positron-python/src/client/activation/jedi/manager.ts
index 7c2d5707402..a99890dff0f 100644
--- a/extensions/positron-python/src/client/activation/jedi/manager.ts
+++ b/extensions/positron-python/src/client/activation/jedi/manager.ts
@@ -68,7 +68,7 @@ export class JediLanguageServerManager implements ILanguageServerManager {
try {
// Version is actually hardcoded in our requirements.txt.
const requirementsTxt = await fs.readFile(
- path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'jedilsp_requirements', 'requirements.txt'),
+ path.join(EXTENSION_ROOT_DIR, 'python_files', 'jedilsp_requirements', 'requirements.txt'),
'utf-8',
);
diff --git a/extensions/positron-python/src/client/activation/languageClientMiddlewareBase.ts b/extensions/positron-python/src/client/activation/languageClientMiddlewareBase.ts
index 5f7b6fa7265..f1e102a4081 100644
--- a/extensions/positron-python/src/client/activation/languageClientMiddlewareBase.ts
+++ b/extensions/positron-python/src/client/activation/languageClientMiddlewareBase.ts
@@ -87,10 +87,7 @@ export class LanguageClientMiddlewareBase implements Middleware {
const settingDict: LSPObject & { pythonPath: string; _envPYTHONPATH: string } = settings[
i
] as LSPObject & { pythonPath: string; _envPYTHONPATH: string };
- settingDict.pythonPath =
- (await this.getPythonPathOverride(uri)) ??
- (await interpreterService.getActiveInterpreter(uri))?.path ??
- 'python';
+ settingDict.pythonPath = (await interpreterService.getActiveInterpreter(uri))?.path ?? 'python';
const env = await envService.getEnvironmentVariables(uri);
const envPYTHONPATH = env.PYTHONPATH;
@@ -106,11 +103,6 @@ export class LanguageClientMiddlewareBase implements Middleware {
},
};
- // eslint-disable-next-line class-methods-use-this
- protected async getPythonPathOverride(_uri: Uri | undefined): Promise {
- return undefined;
- }
-
// eslint-disable-next-line class-methods-use-this, @typescript-eslint/no-empty-function
protected configurationHook(_item: ConfigurationItem, _settings: LSPObject): void {}
diff --git a/extensions/positron-python/src/client/activation/node/languageClientMiddleware.ts b/extensions/positron-python/src/client/activation/node/languageClientMiddleware.ts
index fbc534f17e1..1a5da3d1a34 100644
--- a/extensions/positron-python/src/client/activation/node/languageClientMiddleware.ts
+++ b/extensions/positron-python/src/client/activation/node/languageClientMiddleware.ts
@@ -1,104 +1,21 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
-import { Uri } from 'vscode';
-import { ConfigurationItem, LanguageClient, LSPObject } from 'vscode-languageclient/node';
-import { IJupyterExtensionDependencyManager, IWorkspaceService } from '../../common/application/types';
+import { IJupyterExtensionDependencyManager } from '../../common/application/types';
import { IServiceContainer } from '../../ioc/types';
-import { JupyterExtensionIntegration } from '../../jupyter/jupyterIntegration';
-import { traceLog } from '../../logging';
import { LanguageClientMiddleware } from '../languageClientMiddleware';
-import { LspInteractiveWindowMiddlewareAddon } from './lspInteractiveWindowMiddlewareAddon';
import { LanguageServerType } from '../types';
-import { LspNotebooksExperiment } from './lspNotebooksExperiment';
-
export class NodeLanguageClientMiddleware extends LanguageClientMiddleware {
- private readonly lspNotebooksExperiment: LspNotebooksExperiment;
-
- private readonly jupyterExtensionIntegration: JupyterExtensionIntegration;
-
- private readonly workspaceService: IWorkspaceService;
-
- public constructor(
- serviceContainer: IServiceContainer,
- private getClient: () => LanguageClient | undefined,
- serverVersion?: string,
- ) {
+ public constructor(serviceContainer: IServiceContainer, serverVersion?: string) {
super(serviceContainer, LanguageServerType.Node, serverVersion);
- this.workspaceService = serviceContainer.get(IWorkspaceService);
-
- this.lspNotebooksExperiment = serviceContainer.get(LspNotebooksExperiment);
this.setupHidingMiddleware(serviceContainer);
-
- this.jupyterExtensionIntegration = serviceContainer.get(
- JupyterExtensionIntegration,
- );
- if (!this.notebookAddon) {
- this.notebookAddon = new LspInteractiveWindowMiddlewareAddon(
- this.getClient,
- this.jupyterExtensionIntegration,
- );
- }
}
// eslint-disable-next-line class-methods-use-this
protected shouldCreateHidingMiddleware(_: IJupyterExtensionDependencyManager): boolean {
return false;
}
-
- protected async onExtensionChange(jupyterDependencyManager: IJupyterExtensionDependencyManager): Promise {
- if (jupyterDependencyManager && jupyterDependencyManager.isJupyterExtensionInstalled) {
- await this.lspNotebooksExperiment.onJupyterInstalled();
- }
-
- if (!this.notebookAddon) {
- this.notebookAddon = new LspInteractiveWindowMiddlewareAddon(
- this.getClient,
- this.jupyterExtensionIntegration,
- );
- }
- }
-
- protected async getPythonPathOverride(uri: Uri | undefined): Promise {
- if (!uri) {
- return undefined;
- }
-
- const jupyterPythonPathFunction = this.jupyterExtensionIntegration.getJupyterPythonPathFunction();
- if (!jupyterPythonPathFunction) {
- return undefined;
- }
-
- const result = await jupyterPythonPathFunction(uri);
-
- if (result) {
- traceLog(`Jupyter provided interpreter path override: ${result}`);
- }
-
- return result;
- }
-
- // eslint-disable-next-line class-methods-use-this
- protected configurationHook(item: ConfigurationItem, settings: LSPObject): void {
- if (item.section === 'editor') {
- if (this.workspaceService) {
- // Get editor.formatOnType using Python language id so [python] setting
- // will be honored if present.
- const editorConfig = this.workspaceService.getConfiguration(
- item.section,
- undefined,
- /* languageSpecific */ true,
- );
-
- const settingDict: LSPObject & { formatOnType?: boolean } = settings as LSPObject & {
- formatOnType: boolean;
- };
-
- settingDict.formatOnType = editorConfig.get('formatOnType');
- }
- }
- }
}
diff --git a/extensions/positron-python/src/client/activation/node/lspInteractiveWindowMiddlewareAddon.ts b/extensions/positron-python/src/client/activation/node/lspInteractiveWindowMiddlewareAddon.ts
deleted file mode 100644
index 0a40915d98e..00000000000
--- a/extensions/positron-python/src/client/activation/node/lspInteractiveWindowMiddlewareAddon.ts
+++ /dev/null
@@ -1,187 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-import { Disposable, NotebookCell, NotebookDocument, TextDocument, TextDocumentChangeEvent, Uri } from 'vscode';
-import { Converter } from 'vscode-languageclient/lib/common/codeConverter';
-import {
- DidChangeNotebookDocumentNotification,
- LanguageClient,
- Middleware,
- NotebookCellKind,
- NotebookDocumentChangeEvent,
-} from 'vscode-languageclient/node';
-import * as proto from 'vscode-languageserver-protocol';
-import { JupyterExtensionIntegration } from '../../jupyter/jupyterIntegration';
-
-type TextContent = Required['cells']>['textContent']>[0];
-
-/**
- * Detects the input box text documents of Interactive Windows and makes them appear to be
- * the last cell of their corresponding notebooks.
- */
-export class LspInteractiveWindowMiddlewareAddon implements Middleware, Disposable {
- constructor(
- private readonly getClient: () => LanguageClient | undefined,
- private readonly jupyterExtensionIntegration: JupyterExtensionIntegration,
- ) {
- // Make sure a bunch of functions are bound to this. VS code can call them without a this context
- this.didOpen = this.didOpen.bind(this);
- this.didChange = this.didChange.bind(this);
- this.didClose = this.didClose.bind(this);
- }
-
- public dispose(): void {
- // Nothing to dispose at the moment
- }
-
- // Map of document URIs to NotebookDocuments for all known notebooks.
- private notebookDocumentMap: Map = new Map();
-
- // Map of document URIs to TextDocuments that should be linked to a notebook
- // whose didOpen we're expecting to see in the future.
- private unlinkedTextDocumentMap: Map = new Map();
-
- public async didOpen(document: TextDocument, next: (ev: TextDocument) => Promise): Promise {
- const notebookUri = this.getNotebookUriForTextDocumentUri(document.uri);
- if (!notebookUri) {
- await next(document);
- return;
- }
-
- const notebookDocument = this.notebookDocumentMap.get(notebookUri.toString());
- if (!notebookDocument) {
- this.unlinkedTextDocumentMap.set(notebookUri.toString(), document);
- return;
- }
-
- try {
- const result: NotebookDocumentChangeEvent = {
- cells: {
- structure: {
- array: {
- start: notebookDocument.cellCount,
- deleteCount: 0,
- cells: [{ kind: NotebookCellKind.Code, document: document.uri.toString() }],
- },
- didOpen: [
- {
- uri: document.uri.toString(),
- languageId: document.languageId,
- version: document.version,
- text: document.getText(),
- },
- ],
- didClose: undefined,
- },
- },
- };
-
- await this.getClient()?.sendNotification(DidChangeNotebookDocumentNotification.type, {
- notebookDocument: { version: notebookDocument.version, uri: notebookUri.toString() },
- change: result,
- });
- } catch (error) {
- this.getClient()?.error('Sending DidChangeNotebookDocumentNotification failed', error);
- throw error;
- }
- }
-
- public async didChange(
- event: TextDocumentChangeEvent,
- next: (ev: TextDocumentChangeEvent) => Promise,
- ): Promise {
- const notebookUri = this.getNotebookUriForTextDocumentUri(event.document.uri);
- if (!notebookUri) {
- await next(event);
- return;
- }
-
- const notebookDocument = this.notebookDocumentMap.get(notebookUri.toString());
- if (notebookDocument) {
- const client = this.getClient();
- if (client) {
- client.sendNotification(proto.DidChangeNotebookDocumentNotification.type, {
- notebookDocument: { uri: notebookUri.toString(), version: notebookDocument.version },
- change: {
- cells: {
- textContent: [
- LspInteractiveWindowMiddlewareAddon._asTextContentChange(
- event,
- client.code2ProtocolConverter,
- ),
- ],
- },
- },
- });
- }
- }
- }
-
- private static _asTextContentChange(event: TextDocumentChangeEvent, c2pConverter: Converter): TextContent {
- const params = c2pConverter.asChangeTextDocumentParams(event, event.document.uri, event.document.version);
- return { document: params.textDocument, changes: params.contentChanges };
- }
-
- public async didClose(document: TextDocument, next: (ev: TextDocument) => Promise): Promise {
- const notebookUri = this.getNotebookUriForTextDocumentUri(document.uri);
- if (!notebookUri) {
- await next(document);
- return;
- }
-
- this.unlinkedTextDocumentMap.delete(notebookUri.toString());
- }
-
- public async didOpenNotebook(
- notebookDocument: NotebookDocument,
- cells: NotebookCell[],
- next: (notebookDocument: NotebookDocument, cells: NotebookCell[]) => Promise,
- ): Promise {
- this.notebookDocumentMap.set(notebookDocument.uri.toString(), notebookDocument);
-
- const relatedTextDocument = this.unlinkedTextDocumentMap.get(notebookDocument.uri.toString());
- if (relatedTextDocument) {
- const newCells = [
- ...cells,
- {
- index: notebookDocument.cellCount,
- notebook: notebookDocument,
- kind: NotebookCellKind.Code,
- document: relatedTextDocument,
- metadata: {},
- outputs: [],
- executionSummary: undefined,
- },
- ];
-
- this.unlinkedTextDocumentMap.delete(notebookDocument.uri.toString());
-
- await next(notebookDocument, newCells);
- } else {
- await next(notebookDocument, cells);
- }
- }
-
- public async didCloseNotebook(
- notebookDocument: NotebookDocument,
- cells: NotebookCell[],
- next: (notebookDocument: NotebookDocument, cells: NotebookCell[]) => Promise,
- ): Promise {
- this.notebookDocumentMap.delete(notebookDocument.uri.toString());
-
- await next(notebookDocument, cells);
- }
-
- notebooks = {
- didOpen: this.didOpenNotebook.bind(this),
- didClose: this.didCloseNotebook.bind(this),
- };
-
- private getNotebookUriForTextDocumentUri(textDocumentUri: Uri): Uri | undefined {
- const getNotebookUriFunction = this.jupyterExtensionIntegration.getGetNotebookUriForTextDocumentUriFunction();
- if (!getNotebookUriFunction) {
- return undefined;
- }
-
- return getNotebookUriFunction(textDocumentUri);
- }
-}
diff --git a/extensions/positron-python/src/client/activation/node/lspNotebooksExperiment.ts b/extensions/positron-python/src/client/activation/node/lspNotebooksExperiment.ts
deleted file mode 100644
index de0acde0600..00000000000
--- a/extensions/positron-python/src/client/activation/node/lspNotebooksExperiment.ts
+++ /dev/null
@@ -1,60 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-import { inject, injectable } from 'inversify';
-import { IExtensionSingleActivationService } from '../types';
-import { traceVerbose } from '../../logging';
-import { IJupyterExtensionDependencyManager } from '../../common/application/types';
-import { IServiceContainer } from '../../ioc/types';
-import { sleep } from '../../common/utils/async';
-import { JupyterExtensionIntegration } from '../../jupyter/jupyterIntegration';
-
-@injectable()
-export class LspNotebooksExperiment implements IExtensionSingleActivationService {
- public readonly supportedWorkspaceTypes = { untrustedWorkspace: true, virtualWorkspace: true };
-
- private isJupyterInstalled = false;
-
- constructor(
- @inject(IServiceContainer) private readonly serviceContainer: IServiceContainer,
- @inject(IJupyterExtensionDependencyManager) jupyterDependencyManager: IJupyterExtensionDependencyManager,
- ) {
- this.isJupyterInstalled = jupyterDependencyManager.isJupyterExtensionInstalled;
- }
-
- // eslint-disable-next-line class-methods-use-this
- public activate(): Promise {
- return Promise.resolve();
- }
-
- public async onJupyterInstalled(): Promise {
- if (this.isJupyterInstalled) {
- return;
- }
-
- await this.waitForJupyterToRegisterPythonPathFunction();
-
- this.isJupyterInstalled = true;
- }
-
- private async waitForJupyterToRegisterPythonPathFunction(): Promise {
- const jupyterExtensionIntegration = this.serviceContainer.get(
- JupyterExtensionIntegration,
- );
-
- let success = false;
- for (let tryCount = 0; tryCount < 20; tryCount += 1) {
- const jupyterPythonPathFunction = jupyterExtensionIntegration.getJupyterPythonPathFunction();
- if (jupyterPythonPathFunction) {
- traceVerbose(`Jupyter called registerJupyterPythonPathFunction`);
- success = true;
- break;
- }
-
- await sleep(500);
- }
-
- if (!success) {
- traceVerbose(`Timed out waiting for Jupyter to call registerJupyterPythonPathFunction`);
- }
- }
-}
diff --git a/extensions/positron-python/src/client/activation/node/manager.ts b/extensions/positron-python/src/client/activation/node/manager.ts
index d2288a43367..cf7419aa918 100644
--- a/extensions/positron-python/src/client/activation/node/manager.ts
+++ b/extensions/positron-python/src/client/activation/node/manager.ts
@@ -117,11 +117,7 @@ export class NodeLanguageServerManager implements ILanguageServerManager {
@traceDecoratorVerbose('Starting language server')
protected async startLanguageServer(): Promise {
const options = await this.analysisOptions.getAnalysisOptions();
- this.middleware = new NodeLanguageClientMiddleware(
- this.serviceContainer,
- () => this.languageServerProxy.languageClient,
- this.lsVersion,
- );
+ this.middleware = new NodeLanguageClientMiddleware(this.serviceContainer, this.lsVersion);
options.middleware = this.middleware;
// Make sure the middleware is connected if we restart and we we're already connected.
diff --git a/extensions/positron-python/src/client/activation/node/pylanceApi.ts b/extensions/positron-python/src/client/activation/node/pylanceApi.ts
index 72f20db140e..4b3d21d7527 100644
--- a/extensions/positron-python/src/client/activation/node/pylanceApi.ts
+++ b/extensions/positron-python/src/client/activation/node/pylanceApi.ts
@@ -18,7 +18,6 @@ export interface PylanceApi {
};
notebook?: {
registerJupyterPythonPathFunction(func: (uri: Uri) => Promise): void;
- registerGetNotebookUriForTextDocumentUriFunction(func: (textDocumentUri: Uri) => Uri | undefined): void;
getCompletionItems(
document: TextDocument,
position: Position,
diff --git a/extensions/positron-python/src/client/activation/serviceRegistry.ts b/extensions/positron-python/src/client/activation/serviceRegistry.ts
index 7bfea039962..5e1828e5831 100644
--- a/extensions/positron-python/src/client/activation/serviceRegistry.ts
+++ b/extensions/positron-python/src/client/activation/serviceRegistry.ts
@@ -17,7 +17,6 @@ import { LoadLanguageServerExtension } from './common/loadLanguageServerExtensio
import { PartialModeStatusItem } from './partialModeStatus';
import { ILanguageServerWatcher } from '../languageServer/types';
import { LanguageServerWatcher } from '../languageServer/watcher';
-import { LspNotebooksExperiment } from './node/lspNotebooksExperiment';
export function registerTypes(serviceManager: IServiceManager): void {
serviceManager.addSingleton(IExtensionActivationService, PartialModeStatusItem);
@@ -36,6 +35,4 @@ export function registerTypes(serviceManager: IServiceManager): void {
serviceManager.addSingleton(ILanguageServerWatcher, LanguageServerWatcher);
serviceManager.addBinding(ILanguageServerWatcher, IExtensionActivationService);
- serviceManager.addSingleton(LspNotebooksExperiment, LspNotebooksExperiment);
- serviceManager.addBinding(LspNotebooksExperiment, IExtensionSingleActivationService);
}
diff --git a/extensions/positron-python/src/client/api/types.ts b/extensions/positron-python/src/client/api/types.ts
index 4de554bf5a2..873d5e802d5 100644
--- a/extensions/positron-python/src/client/api/types.ts
+++ b/extensions/positron-python/src/client/api/types.ts
@@ -16,7 +16,7 @@ export interface PythonExtension {
/**
* Generate an array of strings for commands to pass to the Python executable to launch the debugger for remote debugging.
* Users can append another array of strings of what they want to execute along with relevant arguments to Python.
- * E.g `['/Users/..../pythonVSCode/pythonFiles/lib/python/debugpy', '--listen', 'localhost:57039', '--wait-for-client']`
+ * E.g `['/Users/..../pythonVSCode/python_files/lib/python/debugpy', '--listen', 'localhost:57039', '--wait-for-client']`
* @param host
* @param port
* @param waitUntilDebuggerAttaches Defaults to `true`.
@@ -268,6 +268,7 @@ export type KnownEnvironmentTools =
| 'Venv'
| 'VirtualEnvWrapper'
| 'Pyenv'
+ | 'Hatch'
| 'Unknown';
/**
diff --git a/extensions/positron-python/src/client/common/application/commands/reportIssueCommand.ts b/extensions/positron-python/src/client/common/application/commands/reportIssueCommand.ts
index d18299e6698..f5f1f0ac0c0 100644
--- a/extensions/positron-python/src/client/common/application/commands/reportIssueCommand.ts
+++ b/extensions/positron-python/src/client/common/application/commands/reportIssueCommand.ts
@@ -19,6 +19,7 @@ import { EventName } from '../../../telemetry/constants';
import { EnvironmentType } from '../../../pythonEnvironments/info';
import { PythonSettings } from '../../configSettings';
import { SystemVariables } from '../../variables/systemVariables';
+import { getExtensions } from '../../vscodeApis/extensionsApi';
/**
* Allows the user to report an issue related to the Python extension using our template.
@@ -48,6 +49,8 @@ export class ReportIssueCommandHandler implements IExtensionSingleActivationServ
private templatePath = path.join(EXTENSION_ROOT_DIR, 'resources', 'report_issue_template.md');
+ private userDataTemplatePath = path.join(EXTENSION_ROOT_DIR, 'resources', 'report_issue_user_data_template.md');
+
public async openReportIssue(): Promise {
const settings: IPythonSettings = this.configurationService.getSettings();
const argSettings = JSON.parse(await fs.readFile(this.argSettingsPath, 'utf8'));
@@ -86,6 +89,7 @@ export class ReportIssueCommandHandler implements IExtensionSingleActivationServ
}
});
const template = await fs.readFile(this.templatePath, 'utf8');
+ const userTemplate = await fs.readFile(this.userDataTemplatePath, 'utf8');
const interpreter = await this.interpreterService.getActiveInterpreter();
const pythonVersion = interpreter?.version?.raw ?? '';
const languageServer =
@@ -97,14 +101,25 @@ export class ReportIssueCommandHandler implements IExtensionSingleActivationServ
hasMultipleFolders && userSettings !== ''
? `Multiroot scenario, following user settings may not apply:${os.EOL}`
: '';
+
+ const installedExtensions = getExtensions()
+ .filter((extension) => !extension.id.startsWith('vscode.'))
+ .sort((a, b) => a.packageJSON.displayName.localeCompare(b.packageJSON.displayName))
+ .map(
+ (extension) =>
+ `|${extension.packageJSON.displayName}|${extension.id}|${extension.packageJSON.version}|`,
+ );
+
await this.commandManager.executeCommand('workbench.action.openIssueReporter', {
extensionId: 'ms-python.python',
- issueBody: template.format(
+ issueBody: template,
+ data: userTemplate.format(
pythonVersion,
virtualEnvKind,
languageServer,
hasMultipleFoldersText,
userSettings,
+ installedExtensions.join('\n'),
),
});
sendTelemetryEvent(EventName.USE_REPORT_ISSUE_COMMAND, undefined, {});
diff --git a/extensions/positron-python/src/client/common/experiments/groups.ts b/extensions/positron-python/src/client/common/experiments/groups.ts
index 5e302c926cc..d43f376ddc8 100644
--- a/extensions/positron-python/src/client/common/experiments/groups.ts
+++ b/extensions/positron-python/src/client/common/experiments/groups.ts
@@ -19,10 +19,6 @@ export enum DiscoveryUsingWorkers {
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 {
diff --git a/extensions/positron-python/src/client/common/persistentState.ts b/extensions/positron-python/src/client/common/persistentState.ts
index ea86b2e145b..e82299bfc67 100644
--- a/extensions/positron-python/src/client/common/persistentState.ts
+++ b/extensions/positron-python/src/client/common/persistentState.ts
@@ -6,7 +6,7 @@
import { inject, injectable, named } from 'inversify';
import { Memento } from 'vscode';
import { IExtensionSingleActivationService } from '../activation/types';
-import { traceError, traceVerbose, traceWarn } from '../logging';
+import { traceError } from '../logging';
import { ICommandManager } from './application/types';
import { Commands } from './constants';
import {
@@ -92,12 +92,8 @@ export class PersistentState implements IPersistentState {
// Due to a VSCode bug sometimes the changes are not reflected in the storage, atleast not immediately.
// It is noticed however that if we reset the storage first and then update it, it works.
// https://github.com/microsoft/vscode/issues/171827
- traceVerbose('Storage update failed for key', this.key, ' retrying by resetting first');
await this.updateValue(undefined as any, false);
await this.updateValue(newValue, false);
- if (JSON.stringify(this.value) != JSON.stringify(newValue)) {
- traceWarn('Retry failed, storage update failed for key', this.key);
- }
}
} catch (ex) {
traceError('Error while updating storage for key:', this.key, ex);
diff --git a/extensions/positron-python/src/client/common/process/internal/scripts/constants.ts b/extensions/positron-python/src/client/common/process/internal/scripts/constants.ts
index 4448f7e639c..6954592ed3d 100644
--- a/extensions/positron-python/src/client/common/process/internal/scripts/constants.ts
+++ b/extensions/positron-python/src/client/common/process/internal/scripts/constants.ts
@@ -5,4 +5,4 @@ import * as path from 'path';
import { EXTENSION_ROOT_DIR } from '../../../constants';
// It is simpler to hard-code it instead of using vscode.ExtensionContext.extensionPath.
-export const _SCRIPTS_DIR = path.join(EXTENSION_ROOT_DIR, 'pythonFiles');
+export const _SCRIPTS_DIR = path.join(EXTENSION_ROOT_DIR, 'python_files');
diff --git a/extensions/positron-python/src/client/common/process/internal/scripts/index.ts b/extensions/positron-python/src/client/common/process/internal/scripts/index.ts
index 2644fd0d00f..f2c905c0288 100644
--- a/extensions/positron-python/src/client/common/process/internal/scripts/index.ts
+++ b/extensions/positron-python/src/client/common/process/internal/scripts/index.ts
@@ -7,7 +7,7 @@ import { _SCRIPTS_DIR } from './constants';
const SCRIPTS_DIR = _SCRIPTS_DIR;
// "scripts" contains everything relevant to the scripts found under
-// the top-level "pythonFiles" directory. Each of those scripts has
+// the top-level "python_files" directory. Each of those scripts has
// a function in this module which matches the script's filename.
// Each function provides the commandline arguments that should be
// used when invoking a Python executable, whether through spawn/exec
@@ -18,7 +18,7 @@ const SCRIPTS_DIR = _SCRIPTS_DIR;
// into the corresponding object or objects. "parse()" takes a single
// string as the stdout text and returns the relevant data.
//
-// Some of the scripts are located in subdirectories of "pythonFiles".
+// Some of the scripts are located in subdirectories of "python_files".
// For each of those subdirectories there is a sub-module where
// those scripts' functions may be found.
//
diff --git a/extensions/positron-python/src/client/common/terminal/service.ts b/extensions/positron-python/src/client/common/terminal/service.ts
index cf63603a2e9..c3b90181d56 100644
--- a/extensions/positron-python/src/client/common/terminal/service.ts
+++ b/extensions/positron-python/src/client/common/terminal/service.ts
@@ -31,7 +31,7 @@ export class TerminalService implements ITerminalService, Disposable {
private terminalHelper: ITerminalHelper;
private terminalActivator: ITerminalActivator;
private terminalAutoActivator: ITerminalAutoActivation;
- private readonly envVarScript = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'pythonrc.py');
+ private readonly envVarScript = path.join(EXTENSION_ROOT_DIR, 'python_files', 'pythonrc.py');
public get onDidCloseTerminal(): Event {
return this.terminalClosed.event.bind(this.terminalClosed);
}
diff --git a/extensions/positron-python/src/client/common/vscodeApis/extensionsApi.ts b/extensions/positron-python/src/client/common/vscodeApis/extensionsApi.ts
index ece424847a1..4e1664a3dfa 100644
--- a/extensions/positron-python/src/client/common/vscodeApis/extensionsApi.ts
+++ b/extensions/positron-python/src/client/common/vscodeApis/extensionsApi.ts
@@ -32,3 +32,8 @@ export function isExtensionDisabled(extensionId: string): boolean {
export function isInsider(): boolean {
return vscode.env.appName.includes('Insider');
}
+
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+export function getExtensions(): readonly vscode.Extension[] {
+ return vscode.extensions.all;
+}
diff --git a/extensions/positron-python/src/client/debugger/extension/adapter/factory.ts b/extensions/positron-python/src/client/debugger/extension/adapter/factory.ts
index ecbd8afcc28..cfc8cf91aba 100644
--- a/extensions/positron-python/src/client/debugger/extension/adapter/factory.ts
+++ b/extensions/positron-python/src/client/debugger/extension/adapter/factory.ts
@@ -93,7 +93,7 @@ export class DebugAdapterDescriptorFactory implements IDebugAdapterDescriptorFac
const debuggerAdapterPathToUse = path.join(
EXTENSION_ROOT_DIR,
- 'pythonFiles',
+ 'python_files',
'lib',
'python',
'debugpy',
diff --git a/extensions/positron-python/src/client/debugger/extension/adapter/remoteLaunchers.ts b/extensions/positron-python/src/client/debugger/extension/adapter/remoteLaunchers.ts
index f42f101f852..80e0289e3ad 100644
--- a/extensions/positron-python/src/client/debugger/extension/adapter/remoteLaunchers.ts
+++ b/extensions/positron-python/src/client/debugger/extension/adapter/remoteLaunchers.ts
@@ -7,7 +7,7 @@ import * as path from 'path';
import { EXTENSION_ROOT_DIR } from '../../../common/constants';
import '../../../common/extensions';
-const pathToPythonLibDir = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'lib', 'python');
+const pathToPythonLibDir = path.join(EXTENSION_ROOT_DIR, 'python_files', 'lib', 'python');
const pathToDebugger = path.join(pathToPythonLibDir, 'debugpy');
type RemoteDebugOptions = {
diff --git a/extensions/positron-python/src/client/environmentApi.ts b/extensions/positron-python/src/client/environmentApi.ts
index da6a132b2b4..5b77ecde1a9 100644
--- a/extensions/positron-python/src/client/environmentApi.ts
+++ b/extensions/positron-python/src/client/environmentApi.ts
@@ -32,6 +32,7 @@ import {
Resource,
} from './api/types';
import { buildEnvironmentCreationApi } from './pythonEnvironments/creation/createEnvApi';
+import { EnvironmentKnownCache } from './environmentKnownCache';
type ActiveEnvironmentChangeEvent = {
resource: WorkspaceFolder | undefined;
@@ -120,14 +121,27 @@ export function buildEnvironmentApi(
const disposables = serviceContainer.get(IDisposableRegistry);
const extensions = serviceContainer.get(IExtensions);
const envVarsProvider = serviceContainer.get(IEnvironmentVariablesProvider);
+ let knownCache: EnvironmentKnownCache;
+
+ function initKnownCache() {
+ const knownEnvs = discoveryApi
+ .getEnvs()
+ .filter((e) => filterUsingVSCodeContext(e))
+ .map((e) => updateReference(e));
+ return new EnvironmentKnownCache(knownEnvs);
+ }
function sendApiTelemetry(apiName: string, args?: unknown) {
extensions
.determineExtensionFromCallStack()
.then((info) => {
- sendTelemetryEvent(EventName.PYTHON_ENVIRONMENTS_API, undefined, {
- apiName,
- extensionId: info.extensionId,
- });
+ const p = Math.random();
+ if (p <= 0.001) {
+ // Only send API telemetry 1% of the time, as it can be chatty.
+ sendTelemetryEvent(EventName.PYTHON_ENVIRONMENTS_API, undefined, {
+ apiName,
+ extensionId: info.extensionId,
+ });
+ }
traceVerbose(`Extension ${info.extensionId} accessed ${apiName} with args: ${JSON.stringify(args)}`);
})
.ignoreErrors();
@@ -139,10 +153,15 @@ export function buildEnvironmentApi(
// Filter out environments that are not in the current workspace.
return;
}
+ if (!knownCache) {
+ knownCache = initKnownCache();
+ }
if (e.old) {
if (e.new) {
+ const newEnv = updateReference(e.new);
+ knownCache.updateEnv(convertEnvInfo(e.old), newEnv);
traceVerbose('Python API env change detected', env.id, 'update');
- onEnvironmentsChanged.fire({ type: 'update', env: convertEnvInfoAndGetReference(e.new) });
+ onEnvironmentsChanged.fire({ type: 'update', env: newEnv });
reportInterpretersChanged([
{
path: getEnvPath(e.new.executable.filename, e.new.location).path,
@@ -150,8 +169,10 @@ export function buildEnvironmentApi(
},
]);
} else {
+ const oldEnv = updateReference(e.old);
+ knownCache.updateEnv(oldEnv, undefined);
traceVerbose('Python API env change detected', env.id, 'remove');
- onEnvironmentsChanged.fire({ type: 'remove', env: convertEnvInfoAndGetReference(e.old) });
+ onEnvironmentsChanged.fire({ type: 'remove', env: oldEnv });
reportInterpretersChanged([
{
path: getEnvPath(e.old.executable.filename, e.old.location).path,
@@ -160,8 +181,10 @@ export function buildEnvironmentApi(
]);
}
} else if (e.new) {
+ const newEnv = updateReference(e.new);
+ knownCache.addEnv(newEnv);
traceVerbose('Python API env change detected', env.id, 'add');
- onEnvironmentsChanged.fire({ type: 'add', env: convertEnvInfoAndGetReference(e.new) });
+ onEnvironmentsChanged.fire({ type: 'add', env: newEnv });
reportInterpretersChanged([
{
path: getEnvPath(e.new.executable.filename, e.new.location).path,
@@ -179,6 +202,9 @@ export function buildEnvironmentApi(
onEnvironmentsChanged,
onEnvironmentVariablesChanged,
);
+ if (!knownCache!) {
+ knownCache = initKnownCache();
+ }
const environmentApi: PythonExtension['environments'] = {
getEnvironmentVariables: (resource?: Resource) => {
@@ -234,11 +260,9 @@ export function buildEnvironmentApi(
return resolveEnvironment(path, discoveryApi);
},
get known(): Environment[] {
- sendApiTelemetry('known');
- return discoveryApi
- .getEnvs()
- .filter((e) => filterUsingVSCodeContext(e))
- .map((e) => convertEnvInfoAndGetReference(e));
+ // Do not send telemetry for "known", as this may be called 1000s of times so it can significant:
+ // sendApiTelemetry('known');
+ return knownCache.envs;
},
async refreshEnvironments(options?: RefreshOptions) {
if (!workspace.isTrusted) {
@@ -318,6 +342,8 @@ function convertKind(kind: PythonEnvKind): EnvironmentTools | undefined {
return 'Pipenv';
case PythonEnvKind.Poetry:
return 'Poetry';
+ case PythonEnvKind.Hatch:
+ return 'Hatch';
case PythonEnvKind.VirtualEnvWrapper:
return 'VirtualEnvWrapper';
case PythonEnvKind.VirtualEnv:
@@ -351,7 +377,7 @@ export function convertEnvInfo(env: PythonEnvInfo): Environment {
return convertedEnv as Environment;
}
-function convertEnvInfoAndGetReference(env: PythonEnvInfo): Environment {
+function updateReference(env: PythonEnvInfo): Environment {
return getEnvReference(convertEnvInfo(env));
}
diff --git a/extensions/positron-python/src/client/environmentKnownCache.ts b/extensions/positron-python/src/client/environmentKnownCache.ts
new file mode 100644
index 00000000000..287f5bab343
--- /dev/null
+++ b/extensions/positron-python/src/client/environmentKnownCache.ts
@@ -0,0 +1,37 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+import { Environment } from './api/types';
+
+/**
+ * Workaround temp cache until types are consolidated.
+ */
+export class EnvironmentKnownCache {
+ private _envs: Environment[] = [];
+
+ constructor(envs: Environment[]) {
+ this._envs = envs;
+ }
+
+ public get envs(): Environment[] {
+ return this._envs;
+ }
+
+ public addEnv(env: Environment): void {
+ const found = this._envs.find((e) => env.id === e.id);
+ if (!found) {
+ this._envs.push(env);
+ }
+ }
+
+ public updateEnv(oldValue: Environment, newValue: Environment | undefined): void {
+ const index = this._envs.findIndex((e) => oldValue.id === e.id);
+ if (index !== -1) {
+ if (newValue === undefined) {
+ this._envs.splice(index, 1);
+ } else {
+ this._envs[index] = newValue;
+ }
+ }
+ }
+}
diff --git a/extensions/positron-python/src/client/interpreter/autoSelection/index.ts b/extensions/positron-python/src/client/interpreter/autoSelection/index.ts
index 4310374fc00..5ad5362e821 100644
--- a/extensions/positron-python/src/client/interpreter/autoSelection/index.ts
+++ b/extensions/positron-python/src/client/interpreter/autoSelection/index.ts
@@ -209,6 +209,7 @@ export class InterpreterAutoSelectionService implements IInterpreterAutoSelectio
});
}
+ await this.envTypeComparer.initialize(resource);
const inExperiment = this.experimentService.inExperimentSync(DiscoveryUsingWorkers.experiment);
const workspaceUri = this.interpreterHelper.getActiveWorkspaceUri(resource);
let recommendedInterpreter: PythonEnvironment | undefined;
diff --git a/extensions/positron-python/src/client/interpreter/configuration/environmentTypeComparer.ts b/extensions/positron-python/src/client/interpreter/configuration/environmentTypeComparer.ts
index 053f27d3a80..86392d84f35 100644
--- a/extensions/positron-python/src/client/interpreter/configuration/environmentTypeComparer.ts
+++ b/extensions/positron-python/src/client/interpreter/configuration/environmentTypeComparer.ts
@@ -11,10 +11,17 @@ import { Resource } from '../../common/types';
import { Architecture } from '../../common/utils/platform';
import { isActiveStateEnvironmentForWorkspace } from '../../pythonEnvironments/common/environmentManagers/activestate';
import { isParentPath } from '../../pythonEnvironments/common/externalDependencies';
-import { EnvironmentType, PythonEnvironment, virtualEnvTypes } from '../../pythonEnvironments/info';
+import {
+ EnvironmentType,
+ PythonEnvironment,
+ virtualEnvTypes,
+ workspaceVirtualEnvTypes,
+} from '../../pythonEnvironments/info';
import { PythonVersion } from '../../pythonEnvironments/info/pythonVersion';
import { IInterpreterHelper } from '../contracts';
import { IInterpreterComparer } from './types';
+import { getActivePyenvForDirectory } from '../../pythonEnvironments/common/environmentManagers/pyenv';
+import { arePathsSame } from '../../common/platform/fs-paths';
// --- Start Positron ---
import { getPyenvDir } from '../../pythonEnvironments/common/environmentManagers/pyenv';
@@ -36,6 +43,8 @@ export enum EnvLocationHeuristic {
export class EnvironmentTypeComparer implements IInterpreterComparer {
private workspaceFolderPath: string;
+ private preferredPyenvInterpreterPath = new Map();
+
constructor(@inject(IInterpreterHelper) private readonly interpreterHelper: IInterpreterHelper) {
this.workspaceFolderPath = this.interpreterHelper.getActiveWorkspaceUri(undefined)?.folderUri.fsPath ?? '';
}
@@ -64,6 +73,18 @@ export class EnvironmentTypeComparer implements IInterpreterComparer {
return envLocationComparison;
}
+ if (a.envType === EnvironmentType.Pyenv && b.envType === EnvironmentType.Pyenv) {
+ const preferredPyenv = this.preferredPyenvInterpreterPath.get(this.workspaceFolderPath);
+ if (preferredPyenv) {
+ if (arePathsSame(preferredPyenv, b.path)) {
+ return 1;
+ }
+ if (arePathsSame(preferredPyenv, a.path)) {
+ return -1;
+ }
+ }
+ }
+
// Check environment type.
const envTypeComparison = compareEnvironmentType(a, b);
if (envTypeComparison !== 0) {
@@ -95,6 +116,16 @@ export class EnvironmentTypeComparer implements IInterpreterComparer {
return nameA > nameB ? 1 : -1;
}
+ public async initialize(resource: Resource): Promise {
+ const workspaceUri = this.interpreterHelper.getActiveWorkspaceUri(resource);
+ const cwd = workspaceUri?.folderUri.fsPath;
+ if (!cwd) {
+ return;
+ }
+ const preferredPyenvInterpreter = await getActivePyenvForDirectory(cwd);
+ this.preferredPyenvInterpreterPath.set(cwd, preferredPyenvInterpreter);
+ }
+
public getRecommended(interpreters: PythonEnvironment[], resource: Resource): PythonEnvironment | undefined {
// When recommending an intepreter for a workspace, we either want to return a local one
// or fallback on a globally-installed interpreter, and we don't want want to suggest a global environment
@@ -121,8 +152,8 @@ export class EnvironmentTypeComparer implements IInterpreterComparer {
if (getEnvLocationHeuristic(i, workspaceUri?.folderUri.fsPath || '') === EnvLocationHeuristic.Local) {
return true;
}
- if (virtualEnvTypes.includes(i.envType)) {
- // We're not sure if these envs were created for the workspace, so do not recommend them.
+ if (!workspaceVirtualEnvTypes.includes(i.envType) && virtualEnvTypes.includes(i.envType)) {
+ // These are global virtual envs so we're not sure if these envs were created for the workspace, skip them.
return false;
}
if (i.version?.major === 2) {
@@ -265,10 +296,17 @@ export function getEnvLocationHeuristic(environment: PythonEnvironment, workspac
*/
function compareEnvironmentType(a: PythonEnvironment, b: PythonEnvironment): number {
// --- Start Positron ---
- if (!a.type && !b.type && a.envType !== EnvironmentType.Pyenv && b.envType !== EnvironmentType.Pyenv) {
- // Don't lump Pyenv environments together with all other global interpreters.
- // --- End Positron ---
- // Return 0 if two global interpreters are being compared.
+ // if (!a.type && !b.type && a.envType !== EnvironmentType.Pyenv && b.envType !== EnvironmentType.Pyenv) {
+ // Don't lump Pyenv environments together with all other global interpreters.
+ // --- End Positron ---
+ if (!a.type && !b.type) {
+ if (a.envType === EnvironmentType.Pyenv && b.envType !== EnvironmentType.Pyenv) {
+ return -1;
+ }
+ if (a.envType !== EnvironmentType.Pyenv && b.envType === EnvironmentType.Pyenv) {
+ return 1;
+ }
+
return 0;
}
const envTypeByPriority = getPrioritizedEnvironmentType();
@@ -281,6 +319,7 @@ function getPrioritizedEnvironmentType(): EnvironmentType[] {
EnvironmentType.Poetry,
EnvironmentType.Pipenv,
EnvironmentType.VirtualEnvWrapper,
+ EnvironmentType.Hatch,
EnvironmentType.Venv,
EnvironmentType.VirtualEnv,
EnvironmentType.ActiveState,
diff --git a/extensions/positron-python/src/client/interpreter/configuration/interpreterSelector/commands/setInterpreter.ts b/extensions/positron-python/src/client/interpreter/configuration/interpreterSelector/commands/setInterpreter.ts
index 5487f459a7a..5d01cbaafb7 100644
--- a/extensions/positron-python/src/client/interpreter/configuration/interpreterSelector/commands/setInterpreter.ts
+++ b/extensions/positron-python/src/client/interpreter/configuration/interpreterSelector/commands/setInterpreter.ts
@@ -74,6 +74,7 @@ export namespace EnvGroups {
export const Pyenv = 'Pyenv';
export const Venv = 'Venv';
export const Poetry = 'Poetry';
+ export const Hatch = 'Hatch';
export const VirtualEnvWrapper = 'VirtualEnvWrapper';
export const ActiveState = 'ActiveState';
export const Recommended = Common.recommended;
diff --git a/extensions/positron-python/src/client/interpreter/configuration/interpreterSelector/interpreterSelector.ts b/extensions/positron-python/src/client/interpreter/configuration/interpreterSelector/interpreterSelector.ts
index 8c94abe2c8b..6b33245bb90 100644
--- a/extensions/positron-python/src/client/interpreter/configuration/interpreterSelector/interpreterSelector.ts
+++ b/extensions/positron-python/src/client/interpreter/configuration/interpreterSelector/interpreterSelector.ts
@@ -5,7 +5,7 @@
import { inject, injectable } from 'inversify';
import { Disposable, Uri } from 'vscode';
-import { arePathsSame } from '../../../common/platform/fs-paths';
+import { arePathsSame, isParentPath } from '../../../common/platform/fs-paths';
import { IPathUtils, Resource } from '../../../common/types';
import { getEnvPath } from '../../../pythonEnvironments/base/info/env';
import { PythonEnvironment } from '../../../pythonEnvironments/info';
@@ -45,6 +45,13 @@ export class InterpreterSelector implements IInterpreterSelector {
workspaceUri?: Uri,
useDetailedName = false,
): IInterpreterQuickPickItem {
+ if (!useDetailedName) {
+ const workspacePath = workspaceUri?.fsPath;
+ if (workspacePath && isParentPath(interpreter.path, workspacePath)) {
+ // If interpreter is in the workspace, then display the full path.
+ useDetailedName = true;
+ }
+ }
const path =
interpreter.envPath && getEnvPath(interpreter.path, interpreter.envPath).pathType === 'envFolderPath'
? interpreter.envPath
diff --git a/extensions/positron-python/src/client/interpreter/configuration/types.ts b/extensions/positron-python/src/client/interpreter/configuration/types.ts
index 815de29045d..08518d4d12d 100644
--- a/extensions/positron-python/src/client/interpreter/configuration/types.ts
+++ b/extensions/positron-python/src/client/interpreter/configuration/types.ts
@@ -58,6 +58,7 @@ export interface ISpecialQuickPickItem extends QuickPickItem {
export const IInterpreterComparer = Symbol('IInterpreterComparer');
export interface IInterpreterComparer {
+ initialize(resource: Resource): Promise;
compare(a: PythonEnvironment, b: PythonEnvironment): number;
getRecommended(interpreters: PythonEnvironment[], resource: Resource): PythonEnvironment | undefined;
}
diff --git a/extensions/positron-python/src/client/interpreter/interpreterPathCommand.ts b/extensions/positron-python/src/client/interpreter/interpreterPathCommand.ts
index 8402374d50d..0dce208c9bf 100644
--- a/extensions/positron-python/src/client/interpreter/interpreterPathCommand.ts
+++ b/extensions/positron-python/src/client/interpreter/interpreterPathCommand.ts
@@ -46,6 +46,8 @@ export class InterpreterPathCommand implements IExtensionSingleActivationService
workspaceFolderUri = undefined;
}
- return (await this.interpreterService.getActiveInterpreter(workspaceFolderUri))?.path ?? 'python';
+ const interpreterPath =
+ (await this.interpreterService.getActiveInterpreter(workspaceFolderUri))?.path ?? 'python';
+ return interpreterPath.toCommandArgumentForPythonExt();
}
}
diff --git a/extensions/positron-python/src/client/jupyter/jupyterIntegration.ts b/extensions/positron-python/src/client/jupyter/jupyterIntegration.ts
index dbfd1bdf568..a385248bff0 100644
--- a/extensions/positron-python/src/client/jupyter/jupyterIntegration.ts
+++ b/extensions/positron-python/src/client/jupyter/jupyterIntegration.ts
@@ -34,7 +34,7 @@ type PythonApiForJupyterExtension = {
*/
getSuggestions(resource: Resource): Promise;
/**
- * Returns path to where `debugpy` is. In python extension this is `/pythonFiles/lib/python`.
+ * Returns path to where `debugpy` is. In python extension this is `/python_files/lib/python`.
*/
getDebuggerPath(): Promise;
/**
@@ -57,14 +57,6 @@ type PythonApiForJupyterExtension = {
* @param func : The function that Python should call when requesting the Python path.
*/
registerJupyterPythonPathFunction(func: (uri: Uri) => Promise): void;
-
- /**
- * Call to provide a function that the Python extension can call to request the notebook
- * document URI related to a particular text document URI, or undefined if there is no
- * associated notebook.
- * @param func : The function that Python should call when requesting the notebook URI.
- */
- registerGetNotebookUriForTextDocumentUriFunction(func: (textDocumentUri: Uri) => Uri | undefined): void;
};
type JupyterExtensionApi = {
@@ -81,10 +73,6 @@ export class JupyterExtensionIntegration {
private pylanceExtension: Extension | undefined;
- private jupyterPythonPathFunction: ((uri: Uri) => Promise) | undefined;
-
- private getNotebookUriForTextDocumentUriFunction: ((textDocumentUri: Uri) => Uri | undefined) | undefined;
-
constructor(
@inject(IExtensions) private readonly extensions: IExtensions,
@inject(IInterpreterSelector) private readonly interpreterSelector: IInterpreterSelector,
@@ -123,8 +111,6 @@ export class JupyterExtensionIntegration {
getCondaVersion: () => this.condaService.getCondaVersion(),
registerJupyterPythonPathFunction: (func: (uri: Uri) => Promise) =>
this.registerJupyterPythonPathFunction(func),
- registerGetNotebookUriForTextDocumentUriFunction: (func: (textDocumentUri: Uri) => Uri | undefined) =>
- this.registerGetNotebookUriForTextDocumentUriFunction(func),
});
return undefined;
}
@@ -169,28 +155,9 @@ export class JupyterExtensionIntegration {
}
private registerJupyterPythonPathFunction(func: (uri: Uri) => Promise) {
- this.jupyterPythonPathFunction = func;
-
const api = this.getPylanceApi();
if (api) {
api.notebook!.registerJupyterPythonPathFunction(func);
}
}
-
- public getJupyterPythonPathFunction(): ((uri: Uri) => Promise) | undefined {
- return this.jupyterPythonPathFunction;
- }
-
- public registerGetNotebookUriForTextDocumentUriFunction(func: (textDocumentUri: Uri) => Uri | undefined): void {
- this.getNotebookUriForTextDocumentUriFunction = func;
-
- const api = this.getPylanceApi();
- if (api) {
- api.notebook!.registerGetNotebookUriForTextDocumentUriFunction(func);
- }
- }
-
- public getGetNotebookUriForTextDocumentUriFunction(): ((textDocumentUri: Uri) => Uri | undefined) | undefined {
- return this.getNotebookUriForTextDocumentUriFunction;
- }
}
diff --git a/extensions/positron-python/src/client/positron/manager.ts b/extensions/positron-python/src/client/positron/manager.ts
index ed2ef10c80e..6510da79cc2 100644
--- a/extensions/positron-python/src/client/positron/manager.ts
+++ b/extensions/positron-python/src/client/positron/manager.ts
@@ -110,7 +110,7 @@ export class PythonRuntimeManager implements positron.LanguageRuntimeManager {
}
const command = extraData.pythonPath;
- const lsScriptPath = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'positron', 'positron_language_server.py');
+ const lsScriptPath = path.join(EXTENSION_ROOT_DIR, 'python_files', 'positron', 'positron_language_server.py');
const args = [
command,
lsScriptPath,
diff --git a/extensions/positron-python/src/client/pythonEnvironments/base/info/env.ts b/extensions/positron-python/src/client/pythonEnvironments/base/info/env.ts
index aa213167820..b77acde5333 100644
--- a/extensions/positron-python/src/client/pythonEnvironments/base/info/env.ts
+++ b/extensions/positron-python/src/client/pythonEnvironments/base/info/env.ts
@@ -160,7 +160,7 @@ export function setEnvDisplayString(env: PythonEnvInfo): void {
function buildEnvDisplayString(env: PythonEnvInfo, getAllDetails = false): string {
// main parts
- const shouldDisplayKind = getAllDetails || env.searchLocation || globallyInstalledEnvKinds.includes(env.kind);
+ const shouldDisplayKind = getAllDetails || globallyInstalledEnvKinds.includes(env.kind);
const shouldDisplayArch = !virtualEnvKinds.includes(env.kind);
const displayNameParts: string[] = ['Python'];
if (env.version && !isVersionEmpty(env.version)) {
diff --git a/extensions/positron-python/src/client/pythonEnvironments/base/info/envKind.ts b/extensions/positron-python/src/client/pythonEnvironments/base/info/envKind.ts
index ff53a57d2f4..77ed7f22533 100644
--- a/extensions/positron-python/src/client/pythonEnvironments/base/info/envKind.ts
+++ b/extensions/positron-python/src/client/pythonEnvironments/base/info/envKind.ts
@@ -15,6 +15,7 @@ export function getKindDisplayName(kind: PythonEnvKind): string {
[PythonEnvKind.MicrosoftStore, 'Microsoft Store'],
[PythonEnvKind.Pyenv, 'pyenv'],
[PythonEnvKind.Poetry, 'Poetry'],
+ [PythonEnvKind.Hatch, 'Hatch'],
[PythonEnvKind.Custom, 'custom'],
// For now we treat OtherGlobal like Unknown.
[PythonEnvKind.Venv, 'venv'],
@@ -39,12 +40,13 @@ export function getKindDisplayName(kind: PythonEnvKind): string {
* Remarks: This is the order of detection based on how the various distributions and tools
* configure the environment, and the fall back for identification.
* Top level we have the following environment types, since they leave a unique signature
- * in the environment or * use a unique path for the environments they create.
+ * in the environment or use a unique path for the environments they create.
* 1. Conda
* 2. Microsoft Store
* 3. PipEnv
* 4. Pyenv
* 5. Poetry
+ * 6. Hatch
*
* Next level we have the following virtual environment tools. The are here because they
* are consumed by the tools above, and can also be used independently.
@@ -61,6 +63,7 @@ export function getPrioritizedEnvKinds(): PythonEnvKind[] {
PythonEnvKind.MicrosoftStore,
PythonEnvKind.Pipenv,
PythonEnvKind.Poetry,
+ PythonEnvKind.Hatch,
PythonEnvKind.Venv,
PythonEnvKind.VirtualEnvWrapper,
PythonEnvKind.VirtualEnv,
diff --git a/extensions/positron-python/src/client/pythonEnvironments/base/info/index.ts b/extensions/positron-python/src/client/pythonEnvironments/base/info/index.ts
index e55031fe807..a0bf01c27c9 100644
--- a/extensions/positron-python/src/client/pythonEnvironments/base/info/index.ts
+++ b/extensions/positron-python/src/client/pythonEnvironments/base/info/index.ts
@@ -15,6 +15,7 @@ export enum PythonEnvKind {
MicrosoftStore = 'global-microsoft-store',
Pyenv = 'global-pyenv',
Poetry = 'poetry',
+ Hatch = 'hatch',
ActiveState = 'activestate',
Custom = 'global-custom',
OtherGlobal = 'global-other',
@@ -44,6 +45,7 @@ export interface EnvPathType {
export const virtualEnvKinds = [
PythonEnvKind.Poetry,
+ PythonEnvKind.Hatch,
PythonEnvKind.Pipenv,
PythonEnvKind.Venv,
PythonEnvKind.VirtualEnvWrapper,
@@ -194,7 +196,7 @@ type _PythonEnvInfo = PythonEnvBaseInfo & PythonBuildInfo;
* @prop distro - the installed Python distro that this env is using or belongs to
* @prop display - the text to use when showing the env to users
* @prop detailedDisplayName - display name containing all details
- * @prop searchLocation - the root under which a locator found this env, if any
+ * @prop searchLocation - the project to which this env is related to, if any
*/
export type PythonEnvInfo = _PythonEnvInfo & {
distro: PythonDistroInfo;
diff --git a/extensions/positron-python/src/client/pythonEnvironments/base/locator.ts b/extensions/positron-python/src/client/pythonEnvironments/base/locator.ts
index 3fd5194c37d..58798627678 100644
--- a/extensions/positron-python/src/client/pythonEnvironments/base/locator.ts
+++ b/extensions/positron-python/src/client/pythonEnvironments/base/locator.ts
@@ -20,7 +20,7 @@ export type PythonEnvUpdatedEvent = {
/**
* The iteration index of The env info that was previously provided.
*/
- index?: number;
+ index: number;
/**
* The env info that was previously provided.
*/
@@ -144,6 +144,7 @@ export type BasicEnvInfo = {
executablePath: string;
source?: PythonEnvSource[];
envPath?: string;
+ searchLocation?: Uri;
};
/**
@@ -243,7 +244,7 @@ export interface IDiscoveryAPI {
resolveEnv(path: string): Promise;
}
-interface IEmitter {
+export interface IEmitter {
fire(e: E): void;
}
diff --git a/extensions/positron-python/src/client/pythonEnvironments/base/locatorUtils.ts b/extensions/positron-python/src/client/pythonEnvironments/base/locatorUtils.ts
index faeaa84bedf..6af8c0ee1b6 100644
--- a/extensions/positron-python/src/client/pythonEnvironments/base/locatorUtils.ts
+++ b/extensions/positron-python/src/client/pythonEnvironments/base/locatorUtils.ts
@@ -95,8 +95,6 @@ export async function getEnvs(iterator: IPythonEnvsIterator imp
await this.disposables.dispose();
}
- public iterEnvs(query?: PythonLocatorQuery): IPythonEnvsIterator {
- const iterator = this.doIterEnvs(query);
- const it = this._iterEnvs(iterator, query);
- it.onUpdated = iterator.onUpdated;
- return it;
- }
-
- private async *_iterEnvs(
- iterator: IPythonEnvsIterator,
- query?: PythonLocatorQuery,
- ): IPythonEnvsIterator {
+ public async *iterEnvs(query?: PythonLocatorQuery): IPythonEnvsIterator {
await this.activate();
+ const iterator = this.doIterEnvs(query);
if (query?.envPath) {
let result = await iterator.next();
while (!result.done) {
diff --git a/extensions/positron-python/src/client/pythonEnvironments/base/locators/composite/envsCollectionCache.ts b/extensions/positron-python/src/client/pythonEnvironments/base/locators/composite/envsCollectionCache.ts
index dadab2512e1..456e8adfa9a 100644
--- a/extensions/positron-python/src/client/pythonEnvironments/base/locators/composite/envsCollectionCache.ts
+++ b/extensions/positron-python/src/client/pythonEnvironments/base/locators/composite/envsCollectionCache.ts
@@ -245,6 +245,10 @@ export class PythonEnvInfoCache extends PythonEnvsWatcher();
if (iterator.onUpdated !== undefined) {
- iterator.onUpdated(async (event) => {
+ const listener = iterator.onUpdated(async (event) => {
if (isProgressEvent(event)) {
switch (event.stage) {
case ProgressReportStage.discoveryFinished:
state.done = true;
- // listener.dispose();
+ listener.dispose();
break;
case ProgressReportStage.allPathsDiscovered:
if (!query) {
@@ -164,10 +164,6 @@ export class EnvsCollectionService extends PythonEnvsWatcher {
+ const listener = iterator.onUpdated((event) => {
if (isProgressEvent(event)) {
if (event.stage === ProgressReportStage.discoveryFinished) {
state.done = true;
- // For super slow locators such as Windows registry, we expect updates even after discovery
- // is "officially" finished, hence do not dispose listeners.
- // listener.dispose();
+ listener.dispose();
} else {
didUpdate.fire(event);
}
@@ -69,11 +67,15 @@ async function* iterEnvsIterator(
const oldEnv = seen[event.index];
seen[event.index] = event.update;
didUpdate.fire({ index: event.index, old: oldEnv, update: event.update });
- } else if (event.update) {
- didUpdate.fire({ update: event.update });
+ } else {
+ // This implies a problem in a downstream locator
+ traceVerbose(`Expected already iterated env, got ${event.old} (#${event.index})`);
}
+ state.pending -= 1;
checkIfFinishedAndNotify(state, didUpdate);
});
+ } else {
+ didUpdate.fire({ stage: ProgressReportStage.discoveryStarted });
}
let result = await iterator.next();
@@ -89,8 +91,10 @@ async function* iterEnvsIterator(
}
result = await iterator.next();
}
- state.done = true;
- checkIfFinishedAndNotify(state, didUpdate);
+ if (iterator.onUpdated === undefined) {
+ state.done = true;
+ checkIfFinishedAndNotify(state, didUpdate);
+ }
}
async function resolveDifferencesInBackground(
@@ -124,8 +128,8 @@ function checkIfFinishedAndNotify(
) {
if (state.done && state.pending === 0) {
didUpdate.fire({ stage: ProgressReportStage.discoveryFinished });
+ didUpdate.dispose();
traceVerbose(`Finished with environment reducer`);
- state.done = false; // No need to notify again.
}
}
@@ -133,9 +137,24 @@ function resolveEnvCollision(oldEnv: BasicEnvInfo, newEnv: BasicEnvInfo): BasicE
const [env] = sortEnvInfoByPriority(oldEnv, newEnv);
const merged = cloneDeep(env);
merged.source = uniq((oldEnv.source ?? []).concat(newEnv.source ?? []));
+ merged.searchLocation = getMergedSearchLocation(oldEnv, newEnv);
return merged;
}
+function getMergedSearchLocation(oldEnv: BasicEnvInfo, newEnv: BasicEnvInfo): Uri | undefined {
+ if (oldEnv.searchLocation && newEnv.searchLocation) {
+ // Choose the deeper project path of the two, as that can be used to signify
+ // that the environment is related to both the projects.
+ if (isParentPath(oldEnv.searchLocation.fsPath, newEnv.searchLocation.fsPath)) {
+ return oldEnv.searchLocation;
+ }
+ if (isParentPath(newEnv.searchLocation.fsPath, oldEnv.searchLocation.fsPath)) {
+ return newEnv.searchLocation;
+ }
+ }
+ return oldEnv.searchLocation ?? newEnv.searchLocation;
+}
+
/**
* Selects an environment based on the environment selection priority. This should
* match the priority in the environment identifier.
diff --git a/extensions/positron-python/src/client/pythonEnvironments/base/locators/composite/envsResolver.ts b/extensions/positron-python/src/client/pythonEnvironments/base/locators/composite/envsResolver.ts
index 752f5778c73..a7a25cc41c5 100644
--- a/extensions/positron-python/src/client/pythonEnvironments/base/locators/composite/envsResolver.ts
+++ b/extensions/positron-python/src/client/pythonEnvironments/base/locators/composite/envsResolver.ts
@@ -3,7 +3,7 @@
import { cloneDeep } from 'lodash';
import { Event, EventEmitter } from 'vscode';
-import { identifyEnvironment } from '../../../common/environmentIdentifier';
+import { isIdentifierRegistered, identifyEnvironment } from '../../../common/environmentIdentifier';
import { IEnvironmentInfoService } from '../../info/environmentInfoService';
import { PythonEnvInfo, PythonEnvKind } from '../../info';
import { getEnvPath, setEnvDisplayString } from '../../info/env';
@@ -81,15 +81,13 @@ export class PythonEnvsResolver implements IResolvingLocator {
const seen: PythonEnvInfo[] = [];
if (iterator.onUpdated !== undefined) {
- iterator.onUpdated(async (event) => {
+ const listener = iterator.onUpdated(async (event) => {
state.pending += 1;
if (isProgressEvent(event)) {
if (event.stage === ProgressReportStage.discoveryFinished) {
didUpdate.fire({ stage: ProgressReportStage.allPathsDiscovered });
state.done = true;
- // For super slow locators such as Windows registry, we expect updates even after discovery
- // is "officially" finished, hence do not dispose listeners.
- // listener.dispose();
+ listener.dispose();
} else {
didUpdate.fire(event);
}
@@ -97,14 +95,15 @@ export class PythonEnvsResolver implements IResolvingLocator {
throw new Error(
'Unsupported behavior: `undefined` environment updates are not supported from downstream locators in resolver',
);
- } else if (event.index && seen[event.index] !== undefined) {
+ } else if (event.index !== undefined && seen[event.index] !== undefined) {
const old = seen[event.index];
await setKind(event.update, environmentKinds);
seen[event.index] = await resolveBasicEnv(event.update);
didUpdate.fire({ old, index: event.index, update: seen[event.index] });
this.resolveInBackground(event.index, state, didUpdate, seen).ignoreErrors();
} else {
- didUpdate.fire({ update: await this.resolveEnv(event.update.executablePath) });
+ // This implies a problem in a downstream locator
+ traceVerbose(`Expected already iterated env, got ${event.old} (#${event.index})`);
}
state.pending -= 1;
checkIfFinishedAndNotify(state, didUpdate);
@@ -157,6 +156,10 @@ async function setKind(env: BasicEnvInfo, environmentKinds: Map Promise {
- const { kind, source } = env;
+ const { kind, source, searchLocation } = env;
const resolvers = getResolvers();
const resolverForKind = resolvers.get(kind)!;
const resolvedEnv = await resolverForKind(env);
- resolvedEnv.searchLocation = getSearchLocation(resolvedEnv);
+ resolvedEnv.searchLocation = getSearchLocation(resolvedEnv, searchLocation);
resolvedEnv.source = uniq(resolvedEnv.source.concat(source ?? []));
if (getOSType() === OSType.Windows && resolvedEnv.source?.includes(PythonEnvSource.WindowsRegistry)) {
// We can update env further using information we can get from the Windows registry.
@@ -87,7 +87,11 @@ async function getEnvType(env: PythonEnvInfo) {
return undefined;
}
-function getSearchLocation(env: PythonEnvInfo): Uri | undefined {
+function getSearchLocation(env: PythonEnvInfo, searchLocation: Uri | undefined): Uri | undefined {
+ if (searchLocation) {
+ // A search location has already been established by the downstream locators, simply use that.
+ return searchLocation;
+ }
const folders = getWorkspaceFolderPaths();
const isRootedEnv = folders.some((f) => isParentPath(env.executable.filename, f) || isParentPath(env.location, f));
if (isRootedEnv) {
@@ -109,7 +113,7 @@ async function updateEnvUsingRegistry(env: PythonEnvInfo): Promise {
let interpreters = getRegistryInterpretersSync();
if (!interpreters) {
traceError('Expected registry interpreter cache to be initialized already');
- interpreters = await getRegistryInterpreters(true);
+ interpreters = await getRegistryInterpreters();
}
const data = interpreters.find((i) => arePathsSame(i.interpreterPath, env.executable.filename));
if (data) {
diff --git a/extensions/positron-python/src/client/pythonEnvironments/base/locators/lowLevel/condaLocator.ts b/extensions/positron-python/src/client/pythonEnvironments/base/locators/lowLevel/condaLocator.ts
index 651a43ff886..a58bfdd65b2 100644
--- a/extensions/positron-python/src/client/pythonEnvironments/base/locators/lowLevel/condaLocator.ts
+++ b/extensions/positron-python/src/client/pythonEnvironments/base/locators/lowLevel/condaLocator.ts
@@ -6,8 +6,6 @@ import { BasicEnvInfo, IPythonEnvsIterator } from '../../locator';
import { Conda, getCondaEnvironmentsTxt } from '../../../common/environmentManagers/conda';
import { traceError, traceVerbose } from '../../../../logging';
import { FSWatchingLocator } from './fsWatchingLocator';
-import { DiscoveryUsingWorkers } from '../../../../common/experiments/groups';
-import { inExperiment } from '../../../common/externalDependencies';
export class CondaEnvironmentLocator extends FSWatchingLocator {
public readonly providerId: string = 'conda-envs';
@@ -21,11 +19,8 @@ export class CondaEnvironmentLocator extends FSWatchingLocator {
}
// eslint-disable-next-line class-methods-use-this
- public async *doIterEnvs(
- _: unknown,
- useWorkerThreads = inExperiment(DiscoveryUsingWorkers.experiment),
- ): IPythonEnvsIterator {
- const conda = await Conda.getConda(undefined, useWorkerThreads);
+ public async *doIterEnvs(_: unknown): IPythonEnvsIterator {
+ const conda = await Conda.getConda();
if (conda === undefined) {
traceVerbose(`Couldn't locate the conda binary.`);
return;
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 7565913f0a7..71bd30f7cfd 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
@@ -105,9 +105,7 @@ export abstract class FSWatchingLocator extends LazyResourceBasedLocator {
}
// Start the FS watchers.
- traceVerbose('Getting roots');
let roots = await this.getRoots();
- traceVerbose('Found roots');
if (typeof roots === 'string') {
roots = [roots];
}
diff --git a/extensions/positron-python/src/client/pythonEnvironments/base/locators/lowLevel/globalVirtualEnvronmentLocator.ts b/extensions/positron-python/src/client/pythonEnvironments/base/locators/lowLevel/globalVirtualEnvronmentLocator.ts
index 71f3d69e906..cc623be8392 100644
--- a/extensions/positron-python/src/client/pythonEnvironments/base/locators/lowLevel/globalVirtualEnvronmentLocator.ts
+++ b/extensions/positron-python/src/client/pythonEnvironments/base/locators/lowLevel/globalVirtualEnvronmentLocator.ts
@@ -3,6 +3,7 @@
import { toLower, uniq, uniqBy } from 'lodash';
import * as path from 'path';
+import { Uri } from 'vscode';
import { chain, iterable } from '../../../../common/utils/async';
import { getEnvironmentVariable, getOSType, getUserHomeDir, OSType } from '../../../../common/utils/platform';
import { PythonEnvKind } from '../../info';
@@ -10,7 +11,7 @@ import { BasicEnvInfo, IPythonEnvsIterator } from '../../locator';
import { FSWatchingLocator } from './fsWatchingLocator';
import { findInterpretersInDir, looksLikeBasicVirtualPython } from '../../../common/commonUtils';
import { pathExists, untildify } from '../../../common/externalDependencies';
-import { isPipenvEnvironment } from '../../../common/environmentManagers/pipenv';
+import { getProjectDir, isPipenvEnvironment } from '../../../common/environmentManagers/pipenv';
import {
isVenvEnvironment,
isVirtualenvEnvironment,
@@ -57,6 +58,18 @@ async function getGlobalVirtualEnvDirs(): Promise {
return [OSType.Windows, OSType.OSX].includes(getOSType()) ? uniqBy(venvDirs, toLower) : uniq(venvDirs);
}
+async function getSearchLocation(env: BasicEnvInfo): Promise {
+ if (env.kind === PythonEnvKind.Pipenv) {
+ // Pipenv environments are created only for a specific project, so they must only
+ // appear if that particular project is being queried.
+ const project = await getProjectDir(path.dirname(path.dirname(env.executablePath)));
+ if (project) {
+ return Uri.file(project);
+ }
+ }
+ return undefined;
+}
+
/**
* Gets the virtual environment kind for a given interpreter path.
* This only checks for environments created using venv, virtualenv,
@@ -123,8 +136,9 @@ export class GlobalVirtualEnvironmentLocator extends FSWatchingLocator {
// check multiple times. Those checks are file system heavy and
// we can use the kind to determine this anyway.
const kind = await getVirtualEnvKind(filename);
+ const searchLocation = await getSearchLocation({ kind, executablePath: filename });
try {
- yield { kind, executablePath: filename };
+ yield { kind, executablePath: filename, searchLocation };
traceVerbose(`Global Virtual Environment: [added] ${filename}`);
} catch (ex) {
traceError(`Failed to process environment: ${filename}`, ex);
diff --git a/extensions/positron-python/src/client/pythonEnvironments/base/locators/lowLevel/hatchLocator.ts b/extensions/positron-python/src/client/pythonEnvironments/base/locators/lowLevel/hatchLocator.ts
new file mode 100644
index 00000000000..f7746a8c5a2
--- /dev/null
+++ b/extensions/positron-python/src/client/pythonEnvironments/base/locators/lowLevel/hatchLocator.ts
@@ -0,0 +1,57 @@
+'use strict';
+
+import { PythonEnvKind } from '../../info';
+import { BasicEnvInfo, IPythonEnvsIterator } from '../../locator';
+import { LazyResourceBasedLocator } from '../common/resourceBasedLocator';
+import { Hatch } from '../../../common/environmentManagers/hatch';
+import { asyncFilter } from '../../../../common/utils/arrayUtils';
+import { pathExists } from '../../../common/externalDependencies';
+import { traceError, traceVerbose } from '../../../../logging';
+import { chain, iterable } from '../../../../common/utils/async';
+import { getInterpreterPathFromDir } from '../../../common/commonUtils';
+
+/**
+ * Gets all default virtual environment locations to look for in a workspace.
+ */
+async function getVirtualEnvDirs(root: string): Promise {
+ const hatch = await Hatch.getHatch(root);
+ const envDirs = (await hatch?.getEnvList()) ?? [];
+ return asyncFilter(envDirs, pathExists);
+}
+
+/**
+ * Finds and resolves virtual environments created using Hatch.
+ */
+export class HatchLocator extends LazyResourceBasedLocator {
+ public readonly providerId: string = 'hatch';
+
+ public constructor(private readonly root: string) {
+ super();
+ }
+
+ protected doIterEnvs(): IPythonEnvsIterator {
+ async function* iterator(root: string) {
+ const envDirs = await getVirtualEnvDirs(root);
+ const envGenerators = envDirs.map((envDir) => {
+ async function* generator() {
+ traceVerbose(`Searching for Hatch virtual envs in: ${envDir}`);
+ const filename = await getInterpreterPathFromDir(envDir);
+ if (filename !== undefined) {
+ try {
+ yield { executablePath: filename, kind: PythonEnvKind.Hatch };
+ traceVerbose(`Hatch Virtual Environment: [added] ${filename}`);
+ } catch (ex) {
+ traceError(`Failed to process environment: ${filename}`, ex);
+ }
+ }
+ }
+ return generator();
+ });
+
+ yield* iterable(chain(envGenerators));
+ traceVerbose(`Finished searching for Hatch envs`);
+ }
+
+ return iterator(this.root);
+ }
+}
diff --git a/extensions/positron-python/src/client/pythonEnvironments/base/locators/lowLevel/poetryLocator.ts b/extensions/positron-python/src/client/pythonEnvironments/base/locators/lowLevel/poetryLocator.ts
index 4084c7a5cfb..ab1a8cf7744 100644
--- a/extensions/positron-python/src/client/pythonEnvironments/base/locators/lowLevel/poetryLocator.ts
+++ b/extensions/positron-python/src/client/pythonEnvironments/base/locators/lowLevel/poetryLocator.ts
@@ -4,6 +4,7 @@
'use strict';
import * as path from 'path';
+import { Uri } from 'vscode';
import { chain, iterable } from '../../../../common/utils/async';
import { PythonEnvKind } from '../../info';
import { BasicEnvInfo, IPythonEnvsIterator } from '../../locator';
@@ -59,7 +60,7 @@ export class PoetryLocator extends LazyResourceBasedLocator {
// We should extract the kind here to avoid doing is*Environment()
// check multiple times. Those checks are file system heavy and
// we can use the kind to determine this anyway.
- yield { executablePath: filename, kind };
+ yield { executablePath: filename, kind, searchLocation: Uri.file(root) };
traceVerbose(`Poetry Virtual Environment: [added] ${filename}`);
} catch (ex) {
traceError(`Failed to process environment: ${filename}`, ex);
diff --git a/extensions/positron-python/src/client/pythonEnvironments/base/locators/lowLevel/windowsRegistryLocator.ts b/extensions/positron-python/src/client/pythonEnvironments/base/locators/lowLevel/windowsRegistryLocator.ts
index c6ba64cdb46..a574116f185 100644
--- a/extensions/positron-python/src/client/pythonEnvironments/base/locators/lowLevel/windowsRegistryLocator.ts
+++ b/extensions/positron-python/src/client/pythonEnvironments/base/locators/lowLevel/windowsRegistryLocator.ts
@@ -3,76 +3,53 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
-import { EventEmitter } from 'vscode';
import { PythonEnvKind, PythonEnvSource } from '../../info';
-import {
- BasicEnvInfo,
- IPythonEnvsIterator,
- Locator,
- ProgressNotificationEvent,
- ProgressReportStage,
- PythonEnvUpdatedEvent,
-} from '../../locator';
+import { BasicEnvInfo, IPythonEnvsIterator, Locator, PythonLocatorQuery, IEmitter } from '../../locator';
import { getRegistryInterpreters } from '../../../common/windowsUtils';
import { traceError, traceVerbose } from '../../../../logging';
import { isMicrosoftStoreDir } from '../../../common/environmentManagers/microsoftStoreEnv';
-import { inExperiment } from '../../../common/externalDependencies';
+import { PythonEnvsChangedEvent } from '../../watcher';
import { DiscoveryUsingWorkers } from '../../../../common/experiments/groups';
+import { inExperiment } from '../../../common/externalDependencies';
+
+export const WINDOWS_REG_PROVIDER_ID = 'windows-registry';
export class WindowsRegistryLocator extends Locator {
- public readonly providerId: string = 'windows-registry';
+ public readonly providerId: string = WINDOWS_REG_PROVIDER_ID;
// eslint-disable-next-line class-methods-use-this
public iterEnvs(
- _?: unknown,
+ query?: PythonLocatorQuery,
useWorkerThreads = inExperiment(DiscoveryUsingWorkers.experiment),
): IPythonEnvsIterator {
- const didUpdate = new EventEmitter | ProgressNotificationEvent>();
- const iterator = useWorkerThreads ? iterEnvsIterator(didUpdate) : oldIterEnvsIterator();
if (useWorkerThreads) {
- iterator.onUpdated = didUpdate.event;
+ /**
+ * Windows registry is slow and often not necessary, so notify completion immediately, but use watcher
+ * change events to signal for any new envs which are found.
+ */
+ if (query?.providerId === this.providerId) {
+ // Query via change event, so iterate all envs.
+ return iterateEnvs();
+ }
+ return iterateEnvsLazily(this.emitter);
}
- return iterator;
+ return iterateEnvs();
}
}
-/**
- * Windows registry is slow and often not necessary, so notify completion immediately, while still updating lazily as we find stuff.
- * To accomplish this, use an empty iterator while lazily firing environments using updates.
- */
-async function* iterEnvsIterator(
- didUpdate: EventEmitter | ProgressNotificationEvent>,
-): IPythonEnvsIterator {
- updateLazily(didUpdate).ignoreErrors();
+async function* iterateEnvsLazily(changed: IEmitter): IPythonEnvsIterator {
+ loadAllEnvs(changed).ignoreErrors();
}
-async function updateLazily(didUpdate: EventEmitter | ProgressNotificationEvent>) {
+async function loadAllEnvs(changed: IEmitter) {
traceVerbose('Searching for windows registry interpreters');
- const interpreters = await getRegistryInterpreters(true);
- for (const interpreter of interpreters) {
- try {
- // Filter out Microsoft Store app directories. We have a store app locator that handles this.
- // The python.exe available in these directories might not be python. It can be a store install
- // shortcut that takes you to microsoft store.
- if (isMicrosoftStoreDir(interpreter.interpreterPath)) {
- continue;
- }
- const env: BasicEnvInfo = {
- kind: PythonEnvKind.OtherGlobal,
- executablePath: interpreter.interpreterPath,
- source: [PythonEnvSource.WindowsRegistry],
- };
- didUpdate.fire({ update: env });
- } catch (ex) {
- traceError(`Failed to process environment: ${interpreter}`, ex);
- }
- }
- didUpdate.fire({ stage: ProgressReportStage.discoveryFinished });
+ await getRegistryInterpreters();
+ changed.fire({ providerId: WINDOWS_REG_PROVIDER_ID });
traceVerbose('Finished searching for windows registry interpreters');
}
-async function* oldIterEnvsIterator(): IPythonEnvsIterator {
- const interpreters = await getRegistryInterpreters(false);
+async function* iterateEnvs(): IPythonEnvsIterator {
+ const interpreters = await getRegistryInterpreters(); // Value should already be loaded at this point, so this returns immediately.
for (const interpreter of interpreters) {
try {
// Filter out Microsoft Store app directories. We have a store app locator that handles this.
diff --git a/extensions/positron-python/src/client/pythonEnvironments/common/commonUtils.ts b/extensions/positron-python/src/client/pythonEnvironments/common/commonUtils.ts
index 85462531e5e..4bd94e0402a 100644
--- a/extensions/positron-python/src/client/pythonEnvironments/common/commonUtils.ts
+++ b/extensions/positron-python/src/client/pythonEnvironments/common/commonUtils.ts
@@ -5,7 +5,7 @@ import * as fs from 'fs';
import * as path from 'path';
import { convertFileType, DirEntry, FileType, getFileFilter, getFileType } from '../../common/utils/filesystem';
import { getOSType, OSType } from '../../common/utils/platform';
-import { traceError } from '../../logging';
+import { traceError, traceVerbose } from '../../logging';
import { PythonVersion, UNKNOWN_PYTHON_VERSION } from '../base/info';
import { comparePythonVersionSpecificity } from '../base/info/env';
import { parseVersion } from '../base/info/pythonVersion';
@@ -246,8 +246,11 @@ export async function getPythonVersionFromPath(interpreterPath: string, hint?: s
versionA = UNKNOWN_PYTHON_VERSION;
}
const versionB = interpreterPath ? await getPythonVersionFromNearByFiles(interpreterPath) : UNKNOWN_PYTHON_VERSION;
+ traceVerbose('Best effort version B for', interpreterPath, JSON.stringify(versionB));
const versionC = interpreterPath ? await getPythonVersionFromPyvenvCfg(interpreterPath) : UNKNOWN_PYTHON_VERSION;
+ traceVerbose('Best effort version C for', interpreterPath, JSON.stringify(versionC));
const versionD = interpreterPath ? await getPythonVersionFromConda(interpreterPath) : UNKNOWN_PYTHON_VERSION;
+ traceVerbose('Best effort version D for', interpreterPath, JSON.stringify(versionD));
let version = UNKNOWN_PYTHON_VERSION;
for (const v of [versionA, versionB, versionC, versionD]) {
diff --git a/extensions/positron-python/src/client/pythonEnvironments/common/environmentIdentifier.ts b/extensions/positron-python/src/client/pythonEnvironments/common/environmentIdentifier.ts
index 2dbc8b2b93d..880eed52598 100644
--- a/extensions/positron-python/src/client/pythonEnvironments/common/environmentIdentifier.ts
+++ b/extensions/positron-python/src/client/pythonEnvironments/common/environmentIdentifier.ts
@@ -17,8 +17,9 @@ import {
import { isMicrosoftStoreEnvironment } from './environmentManagers/microsoftStoreEnv';
import { isActiveStateEnvironment } from './environmentManagers/activestate';
+const notImplemented = () => Promise.resolve(false);
+
function getIdentifiers(): Map Promise> {
- const notImplemented = () => Promise.resolve(false);
const defaultTrue = () => Promise.resolve(true);
const identifier: Map Promise> = new Map();
Object.values(PythonEnvKind).forEach((k) => {
@@ -39,6 +40,15 @@ function getIdentifiers(): Map Promise
return identifier;
}
+export function isIdentifierRegistered(kind: PythonEnvKind): boolean {
+ const identifiers = getIdentifiers();
+ const identifier = identifiers.get(kind);
+ if (identifier === notImplemented) {
+ return false;
+ }
+ return true;
+}
+
/**
* Returns environment type.
* @param {string} path : Absolute path to the python interpreter binary or path to environment.
diff --git a/extensions/positron-python/src/client/pythonEnvironments/common/environmentManagers/conda.ts b/extensions/positron-python/src/client/pythonEnvironments/common/environmentManagers/conda.ts
index 1cb2e490aef..bb844f45a0f 100644
--- a/extensions/positron-python/src/client/pythonEnvironments/common/environmentManagers/conda.ts
+++ b/extensions/positron-python/src/client/pythonEnvironments/common/environmentManagers/conda.ts
@@ -10,7 +10,6 @@ import {
readFile,
onDidChangePythonSetting,
exec,
- inExperiment,
} from '../externalDependencies';
import { PythonVersion, UNKNOWN_PYTHON_VERSION } from '../../base/info';
@@ -25,7 +24,6 @@ import { OUTPUT_MARKER_SCRIPT } from '../../../common/process/internal/scripts';
import { splitLines } from '../../../common/stringUtils';
import { SpawnOptions } from '../../../common/process/types';
import { sleep } from '../../../common/utils/async';
-import { DiscoveryUsingWorkers } from '../../../common/experiments/groups';
export const AnacondaCompanyName = 'Anaconda, Inc.';
export const CONDAPATH_SETTING_KEY = 'condaPath';
@@ -274,11 +272,7 @@ export class Conda {
private readonly useWorkerThreads?: boolean,
) {
if (this.useWorkerThreads === undefined) {
- try {
- this.useWorkerThreads = inExperiment(DiscoveryUsingWorkers.experiment);
- } catch {
- this.useWorkerThreads = false; // Temporarily support for legacy tests
- }
+ this.useWorkerThreads = false;
}
this.shellCommand = shellCommand ?? command;
onDidChangePythonSetting(CONDAPATH_SETTING_KEY, () => {
@@ -286,9 +280,9 @@ export class Conda {
});
}
- public static async getConda(shellPath?: string, useWorkerThreads?: boolean): Promise {
+ public static async getConda(shellPath?: string): Promise {
if (Conda.condaPromise.get(shellPath) === undefined || isTestExecution()) {
- Conda.condaPromise.set(shellPath, Conda.locate(shellPath, useWorkerThreads));
+ Conda.condaPromise.set(shellPath, Conda.locate(shellPath));
}
return Conda.condaPromise.get(shellPath);
}
@@ -299,15 +293,7 @@ export class Conda {
*
* @return A Conda instance corresponding to the binary, if successful; otherwise, undefined.
*/
- private static async locate(shellPath?: string, useWorkerThread?: boolean): Promise {
- let useWorkerThreads: boolean;
- if (useWorkerThread === undefined) {
- try {
- useWorkerThreads = inExperiment(DiscoveryUsingWorkers.experiment);
- } catch {
- useWorkerThreads = false; // Temporarily support for legacy tests
- }
- }
+ private static async locate(shellPath?: string): Promise {
traceVerbose(`Searching for conda.`);
const home = getUserHomeDir();
let customCondaPath: string | undefined = 'conda';
@@ -334,7 +320,7 @@ export class Conda {
}
async function* getCandidatesFromRegistry() {
- const interps = await getRegistryInterpreters(useWorkerThreads);
+ const interps = await getRegistryInterpreters();
const candidates = interps
.filter((interp) => interp.interpreterPath && interp.distroOrgName === 'ContinuumAnalytics')
.map((interp) => path.join(path.win32.dirname(interp.interpreterPath), suffix));
diff --git a/extensions/positron-python/src/client/pythonEnvironments/common/environmentManagers/hatch.ts b/extensions/positron-python/src/client/pythonEnvironments/common/environmentManagers/hatch.ts
new file mode 100644
index 00000000000..6d7a13ea155
--- /dev/null
+++ b/extensions/positron-python/src/client/pythonEnvironments/common/environmentManagers/hatch.ts
@@ -0,0 +1,116 @@
+import { isTestExecution } from '../../../common/constants';
+import { exec, pathExists } from '../externalDependencies';
+import { traceVerbose } from '../../../logging';
+import { cache } from '../../../common/utils/decorators';
+import { getOSType, OSType } from '../../../common/utils/platform';
+
+/** Wraps the "Hatch" utility, and exposes its functionality.
+ */
+export class Hatch {
+ /**
+ * Locating Hatch binary can be expensive, since it potentially involves spawning or
+ * trying to spawn processes; so we only do it once per session.
+ */
+ private static hatchPromise: Map> = new Map<
+ string,
+ Promise
+ >();
+
+ /**
+ * Creates a Hatch service corresponding to the corresponding "hatch" command.
+ *
+ * @param command - Command used to run hatch. This has the same meaning as the
+ * first argument of spawn() - i.e. it can be a full path, or just a binary name.
+ * @param cwd - The working directory to use as cwd when running hatch.
+ */
+ constructor(public readonly command: string, private cwd: string) {
+ this.fixCwd();
+ }
+
+ /**
+ * Returns a Hatch instance corresponding to the binary which can be used to run commands for the cwd.
+ *
+ * Every directory is a valid Hatch project, so this should always return a Hatch instance.
+ */
+ public static async getHatch(cwd: string): Promise {
+ if (Hatch.hatchPromise.get(cwd) === undefined || isTestExecution()) {
+ Hatch.hatchPromise.set(cwd, Hatch.locate(cwd));
+ }
+ return Hatch.hatchPromise.get(cwd);
+ }
+
+ private static async locate(cwd: string): Promise {
+ // First thing this method awaits on should be hatch command execution,
+ // hence perform all operations before that synchronously.
+ const hatchPath = 'hatch';
+ traceVerbose(`Probing Hatch binary ${hatchPath}`);
+ const hatch = new Hatch(hatchPath, cwd);
+ const virtualenvs = await hatch.getEnvList();
+ if (virtualenvs !== undefined) {
+ traceVerbose(`Found hatch binary ${hatchPath}`);
+ return hatch;
+ }
+ traceVerbose(`Failed to find Hatch binary ${hatchPath}`);
+
+ // Didn't find anything.
+ traceVerbose(`No Hatch binary found`);
+ return undefined;
+ }
+
+ /**
+ * Retrieves list of Python environments known to Hatch for this working directory.
+ * Returns `undefined` if we failed to spawn in some way.
+ *
+ * Corresponds to "hatch env show --json". Swallows errors if any.
+ */
+ public async getEnvList(): Promise {
+ return this.getEnvListCached(this.cwd);
+ }
+
+ /**
+ * Method created to facilitate caching. The caching decorator uses function arguments as cache key,
+ * so pass in cwd on which we need to cache.
+ */
+ @cache(30_000, true, 10_000)
+ private async getEnvListCached(_cwd: string): Promise {
+ const envInfoOutput = await exec(this.command, ['env', 'show', '--json'], {
+ cwd: this.cwd,
+ throwOnStdErr: true,
+ }).catch(traceVerbose);
+ if (!envInfoOutput) {
+ return undefined;
+ }
+ const envPaths = await Promise.all(
+ Object.keys(JSON.parse(envInfoOutput.stdout)).map(async (name) => {
+ const envPathOutput = await exec(this.command, ['env', 'find', name], {
+ cwd: this.cwd,
+ throwOnStdErr: true,
+ }).catch(traceVerbose);
+ if (!envPathOutput) return undefined;
+ const dir = envPathOutput.stdout.trim();
+ return (await pathExists(dir)) ? dir : undefined;
+ }),
+ );
+ return envPaths.flatMap((r) => (r ? [r] : []));
+ }
+
+ /**
+ * Due to an upstream hatch issue on Windows https://github.com/pypa/hatch/issues/1350,
+ * 'hatch env find default' does not handle case-insensitive paths as cwd, which are valid on Windows.
+ * So we need to pass the case-exact path as cwd.
+ * It has been observed that only the drive letter in `cwd` is lowercased here. Unfortunately,
+ * there's no good way to get case of the drive letter correctly without using Win32 APIs:
+ * https://stackoverflow.com/questions/33086985/how-to-obtain-case-exact-path-of-a-file-in-node-js-on-windows
+ * So we do it manually.
+ */
+ private fixCwd(): void {
+ if (getOSType() === OSType.Windows) {
+ if (/^[a-z]:/.test(this.cwd)) {
+ // Replace first character by the upper case version of the character.
+ const a = this.cwd.split(':');
+ a[0] = a[0].toUpperCase();
+ this.cwd = a.join(':');
+ }
+ }
+ }
+}
diff --git a/extensions/positron-python/src/client/pythonEnvironments/common/environmentManagers/pipenv.ts b/extensions/positron-python/src/client/pythonEnvironments/common/environmentManagers/pipenv.ts
index d8b1b2ff649..c8651533ed4 100644
--- a/extensions/positron-python/src/client/pythonEnvironments/common/environmentManagers/pipenv.ts
+++ b/extensions/positron-python/src/client/pythonEnvironments/common/environmentManagers/pipenv.ts
@@ -70,7 +70,7 @@ async function getPipfileIfLocal(interpreterPath: string): Promise {
+export async function getProjectDir(envFolder: string): Promise {
// Global pipenv environments have a .project file with the absolute path to the project
// See https://github.com/pypa/pipenv/blob/v2018.6.25/CHANGELOG.rst#features--improvements
// This is the layout we expect
diff --git a/extensions/positron-python/src/client/pythonEnvironments/common/environmentManagers/poetry.ts b/extensions/positron-python/src/client/pythonEnvironments/common/environmentManagers/poetry.ts
index 48199b5bdc8..5e5fa241620 100644
--- a/extensions/positron-python/src/client/pythonEnvironments/common/environmentManagers/poetry.ts
+++ b/extensions/positron-python/src/client/pythonEnvironments/common/environmentManagers/poetry.ts
@@ -10,7 +10,7 @@ import {
isParentPath,
pathExists,
pathExistsSync,
- readFileSync,
+ readFile,
shellExecute,
} from '../externalDependencies';
import { getEnvironmentDirFromPath } from '../commonUtils';
@@ -63,7 +63,7 @@ async function isLocalPoetryEnvironment(interpreterPath: string): Promise {
// Following check should be performed synchronously so we trigger poetry execution as soon as possible.
- if (!hasValidPyprojectToml(cwd)) {
+ if (!(await hasValidPyprojectToml(cwd))) {
// This check is not expensive and may change during a session, so we need not cache it.
return undefined;
}
@@ -325,12 +325,12 @@ export async function isPoetryEnvironmentRelatedToFolder(
*
* @param folder Folder to look for pyproject.toml file in.
*/
-function hasValidPyprojectToml(folder: string): boolean {
+async function hasValidPyprojectToml(folder: string): Promise {
const pyprojectToml = path.join(folder, 'pyproject.toml');
if (!pathExistsSync(pyprojectToml)) {
return false;
}
- const content = readFileSync(pyprojectToml);
+ const content = await readFile(pyprojectToml);
if (!content.includes('[tool.poetry]')) {
return false;
}
diff --git a/extensions/positron-python/src/client/pythonEnvironments/common/environmentManagers/pyenv.ts b/extensions/positron-python/src/client/pythonEnvironments/common/environmentManagers/pyenv.ts
index 229df897051..3d6c276cc86 100644
--- a/extensions/positron-python/src/client/pythonEnvironments/common/environmentManagers/pyenv.ts
+++ b/extensions/positron-python/src/client/pythonEnvironments/common/environmentManagers/pyenv.ts
@@ -1,6 +1,7 @@
import * as path from 'path';
import { getEnvironmentVariable, getOSType, getUserHomeDir, OSType } from '../../../common/utils/platform';
-import { arePathsSame, isParentPath, pathExists } from '../externalDependencies';
+import { arePathsSame, isParentPath, pathExists, shellExecute } from '../externalDependencies';
+import { traceVerbose } from '../../../logging';
export function getPyenvDir(): string {
// Check if the pyenv environment variables exist: PYENV on Windows, PYENV_ROOT on Unix.
@@ -20,6 +21,26 @@ export function getPyenvDir(): string {
return pyenvDir;
}
+async function getPyenvBinary(): Promise {
+ const pyenvDir = getPyenvDir();
+ const pyenvBin = path.join(pyenvDir, 'bin', 'pyenv');
+ if (await pathExists(pyenvBin)) {
+ return pyenvBin;
+ }
+ return 'pyenv';
+}
+
+export async function getActivePyenvForDirectory(cwd: string): Promise {
+ const pyenvBin = await getPyenvBinary();
+ try {
+ const pyenvInterpreterPath = await shellExecute(`${pyenvBin} which python`, { cwd });
+ return pyenvInterpreterPath.stdout.trim();
+ } catch (ex) {
+ traceVerbose(ex);
+ return undefined;
+ }
+}
+
export function getPyenvVersionsDir(): string {
return path.join(getPyenvDir(), 'versions');
}
diff --git a/extensions/positron-python/src/client/pythonEnvironments/common/externalDependencies.ts b/extensions/positron-python/src/client/pythonEnvironments/common/externalDependencies.ts
index 4c970e5d7f3..387c04790c3 100644
--- a/extensions/positron-python/src/client/pythonEnvironments/common/externalDependencies.ts
+++ b/extensions/positron-python/src/client/pythonEnvironments/common/externalDependencies.ts
@@ -11,7 +11,6 @@ import { chain, iterable } from '../../common/utils/async';
import { getOSType, OSType } from '../../common/utils/platform';
import { IServiceContainer } from '../../ioc/types';
import { traceError, traceVerbose } from '../../logging';
-import { DiscoveryUsingWorkers } from '../../common/experiments/groups';
let internalServiceContainer: IServiceContainer;
export function initializeExternalDependencies(serviceContainer: IServiceContainer): void {
@@ -21,7 +20,7 @@ export function initializeExternalDependencies(serviceContainer: IServiceContain
// processes
export async function shellExecute(command: string, options: ShellOptions = {}): Promise> {
- const useWorker = inExperiment(DiscoveryUsingWorkers.experiment);
+ const useWorker = false;
const service = await internalServiceContainer.get(IProcessServiceFactory).create();
options = { ...options, useWorker };
return service.shellExec(command, options);
@@ -31,7 +30,7 @@ export async function exec(
file: string,
args: string[],
options: SpawnOptions = {},
- useWorker = inExperiment(DiscoveryUsingWorkers.experiment),
+ useWorker = false,
): Promise> {
const service = await internalServiceContainer.get(IProcessServiceFactory).create();
options = { ...options, useWorker };
diff --git a/extensions/positron-python/src/client/pythonEnvironments/common/windowsUtils.ts b/extensions/positron-python/src/client/pythonEnvironments/common/windowsUtils.ts
index e9210371be0..fe15f71522a 100644
--- a/extensions/positron-python/src/client/pythonEnvironments/common/windowsUtils.ts
+++ b/extensions/positron-python/src/client/pythonEnvironments/common/windowsUtils.ts
@@ -126,15 +126,15 @@ export function getRegistryInterpretersSync(): IRegistryInterpreterData[] | unde
let registryInterpretersPromise: Promise | undefined;
-export async function getRegistryInterpreters(useWorkerThreads: boolean): Promise {
+export async function getRegistryInterpreters(): Promise {
if (!isTestExecution() && registryInterpretersPromise !== undefined) {
return registryInterpretersPromise;
}
- registryInterpretersPromise = getRegistryInterpretersImpl(useWorkerThreads);
+ registryInterpretersPromise = getRegistryInterpretersImpl();
return registryInterpretersPromise;
}
-async function getRegistryInterpretersImpl(useWorkerThreads: boolean): Promise {
+async function getRegistryInterpretersImpl(useWorkerThreads = false): Promise {
let registryData: IRegistryInterpreterData[] = [];
for (const arch of ['x64', 'x86']) {
diff --git a/extensions/positron-python/src/client/pythonEnvironments/creation/installedPackagesDiagnostic.ts b/extensions/positron-python/src/client/pythonEnvironments/creation/installedPackagesDiagnostic.ts
index a46a32ce827..0a96ff08801 100644
--- a/extensions/positron-python/src/client/pythonEnvironments/creation/installedPackagesDiagnostic.ts
+++ b/extensions/positron-python/src/client/pythonEnvironments/creation/installedPackagesDiagnostic.ts
@@ -29,7 +29,6 @@ async function setContextForActiveEditor(diagnosticCollection: DiagnosticCollect
}
// undefined here in the logs means no file was selected
- traceVerbose(`Clearing context for python dependencies not installed: ${doc?.uri.fsPath}`);
await executeCommand('setContext', DEPS_NOT_INSTALLED_KEY, false);
}
diff --git a/extensions/positron-python/src/client/pythonEnvironments/index.ts b/extensions/positron-python/src/client/pythonEnvironments/index.ts
index 5a5fceffa69..d3f6166295d 100644
--- a/extensions/positron-python/src/client/pythonEnvironments/index.ts
+++ b/extensions/positron-python/src/client/pythonEnvironments/index.ts
@@ -28,6 +28,7 @@ import { MicrosoftStoreLocator } from './base/locators/lowLevel/microsoftStoreLo
import { getEnvironmentInfoService } from './base/info/environmentInfoService';
import { registerNewDiscoveryForIOC } from './legacyIOC';
import { PoetryLocator } from './base/locators/lowLevel/poetryLocator';
+import { HatchLocator } from './base/locators/lowLevel/hatchLocator';
import { createPythonEnvironments } from './api';
import {
createCollectionCache as createCache,
@@ -39,6 +40,8 @@ import { traceError } from '../logging';
import { ActiveStateLocator } from './base/locators/lowLevel/activeStateLocator';
import { CustomWorkspaceLocator } from './base/locators/lowLevel/customWorkspaceLocator';
+const PYTHON_ENV_INFO_CACHE_KEY = 'PYTHON_ENV_INFO_CACHEv2';
+
/**
* Set up the Python environments component (during extension activation).'
*/
@@ -68,7 +71,7 @@ export async function activate(api: IDiscoveryAPI, ext: ExtensionState): Promise
*/
const folders = vscode.workspace.workspaceFolders;
// Trigger discovery if environment cache is empty.
- const wasTriggered = getGlobalStorage(ext.context, 'PYTHON_ENV_INFO_CACHE', []).get().length > 0;
+ const wasTriggered = getGlobalStorage(ext.context, PYTHON_ENV_INFO_CACHE_KEY, []).get().length > 0;
if (!wasTriggered) {
api.triggerRefresh().ignoreErrors();
folders?.forEach(async (folder) => {
@@ -186,6 +189,7 @@ function createWorkspaceLocator(ext: ExtensionState): WorkspaceLocators {
(root: vscode.Uri) => [
new WorkspaceVirtualEnvironmentLocator(root.fsPath),
new PoetryLocator(root.fsPath),
+ new HatchLocator(root.fsPath),
new CustomWorkspaceLocator(root.fsPath),
],
// Add an ILocator factory func here for each kind of workspace-rooted locator.
@@ -225,7 +229,7 @@ function putIntoStorage(storage: IPersistentStorage, envs: Pyth
}
async function createCollectionCache(ext: ExtensionState): Promise {
- const storage = getGlobalStorage(ext.context, 'PYTHON_ENV_INFO_CACHE', []);
+ const storage = getGlobalStorage(ext.context, PYTHON_ENV_INFO_CACHE_KEY, []);
const cache = await createCache({
get: () => getFromStorage(storage),
store: async (e) => putIntoStorage(storage, e),
diff --git a/extensions/positron-python/src/client/pythonEnvironments/info/index.ts b/extensions/positron-python/src/client/pythonEnvironments/info/index.ts
index 17e8958f631..716d4bcd938 100644
--- a/extensions/positron-python/src/client/pythonEnvironments/info/index.ts
+++ b/extensions/positron-python/src/client/pythonEnvironments/info/index.ts
@@ -19,15 +19,20 @@ export enum EnvironmentType {
Venv = 'Venv',
MicrosoftStore = 'MicrosoftStore',
Poetry = 'Poetry',
+ Hatch = 'Hatch',
VirtualEnvWrapper = 'VirtualEnvWrapper',
ActiveState = 'ActiveState',
Global = 'Global',
System = 'System',
}
+/**
+ * These envs are only created for a specific workspace, which we're able to detect.
+ */
+export const workspaceVirtualEnvTypes = [EnvironmentType.Poetry, EnvironmentType.Pipenv];
export const virtualEnvTypes = [
- EnvironmentType.Poetry,
- EnvironmentType.Pipenv,
+ ...workspaceVirtualEnvTypes,
+ EnvironmentType.Hatch, // This is also a workspace virtual env, but we're not treating it as such as of today.
EnvironmentType.Venv,
EnvironmentType.VirtualEnvWrapper,
EnvironmentType.Conda,
@@ -115,6 +120,9 @@ export function getEnvironmentTypeName(environmentType: EnvironmentType): string
case EnvironmentType.Poetry: {
return 'Poetry';
}
+ case EnvironmentType.Hatch: {
+ return 'Hatch';
+ }
case EnvironmentType.VirtualEnvWrapper: {
return 'virtualenvwrapper';
}
diff --git a/extensions/positron-python/src/client/pythonEnvironments/legacyIOC.ts b/extensions/positron-python/src/client/pythonEnvironments/legacyIOC.ts
index 31de503a0f3..4ef0894a470 100644
--- a/extensions/positron-python/src/client/pythonEnvironments/legacyIOC.ts
+++ b/extensions/positron-python/src/client/pythonEnvironments/legacyIOC.ts
@@ -38,6 +38,7 @@ const convertedKinds = new Map(
[PythonEnvKind.VirtualEnv]: EnvironmentType.VirtualEnv,
[PythonEnvKind.Pipenv]: EnvironmentType.Pipenv,
[PythonEnvKind.Poetry]: EnvironmentType.Poetry,
+ [PythonEnvKind.Hatch]: EnvironmentType.Hatch,
[PythonEnvKind.Venv]: EnvironmentType.Venv,
[PythonEnvKind.VirtualEnvWrapper]: EnvironmentType.VirtualEnvWrapper,
[PythonEnvKind.ActiveState]: EnvironmentType.ActiveState,
diff --git a/extensions/positron-python/src/client/telemetry/index.ts b/extensions/positron-python/src/client/telemetry/index.ts
index bdb098be520..3237bafc224 100644
--- a/extensions/positron-python/src/client/telemetry/index.ts
+++ b/extensions/positron-python/src/client/telemetry/index.ts
@@ -3,11 +3,9 @@
// Licensed under the MIT License.
import TelemetryReporter from '@vscode/extension-telemetry';
-
-import * as path from 'path';
-import * as fs from 'fs-extra';
+import type * as vscodeTypes from 'vscode';
import { DiagnosticCodes } from '../application/diagnostics/constants';
-import { AppinsightsKey, EXTENSION_ROOT_DIR, isTestExecution, isUnitTestExecution } from '../common/constants';
+import { AppinsightsKey, isTestExecution, isUnitTestExecution, PVSC_EXTENSION_ID } from '../common/constants';
import type { TerminalShellType } from '../common/terminal/types';
import { StopWatch } from '../common/utils/stopWatch';
import { isPromise } from '../common/utils/async';
@@ -39,14 +37,20 @@ function isTelemetrySupported(): boolean {
}
}
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+let packageJSON: any;
+
/**
* Checks if the telemetry is disabled
* @returns {boolean}
*/
export function isTelemetryDisabled(): boolean {
- const packageJsonPath = path.join(EXTENSION_ROOT_DIR, 'package.json');
- const packageJson = fs.readJSONSync(packageJsonPath);
- return !packageJson.enableTelemetry;
+ if (!packageJSON) {
+ const vscode = require('vscode') as typeof vscodeTypes;
+ const pythonExtension = vscode.extensions.getExtension(PVSC_EXTENSION_ID)!;
+ packageJSON = pythonExtension.packageJSON;
+ }
+ return !packageJSON.enableTelemetry;
}
const sharedProperties: Record = {};
diff --git a/extensions/positron-python/src/client/telemetry/pylance.ts b/extensions/positron-python/src/client/telemetry/pylance.ts
index 67be7428c7b..c0a3edb013c 100644
--- a/extensions/positron-python/src/client/telemetry/pylance.ts
+++ b/extensions/positron-python/src/client/telemetry/pylance.ts
@@ -27,7 +27,8 @@
"errorstack" : { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
"lsversion" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"method" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
- "modulehash" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
+ "modulehash" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
+ "resultLength" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }
}
*/
/* __GDPR__
@@ -313,6 +314,15 @@
"typeevaltime" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }
}
*/
+/* __GDPR__
+ "language_server/server_side_request" : {
+ "duration" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
+ "lsversion" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
+ "method" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
+ "modulehash" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
+ "resultLength" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }
+ }
+*/
/* __GDPR__
"language_server/settings" : {
"addimportexactmatchonly" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
diff --git a/extensions/positron-python/src/client/terminals/codeExecution/helper.ts b/extensions/positron-python/src/client/terminals/codeExecution/helper.ts
index 48a435c8710..880da969d69 100644
--- a/extensions/positron-python/src/client/terminals/codeExecution/helper.ts
+++ b/extensions/positron-python/src/client/terminals/codeExecution/helper.ts
@@ -20,8 +20,7 @@ import { IInterpreterService } from '../../interpreter/contracts';
import { IServiceContainer } from '../../ioc/types';
import { ICodeExecutionHelper } from '../types';
import { traceError } from '../../logging';
-import { IConfigurationService, IExperimentService, Resource } from '../../common/types';
-import { EnableREPLSmartSend } from '../../common/experiments/groups';
+import { IConfigurationService, Resource } from '../../common/types';
import { sendTelemetryEvent } from '../../telemetry';
import { EventName } from '../../telemetry/constants';
@@ -93,8 +92,7 @@ export class CodeExecutionHelper implements ICodeExecutionHelper {
const startLineVal = activeEditor?.selection?.start.line ?? 0;
const endLineVal = activeEditor?.selection?.end.line ?? 0;
const emptyHighlightVal = activeEditor?.selection?.isEmpty ?? true;
- const smartSendExperimentEnabledVal = pythonSmartSendEnabled(this.serviceContainer);
- let smartSendSettingsEnabledVal = false;
+ let smartSendSettingsEnabledVal = true;
const configuration = this.serviceContainer.get(IConfigurationService);
if (configuration) {
const pythonSettings = configuration.getSettings(this.activeResourceService.getActiveResource());
@@ -107,7 +105,6 @@ export class CodeExecutionHelper implements ICodeExecutionHelper {
startLine: startLineVal,
endLine: endLineVal,
emptyHighlight: emptyHighlightVal,
- smartSendExperimentEnabled: smartSendExperimentEnabledVal,
smartSendSettingsEnabled: smartSendSettingsEnabledVal,
});
observable.proc?.stdin?.write(input);
@@ -117,12 +114,7 @@ export class CodeExecutionHelper implements ICodeExecutionHelper {
const result = await normalizeOutput.promise;
const object = JSON.parse(result);
- if (
- activeEditor?.selection &&
- smartSendExperimentEnabledVal &&
- smartSendSettingsEnabledVal &&
- object.normalized !== 'deprecated'
- ) {
+ if (activeEditor?.selection && smartSendSettingsEnabledVal && object.normalized !== 'deprecated') {
const lineOffset = object.nextBlockLineno - activeEditor!.selection.start.line - 1;
await this.moveToNextBlock(lineOffset, activeEditor);
}
@@ -145,16 +137,15 @@ export class CodeExecutionHelper implements ICodeExecutionHelper {
*/
// eslint-disable-next-line class-methods-use-this
private async moveToNextBlock(lineOffset: number, activeEditor?: TextEditor): Promise {
- if (pythonSmartSendEnabled(this.serviceContainer)) {
- if (activeEditor?.selection?.isEmpty) {
- await this.commandManager.executeCommand('cursorMove', {
- to: 'down',
- by: 'line',
- value: Number(lineOffset),
- });
- await this.commandManager.executeCommand('cursorEnd');
- }
+ if (activeEditor?.selection?.isEmpty) {
+ await this.commandManager.executeCommand('cursorMove', {
+ to: 'down',
+ by: 'line',
+ value: Number(lineOffset),
+ });
+ await this.commandManager.executeCommand('cursorEnd');
}
+
return Promise.resolve();
}
@@ -314,9 +305,3 @@ function getMultiLineSelectionText(textEditor: TextEditor): string {
// ↑<---------------- To here
return selectionText;
}
-
-function pythonSmartSendEnabled(serviceContainer: IServiceContainer): boolean {
- const experiment = serviceContainer.get(IExperimentService);
-
- return experiment ? experiment.inExperimentSync(EnableREPLSmartSend.experiment) : false;
-}
diff --git a/extensions/positron-python/src/client/terminals/envCollectionActivation/service.ts b/extensions/positron-python/src/client/terminals/envCollectionActivation/service.ts
index aaa4ccf23be..b7ec224828d 100644
--- a/extensions/positron-python/src/client/terminals/envCollectionActivation/service.ts
+++ b/extensions/positron-python/src/client/terminals/envCollectionActivation/service.ts
@@ -26,13 +26,13 @@ import {
IPathUtils,
} from '../../common/types';
import { Interpreters } from '../../common/utils/localize';
-import { traceError, traceInfo, traceVerbose, traceWarn } from '../../logging';
+import { traceError, traceInfo, traceLog, traceVerbose, traceWarn } from '../../logging';
import { IInterpreterService } from '../../interpreter/contracts';
import { defaultShells } from '../../interpreter/activation/service';
import { IEnvironmentActivationService } from '../../interpreter/activation/types';
import { EnvironmentType, PythonEnvironment } from '../../pythonEnvironments/info';
import { getSearchPathEnvVarNames } from '../../common/utils/exec';
-import { EnvironmentVariables } from '../../common/variables/types';
+import { EnvironmentVariables, IEnvironmentVariablesProvider } from '../../common/variables/types';
import { TerminalShellType } from '../../common/terminal/types';
import { OSType } from '../../common/utils/platform';
import { normCase } from '../../common/platform/fs-paths';
@@ -81,6 +81,8 @@ export class TerminalEnvVarCollectionService implements IExtensionActivationServ
@inject(ITerminalDeactivateService) private readonly terminalDeactivateService: ITerminalDeactivateService,
@inject(IPathUtils) private readonly pathUtils: IPathUtils,
@inject(IShellIntegrationService) private readonly shellIntegrationService: IShellIntegrationService,
+ @inject(IEnvironmentVariablesProvider)
+ private readonly environmentVariablesProvider: IEnvironmentVariablesProvider,
) {
this.separator = platform.osType === OSType.Windows ? ';' : ':';
this.progressService = new ProgressService(this.shell);
@@ -119,6 +121,13 @@ export class TerminalEnvVarCollectionService implements IExtensionActivationServ
this,
this.disposables,
);
+ this.environmentVariablesProvider.onDidEnvironmentVariablesChange(
+ async (r: Resource) => {
+ await this._applyCollection(r).ignoreErrors();
+ },
+ this,
+ this.disposables,
+ );
this.applicationEnvironment.onDidChangeShell(
async (shell: string) => {
this.processEnvVars = undefined;
@@ -213,7 +222,7 @@ export class TerminalEnvVarCollectionService implements IExtensionActivationServ
if (value !== undefined) {
if (key === 'PS1') {
// We cannot have the full PS1 without executing in terminal, which we do not. Hence prepend it.
- traceVerbose(
+ traceLog(
`Prepending environment variable ${key} in collection with ${value} ${JSON.stringify(
defaultPrependOptions,
)}`,
@@ -233,7 +242,7 @@ export class TerminalEnvVarCollectionService implements IExtensionActivationServ
if (deactivate) {
value = `${deactivate}${this.separator}${value}`;
}
- traceVerbose(
+ traceLog(
`Prepending environment variable ${key} in collection with ${value} ${JSON.stringify(
options,
)}`,
@@ -246,7 +255,7 @@ export class TerminalEnvVarCollectionService implements IExtensionActivationServ
if (deactivate) {
value = `${deactivate}${this.separator}${value}`;
}
- traceVerbose(
+ traceLog(
`Prepending environment variable ${key} in collection to ${value} ${JSON.stringify(
options,
)}`,
@@ -259,7 +268,7 @@ export class TerminalEnvVarCollectionService implements IExtensionActivationServ
applyAtShellIntegration: true,
applyAtProcessCreation: true,
};
- traceVerbose(
+ traceLog(
`Setting environment variable ${key} in collection to ${value} ${JSON.stringify(options)}`,
);
envVarCollection.replace(key, value, options);
@@ -329,6 +338,8 @@ export class TerminalEnvVarCollectionService implements IExtensionActivationServ
}
private async getPS1(shell: string, resource: Resource, env: EnvironmentVariables) {
+ // PS1 returned by shell is not predictable: #22078
+ // Hence calculate it ourselves where possible. Should no longer be needed once #22128 is available.
const customShellType = identifyShellFromShellPath(shell);
if (this.noPromptVariableShells.includes(customShellType)) {
return env.PS1;
@@ -338,7 +349,7 @@ export class TerminalEnvVarCollectionService implements IExtensionActivationServ
const interpreter = await this.interpreterService.getActiveInterpreter(resource);
const shouldSetPS1 = shouldPS1BeSet(interpreter?.type, env);
if (shouldSetPS1) {
- const prompt = getPromptForEnv(interpreter);
+ const prompt = getPromptForEnv(interpreter, env);
if (prompt) {
return prompt;
}
@@ -447,7 +458,7 @@ function shouldSkip(env: string) {
].includes(env);
}
-function getPromptForEnv(interpreter: PythonEnvironment | undefined) {
+function getPromptForEnv(interpreter: PythonEnvironment | undefined, env: EnvironmentVariables) {
if (!interpreter) {
return undefined;
}
@@ -456,6 +467,9 @@ function getPromptForEnv(interpreter: PythonEnvironment | undefined) {
// If conda base environment is selected, it can lead to "(base)" appearing twice if we return the env name.
return undefined;
}
+ if (interpreter.type === PythonEnvType.Virtual && env.VIRTUAL_ENV_PROMPT) {
+ return `(${env.VIRTUAL_ENV_PROMPT}) `;
+ }
return `(${interpreter.envName}) `;
}
if (interpreter.envPath) {
diff --git a/extensions/positron-python/src/client/terminals/envCollectionActivation/shellIntegrationService.ts b/extensions/positron-python/src/client/terminals/envCollectionActivation/shellIntegrationService.ts
index cba2ccbc686..8ab3d84122b 100644
--- a/extensions/positron-python/src/client/terminals/envCollectionActivation/shellIntegrationService.ts
+++ b/extensions/positron-python/src/client/terminals/envCollectionActivation/shellIntegrationService.ts
@@ -57,7 +57,6 @@ export class ShellIntegrationService implements IShellIntegrationService {
}
this.appShell.onDidWriteTerminalData(
(e) => {
- traceVerbose(e.data); // Log this temporarily for analysis
if (e.data.includes('\x1b]633;A\x07') || e.data.includes('\x1b]133;A\x07')) {
let { shell } = this.appEnvironment;
if ('shellPath' in e.terminal.creationOptions && e.terminal.creationOptions.shellPath) {
diff --git a/extensions/positron-python/src/client/testing/common/debugLauncher.ts b/extensions/positron-python/src/client/testing/common/debugLauncher.ts
index 85076461f22..f05fa6bc937 100644
--- a/extensions/positron-python/src/client/testing/common/debugLauncher.ts
+++ b/extensions/positron-python/src/client/testing/common/debugLauncher.ts
@@ -87,7 +87,7 @@ export class DebugLauncher implements ITestDebugLauncher {
debugConfig.rules = [];
}
debugConfig.rules.push({
- path: path.join(EXTENSION_ROOT_DIR, 'pythonFiles'),
+ path: path.join(EXTENSION_ROOT_DIR, 'python_files'),
include: false,
});
@@ -219,7 +219,7 @@ export class DebugLauncher implements ITestDebugLauncher {
);
}
}
- const pluginPath = path.join(EXTENSION_ROOT_DIR, 'pythonFiles');
+ const pluginPath = path.join(EXTENSION_ROOT_DIR, 'python_files');
// check if PYTHONPATH is already set in the environment variables
if (launchArgs.env) {
const additionalPythonPath = [pluginPath];
diff --git a/extensions/positron-python/src/client/testing/testController/common/types.ts b/extensions/positron-python/src/client/testing/testController/common/types.ts
index e51270eb4f9..685f36af007 100644
--- a/extensions/positron-python/src/client/testing/testController/common/types.ts
+++ b/extensions/positron-python/src/client/testing/testController/common/types.ts
@@ -228,7 +228,7 @@ export interface ITestExecutionAdapter {
): Promise;
}
-// Same types as in pythonFiles/unittestadapter/utils.py
+// Same types as in python_files/unittestadapter/utils.py
export type DiscoveredTestType = 'folder' | 'file' | 'class' | 'test';
export type DiscoveredTestCommon = {
diff --git a/extensions/positron-python/src/client/testing/testController/common/utils.ts b/extensions/positron-python/src/client/testing/testController/common/utils.ts
index 0e81154a899..e98fa99b9bd 100644
--- a/extensions/positron-python/src/client/testing/testController/common/utils.ts
+++ b/extensions/positron-python/src/client/testing/testController/common/utils.ts
@@ -359,14 +359,25 @@ export function splitTestNameWithRegex(testName: string): [string, string] {
* @param args - Readonly array of strings to be converted to a map.
* @returns A map representation of the input strings.
*/
-export const argsToMap = (args: ReadonlyArray): { [key: string]: string | null | undefined } => {
- const map: { [key: string]: string | null } = {};
+export const argsToMap = (args: ReadonlyArray): { [key: string]: Array | null | undefined } => {
+ const map: { [key: string]: Array | null } = {};
for (const arg of args) {
const delimiter = arg.indexOf('=');
if (delimiter === -1) {
+ // If no delimiter is found, the entire string becomes a key with a value of null.
map[arg] = null;
} else {
- map[arg.slice(0, delimiter)] = arg.slice(delimiter + 1);
+ const key = arg.slice(0, delimiter);
+ const value = arg.slice(delimiter + 1);
+ if (map[key]) {
+ // add to the array
+ const arr = map[key] as string[];
+ arr.push(value);
+ map[key] = arr;
+ } else {
+ // create a new array
+ map[key] = [value];
+ }
}
}
@@ -383,7 +394,7 @@ export const argsToMap = (args: ReadonlyArray): { [key: string]: string
* @param map - The map to be converted to an array of strings.
* @returns An array of strings representation of the input map.
*/
-export const mapToArgs = (map: { [key: string]: string | null | undefined }): string[] => {
+export const mapToArgs = (map: { [key: string]: Array | null | undefined }): string[] => {
const out: string[] = [];
for (const key of Object.keys(map)) {
const value = map[key];
@@ -391,8 +402,14 @@ export const mapToArgs = (map: { [key: string]: string | null | undefined }): st
// eslint-disable-next-line no-continue
continue;
}
-
- out.push(value === null ? key : `${key}=${value}`);
+ if (value === null) {
+ out.push(key);
+ } else {
+ const values = Array.isArray(value) ? (value as string[]) : [value];
+ for (const v of values) {
+ out.push(`${key}=${v}`);
+ }
+ }
}
return out;
@@ -407,13 +424,18 @@ export const mapToArgs = (map: { [key: string]: string | null | undefined }): st
* @returns The updated map.
*/
export function addArgIfNotExist(
- map: { [key: string]: string | null | undefined },
+ map: { [key: string]: Array | null | undefined },
argKey: string,
argValue: string | null,
-): { [key: string]: string | null | undefined } {
+): { [key: string]: Array | null | undefined } {
// Only add the argument if it doesn't exist in the map.
if (map[argKey] === undefined) {
- map[argKey] = argValue;
+ // if null then set to null, otherwise set to an array with the value
+ if (argValue === null) {
+ map[argKey] = null;
+ } else {
+ map[argKey] = [argValue];
+ }
}
return map;
@@ -426,6 +448,6 @@ export function addArgIfNotExist(
* @param argKey - The argument key to be checked.
* @returns True if the argument key exists in the map, false otherwise.
*/
-export function argKeyExists(map: { [key: string]: string | null | undefined }, argKey: string): boolean {
+export function argKeyExists(map: { [key: string]: Array | null | undefined }, argKey: string): boolean {
return map[argKey] !== undefined;
}
diff --git a/extensions/positron-python/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts b/extensions/positron-python/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts
index ab44c96821e..2d0dab76508 100644
--- a/extensions/positron-python/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts
+++ b/extensions/positron-python/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts
@@ -67,7 +67,7 @@ export class PytestTestDiscoveryAdapter implements ITestDiscoveryAdapter {
}
async runPytestDiscovery(uri: Uri, uuid: string, executionFactory?: IPythonExecutionFactory): Promise {
- const relativePathToPytest = 'pythonFiles';
+ const relativePathToPytest = 'python_files';
const fullPluginPath = path.join(EXTENSION_ROOT_DIR, relativePathToPytest);
const settings = this.configSettings.getSettings(uri);
let pytestArgsMap = argsToMap(settings.testing.pytestArgs);
diff --git a/extensions/positron-python/src/client/testing/testController/pytest/pytestExecutionAdapter.ts b/extensions/positron-python/src/client/testing/testController/pytest/pytestExecutionAdapter.ts
index d366bdfc971..de519548d68 100644
--- a/extensions/positron-python/src/client/testing/testController/pytest/pytestExecutionAdapter.ts
+++ b/extensions/positron-python/src/client/testing/testController/pytest/pytestExecutionAdapter.ts
@@ -103,7 +103,7 @@ export class PytestTestExecutionAdapter implements ITestExecutionAdapter {
debugLauncher?: ITestDebugLauncher,
deferredTillEOT?: Deferred,
): Promise {
- const relativePathToPytest = 'pythonFiles';
+ const relativePathToPytest = 'python_files';
const fullPluginPath = path.join(EXTENSION_ROOT_DIR, relativePathToPytest);
const settings = this.configSettings.getSettings(uri);
const { pytestArgs } = settings.testing;
diff --git a/extensions/positron-python/src/client/testing/testController/unittest/testDiscoveryAdapter.ts b/extensions/positron-python/src/client/testing/testController/unittest/testDiscoveryAdapter.ts
index 75e29afc971..8cc44b3783c 100644
--- a/extensions/positron-python/src/client/testing/testController/unittest/testDiscoveryAdapter.ts
+++ b/extensions/positron-python/src/client/testing/testController/unittest/testDiscoveryAdapter.ts
@@ -83,7 +83,7 @@ export class UnittestTestDiscoveryAdapter implements ITestDiscoveryAdapter {
}
function buildDiscoveryCommand(args: string[]): TestDiscoveryCommand {
- const discoveryScript = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'unittestadapter', 'discovery.py');
+ const discoveryScript = path.join(EXTENSION_ROOT_DIR, 'python_files', 'unittestadapter', 'discovery.py');
return {
script: discoveryScript,
diff --git a/extensions/positron-python/src/client/testing/testController/unittest/testExecutionAdapter.ts b/extensions/positron-python/src/client/testing/testController/unittest/testExecutionAdapter.ts
index d90581a9311..85fd01f093a 100644
--- a/extensions/positron-python/src/client/testing/testController/unittest/testExecutionAdapter.ts
+++ b/extensions/positron-python/src/client/testing/testController/unittest/testExecutionAdapter.ts
@@ -109,7 +109,7 @@ export class UnittestTestExecutionAdapter implements ITestExecutionAdapter {
}
function buildExecutionCommand(args: string[]): TestExecutionCommand {
- const executionScript = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'unittestadapter', 'execution.py');
+ const executionScript = path.join(EXTENSION_ROOT_DIR, 'python_files', 'unittestadapter', 'execution.py');
return {
script: executionScript,
diff --git a/extensions/positron-python/src/test/activation/node/lspInteractiveWindowMiddlewareAddon.unit.test.ts b/extensions/positron-python/src/test/activation/node/lspInteractiveWindowMiddlewareAddon.unit.test.ts
deleted file mode 100644
index 32d9198ef7b..00000000000
--- a/extensions/positron-python/src/test/activation/node/lspInteractiveWindowMiddlewareAddon.unit.test.ts
+++ /dev/null
@@ -1,175 +0,0 @@
-/* eslint-disable @typescript-eslint/no-empty-function */
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-'use strict';
-
-import { NotebookCell, NotebookCellKind, NotebookDocument, TextDocument, Uri } from 'vscode';
-import { expect } from 'chai';
-import { anything, capture, instance, mock, verify, when } from 'ts-mockito';
-import { LanguageClient } from 'vscode-languageclient/node';
-import { LspInteractiveWindowMiddlewareAddon } from '../../../client/activation/node/lspInteractiveWindowMiddlewareAddon';
-import { JupyterExtensionIntegration } from '../../../client/jupyter/jupyterIntegration';
-import { IExtensions } from '../../../client/common/types';
-import { ICondaService, IInterpreterDisplay } from '../../../client/interpreter/contracts';
-import { IInterpreterSelector } from '../../../client/interpreter/configuration/types';
-import { IEnvironmentActivationService } from '../../../client/interpreter/activation/types';
-import { IContextKeyManager, IWorkspaceService } from '../../../client/common/application/types';
-import { MockMemento } from '../../mocks/mementos';
-
-suite('Pylance Language Server - Interactive Window LSP Notebooks', () => {
- const languageClientMock = mock();
- let languageClient: LanguageClient;
- let jupyterApi: JupyterExtensionIntegration;
- let middleware: LspInteractiveWindowMiddlewareAddon;
-
- setup(() => {
- languageClient = instance(languageClientMock);
- jupyterApi = new JupyterExtensionIntegration(
- mock(),
- mock(),
- mock(),
- new MockMemento(),
- mock(),
- mock(),
- mock(),
- mock(),
- );
- jupyterApi.registerGetNotebookUriForTextDocumentUriFunction(getNotebookUriFunction);
- });
- teardown(() => {
- middleware?.dispose();
- });
-
- test('Unrelated document open should be forwarded to next handler unchanged', async () => {
- middleware = makeMiddleware();
-
- const uri = Uri.from({ scheme: 'file', path: 'test.py' });
- const textDocument = createTextDocument(uri);
-
- let nextCalled = false;
- await middleware.didOpen(textDocument, async (_) => {
- nextCalled = true;
- });
-
- return expect(nextCalled).to.be.true;
- });
-
- test('Notebook-related textDocument/didOpen should be swallowed', async () => {
- middleware = makeMiddleware();
-
- const uri = Uri.from({ scheme: 'test-input', path: 'Test' });
- const textDocument = createTextDocument(uri);
-
- let nextCalled = false;
- await middleware.didOpen(textDocument, async (_) => {
- nextCalled = true;
- });
-
- return expect(nextCalled).to.be.false;
- });
-
- test('Notebook-related document should be added at end of cells in notebookDocument/didOpen', async () => {
- middleware = makeMiddleware();
-
- const uri = Uri.from({ scheme: 'test-input', path: 'Test' });
- const textDocument = createTextDocument(uri);
-
- await middleware.didOpen(textDocument, async (_) => {});
-
- const cellCount = 2;
- const [notebookDocument, cells] = createNotebookDocument(getNotebookUriFunction(uri)!, cellCount);
- await middleware.notebooks.didOpen(notebookDocument, cells, async (_, nextCells) => {
- expect(nextCells.length).to.be.equals(cellCount + 1);
- expect(nextCells[cellCount]).to.deep.equal({
- index: cellCount,
- notebook: notebookDocument,
- kind: NotebookCellKind.Code,
- document: textDocument,
- metadata: {},
- outputs: [],
- executionSummary: undefined,
- });
- });
- });
-
- test('Notebook-related document opened after notebook causes notebookDocument/didChange', async () => {
- middleware = makeMiddleware();
-
- const uri = Uri.from({ scheme: 'test-input', path: 'Test' });
- const textDocument = createTextDocument(uri);
-
- const cellCount = 2;
- const [notebookDocument, cells] = createNotebookDocument(getNotebookUriFunction(uri)!, cellCount);
- await middleware.notebooks.didOpen(notebookDocument, cells, async (_) => {});
-
- await middleware.didOpen(textDocument, async (_) => {});
-
- verify(languageClientMock.sendNotification(anything(), anything())).once();
- const message = capture(languageClientMock.sendNotification).last()[1];
-
- expect(message.notebookDocument.uri).to.equal(notebookDocument.uri.toString());
- expect(message.change.cells.structure).to.deep.equal({
- array: {
- start: notebookDocument.cellCount,
- deleteCount: 0,
- cells: [{ kind: NotebookCellKind.Code, document: textDocument.uri.toString() }],
- },
- didOpen: [
- {
- uri: textDocument.uri.toString(),
- languageId: textDocument.languageId,
- version: textDocument.version,
- text: textDocument.getText(),
- },
- ],
- didClose: undefined,
- });
- });
-
- function makeMiddleware(): LspInteractiveWindowMiddlewareAddon {
- return new LspInteractiveWindowMiddlewareAddon(() => languageClient, jupyterApi);
- }
-
- function getNotebookUriFunction(textDocumentUri: Uri): Uri | undefined {
- if (textDocumentUri.scheme === 'test-input') {
- return textDocumentUri.with({ scheme: 'test-notebook' });
- }
-
- return undefined;
- }
-
- function createTextDocument(uri: Uri): TextDocument {
- const textDocumentMock = mock();
- when(textDocumentMock.uri).thenReturn(uri);
- when(textDocumentMock.languageId).thenReturn('python');
- when(textDocumentMock.version).thenReturn(11);
-
- return instance(textDocumentMock);
- }
-
- function createNotebookDocument(uri: Uri, cellCount: number): [NotebookDocument, NotebookCell[]] {
- const notebookDocumentMock = mock();
- when(notebookDocumentMock.uri).thenReturn(uri);
- when(notebookDocumentMock.notebookType).thenReturn('jupyter');
- when(notebookDocumentMock.version).thenReturn(20);
- when(notebookDocumentMock.cellCount).thenReturn(cellCount);
-
- const notebookDocument = instance(notebookDocumentMock);
-
- const cells: NotebookCell[] = [];
- for (let i = 0; i < cellCount; i = i + 1) {
- cells.push({
- index: i,
- notebook: notebookDocument,
- kind: NotebookCellKind.Code,
- document: createTextDocument(Uri.from({ scheme: 'test-cell', path: `cell${i}` })),
- metadata: {},
- outputs: [],
- executionSummary: undefined,
- });
- }
-
- return [notebookDocument, cells];
- }
-});
diff --git a/extensions/positron-python/src/test/api.functional.test.ts b/extensions/positron-python/src/test/api.functional.test.ts
index 74293f55256..851d56c00e0 100644
--- a/extensions/positron-python/src/test/api.functional.test.ts
+++ b/extensions/positron-python/src/test/api.functional.test.ts
@@ -19,7 +19,7 @@ import { IServiceContainer, IServiceManager } from '../client/ioc/types';
import { IDiscoveryAPI } from '../client/pythonEnvironments/base/locator';
suite('Extension API', () => {
- const debuggerPath = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'lib', 'python', 'debugpy');
+ const debuggerPath = path.join(EXTENSION_ROOT_DIR, 'python_files', 'lib', 'python', 'debugpy');
const debuggerHost = 'somehost';
const debuggerPort = 12345;
@@ -37,6 +37,7 @@ suite('Extension API', () => {
interpreterService = mock(InterpreterService);
environmentVariablesProvider = mock();
discoverAPI = mock();
+ when(discoverAPI.getEnvs()).thenReturn([]);
when(serviceContainer.get(IConfigurationService)).thenReturn(
instance(configurationService),
diff --git a/extensions/positron-python/src/test/common.ts b/extensions/positron-python/src/test/common.ts
index 2ef366a3a47..bbf48f0e14c 100644
--- a/extensions/positron-python/src/test/common.ts
+++ b/extensions/positron-python/src/test/common.ts
@@ -22,7 +22,7 @@ const StreamZip = require('node-stream-zip');
export { sleep } from './core';
-const fileInNonRootWorkspace = path.join(EXTENSION_ROOT_DIR_FOR_TESTS, 'src', 'test', 'pythonFiles', 'dummy.py');
+const fileInNonRootWorkspace = path.join(EXTENSION_ROOT_DIR_FOR_TESTS, 'src', 'test', 'python_files', 'dummy.py');
export const rootWorkspaceUri = getWorkspaceRoot();
export const PYTHON_PATH = getPythonPath();
diff --git a/extensions/positron-python/src/test/common/application/commands/issueTemplateVenv2.md b/extensions/positron-python/src/test/common/application/commands/issueTemplate.md
similarity index 51%
rename from extensions/positron-python/src/test/common/application/commands/issueTemplateVenv2.md
rename to extensions/positron-python/src/test/common/application/commands/issueTemplate.md
index fa9142e5ca4..a95af90ff7f 100644
--- a/extensions/positron-python/src/test/common/application/commands/issueTemplateVenv2.md
+++ b/extensions/positron-python/src/test/common/application/commands/issueTemplate.md
@@ -1,6 +1,5 @@
# Behaviour
-## Expected vs. Actual
XXX
@@ -12,13 +11,9 @@ XXX
**After** creating the issue on GitHub, you can add screenshots and GIFs of what is happening. Consider tools like https://www.cockos.com/licecap/, https://github.com/phw/peek or https://www.screentogif.com/ for GIF creation.
-->
-
+
# Diagnostic data
-- Python version (& distribution if applicable, e.g. Anaconda): 3.9.0
-- Type of virtual environment used (e.g. conda, venv, virtualenv, etc.): Venv
-- Value of the `python.languageServer` setting: Pylance
-
Output for Python
in the Output
panel (View
→Output
, change the drop-down the upper-right of the Output
panel to Python
)
@@ -32,22 +27,3 @@ XXX
-
-
-
-User Settings
-
-
-
-```
-Multiroot scenario, following user settings may not apply:
-
-experiments
-• enabled: false
-
-venvPath: ""
-
-```
-
-
-
diff --git a/extensions/positron-python/src/test/common/application/commands/issueTemplateVenv1.md b/extensions/positron-python/src/test/common/application/commands/issueTemplateVenv1.md
deleted file mode 100644
index 09cdd2c32eb..00000000000
--- a/extensions/positron-python/src/test/common/application/commands/issueTemplateVenv1.md
+++ /dev/null
@@ -1,56 +0,0 @@
-
-# Behaviour
-## Expected vs. Actual
-
-XXX
-
-## Steps to reproduce:
-
-1. XXX
-
-
-
-
-# Diagnostic data
-
-- Python version (& distribution if applicable, e.g. Anaconda): 3.9.0
-- Type of virtual environment used (e.g. conda, venv, virtualenv, etc.): Venv
-- Value of the `python.languageServer` setting: Pylance
-
-
-
-Output for Python
in the Output
panel (View
→Output
, change the drop-down the upper-right of the Output
panel to Python
)
-
-
-
-
-```
-XXX
-```
-
-
-
-
-
-
-User Settings
-
-
-
-```
-
-experiments
-• enabled: false
-• optInto: []
-• optOutFrom: []
-
-venvPath: ""
-
-pipenvPath: ""
-
-```
-
-
-
diff --git a/extensions/positron-python/src/test/common/application/commands/issueUserDataTemplateVenv1.md b/extensions/positron-python/src/test/common/application/commands/issueUserDataTemplateVenv1.md
new file mode 100644
index 00000000000..9c1aac03cf5
--- /dev/null
+++ b/extensions/positron-python/src/test/common/application/commands/issueUserDataTemplateVenv1.md
@@ -0,0 +1,30 @@
+- Python version (& distribution if applicable, e.g. Anaconda): 3.9.0
+- Type of virtual environment used (e.g. conda, venv, virtualenv, etc.): Venv
+- Value of the `python.languageServer` setting: Pylance
+
+
+User Settings
+
+
+```
+
+experiments
+• enabled: false
+• optInto: []
+• optOutFrom: []
+
+venvPath: ""
+
+pipenvPath: ""
+
+```
+
+
+
+
+Installed Extensions
+
+|Extension Name|Extension Id|Version|
+|---|---|---|
+|Python|ms-python.python|2020.2|
+
diff --git a/extensions/positron-python/src/test/common/application/commands/issueUserDataTemplateVenv2.md b/extensions/positron-python/src/test/common/application/commands/issueUserDataTemplateVenv2.md
new file mode 100644
index 00000000000..fa218fc35b0
--- /dev/null
+++ b/extensions/positron-python/src/test/common/application/commands/issueUserDataTemplateVenv2.md
@@ -0,0 +1,27 @@
+- Python version (& distribution if applicable, e.g. Anaconda): 3.9.0
+- Type of virtual environment used (e.g. conda, venv, virtualenv, etc.): Venv
+- Value of the `python.languageServer` setting: Pylance
+
+
+User Settings
+
+
+```
+Multiroot scenario, following user settings may not apply:
+
+experiments
+• enabled: false
+
+venvPath: ""
+
+```
+
+
+
+
+Installed Extensions
+
+|Extension Name|Extension Id|Version|
+|---|---|---|
+|Python|ms-python.python|2020.2|
+
diff --git a/extensions/positron-python/src/test/common/application/commands/reportIssueCommand.unit.test.ts b/extensions/positron-python/src/test/common/application/commands/reportIssueCommand.unit.test.ts
index 92b4c0725f3..2a35a6306cd 100644
--- a/extensions/positron-python/src/test/common/application/commands/reportIssueCommand.unit.test.ts
+++ b/extensions/positron-python/src/test/common/application/commands/reportIssueCommand.unit.test.ts
@@ -30,6 +30,7 @@ import { IConfigurationService } from '../../../../client/common/types';
import { EventName } from '../../../../client/telemetry/constants';
import { EnvironmentType, PythonEnvironment } from '../../../../client/pythonEnvironments/info';
import { EXTENSION_ROOT_DIR_FOR_TESTS } from '../../../constants';
+import * as extensionsApi from '../../../../client/common/vscodeApis/extensionsApi';
suite('Report Issue Command', () => {
let reportIssueCommandHandler: ReportIssueCommandHandler;
@@ -38,6 +39,8 @@ suite('Report Issue Command', () => {
let interpreterService: IInterpreterService;
let configurationService: IConfigurationService;
let appEnvironment: IApplicationEnvironment;
+ let expectedIssueBody: string;
+ let getExtensionsStub: sinon.SinonStub;
setup(async () => {
workspaceService = mock(WorkspaceService);
@@ -45,6 +48,7 @@ suite('Report Issue Command', () => {
interpreterService = mock(InterpreterService);
configurationService = mock(ConfigurationService);
appEnvironment = mock();
+ getExtensionsStub = sinon.stub(extensionsApi, 'getExtensions');
when(cmdManager.executeCommand('workbench.action.openIssueReporter', anything())).thenResolve();
when(workspaceService.getConfiguration('python')).thenReturn(
@@ -79,6 +83,27 @@ suite('Report Issue Command', () => {
instance(appEnvironment),
);
await reportIssueCommandHandler.activate();
+
+ const issueTemplatePath = path.join(
+ EXTENSION_ROOT_DIR_FOR_TESTS,
+ 'src',
+ 'test',
+ 'common',
+ 'application',
+ 'commands',
+ 'issueTemplate.md',
+ );
+ expectedIssueBody = fs.readFileSync(issueTemplatePath, 'utf8');
+
+ getExtensionsStub.returns([
+ {
+ id: 'ms-python.python',
+ packageJSON: {
+ displayName: 'Python',
+ version: '2020.2',
+ },
+ },
+ ]);
});
teardown(() => {
@@ -88,27 +113,28 @@ suite('Report Issue Command', () => {
test('Test if issue body is filled correctly when including all the settings', async () => {
await reportIssueCommandHandler.openReportIssue();
- const templatePath = path.join(
+ const userDataTemplatePath = path.join(
EXTENSION_ROOT_DIR_FOR_TESTS,
'src',
'test',
'common',
'application',
'commands',
- 'issueTemplateVenv1.md',
+ 'issueUserDataTemplateVenv1.md',
);
- const expectedIssueBody = fs.readFileSync(templatePath, 'utf8');
+ const expectedData = fs.readFileSync(userDataTemplatePath, 'utf8');
- const args: [string, { extensionId: string; issueBody: string }] = capture<
+ const args: [string, { extensionId: string; issueBody: string; data: string }] = capture<
AllCommands,
- { extensionId: string; issueBody: string }
+ { extensionId: string; issueBody: string; data: string }
>(cmdManager.executeCommand).last();
verify(cmdManager.registerCommand(Commands.ReportIssue, anything(), anything())).once();
verify(cmdManager.executeCommand('workbench.action.openIssueReporter', anything())).once();
expect(args[0]).to.be.equal('workbench.action.openIssueReporter');
- const actual = args[1].issueBody;
- expect(actual).to.be.equal(expectedIssueBody);
+ const { issueBody, data } = args[1];
+ expect(issueBody).to.be.equal(expectedIssueBody);
+ expect(data).to.be.equal(expectedData);
});
test('Test if issue body is filled when only including settings which are explicitly set', async () => {
@@ -128,26 +154,27 @@ suite('Report Issue Command', () => {
await reportIssueCommandHandler.activate();
await reportIssueCommandHandler.openReportIssue();
- const templatePath = path.join(
+ const userDataTemplatePath = path.join(
EXTENSION_ROOT_DIR_FOR_TESTS,
'src',
'test',
'common',
'application',
'commands',
- 'issueTemplateVenv2.md',
+ 'issueUserDataTemplateVenv2.md',
);
- const expectedIssueBody = fs.readFileSync(templatePath, 'utf8');
+ const expectedData = fs.readFileSync(userDataTemplatePath, 'utf8');
- const args: [string, { extensionId: string; issueBody: string }] = capture<
+ const args: [string, { extensionId: string; issueBody: string; data: string }] = capture<
AllCommands,
- { extensionId: string; issueBody: string }
+ { extensionId: string; issueBody: string; data: string }
>(cmdManager.executeCommand).last();
verify(cmdManager.executeCommand('workbench.action.openIssueReporter', anything())).once();
expect(args[0]).to.be.equal('workbench.action.openIssueReporter');
- const actual = args[1].issueBody;
- expect(actual).to.be.equal(expectedIssueBody);
+ const { issueBody, data } = args[1];
+ expect(issueBody).to.be.equal(expectedIssueBody);
+ expect(data).to.be.equal(expectedData);
});
test('Should send telemetry event when run Report Issue Command', async () => {
const sendTelemetryStub = sinon.stub(Telemetry, 'sendTelemetryEvent');
diff --git a/extensions/positron-python/src/test/common/terminals/service.unit.test.ts b/extensions/positron-python/src/test/common/terminals/service.unit.test.ts
index 7336f7094e6..61556e3df2d 100644
--- a/extensions/positron-python/src/test/common/terminals/service.unit.test.ts
+++ b/extensions/positron-python/src/test/common/terminals/service.unit.test.ts
@@ -170,7 +170,7 @@ suite('Terminal Service', () => {
.setup((t) => t.createTerminal(TypeMoq.It.isAny()))
.returns(() => terminal.object)
.verifiable(TypeMoq.Times.atLeastOnce());
- const envVarScript = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'pythonrc.py');
+ const envVarScript = path.join(EXTENSION_ROOT_DIR, 'python_files', 'pythonrc.py');
terminalManager
.setup((t) =>
t.createTerminal({
diff --git a/extensions/positron-python/src/test/common/terminals/synchronousTerminalService.unit.test.ts b/extensions/positron-python/src/test/common/terminals/synchronousTerminalService.unit.test.ts
index f74c529ef47..4b6e77ec809 100644
--- a/extensions/positron-python/src/test/common/terminals/synchronousTerminalService.unit.test.ts
+++ b/extensions/positron-python/src/test/common/terminals/synchronousTerminalService.unit.test.ts
@@ -66,7 +66,7 @@ suite('Terminal Service (synchronous)', () => {
});
});
suite('sendCommand', () => {
- const shellExecFile = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'shell_exec.py');
+ const shellExecFile = path.join(EXTENSION_ROOT_DIR, 'python_files', 'shell_exec.py');
test('run sendCommand in terminalService if there is no cancellation token', async () => {
when(terminalService.sendCommand('cmd', deepEqual(['1', '2']))).thenResolve();
diff --git a/extensions/positron-python/src/test/configuration/environmentTypeComparer.unit.test.ts b/extensions/positron-python/src/test/configuration/environmentTypeComparer.unit.test.ts
index ccd0aa6add2..53f6bccad3c 100644
--- a/extensions/positron-python/src/test/configuration/environmentTypeComparer.unit.test.ts
+++ b/extensions/positron-python/src/test/configuration/environmentTypeComparer.unit.test.ts
@@ -16,6 +16,7 @@ import {
} from '../../client/interpreter/configuration/environmentTypeComparer';
import { IInterpreterHelper } from '../../client/interpreter/contracts';
import { PythonEnvType } from '../../client/pythonEnvironments/base/info';
+import * as pyenv from '../../client/pythonEnvironments/common/environmentManagers/pyenv';
import { EnvironmentType, PythonEnvironment } from '../../client/pythonEnvironments/info';
// --- Start Positron ---
import * as externalDependencies from '../../client/pythonEnvironments/common/externalDependencies';
@@ -28,6 +29,7 @@ suite('Environment sorting', () => {
let interpreterHelper: IInterpreterHelper;
let getActiveWorkspaceUriStub: sinon.SinonStub;
let getInterpreterTypeDisplayNameStub: sinon.SinonStub;
+ const preferredPyenv = path.join('path', 'to', 'preferred', 'pyenv');
setup(() => {
getActiveWorkspaceUriStub = sinon.stub().returns({ folderUri: { fsPath: workspacePath } });
@@ -37,6 +39,8 @@ suite('Environment sorting', () => {
getActiveWorkspaceUri: getActiveWorkspaceUriStub,
getInterpreterTypeDisplayName: getInterpreterTypeDisplayNameStub,
} as unknown) as IInterpreterHelper;
+ const getActivePyenvForDirectory = sinon.stub(pyenv, 'getActivePyenvForDirectory');
+ getActivePyenvForDirectory.resolves(preferredPyenv);
});
teardown(() => {
@@ -171,6 +175,33 @@ suite('Environment sorting', () => {
expected: -1,
},
// --- End Positron ---
+ {
+ title: 'Preferred Pyenv interpreter should come before any global interpreter',
+ envA: {
+ envType: EnvironmentType.Pyenv,
+ version: { major: 3, minor: 12, patch: 2 },
+ path: preferredPyenv,
+ } as PythonEnvironment,
+ envB: {
+ envType: EnvironmentType.Pyenv,
+ version: { major: 3, minor: 10, patch: 2 },
+ path: path.join('path', 'to', 'normal', 'pyenv'),
+ } as PythonEnvironment,
+ expected: -1,
+ },
+ {
+ title: 'Pyenv interpreters should come first when there are global interpreters',
+ envA: {
+ envType: EnvironmentType.Global,
+ version: { major: 3, minor: 10, patch: 2 },
+ } as PythonEnvironment,
+ envB: {
+ envType: EnvironmentType.Pyenv,
+ version: { major: 3, minor: 7, patch: 2 },
+ path: path.join('path', 'to', 'normal', 'pyenv'),
+ } as PythonEnvironment,
+ expected: 1,
+ },
{
title: 'Global environment should not come first when there are global envs',
envA: {
@@ -307,8 +338,9 @@ suite('Environment sorting', () => {
];
testcases.forEach(({ title, envA, envB, expected }) => {
- test(title, () => {
+ test(title, async () => {
const envTypeComparer = new EnvironmentTypeComparer(interpreterHelper);
+ await envTypeComparer.initialize(undefined);
const result = envTypeComparer.compare(envA, envB);
assert.strictEqual(result, expected);
diff --git a/extensions/positron-python/src/test/debugger/extension/adapter/adapter.test.ts b/extensions/positron-python/src/test/debugger/extension/adapter/adapter.test.ts
index 7e20d5b930b..2f60290897a 100644
--- a/extensions/positron-python/src/test/debugger/extension/adapter/adapter.test.ts
+++ b/extensions/positron-python/src/test/debugger/extension/adapter/adapter.test.ts
@@ -19,7 +19,7 @@ function resolveWSFile(wsRoot: string, ...filePath: string[]): string {
}
suite('Debugger Integration', () => {
- const file = resolveWSFile(WS_ROOT, 'pythonFiles', 'debugging', 'wait_for_file.py');
+ const file = resolveWSFile(WS_ROOT, 'python_files', 'debugging', 'wait_for_file.py');
const doneFile = resolveWSFile(WS_ROOT, 'should-not-exist');
const outFile = resolveWSFile(WS_ROOT, 'output.txt');
const resource = vscode.Uri.file(file);
diff --git a/extensions/positron-python/src/test/debugger/extension/adapter/factory.unit.test.ts b/extensions/positron-python/src/test/debugger/extension/adapter/factory.unit.test.ts
index 5728bf0c34c..172c2715b08 100644
--- a/extensions/positron-python/src/test/debugger/extension/adapter/factory.unit.test.ts
+++ b/extensions/positron-python/src/test/debugger/extension/adapter/factory.unit.test.ts
@@ -41,7 +41,7 @@ suite('Debugging - Adapter Factory', () => {
let commandManager: ICommandManager;
const nodeExecutable = undefined;
- const debugAdapterPath = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'lib', 'python', 'debugpy', 'adapter');
+ const debugAdapterPath = path.join(EXTENSION_ROOT_DIR, 'python_files', 'lib', 'python', 'debugpy', 'adapter');
const pythonPath = path.join('path', 'to', 'python', 'interpreter');
const interpreter = {
architecture: Architecture.Unknown,
diff --git a/extensions/positron-python/src/test/debugger/extension/adapter/remoteLaunchers.unit.test.ts b/extensions/positron-python/src/test/debugger/extension/adapter/remoteLaunchers.unit.test.ts
index aa520f66faa..2a75f6316a0 100644
--- a/extensions/positron-python/src/test/debugger/extension/adapter/remoteLaunchers.unit.test.ts
+++ b/extensions/positron-python/src/test/debugger/extension/adapter/remoteLaunchers.unit.test.ts
@@ -52,7 +52,7 @@ suite('External debugpy Debugger Launcher', () => {
});
suite('Path To Debugger Package', () => {
- const pathToPythonLibDir = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'lib', 'python');
+ const pathToPythonLibDir = path.join(EXTENSION_ROOT_DIR, 'python_files', 'lib', 'python');
test('Path to debugpy debugger package', () => {
const actual = launchers.getDebugpyPackagePath();
const expected = path.join(pathToPythonLibDir, 'debugpy');
diff --git a/extensions/positron-python/src/test/environmentApi.unit.test.ts b/extensions/positron-python/src/test/environmentApi.unit.test.ts
index 1d8dc3e5c84..012e1a0bfc6 100644
--- a/extensions/positron-python/src/test/environmentApi.unit.test.ts
+++ b/extensions/positron-python/src/test/environmentApi.unit.test.ts
@@ -74,8 +74,7 @@ suite('Python Environment API', () => {
envVarsProvider = typemoq.Mock.ofType();
extensions
.setup((e) => e.determineExtensionFromCallStack())
- .returns(() => Promise.resolve({ extensionId: 'id', displayName: 'displayName', apiName: 'apiName' }))
- .verifiable(typemoq.Times.atLeastOnce());
+ .returns(() => Promise.resolve({ extensionId: 'id', displayName: 'displayName', apiName: 'apiName' }));
interpreterPathService = typemoq.Mock.ofType();
configService = typemoq.Mock.ofType();
onDidChangeRefreshState = new EventEmitter();
@@ -94,13 +93,12 @@ suite('Python Environment API', () => {
discoverAPI.setup((d) => d.onProgress).returns(() => onDidChangeRefreshState.event);
discoverAPI.setup((d) => d.onChanged).returns(() => onDidChangeEnvironments.event);
+ discoverAPI.setup((d) => d.getEnvs()).returns(() => []);
environmentApi = buildEnvironmentApi(discoverAPI.object, serviceContainer.object);
});
teardown(() => {
- // Verify each API method sends telemetry regarding who called the API.
- extensions.verifyAll();
sinon.restore();
});
@@ -325,6 +323,7 @@ suite('Python Environment API', () => {
},
];
discoverAPI.setup((d) => d.getEnvs()).returns(() => envs);
+ environmentApi = buildEnvironmentApi(discoverAPI.object, serviceContainer.object);
const actual = environmentApi.known;
const actualEnvs = actual?.map((a) => (a as EnvironmentReference).internal);
assert.deepEqual(
@@ -409,10 +408,10 @@ suite('Python Environment API', () => {
// Update events
events = [];
expectedEvents = [];
- const updatedEnv = cloneDeep(envs[0]);
- updatedEnv.arch = Architecture.x86;
- onDidChangeEnvironments.fire({ old: envs[0], new: updatedEnv });
- expectedEvents.push({ env: convertEnvInfo(updatedEnv), type: 'update' });
+ const updatedEnv0 = cloneDeep(envs[0]);
+ updatedEnv0.arch = Architecture.x86;
+ onDidChangeEnvironments.fire({ old: envs[0], new: updatedEnv0 });
+ expectedEvents.push({ env: convertEnvInfo(updatedEnv0), type: 'update' });
eventValues = events.map((e) => ({ env: (e.env as EnvironmentReference).internal, type: e.type }));
assert.deepEqual(eventValues, expectedEvents);
@@ -423,6 +422,11 @@ suite('Python Environment API', () => {
expectedEvents.push({ env: convertEnvInfo(envs[2]), type: 'remove' });
eventValues = events.map((e) => ({ env: (e.env as EnvironmentReference).internal, type: e.type }));
assert.deepEqual(eventValues, expectedEvents);
+
+ const expectedEnvs = [convertEnvInfo(updatedEnv0), convertEnvInfo(envs[1])].sort();
+ const knownEnvs = environmentApi.known.map((e) => (e as EnvironmentReference).internal).sort();
+
+ assert.deepEqual(expectedEnvs, knownEnvs);
});
test('updateActiveEnvironmentPath: no resource', async () => {
diff --git a/extensions/positron-python/src/test/initialize.ts b/extensions/positron-python/src/test/initialize.ts
index 3937589900b..8430f5781d2 100644
--- a/extensions/positron-python/src/test/initialize.ts
+++ b/extensions/positron-python/src/test/initialize.ts
@@ -17,7 +17,7 @@ import { initializePositron } from './positron/initialize';
export * from './constants';
export * from './ciConstants';
-const dummyPythonFile = path.join(__dirname, '..', '..', 'src', 'test', 'pythonFiles', 'dummy.py');
+const dummyPythonFile = path.join(__dirname, '..', '..', 'src', 'test', 'python_files', 'dummy.py');
export const multirootPath = path.join(__dirname, '..', '..', 'src', 'testMultiRootWkspc');
const workspace3Uri = vscode.Uri.file(path.join(multirootPath, 'workspace3'));
diff --git a/extensions/positron-python/src/test/interpreters/activation/service.unit.test.ts b/extensions/positron-python/src/test/interpreters/activation/service.unit.test.ts
index 9b2c121c89b..a0f9b3bd691 100644
--- a/extensions/positron-python/src/test/interpreters/activation/service.unit.test.ts
+++ b/extensions/positron-python/src/test/interpreters/activation/service.unit.test.ts
@@ -161,7 +161,11 @@ suite('Interpreters Activation - Python Environment Variables', () => {
const shellCmd = capture(processService.shellExec).first()[0];
- const printEnvPyFile = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'printEnvVariables.py');
+ const printEnvPyFile = path.join(
+ EXTENSION_ROOT_DIR,
+ 'python_files',
+ 'printEnvVariables.py',
+ );
const expectedCommand = [
...cmd,
`echo '${getEnvironmentPrefix}'`,
diff --git a/extensions/positron-python/src/test/interpreters/activation/terminalEnvVarCollectionService.unit.test.ts b/extensions/positron-python/src/test/interpreters/activation/terminalEnvVarCollectionService.unit.test.ts
index bc5cea1f1e6..faa95a08a3c 100644
--- a/extensions/positron-python/src/test/interpreters/activation/terminalEnvVarCollectionService.unit.test.ts
+++ b/extensions/positron-python/src/test/interpreters/activation/terminalEnvVarCollectionService.unit.test.ts
@@ -38,6 +38,7 @@ import { PathUtils } from '../../../client/common/platform/pathUtils';
import { PythonEnvType } from '../../../client/pythonEnvironments/base/info';
import { PythonEnvironment } from '../../../client/pythonEnvironments/info';
import { IShellIntegrationService, ITerminalDeactivateService } from '../../../client/terminals/types';
+import { IEnvironmentVariablesProvider } from '../../../client/common/variables/types';
suite('Terminal Environment Variable Collection Service', () => {
let platform: IPlatformService;
@@ -74,6 +75,7 @@ suite('Terminal Environment Variable Collection Service', () => {
interpreterService = mock();
context = mock();
shell = mock();
+ const envVarProvider = mock();
shellIntegrationService = mock();
when(shellIntegrationService.isWorking()).thenResolve(true);
globalCollection = mock();
@@ -113,6 +115,7 @@ suite('Terminal Environment Variable Collection Service', () => {
instance(terminalDeactivateService),
new PathUtils(getOSType() === OSType.Windows),
instance(shellIntegrationService),
+ instance(envVarProvider),
);
});
diff --git a/extensions/positron-python/src/test/interpreters/interpreterPathCommand.unit.test.ts b/extensions/positron-python/src/test/interpreters/interpreterPathCommand.unit.test.ts
index 7001453100e..be94be65486 100644
--- a/extensions/positron-python/src/test/interpreters/interpreterPathCommand.unit.test.ts
+++ b/extensions/positron-python/src/test/interpreters/interpreterPathCommand.unit.test.ts
@@ -64,6 +64,17 @@ suite('Interpreter Path Command', () => {
expect(setting).to.equal('settingValue');
});
+ test('If interpreter path contains spaces, double quote it before returning', async () => {
+ const args = ['command', 'folderPath'];
+ when(interpreterService.getActiveInterpreter(anything())).thenCall((arg) => {
+ assert.deepEqual(arg, Uri.file('folderPath'));
+
+ return Promise.resolve({ path: 'setting Value' }) as unknown;
+ });
+ const setting = await interpreterPathCommand._getSelectedInterpreterPath(args);
+ expect(setting).to.equal('"setting Value"');
+ });
+
test('If neither of these exists, value of workspace folder is `undefined`', async () => {
const args = ['command'];
diff --git a/extensions/positron-python/src/test/pythonEnvironments/base/info/envKind.unit.test.ts b/extensions/positron-python/src/test/pythonEnvironments/base/info/envKind.unit.test.ts
index fdf174b4c55..997c8a08c7f 100644
--- a/extensions/positron-python/src/test/pythonEnvironments/base/info/envKind.unit.test.ts
+++ b/extensions/positron-python/src/test/pythonEnvironments/base/info/envKind.unit.test.ts
@@ -13,6 +13,7 @@ const KIND_NAMES: [PythonEnvKind, string][] = [
[PythonEnvKind.MicrosoftStore, 'winStore'],
[PythonEnvKind.Pyenv, 'pyenv'],
[PythonEnvKind.Poetry, 'poetry'],
+ [PythonEnvKind.Hatch, 'hatch'],
[PythonEnvKind.Custom, 'customGlobal'],
[PythonEnvKind.OtherGlobal, 'otherGlobal'],
[PythonEnvKind.Venv, 'venv'],
diff --git a/extensions/positron-python/src/test/pythonEnvironments/base/locators/composite/envsCollectionService.unit.test.ts b/extensions/positron-python/src/test/pythonEnvironments/base/locators/composite/envsCollectionService.unit.test.ts
index f48d91cf24a..739a3f1a3f4 100644
--- a/extensions/positron-python/src/test/pythonEnvironments/base/locators/composite/envsCollectionService.unit.test.ts
+++ b/extensions/positron-python/src/test/pythonEnvironments/base/locators/composite/envsCollectionService.unit.test.ts
@@ -78,6 +78,9 @@ suite('Python envs locator - Environments Collection', async () => {
) {
const env = buildEnvInfo({ executable, searchLocation, name, location, kind });
env.id = id ?? env.id;
+ env.version.major = 3;
+ env.version.minor = 10;
+ env.version.micro = 10;
return env;
}
diff --git a/extensions/positron-python/src/test/pythonEnvironments/base/locators/composite/envsResolver.unit.test.ts b/extensions/positron-python/src/test/pythonEnvironments/base/locators/composite/envsResolver.unit.test.ts
index 4a480cfd6e4..5cda8a1cf73 100644
--- a/extensions/positron-python/src/test/pythonEnvironments/base/locators/composite/envsResolver.unit.test.ts
+++ b/extensions/positron-python/src/test/pythonEnvironments/base/locators/composite/envsResolver.unit.test.ts
@@ -57,7 +57,11 @@ suite('Python envs locator - Environments Resolver', () => {
/**
* Returns the expected environment to be returned by Environment info service
*/
- function createExpectedEnvInfo(env: PythonEnvInfo, expectedDisplay: string): PythonEnvInfo {
+ function createExpectedEnvInfo(
+ env: PythonEnvInfo,
+ expectedDisplay: string,
+ expectedDetailedDisplay: string,
+ ): PythonEnvInfo {
const updatedEnv = cloneDeep(env);
updatedEnv.version = {
...parseVersion('3.8.3-final'),
@@ -67,7 +71,7 @@ suite('Python envs locator - Environments Resolver', () => {
updatedEnv.executable.sysPrefix = 'path';
updatedEnv.arch = Architecture.x64;
updatedEnv.display = expectedDisplay;
- updatedEnv.detailedDisplayName = expectedDisplay;
+ updatedEnv.detailedDisplayName = expectedDetailedDisplay;
if (env.kind === PythonEnvKind.Conda) {
env.type = PythonEnvType.Conda;
}
@@ -82,6 +86,7 @@ suite('Python envs locator - Environments Resolver', () => {
location = '',
display: string | undefined = undefined,
type?: PythonEnvType,
+ detailedDisplay?: string,
): PythonEnvInfo {
return {
name,
@@ -94,7 +99,7 @@ suite('Python envs locator - Environments Resolver', () => {
mtime: -1,
},
display,
- detailedDisplayName: display,
+ detailedDisplayName: detailedDisplay ?? display,
version,
arch: Architecture.Unknown,
distro: { org: '' },
@@ -134,8 +139,9 @@ suite('Python envs locator - Environments Resolver', () => {
undefined,
'win1',
path.join(testVirtualHomeDir, '.venvs', 'win1'),
- "Python ('win1': venv)",
+ "Python ('win1')",
PythonEnvType.Virtual,
+ "Python ('win1': venv)",
);
const envsReturnedByParentLocator = [env1];
const parentLocator = new SimpleLocator(envsReturnedByParentLocator);
@@ -170,7 +176,11 @@ suite('Python envs locator - Environments Resolver', () => {
const envs = await getEnvsWithUpdates(iterator);
assertEnvsEqual(envs, [
- createExpectedEnvInfo(resolvedEnvReturnedByBasicResolver, "Python 3.8.3 ('win1': venv)"),
+ createExpectedEnvInfo(
+ resolvedEnvReturnedByBasicResolver,
+ "Python 3.8.3 ('win1')",
+ "Python 3.8.3 ('win1': venv)",
+ ),
]);
});
@@ -237,7 +247,11 @@ suite('Python envs locator - Environments Resolver', () => {
// Assert
assertEnvsEqual(envs, [
- createExpectedEnvInfo(resolvedUpdatedEnvReturnedByBasicResolver, "Python 3.8.3 ('win1': venv)"),
+ createExpectedEnvInfo(
+ resolvedUpdatedEnvReturnedByBasicResolver,
+ "Python 3.8.3 ('win1')",
+ "Python 3.8.3 ('win1': venv)",
+ ),
]);
didUpdate.dispose();
});
@@ -377,7 +391,11 @@ suite('Python envs locator - Environments Resolver', () => {
assertEnvEqual(
expected,
- createExpectedEnvInfo(resolvedEnvReturnedByBasicResolver, "Python 3.8.3 ('win1': venv)"),
+ createExpectedEnvInfo(
+ resolvedEnvReturnedByBasicResolver,
+ "Python 3.8.3 ('win1')",
+ "Python 3.8.3 ('win1': venv)",
+ ),
);
});
diff --git a/extensions/positron-python/src/test/pythonEnvironments/base/locators/envTestUtils.ts b/extensions/positron-python/src/test/pythonEnvironments/base/locators/envTestUtils.ts
index d1099ee4f84..a46dab274b4 100644
--- a/extensions/positron-python/src/test/pythonEnvironments/base/locators/envTestUtils.ts
+++ b/extensions/positron-python/src/test/pythonEnvironments/base/locators/envTestUtils.ts
@@ -103,10 +103,12 @@ export function assertBasicEnvsEqual(actualEnvs: BasicEnvInfo[], expectedEnvs: B
const [actual, expected] = value;
if (actual) {
actual.source = actual.source ?? [];
+ actual.searchLocation = actual.searchLocation ?? undefined;
actual.source.sort();
}
if (expected) {
expected.source = expected.source ?? [];
+ expected.searchLocation = expected.searchLocation ?? undefined;
expected.source.sort();
}
assert.deepStrictEqual(actual, expected);
diff --git a/extensions/positron-python/src/test/pythonEnvironments/base/locators/lowLevel/condaLocator.testvirtualenvs.ts b/extensions/positron-python/src/test/pythonEnvironments/base/locators/lowLevel/condaLocator.testvirtualenvs.ts
index b3e1084a56b..25a3df90202 100644
--- a/extensions/positron-python/src/test/pythonEnvironments/base/locators/lowLevel/condaLocator.testvirtualenvs.ts
+++ b/extensions/positron-python/src/test/pythonEnvironments/base/locators/lowLevel/condaLocator.testvirtualenvs.ts
@@ -14,8 +14,6 @@ import { PythonEnvsChangedEvent } from '../../../../../client/pythonEnvironments
import { EXTENSION_ROOT_DIR_FOR_TESTS, TEST_TIMEOUT } from '../../../../constants';
import { traceWarn } from '../../../../../client/logging';
import { TEST_LAYOUT_ROOT } from '../../../common/commonTestConstants';
-import { getEnvs } from '../../common';
-import { assertBasicEnvsEqual } from '../envTestUtils';
import { PYTHON_VIRTUAL_ENVS_LOCATION } from '../../../../ciConstants';
import { isCI } from '../../../../../client/common/constants';
import * as externalDependencies from '../../../../../client/pythonEnvironments/common/externalDependencies';
@@ -131,14 +129,4 @@ suite('Conda Env Locator', async () => {
assert.deepEqual(actualEvent!, expectedEvent, 'Unexpected event emitted');
});
-
- test('Worker thread to fetch conda environments is working', async () => {
- locator = new CondaEnvironmentLocator();
- const items = await getEnvs(locator.doIterEnvs(undefined, false));
- const workerItems = await getEnvs(locator.doIterEnvs(undefined, true));
- console.log('Number of items Conda locator returned:', items.length);
- // Make sure items returned when using worker threads v/s not are the same.
- assertBasicEnvsEqual(items, workerItems);
- assert(workerItems.length > 0, 'No environments found');
- }).timeout(TEST_TIMEOUT * 2);
});
diff --git a/extensions/positron-python/src/test/pythonEnvironments/base/locators/lowLevel/globalVirtualEnvironmentLocator.unit.test.ts b/extensions/positron-python/src/test/pythonEnvironments/base/locators/lowLevel/globalVirtualEnvironmentLocator.unit.test.ts
index 6998d9f4050..ede947073ea 100644
--- a/extensions/positron-python/src/test/pythonEnvironments/base/locators/lowLevel/globalVirtualEnvironmentLocator.unit.test.ts
+++ b/extensions/positron-python/src/test/pythonEnvironments/base/locators/lowLevel/globalVirtualEnvironmentLocator.unit.test.ts
@@ -3,6 +3,7 @@
import * as path from 'path';
import * as sinon from 'sinon';
+import { Uri } from 'vscode';
import * as fsWatcher from '../../../../../client/common/platform/fileSystemWatcher';
import * as platformUtils from '../../../../../client/common/utils/platform';
import { PythonEnvKind } from '../../../../../client/pythonEnvironments/base/info';
@@ -22,6 +23,7 @@ suite('GlobalVirtualEnvironment Locator', () => {
let readFileStub: sinon.SinonStub;
let locator: GlobalVirtualEnvironmentLocator;
let watchLocationForPatternStub: sinon.SinonStub;
+ const project2 = path.join(TEST_LAYOUT_ROOT, 'pipenv', 'project2');
setup(async () => {
getEnvVariableStub = sinon.stub(platformUtils, 'getEnvironmentVariable');
@@ -49,7 +51,7 @@ suite('GlobalVirtualEnvironment Locator', () => {
'.project',
);
readFileStub = sinon.stub(externalDependencies, 'readFile');
- readFileStub.withArgs(expectedDotProjectFile).returns(path.join(TEST_LAYOUT_ROOT, 'pipenv', 'project2'));
+ readFileStub.withArgs(expectedDotProjectFile).returns(project2);
readFileStub.callThrough();
});
teardown(async () => {
@@ -131,6 +133,11 @@ suite('GlobalVirtualEnvironment Locator', () => {
});
test('iterEnvs(): Non-Windows', async () => {
+ const pipenv = createBasicEnv(
+ PythonEnvKind.Pipenv,
+ path.join(testVirtualHomeDir, '.local', 'share', 'virtualenvs', 'project2-vnNIWe9P', 'bin', 'python'),
+ );
+ pipenv.searchLocation = Uri.file(project2);
const expectedEnvs = [
createBasicEnv(PythonEnvKind.Venv, path.join(testVirtualHomeDir, '.venvs', 'posix1', 'python')),
createBasicEnv(PythonEnvKind.Venv, path.join(testVirtualHomeDir, '.venvs', 'posix2', 'bin', 'python')),
@@ -147,10 +154,7 @@ suite('GlobalVirtualEnvironment Locator', () => {
PythonEnvKind.VirtualEnvWrapper,
path.join(testVirtualHomeDir, 'workonhome', 'posix2', 'bin', 'python'),
),
- createBasicEnv(
- PythonEnvKind.Pipenv,
- path.join(testVirtualHomeDir, '.local', 'share', 'virtualenvs', 'project2-vnNIWe9P', 'bin', 'python'),
- ),
+ pipenv,
];
locator = new GlobalVirtualEnvironmentLocator();
@@ -179,6 +183,11 @@ suite('GlobalVirtualEnvironment Locator', () => {
test('iterEnvs(): Non-Windows (WORKON_HOME not set)', async () => {
getEnvVariableStub.withArgs('WORKON_HOME').returns(undefined);
+ const pipenv = createBasicEnv(
+ PythonEnvKind.Pipenv,
+ path.join(testVirtualHomeDir, '.local', 'share', 'virtualenvs', 'project2-vnNIWe9P', 'bin', 'python'),
+ );
+ pipenv.searchLocation = Uri.file(project2);
const expectedEnvs = [
createBasicEnv(PythonEnvKind.Venv, path.join(testVirtualHomeDir, '.venvs', 'posix1', 'python')),
createBasicEnv(PythonEnvKind.Venv, path.join(testVirtualHomeDir, '.venvs', 'posix2', 'bin', 'python')),
@@ -190,10 +199,7 @@ suite('GlobalVirtualEnvironment Locator', () => {
PythonEnvKind.VirtualEnvWrapper,
path.join(testVirtualHomeDir, '.virtualenvs', 'posix2', 'bin', 'python'),
),
- createBasicEnv(
- PythonEnvKind.Pipenv,
- path.join(testVirtualHomeDir, '.local', 'share', 'virtualenvs', 'project2-vnNIWe9P', 'bin', 'python'),
- ),
+ pipenv,
];
locator = new GlobalVirtualEnvironmentLocator();
diff --git a/extensions/positron-python/src/test/pythonEnvironments/base/locators/lowLevel/hatchLocator.unit.test.ts b/extensions/positron-python/src/test/pythonEnvironments/base/locators/lowLevel/hatchLocator.unit.test.ts
new file mode 100644
index 00000000000..9a2a69908f2
--- /dev/null
+++ b/extensions/positron-python/src/test/pythonEnvironments/base/locators/lowLevel/hatchLocator.unit.test.ts
@@ -0,0 +1,76 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+import * as sinon from 'sinon';
+import * as path from 'path';
+import { PythonEnvKind } from '../../../../../client/pythonEnvironments/base/info';
+import * as externalDependencies from '../../../../../client/pythonEnvironments/common/externalDependencies';
+import * as platformUtils from '../../../../../client/common/utils/platform';
+import { getEnvs } from '../../../../../client/pythonEnvironments/base/locatorUtils';
+import { HatchLocator } from '../../../../../client/pythonEnvironments/base/locators/lowLevel/hatchLocator';
+import { assertBasicEnvsEqual } from '../envTestUtils';
+import { createBasicEnv } from '../../common';
+import { makeExecHandler, projectDirs, venvDirs } from '../../../common/environmentManagers/hatch.unit.test';
+
+suite('Hatch Locator', () => {
+ let exec: sinon.SinonStub;
+ let getPythonSetting: sinon.SinonStub;
+ let getOSType: sinon.SinonStub;
+ let locator: HatchLocator;
+
+ suiteSetup(() => {
+ getPythonSetting = sinon.stub(externalDependencies, 'getPythonSetting');
+ getPythonSetting.returns('hatch');
+ getOSType = sinon.stub(platformUtils, 'getOSType');
+ exec = sinon.stub(externalDependencies, 'exec');
+ });
+
+ suiteTeardown(() => sinon.restore());
+
+ suite('iterEnvs()', () => {
+ setup(() => {
+ getOSType.returns(platformUtils.OSType.Linux);
+ });
+
+ interface TestArgs {
+ osType?: platformUtils.OSType;
+ pythonBin?: string;
+ }
+
+ const testProj1 = async ({ osType, pythonBin = 'bin/python' }: TestArgs = {}) => {
+ if (osType) {
+ getOSType.returns(osType);
+ }
+
+ locator = new HatchLocator(projectDirs.project1);
+ exec.callsFake(makeExecHandler(venvDirs.project1, { path: true, cwd: projectDirs.project1 }));
+
+ const iterator = locator.iterEnvs();
+ const actualEnvs = await getEnvs(iterator);
+
+ const expectedEnvs = [createBasicEnv(PythonEnvKind.Hatch, path.join(venvDirs.project1.default, pythonBin))];
+ assertBasicEnvsEqual(actualEnvs, expectedEnvs);
+ };
+
+ test('project with only the default env', () => testProj1());
+ test('project with only the default env on Windows', () =>
+ testProj1({
+ osType: platformUtils.OSType.Windows,
+ pythonBin: 'Scripts/python.exe',
+ }));
+
+ test('project with multiple defined envs', async () => {
+ locator = new HatchLocator(projectDirs.project2);
+ exec.callsFake(makeExecHandler(venvDirs.project2, { path: true, cwd: projectDirs.project2 }));
+
+ const iterator = locator.iterEnvs();
+ const actualEnvs = await getEnvs(iterator);
+
+ const expectedEnvs = [
+ createBasicEnv(PythonEnvKind.Hatch, path.join(venvDirs.project2.default, 'bin/python')),
+ createBasicEnv(PythonEnvKind.Hatch, path.join(venvDirs.project2.test, 'bin/python')),
+ ];
+ assertBasicEnvsEqual(actualEnvs, expectedEnvs);
+ });
+ });
+});
diff --git a/extensions/positron-python/src/test/pythonEnvironments/base/locators/lowLevel/poetryLocator.unit.test.ts b/extensions/positron-python/src/test/pythonEnvironments/base/locators/lowLevel/poetryLocator.unit.test.ts
index 95c1a401df5..e7982a4c4e9 100644
--- a/extensions/positron-python/src/test/pythonEnvironments/base/locators/lowLevel/poetryLocator.unit.test.ts
+++ b/extensions/positron-python/src/test/pythonEnvironments/base/locators/lowLevel/poetryLocator.unit.test.ts
@@ -3,7 +3,8 @@
import * as path from 'path';
import * as sinon from 'sinon';
-import { PythonEnvKind } from '../../../../../client/pythonEnvironments/base/info';
+import { Uri } from 'vscode';
+import { PythonEnvKind, PythonEnvSource } from '../../../../../client/pythonEnvironments/base/info';
import * as externalDependencies from '../../../../../client/pythonEnvironments/common/externalDependencies';
import * as platformUtils from '../../../../../client/common/utils/platform';
import { getEnvs } from '../../../../../client/pythonEnvironments/base/locatorUtils';
@@ -11,7 +12,8 @@ import { PoetryLocator } from '../../../../../client/pythonEnvironments/base/loc
import { TEST_LAYOUT_ROOT } from '../../../common/commonTestConstants';
import { assertBasicEnvsEqual } from '../envTestUtils';
import { ExecutionResult, ShellOptions } from '../../../../../client/common/process/types';
-import { createBasicEnv } from '../../common';
+import { createBasicEnv as createBasicEnvCommon } from '../../common';
+import { BasicEnvInfo } from '../../../../../client/pythonEnvironments/base/locator';
suite('Poetry Locator', () => {
let shellExecute: sinon.SinonStub;
@@ -31,6 +33,17 @@ suite('Poetry Locator', () => {
suite('Windows', () => {
const project1 = path.join(testPoetryDir, 'project1');
+
+ function createBasicEnv(
+ kind: PythonEnvKind,
+ executablePath: string,
+ source?: PythonEnvSource[],
+ envPath?: string,
+ ): BasicEnvInfo {
+ const basicEnv = createBasicEnvCommon(kind, executablePath, source, envPath);
+ basicEnv.searchLocation = Uri.file(project1);
+ return basicEnv;
+ }
setup(() => {
locator = new PoetryLocator(project1);
getOSTypeStub.returns(platformUtils.OSType.Windows);
@@ -72,6 +85,17 @@ suite('Poetry Locator', () => {
suite('Non-Windows', () => {
const project2 = path.join(testPoetryDir, 'project2');
+
+ function createBasicEnv(
+ kind: PythonEnvKind,
+ executablePath: string,
+ source?: PythonEnvSource[],
+ envPath?: string,
+ ): BasicEnvInfo {
+ const basicEnv = createBasicEnvCommon(kind, executablePath, source, envPath);
+ basicEnv.searchLocation = Uri.file(project2);
+ return basicEnv;
+ }
setup(() => {
locator = new PoetryLocator(project2);
getOSTypeStub.returns(platformUtils.OSType.Linux);
diff --git a/extensions/positron-python/src/test/pythonEnvironments/base/locators/lowLevel/windowsRegistryLocator.testvirtualenvs.ts b/extensions/positron-python/src/test/pythonEnvironments/base/locators/lowLevel/windowsRegistryLocator.testvirtualenvs.ts
deleted file mode 100644
index 693d7c1b7fe..00000000000
--- a/extensions/positron-python/src/test/pythonEnvironments/base/locators/lowLevel/windowsRegistryLocator.testvirtualenvs.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-import { getEnvs } from '../../../../../client/pythonEnvironments/base/locatorUtils';
-import { WindowsRegistryLocator } from '../../../../../client/pythonEnvironments/base/locators/lowLevel/windowsRegistryLocator';
-import { assertBasicEnvsEqual } from '../envTestUtils';
-import { TEST_TIMEOUT } from '../../../../constants';
-import { getOSType, OSType } from '../../../../../client/common/utils/platform';
-
-suite('Windows Registry Locator', async () => {
- let locator: WindowsRegistryLocator;
-
- setup(function () {
- if (getOSType() !== OSType.Windows) {
- return this.skip();
- }
- locator = new WindowsRegistryLocator();
- return undefined;
- });
-
- test('Worker thread to fetch registry interpreters is working', async () => {
- const items = await getEnvs(locator.iterEnvs(undefined, false));
- const workerItems = await getEnvs(locator.iterEnvs(undefined, true));
- console.log('Number of items Windows registry locator returned:', items.length);
- // Make sure items returned when using worker threads v/s not are the same.
- assertBasicEnvsEqual(items, workerItems);
- }).timeout(TEST_TIMEOUT * 2);
-});
diff --git a/extensions/positron-python/src/test/pythonEnvironments/base/locators/lowLevel/windowsRegistryLocator.unit.test.ts b/extensions/positron-python/src/test/pythonEnvironments/base/locators/lowLevel/windowsRegistryLocator.unit.test.ts
index 4197c36fa9f..07a7a864ef7 100644
--- a/extensions/positron-python/src/test/pythonEnvironments/base/locators/lowLevel/windowsRegistryLocator.unit.test.ts
+++ b/extensions/positron-python/src/test/pythonEnvironments/base/locators/lowLevel/windowsRegistryLocator.unit.test.ts
@@ -4,10 +4,14 @@
import * as assert from 'assert';
import * as path from 'path';
import * as sinon from 'sinon';
+import { expect } from 'chai';
import { PythonEnvKind, PythonEnvSource } from '../../../../../client/pythonEnvironments/base/info';
import { getEnvs } from '../../../../../client/pythonEnvironments/base/locatorUtils';
import * as winreg from '../../../../../client/pythonEnvironments/common/windowsRegistry';
-import { WindowsRegistryLocator } from '../../../../../client/pythonEnvironments/base/locators/lowLevel/windowsRegistryLocator';
+import {
+ WindowsRegistryLocator,
+ WINDOWS_REG_PROVIDER_ID,
+} from '../../../../../client/pythonEnvironments/base/locators/lowLevel/windowsRegistryLocator';
import { createBasicEnv } from '../../common';
import { TEST_LAYOUT_ROOT } from '../../../common/commonTestConstants';
import { assertBasicEnvsEqual } from '../envTestUtils';
@@ -201,7 +205,7 @@ suite('Windows Registry', () => {
}
setup(async () => {
- sinon.stub(externalDependencies, 'inExperiment').returns(false);
+ sinon.stub(externalDependencies, 'inExperiment').returns(true);
stubReadRegistryValues = sinon.stub(winreg, 'readRegistryValues');
stubReadRegistryKeys = sinon.stub(winreg, 'readRegistryKeys');
stubReadRegistryValues.callsFake(fakeRegistryValues);
@@ -222,18 +226,29 @@ suite('Windows Registry', () => {
createBasicEnv(PythonEnvKind.OtherGlobal, path.join(regTestRoot, 'python38', 'python.exe')),
].map((e) => ({ ...e, source: [PythonEnvSource.WindowsRegistry] }));
- const iterator = locator.iterEnvs(undefined, true);
+ const lazyIterator = locator.iterEnvs(undefined, true);
+ const envs = await getEnvs(lazyIterator);
+ expect(envs.length).to.equal(0);
+
+ const iterator = locator.iterEnvs({ providerId: WINDOWS_REG_PROVIDER_ID }, true);
const actualEnvs = await getEnvs(iterator);
assertBasicEnvsEqual(actualEnvs, expectedEnvs);
});
+ test('iterEnvs(): query is undefined', async () => {
+ // Iterate no envs when query is `undefined`, i.e notify completion immediately.
+ const lazyIterator = locator.iterEnvs(undefined, true);
+ const envs = await getEnvs(lazyIterator);
+ expect(envs.length).to.equal(0);
+ });
+
test('iterEnvs(): no registry permission', async () => {
stubReadRegistryKeys.callsFake(() => {
throw Error();
});
- const iterator = locator.iterEnvs(undefined, true);
+ const iterator = locator.iterEnvs({ providerId: WINDOWS_REG_PROVIDER_ID }, true);
const actualEnvs = await getEnvs(iterator);
assert.deepStrictEqual(actualEnvs, []);
@@ -252,7 +267,7 @@ suite('Windows Registry', () => {
createBasicEnv(PythonEnvKind.OtherGlobal, path.join(regTestRoot, 'python38', 'python.exe')),
].map((e) => ({ ...e, source: [PythonEnvSource.WindowsRegistry] }));
- const iterator = locator.iterEnvs(undefined, true);
+ const iterator = locator.iterEnvs({ providerId: WINDOWS_REG_PROVIDER_ID }, true);
const actualEnvs = await getEnvs(iterator);
assertBasicEnvsEqual(actualEnvs, expectedEnvs);
diff --git a/extensions/positron-python/src/test/pythonEnvironments/common/environmentManagers/hatch.unit.test.ts b/extensions/positron-python/src/test/pythonEnvironments/common/environmentManagers/hatch.unit.test.ts
new file mode 100644
index 00000000000..5d348aa2b13
--- /dev/null
+++ b/extensions/positron-python/src/test/pythonEnvironments/common/environmentManagers/hatch.unit.test.ts
@@ -0,0 +1,102 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+import { expect } from 'chai';
+import * as path from 'path';
+import * as sinon from 'sinon';
+import { ExecutionResult, ShellOptions } from '../../../../client/common/process/types';
+import * as externalDependencies from '../../../../client/pythonEnvironments/common/externalDependencies';
+import { Hatch } from '../../../../client/pythonEnvironments/common/environmentManagers/hatch';
+import { TEST_LAYOUT_ROOT } from '../commonTestConstants';
+
+export type HatchCommand = { cmd: 'env show --json' } | { cmd: 'env find'; env: string } | { cmd: null };
+
+export function hatchCommand(args: string[]): HatchCommand {
+ if (args.length < 2) {
+ return { cmd: null };
+ }
+ if (args[0] === 'env' && args[1] === 'show' && args[2] === '--json') {
+ return { cmd: 'env show --json' };
+ }
+ if (args[0] === 'env' && args[1] === 'find') {
+ return { cmd: 'env find', env: args[2] };
+ }
+ return { cmd: null };
+}
+
+interface VerifyOptions {
+ path?: boolean;
+ cwd?: string;
+}
+
+export function makeExecHandler(venvDirs: Record, verify: VerifyOptions = {}) {
+ return async (file: string, args: string[], options: ShellOptions): Promise> => {
+ if (verify.path && file !== 'hatch') {
+ throw new Error('Command failed');
+ }
+ if (verify.cwd) {
+ const cwd = typeof options.cwd === 'string' ? options.cwd : options.cwd?.toString();
+ if (!cwd || !externalDependencies.arePathsSame(cwd, verify.cwd)) {
+ throw new Error('Command failed');
+ }
+ }
+ const cmd = hatchCommand(args);
+ if (cmd.cmd === 'env show --json') {
+ const envs = Object.fromEntries(Object.keys(venvDirs).map((name) => [name, { type: 'virtual' }]));
+ return { stdout: JSON.stringify(envs) };
+ }
+ if (cmd.cmd === 'env find' && cmd.env in venvDirs) {
+ return { stdout: venvDirs[cmd.env] };
+ }
+ throw new Error('Command failed');
+ };
+}
+
+const testHatchDir = path.join(TEST_LAYOUT_ROOT, 'hatch');
+// This is usually in /hatch, e.g. `~/.local/share/hatch`
+const hatchEnvsDir = path.join(testHatchDir, 'env/virtual/python');
+export const projectDirs = {
+ project1: path.join(testHatchDir, 'project1'),
+ project2: path.join(testHatchDir, 'project2'),
+};
+export const venvDirs = {
+ project1: { default: path.join(hatchEnvsDir, 'cK2g6fIm/project1') },
+ project2: {
+ default: path.join(hatchEnvsDir, 'q4In3tK-/project2'),
+ test: path.join(hatchEnvsDir, 'q4In3tK-/test'),
+ },
+};
+
+suite('Hatch binary is located correctly', async () => {
+ let exec: sinon.SinonStub;
+ let getPythonSetting: sinon.SinonStub;
+
+ setup(() => {
+ getPythonSetting = sinon.stub(externalDependencies, 'getPythonSetting');
+ exec = sinon.stub(externalDependencies, 'exec');
+ });
+
+ teardown(() => {
+ sinon.restore();
+ });
+
+ const testPath = async (verify = true) => {
+ // If `verify` is false, don’t verify that the command has been called with that path
+ exec.callsFake(
+ makeExecHandler(venvDirs.project1, verify ? { path: true, cwd: projectDirs.project1 } : undefined),
+ );
+ const hatch = await Hatch.getHatch(projectDirs.project1);
+ expect(hatch?.command).to.equal('hatch');
+ };
+
+ test('Use Hatch on PATH if available', () => testPath());
+
+ test('Return undefined if Hatch cannot be found', async () => {
+ getPythonSetting.returns('hatch');
+ exec.callsFake((_file: string, _args: string[], _options: ShellOptions) =>
+ Promise.reject(new Error('Command failed')),
+ );
+ const hatch = await Hatch.getHatch(projectDirs.project1);
+ expect(hatch?.command).to.equal(undefined);
+ });
+});
diff --git a/extensions/positron-python/src/test/pythonEnvironments/common/envlayouts/hatch/env/virtual/python/cK2g6fIm/project1/Scripts/python.exe b/extensions/positron-python/src/test/pythonEnvironments/common/envlayouts/hatch/env/virtual/python/cK2g6fIm/project1/Scripts/python.exe
new file mode 100644
index 00000000000..a37b666d049
--- /dev/null
+++ b/extensions/positron-python/src/test/pythonEnvironments/common/envlayouts/hatch/env/virtual/python/cK2g6fIm/project1/Scripts/python.exe
@@ -0,0 +1 @@
+Not real python exe
diff --git a/extensions/positron-python/src/test/pythonFiles/environments/conda/bin/python b/extensions/positron-python/src/test/pythonEnvironments/common/envlayouts/hatch/env/virtual/python/cK2g6fIm/project1/bin/python
similarity index 100%
rename from extensions/positron-python/src/test/pythonFiles/environments/conda/bin/python
rename to extensions/positron-python/src/test/pythonEnvironments/common/envlayouts/hatch/env/virtual/python/cK2g6fIm/project1/bin/python
diff --git a/extensions/positron-python/src/test/pythonEnvironments/common/envlayouts/hatch/env/virtual/python/cK2g6fIm/project1/pyvenv.cfg b/extensions/positron-python/src/test/pythonEnvironments/common/envlayouts/hatch/env/virtual/python/cK2g6fIm/project1/pyvenv.cfg
new file mode 100644
index 00000000000..365d6f5eace
--- /dev/null
+++ b/extensions/positron-python/src/test/pythonEnvironments/common/envlayouts/hatch/env/virtual/python/cK2g6fIm/project1/pyvenv.cfg
@@ -0,0 +1,3 @@
+home = /usr/bin/python3.11
+include-system-site-packages = false
+version = 3.11.1
diff --git a/extensions/positron-python/src/test/pythonFiles/environments/conda/envs/numpy/bin/python b/extensions/positron-python/src/test/pythonEnvironments/common/envlayouts/hatch/env/virtual/python/q4In3tK-/project2/bin/python
similarity index 100%
rename from extensions/positron-python/src/test/pythonFiles/environments/conda/envs/numpy/bin/python
rename to extensions/positron-python/src/test/pythonEnvironments/common/envlayouts/hatch/env/virtual/python/q4In3tK-/project2/bin/python
diff --git a/extensions/positron-python/src/test/pythonEnvironments/common/envlayouts/hatch/env/virtual/python/q4In3tK-/project2/pyvenv.cfg b/extensions/positron-python/src/test/pythonEnvironments/common/envlayouts/hatch/env/virtual/python/q4In3tK-/project2/pyvenv.cfg
new file mode 100644
index 00000000000..a67a28be91b
--- /dev/null
+++ b/extensions/positron-python/src/test/pythonEnvironments/common/envlayouts/hatch/env/virtual/python/q4In3tK-/project2/pyvenv.cfg
@@ -0,0 +1,3 @@
+home = /usr/bin/python3.10
+include-system-site-packages = false
+version = 3.10.3
diff --git a/extensions/positron-python/src/test/pythonFiles/environments/conda/envs/scipy/bin/python b/extensions/positron-python/src/test/pythonEnvironments/common/envlayouts/hatch/env/virtual/python/q4In3tK-/test/bin/python
similarity index 100%
rename from extensions/positron-python/src/test/pythonFiles/environments/conda/envs/scipy/bin/python
rename to extensions/positron-python/src/test/pythonEnvironments/common/envlayouts/hatch/env/virtual/python/q4In3tK-/test/bin/python
diff --git a/extensions/positron-python/src/test/pythonEnvironments/common/envlayouts/hatch/env/virtual/python/q4In3tK-/test/pyvenv.cfg b/extensions/positron-python/src/test/pythonEnvironments/common/envlayouts/hatch/env/virtual/python/q4In3tK-/test/pyvenv.cfg
new file mode 100644
index 00000000000..a67a28be91b
--- /dev/null
+++ b/extensions/positron-python/src/test/pythonEnvironments/common/envlayouts/hatch/env/virtual/python/q4In3tK-/test/pyvenv.cfg
@@ -0,0 +1,3 @@
+home = /usr/bin/python3.10
+include-system-site-packages = false
+version = 3.10.3
diff --git a/extensions/positron-python/src/test/pythonFiles/environments/conda/envs/numpy/python.exe b/extensions/positron-python/src/test/pythonEnvironments/common/envlayouts/hatch/project1/.gitkeep
similarity index 100%
rename from extensions/positron-python/src/test/pythonFiles/environments/conda/envs/numpy/python.exe
rename to extensions/positron-python/src/test/pythonEnvironments/common/envlayouts/hatch/project1/.gitkeep
diff --git a/extensions/positron-python/src/test/pythonEnvironments/common/envlayouts/hatch/project2/hatch.toml b/extensions/positron-python/src/test/pythonEnvironments/common/envlayouts/hatch/project2/hatch.toml
new file mode 100644
index 00000000000..9848374b54f
--- /dev/null
+++ b/extensions/positron-python/src/test/pythonEnvironments/common/envlayouts/hatch/project2/hatch.toml
@@ -0,0 +1,6 @@
+# this file is not actually used in tests, as all is mocked out
+
+# The default environment always exists
+#[envs.default]
+
+[envs.test]
diff --git a/extensions/positron-python/src/test/pythonEnvironments/info/interpreter.unit.test.ts b/extensions/positron-python/src/test/pythonEnvironments/info/interpreter.unit.test.ts
index 38a916d1db9..967454dd6c7 100644
--- a/extensions/positron-python/src/test/pythonEnvironments/info/interpreter.unit.test.ts
+++ b/extensions/positron-python/src/test/pythonEnvironments/info/interpreter.unit.test.ts
@@ -11,7 +11,7 @@ import { buildPythonExecInfo } from '../../../client/pythonEnvironments/exec';
import { getInterpreterInfo } from '../../../client/pythonEnvironments/info/interpreter';
import { EXTENSION_ROOT_DIR_FOR_TESTS } from '../../constants';
-const script = pathJoin(EXTENSION_ROOT_DIR_FOR_TESTS, 'pythonFiles', 'interpreterInfo.py');
+const script = pathJoin(EXTENSION_ROOT_DIR_FOR_TESTS, 'python_files', 'interpreterInfo.py');
suite('extractInterpreterInfo()', () => {
// Tests go here.
diff --git a/extensions/positron-python/src/test/pythonFiles/formatting/autoPep8Formatted.py b/extensions/positron-python/src/test/pythonFiles/formatting/autoPep8Formatted.py
deleted file mode 100644
index e63158d6d4f..00000000000
--- a/extensions/positron-python/src/test/pythonFiles/formatting/autoPep8Formatted.py
+++ /dev/null
@@ -1,32 +0,0 @@
-
-import math
-import sys
-
-
-def example1():
- # This is a long comment. This should be wrapped to fit within 72 characters.
- some_tuple = (1, 2, 3, 'a')
- some_variable = {'long': 'Long code lines should be wrapped within 79 characters.',
- 'other': [math.pi, 100, 200, 300, 9876543210, 'This is a long string that goes on'],
- 'more': {'inner': 'This whole logical line should be wrapped.', some_tuple: [1,
- 20, 300, 40000, 500000000, 60000000000000000]}}
- return (some_tuple, some_variable)
-
-
-def example2(): return {'has_key() is deprecated': True}.has_key(
- {'f': 2}.has_key(''))
-
-
-class Example3(object):
- def __init__(self, bar):
- # Comments should have a space after the hash.
- if bar:
- bar += 1
- bar = bar * bar
- return bar
- else:
- some_string = """
- Indentation in multiline strings should not be touched.
-Only actual code should be reindented.
-"""
- return (sys.path, some_string)
diff --git a/extensions/positron-python/src/test/pythonFiles/formatting/autopep8.output b/extensions/positron-python/src/test/pythonFiles/formatting/autopep8.output
deleted file mode 100644
index 80cb3a44581..00000000000
--- a/extensions/positron-python/src/test/pythonFiles/formatting/autopep8.output
+++ /dev/null
@@ -1,50 +0,0 @@
---- original/C:\GIT\issues\s p\vscode-python\src\test\pythonFiles\formatting\fileToformat.py
-+++ fixed/C:\GIT\issues\s p\vscode-python\src\test\pythonFiles\formatting\fileToformat.py
-@@ -1,22 +1,32 @@
-
--import math, sys;
-+import math
-+import sys
-+
-
- def example1():
-- ####This is a long comment. This should be wrapped to fit within 72 characters.
-- some_tuple=( 1,2, 3,'a' );
-- some_variable={'long':'Long code lines should be wrapped within 79 characters.',
-- 'other':[math.pi, 100,200,300,9876543210,'This is a long string that goes on'],
-- 'more':{'inner':'This whole logical line should be wrapped.',some_tuple:[1,
-- 20,300,40000,500000000,60000000000000000]}}
-+ # This is a long comment. This should be wrapped to fit within 72 characters.
-+ some_tuple = (1, 2, 3, 'a')
-+ some_variable = {'long': 'Long code lines should be wrapped within 79 characters.',
-+ 'other': [math.pi, 100, 200, 300, 9876543210, 'This is a long string that goes on'],
-+ 'more': {'inner': 'This whole logical line should be wrapped.', some_tuple: [1,
-+ 20, 300, 40000, 500000000, 60000000000000000]}}
- return (some_tuple, some_variable)
--def example2(): return {'has_key() is deprecated':True}.has_key({'f':2}.has_key(''));
--class Example3( object ):
-- def __init__ ( self, bar ):
-- #Comments should have a space after the hash.
-- if bar : bar+=1; bar=bar* bar ; return bar
-- else:
-- some_string = """
-+
-+
-+def example2(): return {'has_key() is deprecated': True}.has_key(
-+ {'f': 2}.has_key(''))
-+
-+
-+class Example3(object):
-+ def __init__(self, bar):
-+ # Comments should have a space after the hash.
-+ if bar:
-+ bar += 1
-+ bar = bar * bar
-+ return bar
-+ else:
-+ some_string = """
- Indentation in multiline strings should not be touched.
- Only actual code should be reindented.
- """
-- return (sys.path, some_string)
-+ return (sys.path, some_string)
\ No newline at end of file
diff --git a/extensions/positron-python/src/test/pythonFiles/formatting/black.output b/extensions/positron-python/src/test/pythonFiles/formatting/black.output
deleted file mode 100644
index 4c14d61f2b9..00000000000
--- a/extensions/positron-python/src/test/pythonFiles/formatting/black.output
+++ /dev/null
@@ -1,59 +0,0 @@
---- C:\GIT\issues\s p\vscode-python\src\test\pythonFiles\formatting\fileToformat.py 2020-05-11 18:56:39.835398 +0000
-+++ C:\GIT\issues\s p\vscode-python\src\test\pythonFiles\formatting\fileToformat.py 2020-05-11 19:05:50.969508 +0000
-@@ -1,23 +1,42 @@
-+import math, sys
-
--import math, sys;
-
- def example1():
- ####This is a long comment. This should be wrapped to fit within 72 characters.
-- some_tuple=( 1,2, 3,'a' );
-- some_variable={'long':'Long code lines should be wrapped within 79 characters.',
-- 'other':[math.pi, 100,200,300,9876543210,'This is a long string that goes on'],
-- 'more':{'inner':'This whole logical line should be wrapped.',some_tuple:[1,
-- 20,300,40000,500000000,60000000000000000]}}
-+ some_tuple = (1, 2, 3, "a")
-+ some_variable = {
-+ "long": "Long code lines should be wrapped within 79 characters.",
-+ "other": [
-+ math.pi,
-+ 100,
-+ 200,
-+ 300,
-+ 9876543210,
-+ "This is a long string that goes on",
-+ ],
-+ "more": {
-+ "inner": "This whole logical line should be wrapped.",
-+ some_tuple: [1, 20, 300, 40000, 500000000, 60000000000000000],
-+ },
-+ }
- return (some_tuple, some_variable)
--def example2(): return {'has_key() is deprecated':True}.has_key({'f':2}.has_key(''));
--class Example3( object ):
-- def __init__ ( self, bar ):
-- #Comments should have a space after the hash.
-- if bar : bar+=1; bar=bar* bar ; return bar
-- else:
-- some_string = """
-+
-+
-+def example2():
-+ return {"has_key() is deprecated": True}.has_key({"f": 2}.has_key(""))
-+
-+
-+class Example3(object):
-+ def __init__(self, bar):
-+ # Comments should have a space after the hash.
-+ if bar:
-+ bar += 1
-+ bar = bar * bar
-+ return bar
-+ else:
-+ some_string = """
- Indentation in multiline strings should not be touched.
- Only actual code should be reindented.
- """
-- return (sys.path, some_string)
-+ return (sys.path, some_string)
-
\ No newline at end of file
diff --git a/extensions/positron-python/src/test/pythonFiles/formatting/blackFormatted.py b/extensions/positron-python/src/test/pythonFiles/formatting/blackFormatted.py
deleted file mode 100644
index e7bca8b1298..00000000000
--- a/extensions/positron-python/src/test/pythonFiles/formatting/blackFormatted.py
+++ /dev/null
@@ -1,41 +0,0 @@
-import math, sys
-
-
-def example1():
- ####This is a long comment. This should be wrapped to fit within 72 characters.
- some_tuple = (1, 2, 3, "a")
- some_variable = {
- "long": "Long code lines should be wrapped within 79 characters.",
- "other": [
- math.pi,
- 100,
- 200,
- 300,
- 9876543210,
- "This is a long string that goes on",
- ],
- "more": {
- "inner": "This whole logical line should be wrapped.",
- some_tuple: [1, 20, 300, 40000, 500000000, 60000000000000000],
- },
- }
- return (some_tuple, some_variable)
-
-
-def example2():
- return {"has_key() is deprecated": True}.has_key({"f": 2}.has_key(""))
-
-
-class Example3(object):
- def __init__(self, bar):
- # Comments should have a space after the hash.
- if bar:
- bar += 1
- bar = bar * bar
- return bar
- else:
- some_string = """
- Indentation in multiline strings should not be touched.
-Only actual code should be reindented.
-"""
- return (sys.path, some_string)
diff --git a/extensions/positron-python/src/test/pythonFiles/formatting/dummy.ts b/extensions/positron-python/src/test/pythonFiles/formatting/dummy.ts
deleted file mode 100644
index cbab6669e3b..00000000000
--- a/extensions/positron-python/src/test/pythonFiles/formatting/dummy.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-// Dummy ts file to ensure this folder gets created in output directory.
-
-// Code to ensure linter doesn't complain about empty files.
-const a = '1';
diff --git a/extensions/positron-python/src/test/pythonFiles/formatting/fileToFormat.py b/extensions/positron-python/src/test/pythonFiles/formatting/fileToFormat.py
deleted file mode 100644
index 5b544bd8504..00000000000
--- a/extensions/positron-python/src/test/pythonFiles/formatting/fileToFormat.py
+++ /dev/null
@@ -1,22 +0,0 @@
-
-import math, sys;
-
-def example1():
- ####This is a long comment. This should be wrapped to fit within 72 characters.
- some_tuple=( 1,2, 3,'a' );
- some_variable={'long':'Long code lines should be wrapped within 79 characters.',
- 'other':[math.pi, 100,200,300,9876543210,'This is a long string that goes on'],
- 'more':{'inner':'This whole logical line should be wrapped.',some_tuple:[1,
- 20,300,40000,500000000,60000000000000000]}}
- return (some_tuple, some_variable)
-def example2(): return {'has_key() is deprecated':True}.has_key({'f':2}.has_key(''));
-class Example3( object ):
- def __init__ ( self, bar ):
- #Comments should have a space after the hash.
- if bar : bar+=1; bar=bar* bar ; return bar
- else:
- some_string = """
- Indentation in multiline strings should not be touched.
-Only actual code should be reindented.
-"""
- return (sys.path, some_string)
diff --git a/extensions/positron-python/src/test/pythonFiles/formatting/fileToFormatOnEnter.py b/extensions/positron-python/src/test/pythonFiles/formatting/fileToFormatOnEnter.py
deleted file mode 100644
index 8adfd1fa123..00000000000
--- a/extensions/positron-python/src/test/pythonFiles/formatting/fileToFormatOnEnter.py
+++ /dev/null
@@ -1,13 +0,0 @@
-x=1
-"""x=1
-"""
- # comment
-# x=1
-x+1 #
-@x
-x.y
-if x<=1:
-if 1<=x:
-def __init__(self, age = 23)
-while(1)
-x+"""
diff --git a/extensions/positron-python/src/test/pythonFiles/formatting/formatWhenDirty.py b/extensions/positron-python/src/test/pythonFiles/formatting/formatWhenDirty.py
deleted file mode 100644
index 3fe1b80fde8..00000000000
--- a/extensions/positron-python/src/test/pythonFiles/formatting/formatWhenDirty.py
+++ /dev/null
@@ -1,3 +0,0 @@
-x = 0
-if x > 0:
- x = 1
diff --git a/extensions/positron-python/src/test/pythonFiles/formatting/formatWhenDirtyResult.py b/extensions/positron-python/src/test/pythonFiles/formatting/formatWhenDirtyResult.py
deleted file mode 100644
index d0ae06a2a59..00000000000
--- a/extensions/positron-python/src/test/pythonFiles/formatting/formatWhenDirtyResult.py
+++ /dev/null
@@ -1,3 +0,0 @@
-x = 0
-if x > 0:
- x = 1
diff --git a/extensions/positron-python/src/test/pythonFiles/formatting/pythonGrammar.py b/extensions/positron-python/src/test/pythonFiles/formatting/pythonGrammar.py
deleted file mode 100644
index 937cba401d3..00000000000
--- a/extensions/positron-python/src/test/pythonFiles/formatting/pythonGrammar.py
+++ /dev/null
@@ -1,1572 +0,0 @@
-# Python test set -- part 1, grammar.
-# This just tests whether the parser accepts them all.
-
-from test.support import check_syntax_error
-import inspect
-import unittest
-import sys
-# testing import *
-from sys import *
-
-# different import patterns to check that __annotations__ does not interfere
-# with import machinery
-import test.ann_module as ann_module
-import typing
-from collections import ChainMap
-from test import ann_module2
-import test
-
-# These are shared with test_tokenize and other test modules.
-#
-# Note: since several test cases filter out floats by looking for "e" and ".",
-# don't add hexadecimal literals that contain "e" or "E".
-VALID_UNDERSCORE_LITERALS = [
- '0_0_0',
- '4_2',
- '1_0000_0000',
- '0b1001_0100',
- '0xffff_ffff',
- '0o5_7_7',
- '1_00_00.5',
- '1_00_00.5e5',
- '1_00_00e5_1',
- '1e1_0',
- '.1_4',
- '.1_4e1',
- '0b_0',
- '0x_f',
- '0o_5',
- '1_00_00j',
- '1_00_00.5j',
- '1_00_00e5_1j',
- '.1_4j',
- '(1_2.5+3_3j)',
- '(.5_6j)',
-]
-INVALID_UNDERSCORE_LITERALS = [
- # Trailing underscores:
- '0_',
- '42_',
- '1.4j_',
- '0x_',
- '0b1_',
- '0xf_',
- '0o5_',
- '0 if 1_Else 1',
- # Underscores in the base selector:
- '0_b0',
- '0_xf',
- '0_o5',
- # Old-style octal, still disallowed:
- '0_7',
- '09_99',
- # Multiple consecutive underscores:
- '4_______2',
- '0.1__4',
- '0.1__4j',
- '0b1001__0100',
- '0xffff__ffff',
- '0x___',
- '0o5__77',
- '1e1__0',
- '1e1__0j',
- # Underscore right before a dot:
- '1_.4',
- '1_.4j',
- # Underscore right after a dot:
- '1._4',
- '1._4j',
- '._5',
- '._5j',
- # Underscore right after a sign:
- '1.0e+_1',
- '1.0e+_1j',
- # Underscore right before j:
- '1.4_j',
- '1.4e5_j',
- # Underscore right before e:
- '1_e1',
- '1.4_e1',
- '1.4_e1j',
- # Underscore right after e:
- '1e_1',
- '1.4e_1',
- '1.4e_1j',
- # Complex cases with parens:
- '(1+1.5_j_)',
- '(1+1.5_j)',
-]
-
-
-class TokenTests(unittest.TestCase):
-
- def test_backslash(self):
- # Backslash means line continuation:
- x = 1 \
- + 1
- self.assertEqual(x, 2, 'backslash for line continuation')
-
- # Backslash does not means continuation in comments :\
- x = 0
- self.assertEqual(x, 0, 'backslash ending comment')
-
- def test_plain_integers(self):
- self.assertEqual(type(000), type(0))
- self.assertEqual(0xff, 255)
- self.assertEqual(0o377, 255)
- self.assertEqual(2147483647, 0o17777777777)
- self.assertEqual(0b1001, 9)
- # "0x" is not a valid literal
- self.assertRaises(SyntaxError, eval, "0x")
- from sys import maxsize
- if maxsize == 2147483647:
- self.assertEqual(-2147483647 - 1, -0o20000000000)
- # XXX -2147483648
- self.assertTrue(0o37777777777 > 0)
- self.assertTrue(0xffffffff > 0)
- self.assertTrue(0b1111111111111111111111111111111 > 0)
- for s in ('2147483648', '0o40000000000', '0x100000000',
- '0b10000000000000000000000000000000'):
- try:
- x = eval(s)
- except OverflowError:
- self.fail("OverflowError on huge integer literal %r" % s)
- elif maxsize == 9223372036854775807:
- self.assertEqual(-9223372036854775807 - 1, -0o1000000000000000000000)
- self.assertTrue(0o1777777777777777777777 > 0)
- self.assertTrue(0xffffffffffffffff > 0)
- self.assertTrue(0b11111111111111111111111111111111111111111111111111111111111111 > 0)
- for s in '9223372036854775808', '0o2000000000000000000000', \
- '0x10000000000000000', \
- '0b100000000000000000000000000000000000000000000000000000000000000':
- try:
- x = eval(s)
- except OverflowError:
- self.fail("OverflowError on huge integer literal %r" % s)
- else:
- self.fail('Weird maxsize value %r' % maxsize)
-
- def test_long_integers(self):
- x = 0
- x = 0xffffffffffffffff
- x = 0Xffffffffffffffff
- x = 0o77777777777777777
- x = 0O77777777777777777
- x = 123456789012345678901234567890
- x = 0b100000000000000000000000000000000000000000000000000000000000000000000
- x = 0B111111111111111111111111111111111111111111111111111111111111111111111
-
- def test_floats(self):
- x = 3.14
- x = 314.
- x = 0.314
- # XXX x = 000.314
- x = .314
- x = 3e14
- x = 3E14
- x = 3e-14
- x = 3e+14
- x = 3.e14
- x = .3e14
- x = 3.1e4
-
- def test_float_exponent_tokenization(self):
- # See issue 21642.
- self.assertEqual(1 if 1 else 0, 1)
- self.assertEqual(1 if 0 else 0, 0)
- self.assertRaises(SyntaxError, eval, "0 if 1Else 0")
-
- def test_underscore_literals(self):
- for lit in VALID_UNDERSCORE_LITERALS:
- self.assertEqual(eval(lit), eval(lit.replace('_', '')))
- for lit in INVALID_UNDERSCORE_LITERALS:
- self.assertRaises(SyntaxError, eval, lit)
- # Sanity check: no literal begins with an underscore
- self.assertRaises(NameError, eval, "_0")
-
- def test_string_literals(self):
- x = ''; y = ""; self.assertTrue(len(x) == 0 and x == y)
- x = '\''; y = "'"; self.assertTrue(len(x) == 1 and x == y and ord(x) == 39)
- x = '"'; y = "\""; self.assertTrue(len(x) == 1 and x == y and ord(x) == 34)
- x = "doesn't \"shrink\" does it"
- y = 'doesn\'t "shrink" does it'
- self.assertTrue(len(x) == 24 and x == y)
- x = "does \"shrink\" doesn't it"
- y = 'does "shrink" doesn\'t it'
- self.assertTrue(len(x) == 24 and x == y)
- x = """
-The "quick"
-brown fox
-jumps over
-the 'lazy' dog.
-"""
- y = '\nThe "quick"\nbrown fox\njumps over\nthe \'lazy\' dog.\n'
- self.assertEqual(x, y)
- y = '''
-The "quick"
-brown fox
-jumps over
-the 'lazy' dog.
-'''
- self.assertEqual(x, y)
- y = "\n\
-The \"quick\"\n\
-brown fox\n\
-jumps over\n\
-the 'lazy' dog.\n\
-"
- self.assertEqual(x, y)
- y = '\n\
-The \"quick\"\n\
-brown fox\n\
-jumps over\n\
-the \'lazy\' dog.\n\
-'
- self.assertEqual(x, y)
-
- def test_ellipsis(self):
- x = ...
- self.assertTrue(x is Ellipsis)
- self.assertRaises(SyntaxError, eval, ".. .")
-
- def test_eof_error(self):
- samples = ("def foo(", "\ndef foo(", "def foo(\n")
- for s in samples:
- with self.assertRaises(SyntaxError) as cm:
- compile(s, "", "exec")
- self.assertIn("unexpected EOF", str(cm.exception))
-
-var_annot_global: int # a global annotated is necessary for test_var_annot
-
-# custom namespace for testing __annotations__
-
-class CNS:
- def __init__(self):
- self._dct = {}
- def __setitem__(self, item, value):
- self._dct[item.lower()] = value
- def __getitem__(self, item):
- return self._dct[item]
-
-
-class GrammarTests(unittest.TestCase):
-
- check_syntax_error = check_syntax_error
-
- # single_input: NEWLINE | simple_stmt | compound_stmt NEWLINE
- # XXX can't test in a script -- this rule is only used when interactive
-
- # file_input: (NEWLINE | stmt)* ENDMARKER
- # Being tested as this very moment this very module
-
- # expr_input: testlist NEWLINE
- # XXX Hard to test -- used only in calls to input()
-
- def test_eval_input(self):
- # testlist ENDMARKER
- x = eval('1, 0 or 1')
-
- def test_var_annot_basics(self):
- # all these should be allowed
- var1: int = 5
- var2: [int, str]
- my_lst = [42]
- def one():
- return 1
- int.new_attr: int
- [list][0]: type
- my_lst[one() - 1]: int = 5
- self.assertEqual(my_lst, [5])
-
- def test_var_annot_syntax_errors(self):
- # parser pass
- check_syntax_error(self, "def f: int")
- check_syntax_error(self, "x: int: str")
- check_syntax_error(self, "def f():\n"
- " nonlocal x: int\n")
- # AST pass
- check_syntax_error(self, "[x, 0]: int\n")
- check_syntax_error(self, "f(): int\n")
- check_syntax_error(self, "(x,): int")
- check_syntax_error(self, "def f():\n"
- " (x, y): int = (1, 2)\n")
- # symtable pass
- check_syntax_error(self, "def f():\n"
- " x: int\n"
- " global x\n")
- check_syntax_error(self, "def f():\n"
- " global x\n"
- " x: int\n")
-
- def test_var_annot_basic_semantics(self):
- # execution order
- with self.assertRaises(ZeroDivisionError):
- no_name[does_not_exist]: no_name_again = 1 / 0
- with self.assertRaises(NameError):
- no_name[does_not_exist]: 1 / 0 = 0
- global var_annot_global
-
- # function semantics
- def f():
- st: str = "Hello"
- a.b: int = (1, 2)
- return st
- self.assertEqual(f.__annotations__, {})
- def f_OK():
- x: 1 / 0
- f_OK()
- def fbad():
- x: int
- print(x)
- with self.assertRaises(UnboundLocalError):
- fbad()
- def f2bad():
- (no_such_global): int
- print(no_such_global)
- try:
- f2bad()
- except Exception as e:
- self.assertIs(type(e), NameError)
-
- # class semantics
- class C:
- __foo: int
- s: str = "attr"
- z = 2
- def __init__(self, x):
- self.x: int = x
- self.assertEqual(C.__annotations__, {'_C__foo': int, 's': str})
- with self.assertRaises(NameError):
- class CBad:
- no_such_name_defined.attr: int = 0
- with self.assertRaises(NameError):
- class Cbad2(C):
- x: int
- x.y: list = []
-
- def test_var_annot_metaclass_semantics(self):
- class CMeta(type):
- @classmethod
- def __prepare__(metacls, name, bases, **kwds):
- return {'__annotations__': CNS()}
- class CC(metaclass=CMeta):
- XX: 'ANNOT'
- self.assertEqual(CC.__annotations__['xx'], 'ANNOT')
-
- def test_var_annot_module_semantics(self):
- with self.assertRaises(AttributeError):
- print(test.__annotations__)
- self.assertEqual(ann_module.__annotations__,
- {1: 2, 'x': int, 'y': str, 'f': typing.Tuple[int, int]})
- self.assertEqual(ann_module.M.__annotations__,
- {'123': 123, 'o': type})
- self.assertEqual(ann_module2.__annotations__, {})
-
- def test_var_annot_in_module(self):
- # check that functions fail the same way when executed
- # outside of module where they were defined
- from test.ann_module3 import f_bad_ann, g_bad_ann, D_bad_ann
- with self.assertRaises(NameError):
- f_bad_ann()
- with self.assertRaises(NameError):
- g_bad_ann()
- with self.assertRaises(NameError):
- D_bad_ann(5)
-
- def test_var_annot_simple_exec(self):
- gns = {}; lns = {}
- exec("'docstring'\n"
- "__annotations__[1] = 2\n"
- "x: int = 5\n", gns, lns)
- self.assertEqual(lns["__annotations__"], {1: 2, 'x': int})
- with self.assertRaises(KeyError):
- gns['__annotations__']
-
- def test_var_annot_custom_maps(self):
- # tests with custom locals() and __annotations__
- ns = {'__annotations__': CNS()}
- exec('X: int; Z: str = "Z"; (w): complex = 1j', ns)
- self.assertEqual(ns['__annotations__']['x'], int)
- self.assertEqual(ns['__annotations__']['z'], str)
- with self.assertRaises(KeyError):
- ns['__annotations__']['w']
- nonloc_ns = {}
- class CNS2:
- def __init__(self):
- self._dct = {}
- def __setitem__(self, item, value):
- nonlocal nonloc_ns
- self._dct[item] = value
- nonloc_ns[item] = value
- def __getitem__(self, item):
- return self._dct[item]
- exec('x: int = 1', {}, CNS2())
- self.assertEqual(nonloc_ns['__annotations__']['x'], int)
-
- def test_var_annot_refleak(self):
- # complex case: custom locals plus custom __annotations__
- # this was causing refleak
- cns = CNS()
- nonloc_ns = {'__annotations__': cns}
- class CNS2:
- def __init__(self):
- self._dct = {'__annotations__': cns}
- def __setitem__(self, item, value):
- nonlocal nonloc_ns
- self._dct[item] = value
- nonloc_ns[item] = value
- def __getitem__(self, item):
- return self._dct[item]
- exec('X: str', {}, CNS2())
- self.assertEqual(nonloc_ns['__annotations__']['x'], str)
-
- def test_funcdef(self):
- ### [decorators] 'def' NAME parameters ['->' test] ':' suite
- ### decorator: '@' dotted_name [ '(' [arglist] ')' ] NEWLINE
- ### decorators: decorator+
- ### parameters: '(' [typedargslist] ')'
- ### typedargslist: ((tfpdef ['=' test] ',')*
- ### ('*' [tfpdef] (',' tfpdef ['=' test])* [',' '**' tfpdef] | '**' tfpdef)
- ### | tfpdef ['=' test] (',' tfpdef ['=' test])* [','])
- ### tfpdef: NAME [':' test]
- ### varargslist: ((vfpdef ['=' test] ',')*
- ### ('*' [vfpdef] (',' vfpdef ['=' test])* [',' '**' vfpdef] | '**' vfpdef)
- ### | vfpdef ['=' test] (',' vfpdef ['=' test])* [','])
- ### vfpdef: NAME
- def f1(): pass
- f1()
- f1(*())
- f1(*(), **{})
- def f2(one_argument): pass
- def f3(two, arguments): pass
- self.assertEqual(f2.__code__.co_varnames, ('one_argument',))
- self.assertEqual(f3.__code__.co_varnames, ('two', 'arguments'))
- def a1(one_arg,): pass
- def a2(two, args,): pass
- def v0(*rest): pass
- def v1(a, *rest): pass
- def v2(a, b, *rest): pass
-
- f1()
- f2(1)
- f2(1,)
- f3(1, 2)
- f3(1, 2,)
- v0()
- v0(1)
- v0(1,)
- v0(1, 2)
- v0(1, 2, 3, 4, 5, 6, 7, 8, 9, 0)
- v1(1)
- v1(1,)
- v1(1, 2)
- v1(1, 2, 3)
- v1(1, 2, 3, 4, 5, 6, 7, 8, 9, 0)
- v2(1, 2)
- v2(1, 2, 3)
- v2(1, 2, 3, 4)
- v2(1, 2, 3, 4, 5, 6, 7, 8, 9, 0)
-
- def d01(a=1): pass
- d01()
- d01(1)
- d01(*(1,))
- d01(*[] or [2])
- d01(*() or (), *{} and (), **() or {})
- d01(**{'a': 2})
- d01(**{'a': 2} or {})
- def d11(a, b=1): pass
- d11(1)
- d11(1, 2)
- d11(1, **{'b': 2})
- def d21(a, b, c=1): pass
- d21(1, 2)
- d21(1, 2, 3)
- d21(*(1, 2, 3))
- d21(1, *(2, 3))
- d21(1, 2, *(3,))
- d21(1, 2, **{'c': 3})
- def d02(a=1, b=2): pass
- d02()
- d02(1)
- d02(1, 2)
- d02(*(1, 2))
- d02(1, *(2,))
- d02(1, **{'b': 2})
- d02(**{'a': 1, 'b': 2})
- def d12(a, b=1, c=2): pass
- d12(1)
- d12(1, 2)
- d12(1, 2, 3)
- def d22(a, b, c=1, d=2): pass
- d22(1, 2)
- d22(1, 2, 3)
- d22(1, 2, 3, 4)
- def d01v(a=1, *rest): pass
- d01v()
- d01v(1)
- d01v(1, 2)
- d01v(*(1, 2, 3, 4))
- d01v(*(1,))
- d01v(**{'a': 2})
- def d11v(a, b=1, *rest): pass
- d11v(1)
- d11v(1, 2)
- d11v(1, 2, 3)
- def d21v(a, b, c=1, *rest): pass
- d21v(1, 2)
- d21v(1, 2, 3)
- d21v(1, 2, 3, 4)
- d21v(*(1, 2, 3, 4))
- d21v(1, 2, **{'c': 3})
- def d02v(a=1, b=2, *rest): pass
- d02v()
- d02v(1)
- d02v(1, 2)
- d02v(1, 2, 3)
- d02v(1, *(2, 3, 4))
- d02v(**{'a': 1, 'b': 2})
- def d12v(a, b=1, c=2, *rest): pass
- d12v(1)
- d12v(1, 2)
- d12v(1, 2, 3)
- d12v(1, 2, 3, 4)
- d12v(*(1, 2, 3, 4))
- d12v(1, 2, *(3, 4, 5))
- d12v(1, *(2,), **{'c': 3})
- def d22v(a, b, c=1, d=2, *rest): pass
- d22v(1, 2)
- d22v(1, 2, 3)
- d22v(1, 2, 3, 4)
- d22v(1, 2, 3, 4, 5)
- d22v(*(1, 2, 3, 4))
- d22v(1, 2, *(3, 4, 5))
- d22v(1, *(2, 3), **{'d': 4})
-
- # keyword argument type tests
- try:
- str('x', **{b'foo': 1})
- except TypeError:
- pass
- else:
- self.fail('Bytes should not work as keyword argument names')
- # keyword only argument tests
- def pos0key1(*, key): return key
- pos0key1(key=100)
- def pos2key2(p1, p2, *, k1, k2=100): return p1, p2, k1, k2
- pos2key2(1, 2, k1=100)
- pos2key2(1, 2, k1=100, k2=200)
- pos2key2(1, 2, k2=100, k1=200)
- def pos2key2dict(p1, p2, *, k1=100, k2, **kwarg): return p1, p2, k1, k2, kwarg
- pos2key2dict(1, 2, k2=100, tokwarg1=100, tokwarg2=200)
- pos2key2dict(1, 2, tokwarg1=100, tokwarg2=200, k2=100)
-
- self.assertRaises(SyntaxError, eval, "def f(*): pass")
- self.assertRaises(SyntaxError, eval, "def f(*,): pass")
- self.assertRaises(SyntaxError, eval, "def f(*, **kwds): pass")
-
- # keyword arguments after *arglist
- def f(*args, **kwargs):
- return args, kwargs
- self.assertEqual(f(1, x=2, *[3, 4], y=5), ((1, 3, 4),
- {'x': 2, 'y': 5}))
- self.assertEqual(f(1, *(2, 3), 4), ((1, 2, 3, 4), {}))
- self.assertRaises(SyntaxError, eval, "f(1, x=2, *(3,4), x=5)")
- self.assertEqual(f(**{'eggs': 'scrambled', 'spam': 'fried'}),
- ((), {'eggs': 'scrambled', 'spam': 'fried'}))
- self.assertEqual(f(spam='fried', **{'eggs': 'scrambled'}),
- ((), {'eggs': 'scrambled', 'spam': 'fried'}))
-
- # Check ast errors in *args and *kwargs
- check_syntax_error(self, "f(*g(1=2))")
- check_syntax_error(self, "f(**g(1=2))")
-
- # argument annotation tests
- def f(x) -> list: pass
- self.assertEqual(f.__annotations__, {'return': list})
- def f(x: int): pass
- self.assertEqual(f.__annotations__, {'x': int})
- def f(*x: str): pass
- self.assertEqual(f.__annotations__, {'x': str})
- def f(**x: float): pass
- self.assertEqual(f.__annotations__, {'x': float})
- def f(x, y: 1 + 2): pass
- self.assertEqual(f.__annotations__, {'y': 3})
- def f(a, b: 1, c: 2, d): pass
- self.assertEqual(f.__annotations__, {'b': 1, 'c': 2})
- def f(a, b: 1, c: 2, d, e: 3 = 4, f=5, *g: 6): pass
- self.assertEqual(f.__annotations__,
- {'b': 1, 'c': 2, 'e': 3, 'g': 6})
- def f(a, b: 1, c: 2, d, e: 3 = 4, f=5, *g: 6, h: 7, i=8, j: 9 = 10,
- **k: 11) -> 12: pass
- self.assertEqual(f.__annotations__,
- {'b': 1, 'c': 2, 'e': 3, 'g': 6, 'h': 7, 'j': 9,
- 'k': 11, 'return': 12})
- # Check for issue #20625 -- annotations mangling
- class Spam:
- def f(self, *, __kw: 1):
- pass
- class Ham(Spam): pass
- self.assertEqual(Spam.f.__annotations__, {'_Spam__kw': 1})
- self.assertEqual(Ham.f.__annotations__, {'_Spam__kw': 1})
- # Check for SF Bug #1697248 - mixing decorators and a return annotation
- def null(x): return x
- @null
- def f(x) -> list: pass
- self.assertEqual(f.__annotations__, {'return': list})
-
- # test closures with a variety of opargs
- closure = 1
- def f(): return closure
- def f(x=1): return closure
- def f(*, k=1): return closure
- def f() -> int: return closure
-
- # Check trailing commas are permitted in funcdef argument list
- def f(a,): pass
- def f(*args,): pass
- def f(**kwds,): pass
- def f(a, *args,): pass
- def f(a, **kwds,): pass
- def f(*args, b,): pass
- def f(*, b,): pass
- def f(*args, **kwds,): pass
- def f(a, *args, b,): pass
- def f(a, *, b,): pass
- def f(a, *args, **kwds,): pass
- def f(*args, b, **kwds,): pass
- def f(*, b, **kwds,): pass
- def f(a, *args, b, **kwds,): pass
- def f(a, *, b, **kwds,): pass
-
- def test_lambdef(self):
- ### lambdef: 'lambda' [varargslist] ':' test
- l1 = lambda: 0
- self.assertEqual(l1(), 0)
- l2 = lambda: a[d] # XXX just testing the expression
- l3 = lambda: [2 < x for x in [-1, 3, 0]]
- self.assertEqual(l3(), [0, 1, 0])
- l4 = lambda x=lambda y=lambda z=1: z: y(): x()
- self.assertEqual(l4(), 1)
- l5 = lambda x, y, z=2: x + y + z
- self.assertEqual(l5(1, 2), 5)
- self.assertEqual(l5(1, 2, 3), 6)
- check_syntax_error(self, "lambda x: x = 2")
- check_syntax_error(self, "lambda (None,): None")
- l6 = lambda x, y, *, k=20: x + y + k
- self.assertEqual(l6(1, 2), 1 + 2 + 20)
- self.assertEqual(l6(1, 2, k=10), 1 + 2 + 10)
-
- # check that trailing commas are permitted
- l10 = lambda a,: 0
- l11 = lambda *args,: 0
- l12 = lambda **kwds,: 0
- l13 = lambda a, *args,: 0
- l14 = lambda a, **kwds,: 0
- l15 = lambda *args, b,: 0
- l16 = lambda *, b,: 0
- l17 = lambda *args, **kwds,: 0
- l18 = lambda a, *args, b,: 0
- l19 = lambda a, *, b,: 0
- l20 = lambda a, *args, **kwds,: 0
- l21 = lambda *args, b, **kwds,: 0
- l22 = lambda *, b, **kwds,: 0
- l23 = lambda a, *args, b, **kwds,: 0
- l24 = lambda a, *, b, **kwds,: 0
-
-
- ### stmt: simple_stmt | compound_stmt
- # Tested below
-
- def test_simple_stmt(self):
- ### simple_stmt: small_stmt (';' small_stmt)* [';']
- x = 1; pass; del x
- def foo():
- # verify statements that end with semi-colons
- x = 1; pass; del x;
- foo()
-
- ### small_stmt: expr_stmt | pass_stmt | del_stmt | flow_stmt | import_stmt | global_stmt | access_stmt
- # Tested below
-
- def test_expr_stmt(self):
- # (exprlist '=')* exprlist
- 1
- 1, 2, 3
- x = 1
- x = 1, 2, 3
- x = y = z = 1, 2, 3
- x, y, z = 1, 2, 3
- abc = a, b, c = x, y, z = xyz = 1, 2, (3, 4)
-
- check_syntax_error(self, "x + 1 = 1")
- check_syntax_error(self, "a + 1 = b + 2")
-
- # Check the heuristic for print & exec covers significant cases
- # As well as placing some limits on false positives
- def test_former_statements_refer_to_builtins(self):
- keywords = "print", "exec"
- # Cases where we want the custom error
- cases = [
- "{} foo",
- "{} {{1:foo}}",
- "if 1: {} foo",
- "if 1: {} {{1:foo}}",
- "if 1:\n {} foo",
- "if 1:\n {} {{1:foo}}",
- ]
- for keyword in keywords:
- custom_msg = "call to '{}'".format(keyword)
- for case in cases:
- source = case.format(keyword)
- with self.subTest(source=source):
- with self.assertRaisesRegex(SyntaxError, custom_msg):
- exec(source)
- source = source.replace("foo", "(foo.)")
- with self.subTest(source=source):
- with self.assertRaisesRegex(SyntaxError, "invalid syntax"):
- exec(source)
-
- def test_del_stmt(self):
- # 'del' exprlist
- abc = [1, 2, 3]
- x, y, z = abc
- xyz = x, y, z
-
- del abc
- del x, y, (z, xyz)
-
- def test_pass_stmt(self):
- # 'pass'
- pass
-
- # flow_stmt: break_stmt | continue_stmt | return_stmt | raise_stmt
- # Tested below
-
- def test_break_stmt(self):
- # 'break'
- while 1: break
-
- def test_continue_stmt(self):
- # 'continue'
- i = 1
- while i: i = 0; continue
-
- msg = ""
- while not msg:
- msg = "ok"
- try:
- continue
- msg = "continue failed to continue inside try"
- except:
- msg = "continue inside try called except block"
- if msg != "ok":
- self.fail(msg)
-
- msg = ""
- while not msg:
- msg = "finally block not called"
- try:
- continue
- finally:
- msg = "ok"
- if msg != "ok":
- self.fail(msg)
-
- def test_break_continue_loop(self):
- # This test warrants an explanation. It is a test specifically for SF bugs
- # #463359 and #462937. The bug is that a 'break' statement executed or
- # exception raised inside a try/except inside a loop, *after* a continue
- # statement has been executed in that loop, will cause the wrong number of
- # arguments to be popped off the stack and the instruction pointer reset to
- # a very small number (usually 0.) Because of this, the following test
- # *must* written as a function, and the tracking vars *must* be function
- # arguments with default values. Otherwise, the test will loop and loop.
-
- def test_inner(extra_burning_oil=1, count=0):
- big_hippo = 2
- while big_hippo:
- count += 1
- try:
- if extra_burning_oil and big_hippo == 1:
- extra_burning_oil -= 1
- break
- big_hippo -= 1
- continue
- except:
- raise
- if count > 2 or big_hippo != 1:
- self.fail("continue then break in try/except in loop broken!")
- test_inner()
-
- def test_return(self):
- # 'return' [testlist]
- def g1(): return
- def g2(): return 1
- g1()
- x = g2()
- check_syntax_error(self, "class foo:return 1")
-
- def test_break_in_finally(self):
- count = 0
- while count < 2:
- count += 1
- try:
- pass
- finally:
- break
- self.assertEqual(count, 1)
-
- count = 0
- while count < 2:
- count += 1
- try:
- continue
- finally:
- break
- self.assertEqual(count, 1)
-
- count = 0
- while count < 2:
- count += 1
- try:
- 1 / 0
- finally:
- break
- self.assertEqual(count, 1)
-
- for count in [0, 1]:
- self.assertEqual(count, 0)
- try:
- pass
- finally:
- break
- self.assertEqual(count, 0)
-
- for count in [0, 1]:
- self.assertEqual(count, 0)
- try:
- continue
- finally:
- break
- self.assertEqual(count, 0)
-
- for count in [0, 1]:
- self.assertEqual(count, 0)
- try:
- 1 / 0
- finally:
- break
- self.assertEqual(count, 0)
-
- def test_continue_in_finally(self):
- count = 0
- while count < 2:
- count += 1
- try:
- pass
- finally:
- continue
- break
- self.assertEqual(count, 2)
-
- count = 0
- while count < 2:
- count += 1
- try:
- break
- finally:
- continue
- self.assertEqual(count, 2)
-
- count = 0
- while count < 2:
- count += 1
- try:
- 1 / 0
- finally:
- continue
- break
- self.assertEqual(count, 2)
-
- for count in [0, 1]:
- try:
- pass
- finally:
- continue
- break
- self.assertEqual(count, 1)
-
- for count in [0, 1]:
- try:
- break
- finally:
- continue
- self.assertEqual(count, 1)
-
- for count in [0, 1]:
- try:
- 1 / 0
- finally:
- continue
- break
- self.assertEqual(count, 1)
-
- def test_return_in_finally(self):
- def g1():
- try:
- pass
- finally:
- return 1
- self.assertEqual(g1(), 1)
-
- def g2():
- try:
- return 2
- finally:
- return 3
- self.assertEqual(g2(), 3)
-
- def g3():
- try:
- 1 / 0
- finally:
- return 4
- self.assertEqual(g3(), 4)
-
- def test_yield(self):
- # Allowed as standalone statement
- def g(): yield 1
- def g(): yield from ()
- # Allowed as RHS of assignment
- def g(): x = yield 1
- def g(): x = yield from ()
- # Ordinary yield accepts implicit tuples
- def g(): yield 1, 1
- def g(): x = yield 1, 1
- # 'yield from' does not
- check_syntax_error(self, "def g(): yield from (), 1")
- check_syntax_error(self, "def g(): x = yield from (), 1")
- # Requires parentheses as subexpression
- def g(): 1, (yield 1)
- def g(): 1, (yield from ())
- check_syntax_error(self, "def g(): 1, yield 1")
- check_syntax_error(self, "def g(): 1, yield from ()")
- # Requires parentheses as call argument
- def g(): f((yield 1))
- def g(): f((yield 1), 1)
- def g(): f((yield from ()))
- def g(): f((yield from ()), 1)
- check_syntax_error(self, "def g(): f(yield 1)")
- check_syntax_error(self, "def g(): f(yield 1, 1)")
- check_syntax_error(self, "def g(): f(yield from ())")
- check_syntax_error(self, "def g(): f(yield from (), 1)")
- # Not allowed at top level
- check_syntax_error(self, "yield")
- check_syntax_error(self, "yield from")
- # Not allowed at class scope
- check_syntax_error(self, "class foo:yield 1")
- check_syntax_error(self, "class foo:yield from ()")
- # Check annotation refleak on SyntaxError
- check_syntax_error(self, "def g(a:(yield)): pass")
-
- def test_yield_in_comprehensions(self):
- # Check yield in comprehensions
- def g(): [x for x in [(yield 1)]]
- def g(): [x for x in [(yield from ())]]
-
- check = self.check_syntax_error
- check("def g(): [(yield x) for x in ()]",
- "'yield' inside list comprehension")
- check("def g(): [x for x in () if not (yield x)]",
- "'yield' inside list comprehension")
- check("def g(): [y for x in () for y in [(yield x)]]",
- "'yield' inside list comprehension")
- check("def g(): {(yield x) for x in ()}",
- "'yield' inside set comprehension")
- check("def g(): {(yield x): x for x in ()}",
- "'yield' inside dict comprehension")
- check("def g(): {x: (yield x) for x in ()}",
- "'yield' inside dict comprehension")
- check("def g(): ((yield x) for x in ())",
- "'yield' inside generator expression")
- check("def g(): [(yield from x) for x in ()]",
- "'yield' inside list comprehension")
- check("class C: [(yield x) for x in ()]",
- "'yield' inside list comprehension")
- check("[(yield x) for x in ()]",
- "'yield' inside list comprehension")
-
- def test_raise(self):
- # 'raise' test [',' test]
- try: raise RuntimeError('just testing')
- except RuntimeError: pass
- try: raise KeyboardInterrupt
- except KeyboardInterrupt: pass
-
- def test_import(self):
- # 'import' dotted_as_names
- import sys
- import time, sys
- # 'from' dotted_name 'import' ('*' | '(' import_as_names ')' | import_as_names)
- from time import time
- from time import (time)
- # not testable inside a function, but already done at top of the module
- # from sys import *
- from sys import path, argv
- from sys import (path, argv)
- from sys import (path, argv,)
-
- def test_global(self):
- # 'global' NAME (',' NAME)*
- global a
- global a, b
- global one, two, three, four, five, six, seven, eight, nine, ten
-
- def test_nonlocal(self):
- # 'nonlocal' NAME (',' NAME)*
- x = 0
- y = 0
- def f():
- nonlocal x
- nonlocal x, y
-
- def test_assert(self):
- # assertTruestmt: 'assert' test [',' test]
- assert 1
- assert 1, 1
- assert lambda x: x
- assert 1, lambda x: x + 1
-
- try:
- assert True
- except AssertionError as e:
- self.fail("'assert True' should not have raised an AssertionError")
-
- try:
- assert True, 'this should always pass'
- except AssertionError as e:
- self.fail("'assert True, msg' should not have "
- "raised an AssertionError")
-
- # these tests fail if python is run with -O, so check __debug__
- @unittest.skipUnless(__debug__, "Won't work if __debug__ is False")
- def testAssert2(self):
- try:
- assert 0, "msg"
- except AssertionError as e:
- self.assertEqual(e.args[0], "msg")
- else:
- self.fail("AssertionError not raised by assert 0")
-
- try:
- assert False
- except AssertionError as e:
- self.assertEqual(len(e.args), 0)
- else:
- self.fail("AssertionError not raised by 'assert False'")
-
-
- ### compound_stmt: if_stmt | while_stmt | for_stmt | try_stmt | funcdef | classdef
- # Tested below
-
- def test_if(self):
- # 'if' test ':' suite ('elif' test ':' suite)* ['else' ':' suite]
- if 1: pass
- if 1: pass
- else: pass
- if 0: pass
- elif 0: pass
- if 0: pass
- elif 0: pass
- elif 0: pass
- elif 0: pass
- else: pass
-
- def test_while(self):
- # 'while' test ':' suite ['else' ':' suite]
- while 0: pass
- while 0: pass
- else: pass
-
- # Issue1920: "while 0" is optimized away,
- # ensure that the "else" clause is still present.
- x = 0
- while 0:
- x = 1
- else:
- x = 2
- self.assertEqual(x, 2)
-
- def test_for(self):
- # 'for' exprlist 'in' exprlist ':' suite ['else' ':' suite]
- for i in 1, 2, 3: pass
- for i, j, k in (): pass
- else: pass
- class Squares:
- def __init__(self, max):
- self.max = max
- self.sofar = []
- def __len__(self): return len(self.sofar)
- def __getitem__(self, i):
- if not 0 <= i < self.max: raise IndexError
- n = len(self.sofar)
- while n <= i:
- self.sofar.append(n * n)
- n = n + 1
- return self.sofar[i]
- n = 0
- for x in Squares(10): n = n + x
- if n != 285:
- self.fail('for over growing sequence')
-
- result = []
- for x, in [(1,), (2,), (3,)]:
- result.append(x)
- self.assertEqual(result, [1, 2, 3])
-
- def test_try(self):
- ### try_stmt: 'try' ':' suite (except_clause ':' suite)+ ['else' ':' suite]
- ### | 'try' ':' suite 'finally' ':' suite
- ### except_clause: 'except' [expr ['as' expr]]
- try:
- 1 / 0
- except ZeroDivisionError:
- pass
- else:
- pass
- try: 1 / 0
- except EOFError: pass
- except TypeError as msg: pass
- except: pass
- else: pass
- try: 1 / 0
- except (EOFError, TypeError, ZeroDivisionError): pass
- try: 1 / 0
- except (EOFError, TypeError, ZeroDivisionError) as msg: pass
- try: pass
- finally: pass
-
- def test_suite(self):
- # simple_stmt | NEWLINE INDENT NEWLINE* (stmt NEWLINE*)+ DEDENT
- if 1: pass
- if 1:
- pass
- if 1:
- #
- #
- #
- pass
- pass
- #
- pass
- #
-
- def test_test(self):
- ### and_test ('or' and_test)*
- ### and_test: not_test ('and' not_test)*
- ### not_test: 'not' not_test | comparison
- if not 1: pass
- if 1 and 1: pass
- if 1 or 1: pass
- if not not not 1: pass
- if not 1 and 1 and 1: pass
- if 1 and 1 or 1 and 1 and 1 or not 1 and 1: pass
-
- def test_comparison(self):
- ### comparison: expr (comp_op expr)*
- ### comp_op: '<'|'>'|'=='|'>='|'<='|'!='|'in'|'not' 'in'|'is'|'is' 'not'
- if 1: pass
- x = (1 == 1)
- if 1 == 1: pass
- if 1 != 1: pass
- if 1 < 1: pass
- if 1 > 1: pass
- if 1 <= 1: pass
- if 1 >= 1: pass
- if 1 is 1: pass
- if 1 is not 1: pass
- if 1 in (): pass
- if 1 not in (): pass
- if 1 < 1 > 1 == 1 >= 1 <= 1 != 1 in 1 not in 1 is 1 is not 1: pass
-
- def test_binary_mask_ops(self):
- x = 1 & 1
- x = 1 ^ 1
- x = 1 | 1
-
- def test_shift_ops(self):
- x = 1 << 1
- x = 1 >> 1
- x = 1 << 1 >> 1
-
- def test_additive_ops(self):
- x = 1
- x = 1 + 1
- x = 1 - 1 - 1
- x = 1 - 1 + 1 - 1 + 1
-
- def test_multiplicative_ops(self):
- x = 1 * 1
- x = 1 / 1
- x = 1 % 1
- x = 1 / 1 * 1 % 1
-
- def test_unary_ops(self):
- x = +1
- x = -1
- x = ~1
- x = ~1 ^ 1 & 1 | 1 & 1 ^ -1
- x = -1 * 1 / 1 + 1 * 1 - -1 * 1
-
- def test_selectors(self):
- ### trailer: '(' [testlist] ')' | '[' subscript ']' | '.' NAME
- ### subscript: expr | [expr] ':' [expr]
-
- import sys, time
- c = sys.path[0]
- x = time.time()
- x = sys.modules['time'].time()
- a = '01234'
- c = a[0]
- c = a[-1]
- s = a[0:5]
- s = a[:5]
- s = a[0:]
- s = a[:]
- s = a[-5:]
- s = a[:-1]
- s = a[-4:-3]
- # A rough test of SF bug 1333982. http://python.org/sf/1333982
- # The testing here is fairly incomplete.
- # Test cases should include: commas with 1 and 2 colons
- d = {}
- d[1] = 1
- d[1,] = 2
- d[1, 2] = 3
- d[1, 2, 3] = 4
- L = list(d)
- L.sort(key=lambda x: (type(x).__name__, x))
- self.assertEqual(str(L), '[1, (1,), (1, 2), (1, 2, 3)]')
-
- def test_atoms(self):
- ### atom: '(' [testlist] ')' | '[' [testlist] ']' | '{' [dictsetmaker] '}' | NAME | NUMBER | STRING
- ### dictsetmaker: (test ':' test (',' test ':' test)* [',']) | (test (',' test)* [','])
-
- x = (1)
- x = (1 or 2 or 3)
- x = (1 or 2 or 3, 2, 3)
-
- x = []
- x = [1]
- x = [1 or 2 or 3]
- x = [1 or 2 or 3, 2, 3]
- x = []
-
- x = {}
- x = {'one': 1}
- x = {'one': 1,}
- x = {'one' or 'two': 1 or 2}
- x = {'one': 1, 'two': 2}
- x = {'one': 1, 'two': 2,}
- x = {'one': 1, 'two': 2, 'three': 3, 'four': 4, 'five': 5, 'six': 6}
-
- x = {'one'}
- x = {'one', 1,}
- x = {'one', 'two', 'three'}
- x = {2, 3, 4,}
-
- x = x
- x = 'x'
- x = 123
-
- ### exprlist: expr (',' expr)* [',']
- ### testlist: test (',' test)* [',']
- # These have been exercised enough above
-
- def test_classdef(self):
- # 'class' NAME ['(' [testlist] ')'] ':' suite
- class B: pass
- class B2(): pass
- class C1(B): pass
- class C2(B): pass
- class D(C1, C2, B): pass
- class C:
- def meth1(self): pass
- def meth2(self, arg): pass
- def meth3(self, a1, a2): pass
-
- # decorator: '@' dotted_name [ '(' [arglist] ')' ] NEWLINE
- # decorators: decorator+
- # decorated: decorators (classdef | funcdef)
- def class_decorator(x): return x
- @class_decorator
- class G: pass
-
- def test_dictcomps(self):
- # dictorsetmaker: ( (test ':' test (comp_for |
- # (',' test ':' test)* [','])) |
- # (test (comp_for | (',' test)* [','])) )
- nums = [1, 2, 3]
- self.assertEqual({i: i + 1 for i in nums}, {1: 2, 2: 3, 3: 4})
-
- def test_listcomps(self):
- # list comprehension tests
- nums = [1, 2, 3, 4, 5]
- strs = ["Apple", "Banana", "Coconut"]
- spcs = [" Apple", " Banana ", "Coco nut "]
-
- self.assertEqual([s.strip() for s in spcs], ['Apple', 'Banana', 'Coco nut'])
- self.assertEqual([3 * x for x in nums], [3, 6, 9, 12, 15])
- self.assertEqual([x for x in nums if x > 2], [3, 4, 5])
- self.assertEqual([(i, s) for i in nums for s in strs],
- [(1, 'Apple'), (1, 'Banana'), (1, 'Coconut'),
- (2, 'Apple'), (2, 'Banana'), (2, 'Coconut'),
- (3, 'Apple'), (3, 'Banana'), (3, 'Coconut'),
- (4, 'Apple'), (4, 'Banana'), (4, 'Coconut'),
- (5, 'Apple'), (5, 'Banana'), (5, 'Coconut')])
- self.assertEqual([(i, s) for i in nums for s in [f for f in strs if "n" in f]],
- [(1, 'Banana'), (1, 'Coconut'), (2, 'Banana'), (2, 'Coconut'),
- (3, 'Banana'), (3, 'Coconut'), (4, 'Banana'), (4, 'Coconut'),
- (5, 'Banana'), (5, 'Coconut')])
- self.assertEqual([(lambda a:[a ** i for i in range(a + 1)])(j) for j in range(5)],
- [[1], [1, 1], [1, 2, 4], [1, 3, 9, 27], [1, 4, 16, 64, 256]])
-
- def test_in_func(l):
- return [0 < x < 3 for x in l if x > 2]
-
- self.assertEqual(test_in_func(nums), [False, False, False])
-
- def test_nested_front():
- self.assertEqual([[y for y in [x, x + 1]] for x in [1, 3, 5]],
- [[1, 2], [3, 4], [5, 6]])
-
- test_nested_front()
-
- check_syntax_error(self, "[i, s for i in nums for s in strs]")
- check_syntax_error(self, "[x if y]")
-
- suppliers = [
- (1, "Boeing"),
- (2, "Ford"),
- (3, "Macdonalds")
- ]
-
- parts = [
- (10, "Airliner"),
- (20, "Engine"),
- (30, "Cheeseburger")
- ]
-
- suppart = [
- (1, 10), (1, 20), (2, 20), (3, 30)
- ]
-
- x = [
- (sname, pname)
- for (sno, sname) in suppliers
- for (pno, pname) in parts
- for (sp_sno, sp_pno) in suppart
- if sno == sp_sno and pno == sp_pno
- ]
-
- self.assertEqual(x, [('Boeing', 'Airliner'), ('Boeing', 'Engine'), ('Ford', 'Engine'),
- ('Macdonalds', 'Cheeseburger')])
-
- def test_genexps(self):
- # generator expression tests
- g = ([x for x in range(10)] for x in range(1))
- self.assertEqual(next(g), [x for x in range(10)])
- try:
- next(g)
- self.fail('should produce StopIteration exception')
- except StopIteration:
- pass
-
- a = 1
- try:
- g = (a for d in a)
- next(g)
- self.fail('should produce TypeError')
- except TypeError:
- pass
-
- self.assertEqual(list((x, y) for x in 'abcd' for y in 'abcd'), [(x, y) for x in 'abcd' for y in 'abcd'])
- self.assertEqual(list((x, y) for x in 'ab' for y in 'xy'), [(x, y) for x in 'ab' for y in 'xy'])
-
- a = [x for x in range(10)]
- b = (x for x in (y for y in a))
- self.assertEqual(sum(b), sum([x for x in range(10)]))
-
- self.assertEqual(sum(x ** 2 for x in range(10)), sum([x ** 2 for x in range(10)]))
- self.assertEqual(sum(x * x for x in range(10) if x % 2), sum([x * x for x in range(10) if x % 2]))
- self.assertEqual(sum(x for x in (y for y in range(10))), sum([x for x in range(10)]))
- self.assertEqual(sum(x for x in (y for y in (z for z in range(10)))), sum([x for x in range(10)]))
- self.assertEqual(sum(x for x in [y for y in (z for z in range(10))]), sum([x for x in range(10)]))
- self.assertEqual(sum(x for x in (y for y in (z for z in range(10) if True)) if True), sum([x for x in range(10)]))
- self.assertEqual(sum(x for x in (y for y in (z for z in range(10) if True) if False) if True), 0)
- check_syntax_error(self, "foo(x for x in range(10), 100)")
- check_syntax_error(self, "foo(100, x for x in range(10))")
-
- def test_comprehension_specials(self):
- # test for outmost iterable precomputation
- x = 10; g = (i for i in range(x)); x = 5
- self.assertEqual(len(list(g)), 10)
-
- # This should hold, since we're only precomputing outmost iterable.
- x = 10; t = False; g = ((i, j) for i in range(x) if t for j in range(x))
- x = 5; t = True;
- self.assertEqual([(i, j) for i in range(10) for j in range(5)], list(g))
-
- # Grammar allows multiple adjacent 'if's in listcomps and genexps,
- # even though it's silly. Make sure it works (ifelse broke this.)
- self.assertEqual([x for x in range(10) if x % 2 if x % 3], [1, 5, 7])
- self.assertEqual(list(x for x in range(10) if x % 2 if x % 3), [1, 5, 7])
-
- # verify unpacking single element tuples in listcomp/genexp.
- self.assertEqual([x for x, in [(4,), (5,), (6,)]], [4, 5, 6])
- self.assertEqual(list(x for x, in [(7,), (8,), (9,)]), [7, 8, 9])
-
- def test_with_statement(self):
- class manager(object):
- def __enter__(self):
- return (1, 2)
- def __exit__(self, *args):
- pass
-
- with manager():
- pass
- with manager() as x:
- pass
- with manager() as (x, y):
- pass
- with manager(), manager():
- pass
- with manager() as x, manager() as y:
- pass
- with manager() as x, manager():
- pass
-
- def test_if_else_expr(self):
- # Test ifelse expressions in various cases
- def _checkeval(msg, ret):
- "helper to check that evaluation of expressions is done correctly"
- print(msg)
- return ret
-
- # the next line is not allowed anymore
- #self.assertEqual([ x() for x in lambda: True, lambda: False if x() ], [True])
- self.assertEqual([x() for x in (lambda:True, lambda:False) if x()], [True])
- self.assertEqual([x(False) for x in (lambda x:False if x else True, lambda x:True if x else False) if x(False)], [True])
- self.assertEqual((5 if 1 else _checkeval("check 1", 0)), 5)
- self.assertEqual((_checkeval("check 2", 0) if 0 else 5), 5)
- self.assertEqual((5 and 6 if 0 else 1), 1)
- self.assertEqual(((5 and 6) if 0 else 1), 1)
- self.assertEqual((5 and (6 if 1 else 1)), 6)
- self.assertEqual((0 or _checkeval("check 3", 2) if 0 else 3), 3)
- self.assertEqual((1 or _checkeval("check 4", 2) if 1 else _checkeval("check 5", 3)), 1)
- self.assertEqual((0 or 5 if 1 else _checkeval("check 6", 3)), 5)
- self.assertEqual((not 5 if 1 else 1), False)
- self.assertEqual((not 5 if 0 else 1), 1)
- self.assertEqual((6 + 1 if 1 else 2), 7)
- self.assertEqual((6 - 1 if 1 else 2), 5)
- self.assertEqual((6 * 2 if 1 else 4), 12)
- self.assertEqual((6 / 2 if 1 else 3), 3)
- self.assertEqual((6 < 4 if 0 else 2), 2)
-
- def test_paren_evaluation(self):
- self.assertEqual(16 // (4 // 2), 8)
- self.assertEqual((16 // 4) // 2, 2)
- self.assertEqual(16 // 4 // 2, 2)
- self.assertTrue(False is (2 is 3))
- self.assertFalse((False is 2) is 3)
- self.assertFalse(False is 2 is 3)
-
- def test_matrix_mul(self):
- # This is not intended to be a comprehensive test, rather just to be few
- # samples of the @ operator in test_grammar.py.
- class M:
- def __matmul__(self, o):
- return 4
- def __imatmul__(self, o):
- self.other = o
- return self
- m = M()
- self.assertEqual(m @ m, 4)
- m @= 42
- self.assertEqual(m.other, 42)
-
- def test_async_await(self):
- async def test():
- def sum():
- pass
- if 1:
- await someobj()
-
- self.assertEqual(test.__name__, 'test')
- self.assertTrue(bool(test.__code__.co_flags & inspect.CO_COROUTINE))
-
- def decorator(func):
- setattr(func, '_marked', True)
- return func
-
- @decorator
- async def test2():
- return 22
- self.assertTrue(test2._marked)
- self.assertEqual(test2.__name__, 'test2')
- self.assertTrue(bool(test2.__code__.co_flags & inspect.CO_COROUTINE))
-
- def test_async_for(self):
- class Done(Exception): pass
-
- class AIter:
- def __aiter__(self):
- return self
- async def __anext__(self):
- raise StopAsyncIteration
-
- async def foo():
- async for i in AIter():
- pass
- async for i, j in AIter():
- pass
- async for i in AIter():
- pass
- else:
- pass
- raise Done
-
- with self.assertRaises(Done):
- foo().send(None)
-
- def test_async_with(self):
- class Done(Exception): pass
-
- class manager:
- async def __aenter__(self):
- return (1, 2)
- async def __aexit__(self, *exc):
- return False
-
- async def foo():
- async with manager():
- pass
- async with manager() as x:
- pass
- async with manager() as (x, y):
- pass
- async with manager(), manager():
- pass
- async with manager() as x, manager() as y:
- pass
- async with manager() as x, manager():
- pass
- raise Done
-
- with self.assertRaises(Done):
- foo().send(None)
-
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/extensions/positron-python/src/test/pythonFiles/formatting/yapf.output b/extensions/positron-python/src/test/pythonFiles/formatting/yapf.output
deleted file mode 100644
index 43897b1753b..00000000000
--- a/extensions/positron-python/src/test/pythonFiles/formatting/yapf.output
+++ /dev/null
@@ -1,57 +0,0 @@
---- C:\GIT\issues\s p\vscode-python\src\test\pythonFiles\formatting\fileToformat.py (original)
-+++ C:\GIT\issues\s p\vscode-python\src\test\pythonFiles\formatting\fileToformat.py (reformatted)
-@@ -1,22 +1,40 @@
-+import math, sys
-
--import math, sys;
-
- def example1():
- ####This is a long comment. This should be wrapped to fit within 72 characters.
-- some_tuple=( 1,2, 3,'a' );
-- some_variable={'long':'Long code lines should be wrapped within 79 characters.',
-- 'other':[math.pi, 100,200,300,9876543210,'This is a long string that goes on'],
-- 'more':{'inner':'This whole logical line should be wrapped.',some_tuple:[1,
-- 20,300,40000,500000000,60000000000000000]}}
-+ some_tuple = (1, 2, 3, 'a')
-+ some_variable = {
-+ 'long':
-+ 'Long code lines should be wrapped within 79 characters.',
-+ 'other': [
-+ math.pi, 100, 200, 300, 9876543210,
-+ 'This is a long string that goes on'
-+ ],
-+ 'more': {
-+ 'inner': 'This whole logical line should be wrapped.',
-+ some_tuple: [1, 20, 300, 40000, 500000000, 60000000000000000]
-+ }
-+ }
- return (some_tuple, some_variable)
--def example2(): return {'has_key() is deprecated':True}.has_key({'f':2}.has_key(''));
--class Example3( object ):
-- def __init__ ( self, bar ):
-- #Comments should have a space after the hash.
-- if bar : bar+=1; bar=bar* bar ; return bar
-- else:
-- some_string = """
-+
-+
-+def example2():
-+ return {
-+ 'has_key() is deprecated': True
-+ }.has_key({'f': 2}.has_key(''))
-+
-+
-+class Example3(object):
-+ def __init__(self, bar):
-+ #Comments should have a space after the hash.
-+ if bar:
-+ bar += 1
-+ bar = bar * bar
-+ return bar
-+ else:
-+ some_string = """
- Indentation in multiline strings should not be touched.
- Only actual code should be reindented.
- """
-- return (sys.path, some_string)
-+ return (sys.path, some_string)
diff --git a/extensions/positron-python/src/test/pythonFiles/formatting/yapfFormatted.py b/extensions/positron-python/src/test/pythonFiles/formatting/yapfFormatted.py
deleted file mode 100644
index aa3b079379a..00000000000
--- a/extensions/positron-python/src/test/pythonFiles/formatting/yapfFormatted.py
+++ /dev/null
@@ -1,40 +0,0 @@
-import math, sys
-
-
-def example1():
- ####This is a long comment. This should be wrapped to fit within 72 characters.
- some_tuple = (1, 2, 3, 'a')
- some_variable = {
- 'long':
- 'Long code lines should be wrapped within 79 characters.',
- 'other': [
- math.pi, 100, 200, 300, 9876543210,
- 'This is a long string that goes on'
- ],
- 'more': {
- 'inner': 'This whole logical line should be wrapped.',
- some_tuple: [1, 20, 300, 40000, 500000000, 60000000000000000]
- }
- }
- return (some_tuple, some_variable)
-
-
-def example2():
- return {
- 'has_key() is deprecated': True
- }.has_key({'f': 2}.has_key(''))
-
-
-class Example3(object):
- def __init__(self, bar):
- #Comments should have a space after the hash.
- if bar:
- bar += 1
- bar = bar * bar
- return bar
- else:
- some_string = """
- Indentation in multiline strings should not be touched.
-Only actual code should be reindented.
-"""
- return (sys.path, some_string)
diff --git a/extensions/positron-python/src/test/pythonFiles/linting/cwd/.pylintrc b/extensions/positron-python/src/test/pythonFiles/linting/cwd/.pylintrc
deleted file mode 100644
index 8530187c095..00000000000
--- a/extensions/positron-python/src/test/pythonFiles/linting/cwd/.pylintrc
+++ /dev/null
@@ -1,2 +0,0 @@
-[MESSAGES CONTROL]
-disable=C0326,I0011,I0012,C0304,C0103,W0613,E0001,E1101
diff --git a/extensions/positron-python/src/test/pythonFiles/linting/file.py b/extensions/positron-python/src/test/pythonFiles/linting/file.py
deleted file mode 100644
index 7b625a76924..00000000000
--- a/extensions/positron-python/src/test/pythonFiles/linting/file.py
+++ /dev/null
@@ -1,87 +0,0 @@
-"""pylint option block-disable"""
-
-__revision__ = None
-
-class Foo(object):
- """block-disable test"""
-
- def __init__(self):
- pass
-
- def meth1(self, arg):
- """this issues a message"""
- print (self)
-
- def meth2(self, arg):
- """and this one not"""
- # pylint: disable=unused-argument
- print (self\
- + "foo")
-
- def meth3(self):
- """test one line disabling"""
- # no error
- print (self.bla) # pylint: disable=no-member
- # error
- print (self.blop)
-
- def meth4(self):
- """test re-enabling"""
- # pylint: disable=no-member
- # no error
- print (self.bla)
- print (self.blop)
- # pylint: enable=no-member
- # error
- print (self.blip)
-
- def meth5(self):
- """test IF sub-block re-enabling"""
- # pylint: disable=no-member
- # no error
- print (self.bla)
- if self.blop:
- # pylint: enable=no-member
- # error
- print (self.blip)
- else:
- # no error
- print (self.blip)
- # no error
- print (self.blip)
-
- def meth6(self):
- """test TRY/EXCEPT sub-block re-enabling"""
- # pylint: disable=no-member
- # no error
- print (self.bla)
- try:
- # pylint: enable=no-member
- # error
- print (self.blip)
- except UndefinedName: # pylint: disable=undefined-variable
- # no error
- print (self.blip)
- # no error
- print (self.blip)
-
- def meth7(self):
- """test one line block opening disabling"""
- if self.blop: # pylint: disable=no-member
- # error
- print (self.blip)
- else:
- # error
- print (self.blip)
- # error
- print (self.blip)
-
-
- def meth8(self):
- """test late disabling"""
- # error
- print (self.blip)
- # pylint: disable=no-member
- # no error
- print (self.bla)
- print (self.blop)
diff --git a/extensions/positron-python/src/test/pythonFiles/linting/flake8config/.flake8 b/extensions/positron-python/src/test/pythonFiles/linting/flake8config/.flake8
deleted file mode 100644
index 99ff2b9f819..00000000000
--- a/extensions/positron-python/src/test/pythonFiles/linting/flake8config/.flake8
+++ /dev/null
@@ -1,2 +0,0 @@
-[flake8]
-ignore = E302,E901,E127,E261,E261,E261,E303
\ No newline at end of file
diff --git a/extensions/positron-python/src/test/pythonFiles/linting/flake8config/file.py b/extensions/positron-python/src/test/pythonFiles/linting/flake8config/file.py
deleted file mode 100644
index 9abe4993ddd..00000000000
--- a/extensions/positron-python/src/test/pythonFiles/linting/flake8config/file.py
+++ /dev/null
@@ -1,86 +0,0 @@
-"""pylint option block-disable"""
-
-__revision__ = None
-
-
-class Foo(object):
- """block-disable test"""
-
- def __init__(self):
- pass
-
- def meth1(self, arg):
- """this issues a message"""
- print(self)
-
- def meth2(self, arg):
- """and this one not"""
- # pylint: disable=unused-argument
- print(self + "foo")
-
- def meth3(self):
- """test one line disabling"""
- # no error
- print(self.bla) # pylint: disable=no-member
- # error
- print(self.blop)
-
- def meth4(self):
- """test re-enabling"""
- # pylint: disable=no-member
- # no error
- print(self.bla)
- print(self.blop)
- # pylint: enable=no-member
- # error
- print(self.blip)
-
- def meth5(self):
- """test IF sub-block re-enabling"""
- # pylint: disable=no-member
- # no error
- print(self.bla)
- if self.blop:
- # pylint: enable=no-member
- # error
- print(self.blip)
- else:
- # no error
- print(self.blip)
- # no error
- print(self.blip)
-
- def meth6(self):
- """test TRY/EXCEPT sub-block re-enabling"""
- # pylint: disable=no-member
- # no error
- print(self.bla)
- try:
- # pylint: enable=no-member
- # error
- print(self.blip)
- except UndefinedName: # pylint: disable=undefined-variable
- # no error
- print(self.blip)
- # no error
- print(self.blip)
-
- def meth7(self):
- """test one line block opening disabling"""
- if self.blop: # pylint: disable=no-member
- # error
- print(self.blip)
- else:
- # error
- print(self.blip)
- # error
- print(self.blip)
-
- def meth8(self):
- """test late disabling"""
- # error
- print(self.blip)
- # pylint: disable=no-member
- # no error
- print(self.bla)
- print(self.blop)
diff --git a/extensions/positron-python/src/test/pythonFiles/linting/minCheck.py b/extensions/positron-python/src/test/pythonFiles/linting/minCheck.py
deleted file mode 100644
index d93fa56f7e8..00000000000
--- a/extensions/positron-python/src/test/pythonFiles/linting/minCheck.py
+++ /dev/null
@@ -1 +0,0 @@
-filter(lambda x: x == 1, [1, 1, 2])
diff --git a/extensions/positron-python/src/test/pythonFiles/linting/print.py b/extensions/positron-python/src/test/pythonFiles/linting/print.py
deleted file mode 100644
index fca61311fc8..00000000000
--- a/extensions/positron-python/src/test/pythonFiles/linting/print.py
+++ /dev/null
@@ -1 +0,0 @@
-print x
\ No newline at end of file
diff --git a/extensions/positron-python/src/test/pythonFiles/linting/pycodestyleconfig/.pycodestyle b/extensions/positron-python/src/test/pythonFiles/linting/pycodestyleconfig/.pycodestyle
deleted file mode 100644
index b7c78f49db8..00000000000
--- a/extensions/positron-python/src/test/pythonFiles/linting/pycodestyleconfig/.pycodestyle
+++ /dev/null
@@ -1,2 +0,0 @@
-[pycodestyle]
-ignore = E302,E901,E127,E261,E261,E261,E303
diff --git a/extensions/positron-python/src/test/pythonFiles/linting/pycodestyleconfig/file.py b/extensions/positron-python/src/test/pythonFiles/linting/pycodestyleconfig/file.py
deleted file mode 100644
index 047ba0dc679..00000000000
--- a/extensions/positron-python/src/test/pythonFiles/linting/pycodestyleconfig/file.py
+++ /dev/null
@@ -1,87 +0,0 @@
-"""pylint option block-disable"""
-
-__revision__ = None
-
-class Foo(object):
- """block-disable test"""
-
- def __init__(self):
- pass
-
- def meth1(self, arg):
- """this issues a message"""
- print self
-
- def meth2(self, arg):
- """and this one not"""
- # pylint: disable=unused-argument
- print self\
- + "foo"
-
- def meth3(self):
- """test one line disabling"""
- # no error
- print self.bla # pylint: disable=no-member
- # error
- print self.blop
-
- def meth4(self):
- """test re-enabling"""
- # pylint: disable=no-member
- # no error
- print self.bla
- print self.blop
- # pylint: enable=no-member
- # error
- print self.blip
-
- def meth5(self):
- """test IF sub-block re-enabling"""
- # pylint: disable=no-member
- # no error
- print self.bla
- if self.blop:
- # pylint: enable=no-member
- # error
- print self.blip
- else:
- # no error
- print self.blip
- # no error
- print self.blip
-
- def meth6(self):
- """test TRY/EXCEPT sub-block re-enabling"""
- # pylint: disable=no-member
- # no error
- print self.bla
- try:
- # pylint: enable=no-member
- # error
- print self.blip
- except UndefinedName: # pylint: disable=undefined-variable
- # no error
- print self.blip
- # no error
- print self.blip
-
- def meth7(self):
- """test one line block opening disabling"""
- if self.blop: # pylint: disable=no-member
- # error
- print self.blip
- else:
- # error
- print self.blip
- # error
- print self.blip
-
-
- def meth8(self):
- """test late disabling"""
- # error
- print self.blip
- # pylint: disable=no-member
- # no error
- print self.bla
- print self.blop
\ No newline at end of file
diff --git a/extensions/positron-python/src/test/pythonFiles/linting/pydocstyleconfig27/.pydocstyle b/extensions/positron-python/src/test/pythonFiles/linting/pydocstyleconfig27/.pydocstyle
deleted file mode 100644
index 19020834ad3..00000000000
--- a/extensions/positron-python/src/test/pythonFiles/linting/pydocstyleconfig27/.pydocstyle
+++ /dev/null
@@ -1,2 +0,0 @@
-[pydocstyle]
-ignore=D400,D401,D402,D403,D404,D203,D102,D107
diff --git a/extensions/positron-python/src/test/pythonFiles/linting/pydocstyleconfig27/file.py b/extensions/positron-python/src/test/pythonFiles/linting/pydocstyleconfig27/file.py
deleted file mode 100644
index 047ba0dc679..00000000000
--- a/extensions/positron-python/src/test/pythonFiles/linting/pydocstyleconfig27/file.py
+++ /dev/null
@@ -1,87 +0,0 @@
-"""pylint option block-disable"""
-
-__revision__ = None
-
-class Foo(object):
- """block-disable test"""
-
- def __init__(self):
- pass
-
- def meth1(self, arg):
- """this issues a message"""
- print self
-
- def meth2(self, arg):
- """and this one not"""
- # pylint: disable=unused-argument
- print self\
- + "foo"
-
- def meth3(self):
- """test one line disabling"""
- # no error
- print self.bla # pylint: disable=no-member
- # error
- print self.blop
-
- def meth4(self):
- """test re-enabling"""
- # pylint: disable=no-member
- # no error
- print self.bla
- print self.blop
- # pylint: enable=no-member
- # error
- print self.blip
-
- def meth5(self):
- """test IF sub-block re-enabling"""
- # pylint: disable=no-member
- # no error
- print self.bla
- if self.blop:
- # pylint: enable=no-member
- # error
- print self.blip
- else:
- # no error
- print self.blip
- # no error
- print self.blip
-
- def meth6(self):
- """test TRY/EXCEPT sub-block re-enabling"""
- # pylint: disable=no-member
- # no error
- print self.bla
- try:
- # pylint: enable=no-member
- # error
- print self.blip
- except UndefinedName: # pylint: disable=undefined-variable
- # no error
- print self.blip
- # no error
- print self.blip
-
- def meth7(self):
- """test one line block opening disabling"""
- if self.blop: # pylint: disable=no-member
- # error
- print self.blip
- else:
- # error
- print self.blip
- # error
- print self.blip
-
-
- def meth8(self):
- """test late disabling"""
- # error
- print self.blip
- # pylint: disable=no-member
- # no error
- print self.bla
- print self.blop
\ No newline at end of file
diff --git a/extensions/positron-python/src/test/pythonFiles/linting/pylintconfig/.pylintrc b/extensions/positron-python/src/test/pythonFiles/linting/pylintconfig/.pylintrc
deleted file mode 100644
index 74ea19101f8..00000000000
--- a/extensions/positron-python/src/test/pythonFiles/linting/pylintconfig/.pylintrc
+++ /dev/null
@@ -1,2 +0,0 @@
-[MESSAGES CONTROL]
-disable=I0011,C0304,C0103,W0613,E0001,E1101
diff --git a/extensions/positron-python/src/test/pythonFiles/linting/pylintconfig/file.py b/extensions/positron-python/src/test/pythonFiles/linting/pylintconfig/file.py
deleted file mode 100644
index 047ba0dc679..00000000000
--- a/extensions/positron-python/src/test/pythonFiles/linting/pylintconfig/file.py
+++ /dev/null
@@ -1,87 +0,0 @@
-"""pylint option block-disable"""
-
-__revision__ = None
-
-class Foo(object):
- """block-disable test"""
-
- def __init__(self):
- pass
-
- def meth1(self, arg):
- """this issues a message"""
- print self
-
- def meth2(self, arg):
- """and this one not"""
- # pylint: disable=unused-argument
- print self\
- + "foo"
-
- def meth3(self):
- """test one line disabling"""
- # no error
- print self.bla # pylint: disable=no-member
- # error
- print self.blop
-
- def meth4(self):
- """test re-enabling"""
- # pylint: disable=no-member
- # no error
- print self.bla
- print self.blop
- # pylint: enable=no-member
- # error
- print self.blip
-
- def meth5(self):
- """test IF sub-block re-enabling"""
- # pylint: disable=no-member
- # no error
- print self.bla
- if self.blop:
- # pylint: enable=no-member
- # error
- print self.blip
- else:
- # no error
- print self.blip
- # no error
- print self.blip
-
- def meth6(self):
- """test TRY/EXCEPT sub-block re-enabling"""
- # pylint: disable=no-member
- # no error
- print self.bla
- try:
- # pylint: enable=no-member
- # error
- print self.blip
- except UndefinedName: # pylint: disable=undefined-variable
- # no error
- print self.blip
- # no error
- print self.blip
-
- def meth7(self):
- """test one line block opening disabling"""
- if self.blop: # pylint: disable=no-member
- # error
- print self.blip
- else:
- # error
- print self.blip
- # error
- print self.blip
-
-
- def meth8(self):
- """test late disabling"""
- # error
- print self.blip
- # pylint: disable=no-member
- # no error
- print self.bla
- print self.blop
\ No newline at end of file
diff --git a/extensions/positron-python/src/test/pythonFiles/linting/pylintconfig/file2.py b/extensions/positron-python/src/test/pythonFiles/linting/pylintconfig/file2.py
deleted file mode 100644
index f375c984aa2..00000000000
--- a/extensions/positron-python/src/test/pythonFiles/linting/pylintconfig/file2.py
+++ /dev/null
@@ -1,19 +0,0 @@
-"""pylint option block-disable"""
-
-__revision__ = None
-
-class Foo(object):
- """block-disable test"""
-
- def __init__(self):
- pass
-
- def meth1(self, arg):
- """meth1"""
- print self.blop
-
- def meth2(self, arg):
- """meth2"""
- # pylint: disable=unused-argument
- print self\
- + "foo"
diff --git a/extensions/positron-python/src/test/pythonFiles/linting/threeLineLints.py b/extensions/positron-python/src/test/pythonFiles/linting/threeLineLints.py
deleted file mode 100644
index e8b578d93f1..00000000000
--- a/extensions/positron-python/src/test/pythonFiles/linting/threeLineLints.py
+++ /dev/null
@@ -1,24 +0,0 @@
-"""pylint messages with three lines of output"""
-
-__revision__ = None
-
-class Foo(object):
-
- def __init__(self):
- pass
-
- def meth1(self,arg):
- """missing a space between 'self' and 'arg'. This should trigger the
- following three line lint warning::
-
- C: 10, 0: Exactly one space required after comma
- def meth1(self,arg):
- ^ (bad-whitespace)
-
- The following three lines of tuples should also cause three-line lint
- errors due to "Exactly one space required after comma" messages.
- """
- a = (1,2)
- b = (1,2)
- c = (1,2)
- print (self)
diff --git a/extensions/positron-python/src/test/pythonFiles/refactoring/source folder/.vscode/.ropeproject/config.py b/extensions/positron-python/src/test/pythonFiles/refactoring/source folder/.vscode/.ropeproject/config.py
deleted file mode 100644
index dee2d1ae9a6..00000000000
--- a/extensions/positron-python/src/test/pythonFiles/refactoring/source folder/.vscode/.ropeproject/config.py
+++ /dev/null
@@ -1,114 +0,0 @@
-# The default ``config.py``
-# flake8: noqa
-
-
-def set_prefs(prefs):
- """This function is called before opening the project"""
-
- # Specify which files and folders to ignore in the project.
- # Changes to ignored resources are not added to the history and
- # VCSs. Also they are not returned in `Project.get_files()`.
- # Note that ``?`` and ``*`` match all characters but slashes.
- # '*.pyc': matches 'test.pyc' and 'pkg/test.pyc'
- # 'mod*.pyc': matches 'test/mod1.pyc' but not 'mod/1.pyc'
- # '.svn': matches 'pkg/.svn' and all of its children
- # 'build/*.o': matches 'build/lib.o' but not 'build/sub/lib.o'
- # 'build//*.o': matches 'build/lib.o' and 'build/sub/lib.o'
- prefs['ignored_resources'] = ['*.pyc', '*~', '.ropeproject',
- '.hg', '.svn', '_svn', '.git', '.tox']
-
- # Specifies which files should be considered python files. It is
- # useful when you have scripts inside your project. Only files
- # ending with ``.py`` are considered to be python files by
- # default.
- # prefs['python_files'] = ['*.py']
-
- # Custom source folders: By default rope searches the project
- # for finding source folders (folders that should be searched
- # for finding modules). You can add paths to that list. Note
- # that rope guesses project source folders correctly most of the
- # time; use this if you have any problems.
- # The folders should be relative to project root and use '/' for
- # separating folders regardless of the platform rope is running on.
- # 'src/my_source_folder' for instance.
- # prefs.add('source_folders', 'src')
-
- # You can extend python path for looking up modules
- # prefs.add('python_path', '~/python/')
-
- # Should rope save object information or not.
- prefs['save_objectdb'] = True
- prefs['compress_objectdb'] = False
-
- # If `True`, rope analyzes each module when it is being saved.
- prefs['automatic_soa'] = True
- # The depth of calls to follow in static object analysis
- prefs['soa_followed_calls'] = 0
-
- # If `False` when running modules or unit tests "dynamic object
- # analysis" is turned off. This makes them much faster.
- prefs['perform_doa'] = True
-
- # Rope can check the validity of its object DB when running.
- prefs['validate_objectdb'] = True
-
- # How many undos to hold?
- prefs['max_history_items'] = 32
-
- # Shows whether to save history across sessions.
- prefs['save_history'] = True
- prefs['compress_history'] = False
-
- # Set the number spaces used for indenting. According to
- # :PEP:`8`, it is best to use 4 spaces. Since most of rope's
- # unit-tests use 4 spaces it is more reliable, too.
- prefs['indent_size'] = 4
-
- # Builtin and c-extension modules that are allowed to be imported
- # and inspected by rope.
- prefs['extension_modules'] = []
-
- # Add all standard c-extensions to extension_modules list.
- prefs['import_dynload_stdmods'] = True
-
- # If `True` modules with syntax errors are considered to be empty.
- # The default value is `False`; When `False` syntax errors raise
- # `rope.base.exceptions.ModuleSyntaxError` exception.
- prefs['ignore_syntax_errors'] = False
-
- # If `True`, rope ignores unresolvable imports. Otherwise, they
- # appear in the importing namespace.
- prefs['ignore_bad_imports'] = False
-
- # If `True`, rope will insert new module imports as
- # `from import ` by default.
- prefs['prefer_module_from_imports'] = False
-
- # If `True`, rope will transform a comma list of imports into
- # multiple separate import statements when organizing
- # imports.
- prefs['split_imports'] = False
-
- # If `True`, rope will remove all top-level import statements and
- # reinsert them at the top of the module when making changes.
- prefs['pull_imports_to_top'] = True
-
- # If `True`, rope will sort imports alphabetically by module name instead
- # of alphabetically by import statement, with from imports after normal
- # imports.
- prefs['sort_imports_alphabetically'] = False
-
- # Location of implementation of
- # rope.base.oi.type_hinting.interfaces.ITypeHintingFactory In general
- # case, you don't have to change this value, unless you're an rope expert.
- # Change this value to inject you own implementations of interfaces
- # listed in module rope.base.oi.type_hinting.providers.interfaces
- # For example, you can add you own providers for Django Models, or disable
- # the search type-hinting in a class hierarchy, etc.
- prefs['type_hinting_factory'] = (
- 'rope.base.oi.type_hinting.factory.default_type_hinting_factory')
-
-
-def project_opened(project):
- """This function is called after opening the project"""
- # Do whatever you like here!
diff --git a/extensions/positron-python/src/test/pythonFiles/refactoring/source folder/.vscode/.ropeproject/objectdb b/extensions/positron-python/src/test/pythonFiles/refactoring/source folder/.vscode/.ropeproject/objectdb
deleted file mode 100644
index 0a47446c0ad..00000000000
Binary files a/extensions/positron-python/src/test/pythonFiles/refactoring/source folder/.vscode/.ropeproject/objectdb and /dev/null differ
diff --git a/extensions/positron-python/src/test/pythonFiles/sorting/noconfig/after.py b/extensions/positron-python/src/test/pythonFiles/sorting/noconfig/after.py
deleted file mode 100644
index b768c396014..00000000000
--- a/extensions/positron-python/src/test/pythonFiles/sorting/noconfig/after.py
+++ /dev/null
@@ -1,16 +0,0 @@
-import io; sys; json
-import traceback
-
-import rope
-import rope.base.project
-import rope.base.taskhandle
-from rope.base import libutils
-from rope.refactor.extract import ExtractMethod, ExtractVariable
-from rope.refactor.rename import Rename
-
-WORKSPACE_ROOT = sys.argv[1]
-ROPE_PROJECT_FOLDER = sys.argv[2]
-
-
-def test():
- pass
diff --git a/extensions/positron-python/src/test/pythonFiles/sorting/noconfig/before.py b/extensions/positron-python/src/test/pythonFiles/sorting/noconfig/before.py
deleted file mode 100644
index fcd7318b5c0..00000000000
--- a/extensions/positron-python/src/test/pythonFiles/sorting/noconfig/before.py
+++ /dev/null
@@ -1,18 +0,0 @@
-import io; sys; json
-import traceback
-import rope
-
-import rope.base.project
-import rope.base.taskhandle
-
-WORKSPACE_ROOT = sys.argv[1]
-ROPE_PROJECT_FOLDER = sys.argv[2]
-
-
-def test():
- pass
-
-from rope.base import libutils
-from rope.refactor.rename import Rename
-from rope.refactor.extract import ExtractMethod, ExtractVariable
-
\ No newline at end of file
diff --git a/extensions/positron-python/src/test/pythonFiles/sorting/noconfig/original.py b/extensions/positron-python/src/test/pythonFiles/sorting/noconfig/original.py
deleted file mode 100644
index fcd7318b5c0..00000000000
--- a/extensions/positron-python/src/test/pythonFiles/sorting/noconfig/original.py
+++ /dev/null
@@ -1,18 +0,0 @@
-import io; sys; json
-import traceback
-import rope
-
-import rope.base.project
-import rope.base.taskhandle
-
-WORKSPACE_ROOT = sys.argv[1]
-ROPE_PROJECT_FOLDER = sys.argv[2]
-
-
-def test():
- pass
-
-from rope.base import libutils
-from rope.refactor.rename import Rename
-from rope.refactor.extract import ExtractMethod, ExtractVariable
-
\ No newline at end of file
diff --git a/extensions/positron-python/src/test/pythonFiles/sorting/withconfig/.isort.cfg b/extensions/positron-python/src/test/pythonFiles/sorting/withconfig/.isort.cfg
deleted file mode 100644
index 68da732e2b4..00000000000
--- a/extensions/positron-python/src/test/pythonFiles/sorting/withconfig/.isort.cfg
+++ /dev/null
@@ -1,2 +0,0 @@
-[settings]
-force_single_line=True
\ No newline at end of file
diff --git a/extensions/positron-python/src/test/pythonFiles/sorting/withconfig/after.py b/extensions/positron-python/src/test/pythonFiles/sorting/withconfig/after.py
deleted file mode 100644
index e1fd315dbf9..00000000000
--- a/extensions/positron-python/src/test/pythonFiles/sorting/withconfig/after.py
+++ /dev/null
@@ -1,3 +0,0 @@
-from third_party import (lib1, lib2, lib3,
- lib4, lib5, lib6,
- lib7, lib8, lib9)
\ No newline at end of file
diff --git a/extensions/positron-python/src/test/pythonFiles/sorting/withconfig/before.1.py b/extensions/positron-python/src/test/pythonFiles/sorting/withconfig/before.1.py
deleted file mode 100644
index e1fd315dbf9..00000000000
--- a/extensions/positron-python/src/test/pythonFiles/sorting/withconfig/before.1.py
+++ /dev/null
@@ -1,3 +0,0 @@
-from third_party import (lib1, lib2, lib3,
- lib4, lib5, lib6,
- lib7, lib8, lib9)
\ No newline at end of file
diff --git a/extensions/positron-python/src/test/pythonFiles/sorting/withconfig/before.py b/extensions/positron-python/src/test/pythonFiles/sorting/withconfig/before.py
deleted file mode 100644
index e1fd315dbf9..00000000000
--- a/extensions/positron-python/src/test/pythonFiles/sorting/withconfig/before.py
+++ /dev/null
@@ -1,3 +0,0 @@
-from third_party import (lib1, lib2, lib3,
- lib4, lib5, lib6,
- lib7, lib8, lib9)
\ No newline at end of file
diff --git a/extensions/positron-python/src/test/pythonFiles/sorting/withconfig/original.1.py b/extensions/positron-python/src/test/pythonFiles/sorting/withconfig/original.1.py
deleted file mode 100644
index e1fd315dbf9..00000000000
--- a/extensions/positron-python/src/test/pythonFiles/sorting/withconfig/original.1.py
+++ /dev/null
@@ -1,3 +0,0 @@
-from third_party import (lib1, lib2, lib3,
- lib4, lib5, lib6,
- lib7, lib8, lib9)
\ No newline at end of file
diff --git a/extensions/positron-python/src/test/pythonFiles/sorting/withconfig/original.py b/extensions/positron-python/src/test/pythonFiles/sorting/withconfig/original.py
deleted file mode 100644
index e1fd315dbf9..00000000000
--- a/extensions/positron-python/src/test/pythonFiles/sorting/withconfig/original.py
+++ /dev/null
@@ -1,3 +0,0 @@
-from third_party import (lib1, lib2, lib3,
- lib4, lib5, lib6,
- lib7, lib8, lib9)
\ No newline at end of file
diff --git a/extensions/positron-python/src/test/pythonFiles/datascience/simple_nb.ipynb b/extensions/positron-python/src/test/python_files/datascience/simple_nb.ipynb
similarity index 100%
rename from extensions/positron-python/src/test/pythonFiles/datascience/simple_nb.ipynb
rename to extensions/positron-python/src/test/python_files/datascience/simple_nb.ipynb
diff --git a/extensions/positron-python/src/test/pythonFiles/datascience/simple_note_book.py b/extensions/positron-python/src/test/python_files/datascience/simple_note_book.py
similarity index 100%
rename from extensions/positron-python/src/test/pythonFiles/datascience/simple_note_book.py
rename to extensions/positron-python/src/test/python_files/datascience/simple_note_book.py
diff --git a/extensions/positron-python/src/test/pythonFiles/debugging/forever.py b/extensions/positron-python/src/test/python_files/debugging/forever.py
similarity index 100%
rename from extensions/positron-python/src/test/pythonFiles/debugging/forever.py
rename to extensions/positron-python/src/test/python_files/debugging/forever.py
diff --git a/extensions/positron-python/src/test/pythonFiles/debugging/logMessage.py b/extensions/positron-python/src/test/python_files/debugging/logMessage.py
similarity index 100%
rename from extensions/positron-python/src/test/pythonFiles/debugging/logMessage.py
rename to extensions/positron-python/src/test/python_files/debugging/logMessage.py
diff --git a/extensions/positron-python/src/test/pythonFiles/debugging/loopyTest.py b/extensions/positron-python/src/test/python_files/debugging/loopyTest.py
similarity index 100%
rename from extensions/positron-python/src/test/pythonFiles/debugging/loopyTest.py
rename to extensions/positron-python/src/test/python_files/debugging/loopyTest.py
diff --git a/extensions/positron-python/src/test/pythonFiles/debugging/multiThread.py b/extensions/positron-python/src/test/python_files/debugging/multiThread.py
similarity index 100%
rename from extensions/positron-python/src/test/pythonFiles/debugging/multiThread.py
rename to extensions/positron-python/src/test/python_files/debugging/multiThread.py
diff --git a/extensions/positron-python/src/test/pythonFiles/debugging/printSysArgv.py b/extensions/positron-python/src/test/python_files/debugging/printSysArgv.py
similarity index 100%
rename from extensions/positron-python/src/test/pythonFiles/debugging/printSysArgv.py
rename to extensions/positron-python/src/test/python_files/debugging/printSysArgv.py
diff --git a/extensions/positron-python/src/test/pythonFiles/debugging/sample2.py b/extensions/positron-python/src/test/python_files/debugging/sample2.py
similarity index 100%
rename from extensions/positron-python/src/test/pythonFiles/debugging/sample2.py
rename to extensions/positron-python/src/test/python_files/debugging/sample2.py
diff --git a/extensions/positron-python/src/test/pythonFiles/debugging/sample2WithoutSleep.py b/extensions/positron-python/src/test/python_files/debugging/sample2WithoutSleep.py
similarity index 100%
rename from extensions/positron-python/src/test/pythonFiles/debugging/sample2WithoutSleep.py
rename to extensions/positron-python/src/test/python_files/debugging/sample2WithoutSleep.py
diff --git a/extensions/positron-python/src/test/pythonFiles/debugging/sample3WithEx.py b/extensions/positron-python/src/test/python_files/debugging/sample3WithEx.py
similarity index 100%
rename from extensions/positron-python/src/test/pythonFiles/debugging/sample3WithEx.py
rename to extensions/positron-python/src/test/python_files/debugging/sample3WithEx.py
diff --git a/extensions/positron-python/src/test/pythonFiles/debugging/sampleWithAssertEx.py b/extensions/positron-python/src/test/python_files/debugging/sampleWithAssertEx.py
similarity index 100%
rename from extensions/positron-python/src/test/pythonFiles/debugging/sampleWithAssertEx.py
rename to extensions/positron-python/src/test/python_files/debugging/sampleWithAssertEx.py
diff --git a/extensions/positron-python/src/test/pythonFiles/debugging/sampleWithSleep.py b/extensions/positron-python/src/test/python_files/debugging/sampleWithSleep.py
similarity index 100%
rename from extensions/positron-python/src/test/pythonFiles/debugging/sampleWithSleep.py
rename to extensions/positron-python/src/test/python_files/debugging/sampleWithSleep.py
diff --git a/extensions/positron-python/src/test/pythonFiles/debugging/simplePrint.py b/extensions/positron-python/src/test/python_files/debugging/simplePrint.py
similarity index 100%
rename from extensions/positron-python/src/test/pythonFiles/debugging/simplePrint.py
rename to extensions/positron-python/src/test/python_files/debugging/simplePrint.py
diff --git a/extensions/positron-python/src/test/pythonFiles/debugging/stackFrame.py b/extensions/positron-python/src/test/python_files/debugging/stackFrame.py
similarity index 100%
rename from extensions/positron-python/src/test/pythonFiles/debugging/stackFrame.py
rename to extensions/positron-python/src/test/python_files/debugging/stackFrame.py
diff --git a/extensions/positron-python/src/test/pythonFiles/debugging/startAndWait.py b/extensions/positron-python/src/test/python_files/debugging/startAndWait.py
similarity index 100%
rename from extensions/positron-python/src/test/pythonFiles/debugging/startAndWait.py
rename to extensions/positron-python/src/test/python_files/debugging/startAndWait.py
diff --git a/extensions/positron-python/src/test/pythonFiles/debugging/stdErrOutput.py b/extensions/positron-python/src/test/python_files/debugging/stdErrOutput.py
similarity index 100%
rename from extensions/positron-python/src/test/pythonFiles/debugging/stdErrOutput.py
rename to extensions/positron-python/src/test/python_files/debugging/stdErrOutput.py
diff --git a/extensions/positron-python/src/test/pythonFiles/debugging/stdOutOutput.py b/extensions/positron-python/src/test/python_files/debugging/stdOutOutput.py
similarity index 100%
rename from extensions/positron-python/src/test/pythonFiles/debugging/stdOutOutput.py
rename to extensions/positron-python/src/test/python_files/debugging/stdOutOutput.py
diff --git a/extensions/positron-python/src/test/pythonFiles/debugging/wait_for_file.py b/extensions/positron-python/src/test/python_files/debugging/wait_for_file.py
similarity index 100%
rename from extensions/positron-python/src/test/pythonFiles/debugging/wait_for_file.py
rename to extensions/positron-python/src/test/python_files/debugging/wait_for_file.py
diff --git a/extensions/positron-python/src/test/pythonFiles/dummy.py b/extensions/positron-python/src/test/python_files/dummy.py
similarity index 100%
rename from extensions/positron-python/src/test/pythonFiles/dummy.py
rename to extensions/positron-python/src/test/python_files/dummy.py
diff --git a/extensions/positron-python/src/test/pythonFiles/environments/conda/Scripts/conda.exe b/extensions/positron-python/src/test/python_files/environments/conda/Scripts/conda.exe
similarity index 100%
rename from extensions/positron-python/src/test/pythonFiles/environments/conda/Scripts/conda.exe
rename to extensions/positron-python/src/test/python_files/environments/conda/Scripts/conda.exe
diff --git a/extensions/positron-python/src/test/pythonFiles/environments/conda/envs/scipy/python.exe b/extensions/positron-python/src/test/python_files/environments/conda/bin/python
similarity index 100%
rename from extensions/positron-python/src/test/pythonFiles/environments/conda/envs/scipy/python.exe
rename to extensions/positron-python/src/test/python_files/environments/conda/bin/python
diff --git a/extensions/positron-python/src/test/pythonFiles/environments/path1/one b/extensions/positron-python/src/test/python_files/environments/conda/envs/numpy/bin/python
similarity index 100%
rename from extensions/positron-python/src/test/pythonFiles/environments/path1/one
rename to extensions/positron-python/src/test/python_files/environments/conda/envs/numpy/bin/python
diff --git a/extensions/positron-python/src/test/pythonFiles/environments/path1/python.exe b/extensions/positron-python/src/test/python_files/environments/conda/envs/numpy/python.exe
similarity index 100%
rename from extensions/positron-python/src/test/pythonFiles/environments/path1/python.exe
rename to extensions/positron-python/src/test/python_files/environments/conda/envs/numpy/python.exe
diff --git a/extensions/positron-python/src/test/pythonFiles/environments/path1/one.exe b/extensions/positron-python/src/test/python_files/environments/conda/envs/scipy/bin/python
similarity index 100%
rename from extensions/positron-python/src/test/pythonFiles/environments/path1/one.exe
rename to extensions/positron-python/src/test/python_files/environments/conda/envs/scipy/bin/python
diff --git a/extensions/positron-python/src/test/pythonFiles/environments/path2/python.exe b/extensions/positron-python/src/test/python_files/environments/conda/envs/scipy/python.exe
similarity index 100%
rename from extensions/positron-python/src/test/pythonFiles/environments/path2/python.exe
rename to extensions/positron-python/src/test/python_files/environments/conda/envs/scipy/python.exe
diff --git a/extensions/positron-python/src/test/pythonFiles/environments/path2/one b/extensions/positron-python/src/test/python_files/environments/path1/one
similarity index 100%
rename from extensions/positron-python/src/test/pythonFiles/environments/path2/one
rename to extensions/positron-python/src/test/python_files/environments/path1/one
diff --git a/extensions/positron-python/src/test/pythonFiles/environments/path2/one.exe b/extensions/positron-python/src/test/python_files/environments/path1/one.exe
similarity index 100%
rename from extensions/positron-python/src/test/pythonFiles/environments/path2/one.exe
rename to extensions/positron-python/src/test/python_files/environments/path1/one.exe
diff --git a/extensions/positron-python/src/test/pythonFiles/tensorBoard/noMatch.py b/extensions/positron-python/src/test/python_files/environments/path1/python.exe
similarity index 100%
rename from extensions/positron-python/src/test/pythonFiles/tensorBoard/noMatch.py
rename to extensions/positron-python/src/test/python_files/environments/path1/python.exe
diff --git a/extensions/positron-python/src/test/python_files/environments/path2/one b/extensions/positron-python/src/test/python_files/environments/path2/one
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/extensions/positron-python/src/test/python_files/environments/path2/one.exe b/extensions/positron-python/src/test/python_files/environments/path2/one.exe
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/extensions/positron-python/src/test/python_files/environments/path2/python.exe b/extensions/positron-python/src/test/python_files/environments/path2/python.exe
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/extensions/positron-python/src/test/pythonFiles/intellisense/test.py b/extensions/positron-python/src/test/python_files/intellisense/test.py
similarity index 100%
rename from extensions/positron-python/src/test/pythonFiles/intellisense/test.py
rename to extensions/positron-python/src/test/python_files/intellisense/test.py
diff --git a/extensions/positron-python/src/test/pythonFiles/shebang/plain.py b/extensions/positron-python/src/test/python_files/shebang/plain.py
similarity index 100%
rename from extensions/positron-python/src/test/pythonFiles/shebang/plain.py
rename to extensions/positron-python/src/test/python_files/shebang/plain.py
diff --git a/extensions/positron-python/src/test/pythonFiles/shebang/shebang.py b/extensions/positron-python/src/test/python_files/shebang/shebang.py
similarity index 100%
rename from extensions/positron-python/src/test/pythonFiles/shebang/shebang.py
rename to extensions/positron-python/src/test/python_files/shebang/shebang.py
diff --git a/extensions/positron-python/src/test/pythonFiles/shebang/shebangEnv.py b/extensions/positron-python/src/test/python_files/shebang/shebangEnv.py
similarity index 100%
rename from extensions/positron-python/src/test/pythonFiles/shebang/shebangEnv.py
rename to extensions/positron-python/src/test/python_files/shebang/shebangEnv.py
diff --git a/extensions/positron-python/src/test/pythonFiles/shebang/shebangInvalid.py b/extensions/positron-python/src/test/python_files/shebang/shebangInvalid.py
similarity index 100%
rename from extensions/positron-python/src/test/pythonFiles/shebang/shebangInvalid.py
rename to extensions/positron-python/src/test/python_files/shebang/shebangInvalid.py
diff --git a/extensions/positron-python/src/test/python_files/tensorBoard/noMatch.py b/extensions/positron-python/src/test/python_files/tensorBoard/noMatch.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/extensions/positron-python/src/test/pythonFiles/tensorBoard/sourcefile.py b/extensions/positron-python/src/test/python_files/tensorBoard/sourcefile.py
similarity index 100%
rename from extensions/positron-python/src/test/pythonFiles/tensorBoard/sourcefile.py
rename to extensions/positron-python/src/test/python_files/tensorBoard/sourcefile.py
diff --git a/extensions/positron-python/src/test/pythonFiles/tensorBoard/tensorboard_import.ipynb b/extensions/positron-python/src/test/python_files/tensorBoard/tensorboard_import.ipynb
similarity index 100%
rename from extensions/positron-python/src/test/pythonFiles/tensorBoard/tensorboard_import.ipynb
rename to extensions/positron-python/src/test/python_files/tensorBoard/tensorboard_import.ipynb
diff --git a/extensions/positron-python/src/test/pythonFiles/tensorBoard/tensorboard_imports.py b/extensions/positron-python/src/test/python_files/tensorBoard/tensorboard_imports.py
similarity index 100%
rename from extensions/positron-python/src/test/pythonFiles/tensorBoard/tensorboard_imports.py
rename to extensions/positron-python/src/test/python_files/tensorBoard/tensorboard_imports.py
diff --git a/extensions/positron-python/src/test/pythonFiles/tensorBoard/tensorboard_launch.py b/extensions/positron-python/src/test/python_files/tensorBoard/tensorboard_launch.py
similarity index 100%
rename from extensions/positron-python/src/test/pythonFiles/tensorBoard/tensorboard_launch.py
rename to extensions/positron-python/src/test/python_files/tensorBoard/tensorboard_launch.py
diff --git a/extensions/positron-python/src/test/pythonFiles/tensorBoard/tensorboard_nbextension.ipynb b/extensions/positron-python/src/test/python_files/tensorBoard/tensorboard_nbextension.ipynb
similarity index 100%
rename from extensions/positron-python/src/test/pythonFiles/tensorBoard/tensorboard_nbextension.ipynb
rename to extensions/positron-python/src/test/python_files/tensorBoard/tensorboard_nbextension.ipynb
diff --git a/extensions/positron-python/src/test/pythonFiles/terminalExec/sample1_normalized.py b/extensions/positron-python/src/test/python_files/terminalExec/sample1_normalized.py
similarity index 100%
rename from extensions/positron-python/src/test/pythonFiles/terminalExec/sample1_normalized.py
rename to extensions/positron-python/src/test/python_files/terminalExec/sample1_normalized.py
diff --git a/extensions/positron-python/src/test/pythonFiles/terminalExec/sample1_normalized_selection.py b/extensions/positron-python/src/test/python_files/terminalExec/sample1_normalized_selection.py
similarity index 100%
rename from extensions/positron-python/src/test/pythonFiles/terminalExec/sample1_normalized_selection.py
rename to extensions/positron-python/src/test/python_files/terminalExec/sample1_normalized_selection.py
diff --git a/extensions/positron-python/src/test/pythonFiles/terminalExec/sample1_raw.py b/extensions/positron-python/src/test/python_files/terminalExec/sample1_raw.py
similarity index 100%
rename from extensions/positron-python/src/test/pythonFiles/terminalExec/sample1_raw.py
rename to extensions/positron-python/src/test/python_files/terminalExec/sample1_raw.py
diff --git a/extensions/positron-python/src/test/pythonFiles/terminalExec/sample2_normalized.py b/extensions/positron-python/src/test/python_files/terminalExec/sample2_normalized.py
similarity index 100%
rename from extensions/positron-python/src/test/pythonFiles/terminalExec/sample2_normalized.py
rename to extensions/positron-python/src/test/python_files/terminalExec/sample2_normalized.py
diff --git a/extensions/positron-python/src/test/pythonFiles/terminalExec/sample2_normalized_selection.py b/extensions/positron-python/src/test/python_files/terminalExec/sample2_normalized_selection.py
similarity index 100%
rename from extensions/positron-python/src/test/pythonFiles/terminalExec/sample2_normalized_selection.py
rename to extensions/positron-python/src/test/python_files/terminalExec/sample2_normalized_selection.py
diff --git a/extensions/positron-python/src/test/pythonFiles/terminalExec/sample2_raw.py b/extensions/positron-python/src/test/python_files/terminalExec/sample2_raw.py
similarity index 100%
rename from extensions/positron-python/src/test/pythonFiles/terminalExec/sample2_raw.py
rename to extensions/positron-python/src/test/python_files/terminalExec/sample2_raw.py
diff --git a/extensions/positron-python/src/test/pythonFiles/terminalExec/sample3_normalized.py b/extensions/positron-python/src/test/python_files/terminalExec/sample3_normalized.py
similarity index 100%
rename from extensions/positron-python/src/test/pythonFiles/terminalExec/sample3_normalized.py
rename to extensions/positron-python/src/test/python_files/terminalExec/sample3_normalized.py
diff --git a/extensions/positron-python/src/test/pythonFiles/terminalExec/sample3_normalized_selection.py b/extensions/positron-python/src/test/python_files/terminalExec/sample3_normalized_selection.py
similarity index 100%
rename from extensions/positron-python/src/test/pythonFiles/terminalExec/sample3_normalized_selection.py
rename to extensions/positron-python/src/test/python_files/terminalExec/sample3_normalized_selection.py
diff --git a/extensions/positron-python/src/test/pythonFiles/terminalExec/sample3_raw.py b/extensions/positron-python/src/test/python_files/terminalExec/sample3_raw.py
similarity index 100%
rename from extensions/positron-python/src/test/pythonFiles/terminalExec/sample3_raw.py
rename to extensions/positron-python/src/test/python_files/terminalExec/sample3_raw.py
diff --git a/extensions/positron-python/src/test/pythonFiles/terminalExec/sample4_normalized.py b/extensions/positron-python/src/test/python_files/terminalExec/sample4_normalized.py
similarity index 100%
rename from extensions/positron-python/src/test/pythonFiles/terminalExec/sample4_normalized.py
rename to extensions/positron-python/src/test/python_files/terminalExec/sample4_normalized.py
diff --git a/extensions/positron-python/src/test/pythonFiles/terminalExec/sample4_normalized_selection.py b/extensions/positron-python/src/test/python_files/terminalExec/sample4_normalized_selection.py
similarity index 100%
rename from extensions/positron-python/src/test/pythonFiles/terminalExec/sample4_normalized_selection.py
rename to extensions/positron-python/src/test/python_files/terminalExec/sample4_normalized_selection.py
diff --git a/extensions/positron-python/src/test/pythonFiles/terminalExec/sample4_raw.py b/extensions/positron-python/src/test/python_files/terminalExec/sample4_raw.py
similarity index 100%
rename from extensions/positron-python/src/test/pythonFiles/terminalExec/sample4_raw.py
rename to extensions/positron-python/src/test/python_files/terminalExec/sample4_raw.py
diff --git a/extensions/positron-python/src/test/pythonFiles/terminalExec/sample5_normalized.py b/extensions/positron-python/src/test/python_files/terminalExec/sample5_normalized.py
similarity index 100%
rename from extensions/positron-python/src/test/pythonFiles/terminalExec/sample5_normalized.py
rename to extensions/positron-python/src/test/python_files/terminalExec/sample5_normalized.py
diff --git a/extensions/positron-python/src/test/pythonFiles/terminalExec/sample5_normalized_selection.py b/extensions/positron-python/src/test/python_files/terminalExec/sample5_normalized_selection.py
similarity index 100%
rename from extensions/positron-python/src/test/pythonFiles/terminalExec/sample5_normalized_selection.py
rename to extensions/positron-python/src/test/python_files/terminalExec/sample5_normalized_selection.py
diff --git a/extensions/positron-python/src/test/pythonFiles/terminalExec/sample5_raw.py b/extensions/positron-python/src/test/python_files/terminalExec/sample5_raw.py
similarity index 100%
rename from extensions/positron-python/src/test/pythonFiles/terminalExec/sample5_raw.py
rename to extensions/positron-python/src/test/python_files/terminalExec/sample5_raw.py
diff --git a/extensions/positron-python/src/test/pythonFiles/terminalExec/sample6_normalized.py b/extensions/positron-python/src/test/python_files/terminalExec/sample6_normalized.py
similarity index 100%
rename from extensions/positron-python/src/test/pythonFiles/terminalExec/sample6_normalized.py
rename to extensions/positron-python/src/test/python_files/terminalExec/sample6_normalized.py
diff --git a/extensions/positron-python/src/test/pythonFiles/terminalExec/sample6_normalized_selection.py b/extensions/positron-python/src/test/python_files/terminalExec/sample6_normalized_selection.py
similarity index 100%
rename from extensions/positron-python/src/test/pythonFiles/terminalExec/sample6_normalized_selection.py
rename to extensions/positron-python/src/test/python_files/terminalExec/sample6_normalized_selection.py
diff --git a/extensions/positron-python/src/test/pythonFiles/terminalExec/sample6_raw.py b/extensions/positron-python/src/test/python_files/terminalExec/sample6_raw.py
similarity index 100%
rename from extensions/positron-python/src/test/pythonFiles/terminalExec/sample6_raw.py
rename to extensions/positron-python/src/test/python_files/terminalExec/sample6_raw.py
diff --git a/extensions/positron-python/src/test/pythonFiles/terminalExec/sample7_normalized.py b/extensions/positron-python/src/test/python_files/terminalExec/sample7_normalized.py
similarity index 100%
rename from extensions/positron-python/src/test/pythonFiles/terminalExec/sample7_normalized.py
rename to extensions/positron-python/src/test/python_files/terminalExec/sample7_normalized.py
diff --git a/extensions/positron-python/src/test/pythonFiles/terminalExec/sample7_normalized_selection.py b/extensions/positron-python/src/test/python_files/terminalExec/sample7_normalized_selection.py
similarity index 100%
rename from extensions/positron-python/src/test/pythonFiles/terminalExec/sample7_normalized_selection.py
rename to extensions/positron-python/src/test/python_files/terminalExec/sample7_normalized_selection.py
diff --git a/extensions/positron-python/src/test/pythonFiles/terminalExec/sample7_raw.py b/extensions/positron-python/src/test/python_files/terminalExec/sample7_raw.py
similarity index 100%
rename from extensions/positron-python/src/test/pythonFiles/terminalExec/sample7_raw.py
rename to extensions/positron-python/src/test/python_files/terminalExec/sample7_raw.py
diff --git a/extensions/positron-python/src/test/pythonFiles/terminalExec/sample8_normalized.py b/extensions/positron-python/src/test/python_files/terminalExec/sample8_normalized.py
similarity index 100%
rename from extensions/positron-python/src/test/pythonFiles/terminalExec/sample8_normalized.py
rename to extensions/positron-python/src/test/python_files/terminalExec/sample8_normalized.py
diff --git a/extensions/positron-python/src/test/pythonFiles/terminalExec/sample8_normalized_selection.py b/extensions/positron-python/src/test/python_files/terminalExec/sample8_normalized_selection.py
similarity index 100%
rename from extensions/positron-python/src/test/pythonFiles/terminalExec/sample8_normalized_selection.py
rename to extensions/positron-python/src/test/python_files/terminalExec/sample8_normalized_selection.py
diff --git a/extensions/positron-python/src/test/pythonFiles/terminalExec/sample8_raw.py b/extensions/positron-python/src/test/python_files/terminalExec/sample8_raw.py
similarity index 100%
rename from extensions/positron-python/src/test/pythonFiles/terminalExec/sample8_raw.py
rename to extensions/positron-python/src/test/python_files/terminalExec/sample8_raw.py
diff --git a/extensions/positron-python/src/test/pythonFiles/terminalExec/sample_invalid_smart_selection.py b/extensions/positron-python/src/test/python_files/terminalExec/sample_invalid_smart_selection.py
similarity index 100%
rename from extensions/positron-python/src/test/pythonFiles/terminalExec/sample_invalid_smart_selection.py
rename to extensions/positron-python/src/test/python_files/terminalExec/sample_invalid_smart_selection.py
diff --git a/extensions/positron-python/src/test/pythonFiles/terminalExec/sample_normalized.py b/extensions/positron-python/src/test/python_files/terminalExec/sample_normalized.py
similarity index 100%
rename from extensions/positron-python/src/test/pythonFiles/terminalExec/sample_normalized.py
rename to extensions/positron-python/src/test/python_files/terminalExec/sample_normalized.py
diff --git a/extensions/positron-python/src/test/pythonFiles/terminalExec/sample_normalized_selection.py b/extensions/positron-python/src/test/python_files/terminalExec/sample_normalized_selection.py
similarity index 100%
rename from extensions/positron-python/src/test/pythonFiles/terminalExec/sample_normalized_selection.py
rename to extensions/positron-python/src/test/python_files/terminalExec/sample_normalized_selection.py
diff --git a/extensions/positron-python/src/test/pythonFiles/terminalExec/sample_raw.py b/extensions/positron-python/src/test/python_files/terminalExec/sample_raw.py
similarity index 100%
rename from extensions/positron-python/src/test/pythonFiles/terminalExec/sample_raw.py
rename to extensions/positron-python/src/test/python_files/terminalExec/sample_raw.py
diff --git a/extensions/positron-python/src/test/pythonFiles/terminalExec/sample_smart_selection.py b/extensions/positron-python/src/test/python_files/terminalExec/sample_smart_selection.py
similarity index 100%
rename from extensions/positron-python/src/test/pythonFiles/terminalExec/sample_smart_selection.py
rename to extensions/positron-python/src/test/python_files/terminalExec/sample_smart_selection.py
diff --git a/extensions/positron-python/src/test/smoke/datascience.smoke.test.ts b/extensions/positron-python/src/test/smoke/datascience.smoke.test.ts
index ebd101b5849..30298183c51 100644
--- a/extensions/positron-python/src/test/smoke/datascience.smoke.test.ts
+++ b/extensions/positron-python/src/test/smoke/datascience.smoke.test.ts
@@ -35,7 +35,7 @@ suite('Smoke Test: Datascience', () => {
EXTENSION_ROOT_DIR_FOR_TESTS,
'src',
'test',
- 'pythonFiles',
+ 'python_files',
'datascience',
'simple_note_book.py',
);
@@ -60,7 +60,7 @@ suite('Smoke Test: Datascience', () => {
EXTENSION_ROOT_DIR_FOR_TESTS,
'src',
'test',
- 'pythonFiles',
+ 'python_files',
'datascience',
'simple_nb.ipynb',
);
diff --git a/extensions/positron-python/src/test/smoke/jedilsp.smoke.test.ts b/extensions/positron-python/src/test/smoke/jedilsp.smoke.test.ts
index acde436442e..e25d2bcdd1c 100644
--- a/extensions/positron-python/src/test/smoke/jedilsp.smoke.test.ts
+++ b/extensions/positron-python/src/test/smoke/jedilsp.smoke.test.ts
@@ -24,7 +24,7 @@ suite('Smoke Test: Jedi LSP', () => {
teardown(closeActiveWindows);
test('Verify diagnostics on a python file', async () => {
- const file = path.join(EXTENSION_ROOT_DIR_FOR_TESTS, 'src', 'test', 'pythonFiles', 'intellisense', 'test.py');
+ const file = path.join(EXTENSION_ROOT_DIR_FOR_TESTS, 'src', 'test', 'python_files', 'intellisense', 'test.py');
const outputFile = path.join(path.dirname(file), 'ds.log');
if (await fs.pathExists(outputFile)) {
await fs.unlink(outputFile);
diff --git a/extensions/positron-python/src/test/telemetry/index.unit.test.ts b/extensions/positron-python/src/test/telemetry/index.unit.test.ts
index d369dce2785..e155ac0a209 100644
--- a/extensions/positron-python/src/test/telemetry/index.unit.test.ts
+++ b/extensions/positron-python/src/test/telemetry/index.unit.test.ts
@@ -10,7 +10,6 @@ import * as fs from 'fs-extra';
import {
_resetSharedProperties,
clearTelemetryReporter,
- isTelemetryDisabled,
sendTelemetryEvent,
setSharedProperty,
} from '../../client/telemetry';
@@ -60,29 +59,6 @@ suite('Telemetry', () => {
sinon.restore();
});
- const testsForisTelemetryDisabled = [
- {
- testName: 'Returns true',
- settings: { enableTelemetry: true },
- expectedResult: false,
- },
- {
- testName: 'Returns false ',
- settings: { enableTelemetry: false },
- expectedResult: true,
- },
- ];
-
- suite('Function isTelemetryDisabled()', () => {
- testsForisTelemetryDisabled.forEach((testParams) => {
- test(testParams.testName, async () => {
- readJSONSyncStub.returns(testParams.settings);
- expect(isTelemetryDisabled()).to.equal(testParams.expectedResult);
- sinon.assert.calledOnce(readJSONSyncStub);
- });
- });
- });
-
test('Send Telemetry', () => {
rewiremock.enable();
rewiremock('@vscode/extension-telemetry').with({ default: Reporter });
diff --git a/extensions/positron-python/src/test/tensorBoard/tensorBoardSession.test.ts b/extensions/positron-python/src/test/tensorBoard/tensorBoardSession.test.ts
index 2faaf8e3961..626740f4f53 100644
--- a/extensions/positron-python/src/test/tensorBoard/tensorBoardSession.test.ts
+++ b/extensions/positron-python/src/test/tensorBoard/tensorBoardSession.test.ts
@@ -461,7 +461,7 @@ suite('TensorBoard session creation', async () => {
EXTENSION_ROOT_DIR_FOR_TESTS,
'src',
'test',
- 'pythonFiles',
+ 'python_files',
'tensorBoard',
'sourcefile.py',
);
diff --git a/extensions/positron-python/src/test/terminals/codeExecution/helper.test.ts b/extensions/positron-python/src/test/terminals/codeExecution/helper.test.ts
index ac47037a234..2ea00e77c92 100644
--- a/extensions/positron-python/src/test/terminals/codeExecution/helper.test.ts
+++ b/extensions/positron-python/src/test/terminals/codeExecution/helper.test.ts
@@ -10,6 +10,7 @@ import { SemVer } from 'semver';
import * as TypeMoq from 'typemoq';
import { Position, Range, Selection, TextDocument, TextEditor, TextLine, Uri } from 'vscode';
import {
+ IActiveResourceService,
IApplicationShell,
ICommandManager,
IDocumentManager,
@@ -23,6 +24,7 @@ import {
IProcessServiceFactory,
ObservableExecutionResult,
} from '../../../client/common/process/types';
+import { IConfigurationService, IPythonSettings } from '../../../client/common/types';
import { Architecture } from '../../../client/common/utils/platform';
import { IEnvironmentVariablesProvider } from '../../../client/common/variables/types';
import { IInterpreterService } from '../../../client/interpreter/contracts';
@@ -32,9 +34,10 @@ import { CodeExecutionHelper } from '../../../client/terminals/codeExecution/hel
import { ICodeExecutionHelper } from '../../../client/terminals/types';
import { PYTHON_PATH } from '../../common';
-const TEST_FILES_PATH = path.join(EXTENSION_ROOT_DIR, 'src', 'test', 'pythonFiles', 'terminalExec');
+const TEST_FILES_PATH = path.join(EXTENSION_ROOT_DIR, 'src', 'test', 'python_files', 'terminalExec');
suite('Terminal - Code Execution Helper', () => {
+ let activeResourceService: TypeMoq.IMock;
let documentManager: TypeMoq.IMock;
let applicationShell: TypeMoq.IMock;
let helper: ICodeExecutionHelper;
@@ -44,6 +47,8 @@ suite('Terminal - Code Execution Helper', () => {
let interpreterService: TypeMoq.IMock;
let commandManager: TypeMoq.IMock;
let workspaceService: TypeMoq.IMock;
+ let configurationService: TypeMoq.IMock;
+ let pythonSettings: TypeMoq.IMock;
const workingPython: PythonEnvironment = {
path: PYTHON_PATH,
version: new SemVer('3.6.6-final'),
@@ -57,13 +62,16 @@ suite('Terminal - Code Execution Helper', () => {
setup(() => {
const serviceContainer = TypeMoq.Mock.ofType();
commandManager = TypeMoq.Mock.ofType();
+ configurationService = TypeMoq.Mock.ofType();
workspaceService = TypeMoq.Mock.ofType();
documentManager = TypeMoq.Mock.ofType();
applicationShell = TypeMoq.Mock.ofType();
const envVariablesProvider = TypeMoq.Mock.ofType();
processService = TypeMoq.Mock.ofType();
interpreterService = TypeMoq.Mock.ofType();
-
+ activeResourceService = TypeMoq.Mock.ofType();
+ pythonSettings = TypeMoq.Mock.ofType();
+ const resource = Uri.parse('a');
// eslint-disable-next-line @typescript-eslint/no-explicit-any
processService.setup((x: any) => x.then).returns(() => undefined);
interpreterService
@@ -95,6 +103,24 @@ suite('Terminal - Code Execution Helper', () => {
serviceContainer
.setup((c) => c.get(TypeMoq.It.isValue(IEnvironmentVariablesProvider), TypeMoq.It.isAny()))
.returns(() => envVariablesProvider.object);
+ serviceContainer
+ .setup((c) => c.get(TypeMoq.It.isValue(IConfigurationService)))
+ .returns(() => configurationService.object);
+ serviceContainer
+ .setup((c) => c.get(TypeMoq.It.isValue(IActiveResourceService)))
+ .returns(() => activeResourceService.object);
+ activeResourceService.setup((a) => a.getActiveResource()).returns(() => resource);
+ pythonSettings.setup((s) => s.REPL).returns(() => ({ enableREPLSmartSend: false, REPLSmartSend: false }));
+ configurationService.setup((x) => x.getSettings(TypeMoq.It.isAny())).returns(() => pythonSettings.object);
+ configurationService
+ .setup((c) => c.getSettings(TypeMoq.It.isAny()))
+ .returns({
+ REPL: {
+ EnableREPLSmartSend: false,
+ REPLSmartSend: false,
+ },
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ } as any);
helper = new CodeExecutionHelper(serviceContainer.object);
document = TypeMoq.Mock.ofType();
@@ -118,6 +144,15 @@ suite('Terminal - Code Execution Helper', () => {
});
async function ensureCodeIsNormalized(source: string, expectedSource: string) {
+ configurationService
+ .setup((c) => c.getSettings(TypeMoq.It.isAny()))
+ .returns({
+ REPL: {
+ EnableREPLSmartSend: false,
+ REPLSmartSend: false,
+ },
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ } as any);
const actualProcessService = new ProcessService();
processService
.setup((p) => p.execObservable(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()))
@@ -131,6 +166,15 @@ suite('Terminal - Code Execution Helper', () => {
['', '1', '2', '3', '4', '5', '6', '7', '8'].forEach((fileNameSuffix) => {
test(`Ensure code is normalized (Sample${fileNameSuffix})`, async () => {
+ configurationService
+ .setup((c) => c.getSettings(TypeMoq.It.isAny()))
+ .returns({
+ REPL: {
+ EnableREPLSmartSend: false,
+ REPLSmartSend: false,
+ },
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ } as any);
const code = await fs.readFile(path.join(TEST_FILES_PATH, `sample${fileNameSuffix}_raw.py`), 'utf8');
const expectedCode = await fs.readFile(
path.join(TEST_FILES_PATH, `sample${fileNameSuffix}_normalized_selection.py`),
diff --git a/extensions/positron-python/src/test/terminals/codeExecution/smartSend.test.ts b/extensions/positron-python/src/test/terminals/codeExecution/smartSend.test.ts
index 4963629d0fd..f93df2ac11e 100644
--- a/extensions/positron-python/src/test/terminals/codeExecution/smartSend.test.ts
+++ b/extensions/positron-python/src/test/terminals/codeExecution/smartSend.test.ts
@@ -16,7 +16,6 @@ import { IConfigurationService, IExperimentService, IPythonSettings } from '../.
import { CodeExecutionHelper } from '../../../client/terminals/codeExecution/helper';
import { IServiceContainer } from '../../../client/ioc/types';
import { ICodeExecutionHelper } from '../../../client/terminals/types';
-import { EnableREPLSmartSend } from '../../../client/common/experiments/groups';
import { Commands, EXTENSION_ROOT_DIR } from '../../../client/common/constants';
import { EnvironmentType, PythonEnvironment } from '../../../client/pythonEnvironments/info';
import { PYTHON_PATH } from '../../common';
@@ -24,7 +23,7 @@ import { Architecture } from '../../../client/common/utils/platform';
import { ProcessService } from '../../../client/common/process/proc';
import { l10n } from '../../mocks/vsc';
-const TEST_FILES_PATH = path.join(EXTENSION_ROOT_DIR, 'src', 'test', 'pythonFiles', 'terminalExec');
+const TEST_FILES_PATH = path.join(EXTENSION_ROOT_DIR, 'src', 'test', 'python_files', 'terminalExec');
suite('REPL - Smart Send', () => {
let documentManager: TypeMoq.IMock;
@@ -117,10 +116,6 @@ suite('REPL - Smart Send', () => {
});
test('Cursor is not moved when explicit selection is present', async () => {
- experimentService
- .setup((exp) => exp.inExperimentSync(TypeMoq.It.isValue(EnableREPLSmartSend.experiment)))
- .returns(() => true);
-
const activeEditor = TypeMoq.Mock.ofType();
const firstIndexPosition = new Position(0, 0);
const selection = TypeMoq.Mock.ofType();
@@ -164,15 +159,10 @@ suite('REPL - Smart Send', () => {
});
test('Smart send should perform smart selection and move cursor', async () => {
- experimentService
- .setup((exp) => exp.inExperimentSync(TypeMoq.It.isValue(EnableREPLSmartSend.experiment)))
- .returns(() => true);
-
configurationService
.setup((c) => c.getSettings(TypeMoq.It.isAny()))
.returns({
REPL: {
- EnableREPLSmartSend: true,
REPLSmartSend: true,
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -228,9 +218,6 @@ suite('REPL - Smart Send', () => {
// Do not perform smart selection when there is explicit selection
test('Smart send should not perform smart selection when there is explicit selection', async () => {
- experimentService
- .setup((exp) => exp.inExperimentSync(TypeMoq.It.isValue(EnableREPLSmartSend.experiment)))
- .returns(() => true);
const activeEditor = TypeMoq.Mock.ofType();
const firstIndexPosition = new Position(0, 0);
const selection = TypeMoq.Mock.ofType();
@@ -257,15 +244,10 @@ suite('REPL - Smart Send', () => {
});
test('Smart Send should provide warning when code is not valid', async () => {
- experimentService
- .setup((exp) => exp.inExperimentSync(TypeMoq.It.isValue(EnableREPLSmartSend.experiment)))
- .returns(() => true);
-
configurationService
.setup((c) => c.getSettings(TypeMoq.It.isAny()))
.returns({
REPL: {
- EnableREPLSmartSend: true,
REPLSmartSend: true,
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
diff --git a/extensions/positron-python/src/test/testing/common/debugLauncher.unit.test.ts b/extensions/positron-python/src/test/testing/common/debugLauncher.unit.test.ts
index 1c69bac2059..ef3e678c13f 100644
--- a/extensions/positron-python/src/test/testing/common/debugLauncher.unit.test.ts
+++ b/extensions/positron-python/src/test/testing/common/debugLauncher.unit.test.ts
@@ -151,10 +151,10 @@ suite('Unit Tests - Debug Launcher', () => {
if (!pythonTestAdapterRewriteExperiment) {
switch (testProvider) {
case 'unittest': {
- return path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'visualstudio_py_testlauncher.py');
+ return path.join(EXTENSION_ROOT_DIR, 'python_files', 'visualstudio_py_testlauncher.py');
}
case 'pytest': {
- return path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'testlauncher.py');
+ return path.join(EXTENSION_ROOT_DIR, 'python_files', 'testlauncher.py');
}
default: {
throw new Error(`Unknown test provider '${testProvider}'`);
@@ -208,14 +208,14 @@ suite('Unit Tests - Debug Launcher', () => {
if (!expected) {
expected = getDefaultDebugConfig();
}
- expected.rules = [{ path: path.join(EXTENSION_ROOT_DIR, 'pythonFiles'), include: false }];
+ expected.rules = [{ path: path.join(EXTENSION_ROOT_DIR, 'python_files'), include: false }];
expected.program = testLaunchScript;
expected.args = options.args;
if (!expected.cwd) {
expected.cwd = workspaceFolders[0].uri.fsPath;
}
- const pluginPath = path.join(EXTENSION_ROOT_DIR, 'pythonFiles');
+ const pluginPath = path.join(EXTENSION_ROOT_DIR, 'python_files');
const pythonPath = `${pluginPath}${path.delimiter}${expected.cwd}`;
expected.env.PYTHONPATH = pythonPath;
@@ -345,7 +345,7 @@ suite('Unit Tests - Debug Launcher', () => {
};
const expected = getDefaultDebugConfig();
expected.cwd = 'path/to/settings/cwd';
- const pluginPath = path.join(EXTENSION_ROOT_DIR, 'pythonFiles');
+ const pluginPath = path.join(EXTENSION_ROOT_DIR, 'python_files');
const pythonPath = `${pluginPath}${path.delimiter}${expected.cwd}`;
expected.env.PYTHONPATH = pythonPath;
diff --git a/extensions/positron-python/src/test/testing/testController/pytest/pytestDiscoveryAdapter.unit.test.ts b/extensions/positron-python/src/test/testing/testController/pytest/pytestDiscoveryAdapter.unit.test.ts
index 45f6d4ffa8e..3b1e8fec6d6 100644
--- a/extensions/positron-python/src/test/testing/testController/pytest/pytestDiscoveryAdapter.unit.test.ts
+++ b/extensions/positron-python/src/test/testing/testController/pytest/pytestDiscoveryAdapter.unit.test.ts
@@ -46,7 +46,7 @@ suite('pytest test discovery adapter', () => {
uuid = 'uuid123';
expectedPath = path.join('/', 'my', 'test', 'path');
uri = Uri.file(expectedPath);
- const relativePathToPytest = 'pythonFiles';
+ const relativePathToPytest = 'python_files';
const fullPluginPath = path.join(EXTENSION_ROOT_DIR, relativePathToPytest);
expectedExtraVariables = {
PYTHONPATH: fullPluginPath,
diff --git a/extensions/positron-python/src/test/testing/testController/pytest/pytestExecutionAdapter.unit.test.ts b/extensions/positron-python/src/test/testing/testController/pytest/pytestExecutionAdapter.unit.test.ts
index d2ab19368b2..26a19ed0fd0 100644
--- a/extensions/positron-python/src/test/testing/testController/pytest/pytestExecutionAdapter.unit.test.ts
+++ b/extensions/positron-python/src/test/testing/testController/pytest/pytestExecutionAdapter.unit.test.ts
@@ -169,7 +169,7 @@ suite('pytest test execution adapter', () => {
await deferred4.promise;
mockProc.trigger('close');
- const pathToPythonFiles = path.join(EXTENSION_ROOT_DIR, 'pythonFiles');
+ const pathToPythonFiles = path.join(EXTENSION_ROOT_DIR, 'python_files');
const pathToPythonScript = path.join(pathToPythonFiles, 'vscode_pytest', 'run_pytest_script.py');
const rootDirArg = `--rootdir=${myTestPath}`;
const expectedArgs = [pathToPythonScript, rootDirArg];
@@ -237,7 +237,7 @@ suite('pytest test execution adapter', () => {
await deferred4.promise;
mockProc.trigger('close');
- const pathToPythonFiles = path.join(EXTENSION_ROOT_DIR, 'pythonFiles');
+ const pathToPythonFiles = path.join(EXTENSION_ROOT_DIR, 'python_files');
const pathToPythonScript = path.join(pathToPythonFiles, 'vscode_pytest', 'run_pytest_script.py');
const expectedArgs = [pathToPythonScript, `--rootdir=${newCwd}`];
const expectedExtraVariables = {
diff --git a/extensions/positron-python/src/test/testing/testController/unittest/testDiscoveryAdapter.unit.test.ts b/extensions/positron-python/src/test/testing/testController/unittest/testDiscoveryAdapter.unit.test.ts
index 0eee88120f6..c5fb0d421da 100644
--- a/extensions/positron-python/src/test/testing/testController/unittest/testDiscoveryAdapter.unit.test.ts
+++ b/extensions/positron-python/src/test/testing/testController/unittest/testDiscoveryAdapter.unit.test.ts
@@ -42,7 +42,7 @@ suite('Unittest test discovery adapter', () => {
} as unknown) as ITestServer;
const uri = Uri.file('/foo/bar');
- const script = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'unittestadapter', 'discovery.py');
+ const script = path.join(EXTENSION_ROOT_DIR, 'python_files', 'unittestadapter', 'discovery.py');
const adapter = new UnittestTestDiscoveryAdapter(stubTestServer, stubConfigSettings, outputChannel.object);
adapter.discoverTests(uri);
@@ -78,7 +78,7 @@ suite('Unittest test discovery adapter', () => {
const uri = Uri.file('/foo/bar');
const newCwd = '/foo';
- const script = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'unittestadapter', 'discovery.py');
+ const script = path.join(EXTENSION_ROOT_DIR, 'python_files', 'unittestadapter', 'discovery.py');
const adapter = new UnittestTestDiscoveryAdapter(stubTestServer, stubConfigSettings, outputChannel.object);
adapter.discoverTests(uri);
diff --git a/extensions/positron-python/src/test/testing/testController/unittest/testExecutionAdapter.unit.test.ts b/extensions/positron-python/src/test/testing/testController/unittest/testExecutionAdapter.unit.test.ts
index e2903d353bb..bb82f49b477 100644
--- a/extensions/positron-python/src/test/testing/testController/unittest/testExecutionAdapter.unit.test.ts
+++ b/extensions/positron-python/src/test/testing/testController/unittest/testExecutionAdapter.unit.test.ts
@@ -50,7 +50,7 @@ suite('Unittest test execution adapter', () => {
} as unknown) as ITestServer;
const uri = Uri.file('/foo/bar');
- const script = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'unittestadapter', 'execution.py');
+ const script = path.join(EXTENSION_ROOT_DIR, 'python_files', 'unittestadapter', 'execution.py');
const adapter = new UnittestTestExecutionAdapter(stubTestServer, stubConfigSettings, outputChannel.object);
const testIds = ['test1id', 'test2id'];
@@ -94,7 +94,7 @@ suite('Unittest test execution adapter', () => {
const newCwd = '/foo';
const uri = Uri.file('/foo/bar');
- const script = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'unittestadapter', 'execution.py');
+ const script = path.join(EXTENSION_ROOT_DIR, 'python_files', 'unittestadapter', 'execution.py');
const adapter = new UnittestTestExecutionAdapter(stubTestServer, stubConfigSettings, outputChannel.object);
const testIds = ['test1id', 'test2id'];
diff --git a/extensions/positron-python/src/test/testing/testController/utils.unit.test.ts b/extensions/positron-python/src/test/testing/testController/utils.unit.test.ts
index 5bcf8dfa10c..36ee42ab022 100644
--- a/extensions/positron-python/src/test/testing/testController/utils.unit.test.ts
+++ b/extensions/positron-python/src/test/testing/testController/utils.unit.test.ts
@@ -165,10 +165,10 @@ ${data}${secondPayload}`;
suite('Test Controller Utils: Args Mapping', () => {
test('Converts map with mixed values to array of strings', async () => {
const inputMap = {
- key1: 'value1',
+ key1: ['value1'],
key2: null,
key3: undefined,
- key4: 'value4',
+ key4: ['value4'],
};
const expectedOutput = ['key1=value1', 'key2', 'key4=value4'];
@@ -209,6 +209,17 @@ ${data}${secondPayload}`;
assert.deepStrictEqual(result, expectedOutput);
});
+ test('Handles mapToArgs for a key with multiple values', async () => {
+ const inputMap = {
+ key1: null,
+ key2: ['value1', 'value2'],
+ };
+ const expectedOutput = ['key1', 'key2=value1', 'key2=value2'];
+
+ const result = mapToArgs(inputMap);
+
+ assert.deepStrictEqual(result, expectedOutput);
+ });
test('Adds new argument if it does not exist', () => {
const map = {};
const argKey = 'newKey';
@@ -216,17 +227,17 @@ ${data}${secondPayload}`;
const updatedMap = addArgIfNotExist(map, argKey, argValue);
- assert.deepStrictEqual(updatedMap, { [argKey]: argValue });
+ assert.deepStrictEqual(updatedMap, { [argKey]: [argValue] });
});
test('Does not overwrite existing argument', () => {
- const map = { existingKey: 'existingValue' };
+ const map = { existingKey: ['existingValue'] };
const argKey = 'existingKey';
const argValue = 'newValue';
const updatedMap = addArgIfNotExist(map, argKey, argValue);
- assert.deepStrictEqual(updatedMap, { [argKey]: 'existingValue' });
+ assert.deepStrictEqual(updatedMap, { [argKey]: ['existingValue'] });
});
test('Handles null value for new key', () => {
@@ -249,21 +260,9 @@ ${data}${secondPayload}`;
assert.deepStrictEqual(updatedMap, { [argKey]: null });
});
- test('Accepts addition if key exists with undefined value', () => {
- const map = { undefinedKey: undefined };
- const argKey = 'undefinedKey';
- const argValue = 'newValue';
-
- // Attempting to add a key that is explicitly set to undefined
- const updatedMap = addArgIfNotExist(map, argKey, argValue);
-
- // Expect the map to remain unchanged because the key exists as undefined
- assert.strictEqual(map[argKey], argValue);
- assert.deepStrictEqual(updatedMap, { [argKey]: argValue });
- });
test('Complex test for argKeyExists with various key types', () => {
const map = {
- stringKey: 'stringValue',
+ stringKey: ['stringValue'],
nullKey: null,
// Note: not adding an 'undefinedKey' explicitly since it's not present and hence undefined by default
};
@@ -289,7 +288,15 @@ ${data}${secondPayload}`;
});
test('Converts array of strings with "=" into a map', () => {
const args = ['key1=value1', 'key2=value2'];
- const expectedMap = { key1: 'value1', key2: 'value2' };
+ const expectedMap = { key1: ['value1'], key2: ['value2'] };
+
+ const resultMap = argsToMap(args);
+
+ assert.deepStrictEqual(resultMap, expectedMap);
+ });
+ test('Handles argsToMap for multiple values for the same key', () => {
+ const args = ['key1=value1', 'key1=value2'];
+ const expectedMap = { key1: ['value1', 'value2'] };
const resultMap = argsToMap(args);
@@ -307,7 +314,7 @@ ${data}${secondPayload}`;
test('Handles mixed keys with and without "="', () => {
const args = ['key1=value1', 'key2'];
- const expectedMap = { key1: 'value1', key2: null };
+ const expectedMap = { key1: ['value1'], key2: null };
const resultMap = argsToMap(args);
@@ -316,7 +323,7 @@ ${data}${secondPayload}`;
test('Handles strings with multiple "=" characters', () => {
const args = ['key1=part1=part2'];
- const expectedMap = { key1: 'part1=part2' };
+ const expectedMap = { key1: ['part1=part2'] };
const resultMap = argsToMap(args);
diff --git a/extensions/positron-python/yarn.lock b/extensions/positron-python/yarn.lock
index 2ed9da0a683..8941393c956 100644
--- a/extensions/positron-python/yarn.lock
+++ b/extensions/positron-python/yarn.lock
@@ -7857,7 +7857,7 @@ semver@^6.0.0, semver@^6.3.0, semver@^6.3.1:
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
-semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.7, semver@^7.5.1, semver@^7.5.2, semver@^7.5.3:
+semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@^7.5.1, semver@^7.5.2, semver@^7.5.3:
version "7.5.4"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e"
integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==
@@ -7871,6 +7871,13 @@ semver@^7.3.5:
dependencies:
lru-cache "^6.0.0"
+semver@^7.3.7:
+ version "7.6.0"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.0.tgz#1a46a4db4bffcccd97b743b5005c8325f23d4e2d"
+ integrity sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==
+ dependencies:
+ lru-cache "^6.0.0"
+
serialize-javascript@6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8"
@@ -9144,11 +9151,20 @@ vscode-jsonrpc@8.1.0-next.7:
resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-8.1.0-next.7.tgz#4ecfada9866f677da9a5915abfceb7b7c90962f6"
integrity sha512-UJlY2e4wnI+GkaNYM2TERqrNvTe0XScny7lUA4f+F+z6XI5pDJnHj6udXiGJofT/tX57d8C6fnlfgrCfF6aptQ==
-vscode-jsonrpc@8.2.0, vscode-jsonrpc@^8.2.0:
+vscode-jsonrpc@8.2.0:
version "8.2.0"
resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz#f43dfa35fb51e763d17cd94dcca0c9458f35abf9"
integrity sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==
+vscode-languageclient@9.0.1:
+ version "9.0.1"
+ resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-9.0.1.tgz#cdfe20267726c8d4db839dc1e9d1816e1296e854"
+ integrity sha512-JZiimVdvimEuHh5olxhxkht09m3JzUGwggb5eRUkzzJhZ2KjCN0nh55VfiED9oez9DyF8/fz1g1iBV3h+0Z2EA==
+ dependencies:
+ minimatch "^5.1.0"
+ semver "^7.3.7"
+ vscode-languageserver-protocol "3.17.5"
+
vscode-languageclient@^8.0.2-next.4:
version "8.0.2"
resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-8.0.2.tgz#f1f23ce8c8484aa11e4b7dfb24437d3e59bb61c6"
@@ -9158,15 +9174,6 @@ vscode-languageclient@^8.0.2-next.4:
semver "^7.3.5"
vscode-languageserver-protocol "3.17.2"
-vscode-languageclient@^9.0.1:
- version "9.0.1"
- resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-9.0.1.tgz#cdfe20267726c8d4db839dc1e9d1816e1296e854"
- integrity sha512-JZiimVdvimEuHh5olxhxkht09m3JzUGwggb5eRUkzzJhZ2KjCN0nh55VfiED9oez9DyF8/fz1g1iBV3h+0Z2EA==
- dependencies:
- minimatch "^5.1.0"
- semver "^7.3.7"
- vscode-languageserver-protocol "3.17.5"
-
vscode-languageserver-protocol@3.17.2, vscode-languageserver-protocol@^3.17.2-next.5:
version "3.17.2"
resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.2.tgz#beaa46aea06ed061576586c5e11368a9afc1d378"
@@ -9175,7 +9182,7 @@ vscode-languageserver-protocol@3.17.2, vscode-languageserver-protocol@^3.17.2-ne
vscode-jsonrpc "8.0.2"
vscode-languageserver-types "3.17.2"
-vscode-languageserver-protocol@3.17.5, vscode-languageserver-protocol@^3.17.5:
+vscode-languageserver-protocol@3.17.5:
version "3.17.5"
resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz#864a8b8f390835572f4e13bd9f8313d0e3ac4bea"
integrity sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==
diff --git a/extensions/positron-r/package.json b/extensions/positron-r/package.json
index 031c5079590..4525c36e49d 100644
--- a/extensions/positron-r/package.json
+++ b/extensions/positron-r/package.json
@@ -504,6 +504,7 @@
],
"devDependencies": {
"@types/decompress": "^4.2.7",
+ "@types/fs-extra": "^9.0.13",
"@types/glob": "^7.2.0",
"@types/mocha": "^9.1.0",
"@types/node": "14.x",
@@ -521,6 +522,7 @@
"vsce": "^2.11.0"
},
"dependencies": {
+ "fs-extra": "^10.0.1",
"p-queue": "^6.6.2",
"split2": "^4.2.0",
"vscode-languageclient": "^9.0.1",
@@ -533,7 +535,7 @@
},
"positron": {
"binaryDependencies": {
- "ark": "0.1.78"
+ "ark": "0.1.80"
},
"minimumRVersion": "4.2.0"
}
diff --git a/extensions/positron-r/resources/testing/tree-sitter-r.wasm b/extensions/positron-r/resources/testing/tree-sitter-r.wasm
old mode 100644
new mode 100755
index dc3c3f75c75..33e4d9c454d
Binary files a/extensions/positron-r/resources/testing/tree-sitter-r.wasm and b/extensions/positron-r/resources/testing/tree-sitter-r.wasm differ
diff --git a/extensions/positron-r/src/constants.ts b/extensions/positron-r/src/constants.ts
index 1f93d3212e5..1f8e37825eb 100644
--- a/extensions/positron-r/src/constants.ts
+++ b/extensions/positron-r/src/constants.ts
@@ -3,10 +3,13 @@
*--------------------------------------------------------------------------------------------*/
import * as path from 'path';
+import * as fs from 'fs-extra';
// The extension root directory.
export const EXTENSION_ROOT_DIR = path.join(__dirname, '..');
-// The minimum supported version of R
-const packageJson = require(path.join(EXTENSION_ROOT_DIR, 'package.json'));
+// Read the package.json file.
+const packageJson = fs.readJSONSync(path.join(EXTENSION_ROOT_DIR, 'package.json'));
+
+// The minimum supported version of R.
export const MINIMUM_R_VERSION = packageJson.positron.minimumRVersion;
diff --git a/extensions/positron-r/src/test/editor-utils.ts b/extensions/positron-r/src/test/editor-utils.ts
index 582449e0781..8d010de2996 100644
--- a/extensions/positron-r/src/test/editor-utils.ts
+++ b/extensions/positron-r/src/test/editor-utils.ts
@@ -61,6 +61,10 @@ export async function withFileEditor(
const doc = await vscode.workspace.openTextDocument(file);
const editor = await vscode.window.showTextDocument(doc);
+ editor.options.insertSpaces = true;
+ editor.options.indentSize = 4;
+ editor.options.tabSize = 4;
+
if (cursorIndex >= 0) {
const pos = doc.positionAt(cursorIndex);
editor.selection = new vscode.Selection(pos, pos);
diff --git a/extensions/positron-r/src/test/snapshots/.vscode/settings.json b/extensions/positron-r/src/test/snapshots/.vscode/settings.json
index c755b1fd50a..af2e02cb363 100644
--- a/extensions/positron-r/src/test/snapshots/.vscode/settings.json
+++ b/extensions/positron-r/src/test/snapshots/.vscode/settings.json
@@ -1,3 +1,6 @@
{
- "workbench.localHistory.enabled": false
-}
\ No newline at end of file
+ "workbench.localHistory.enabled": false,
+ "editor.insertSpaces": true,
+ "editor.indentSize": 4,
+ "editor.tabSize": 4,
+}
diff --git a/extensions/positron-r/src/test/snapshots/indentation-cases.R b/extensions/positron-r/src/test/snapshots/indentation-cases.R
index b37623f068d..ebbc2f913e1 100644
--- a/extensions/positron-r/src/test/snapshots/indentation-cases.R
+++ b/extensions/positron-r/src/test/snapshots/indentation-cases.R
@@ -20,123 +20,123 @@ data |>"<>"
# Starting a pipeline (one empty line)
# FIXME
data |>
- "<>"
+ "<>"
# ---
# Starting a pipeline (multiple empty lines)
# FIXME
data |>
- "<>"
+ "<>"
# ---
# Continuing a pipeline
1 +
- 2 +"<>"
+ 2 +"<>"
# ---
# Continuing a one-liner pipeline
# https://github.com/posit-dev/positron/issues/1316
data |>
- fn() |>"<>"
+ fn() |>"<>"
# ---
# Continuing a one-liner pipeline (trailing whitespace)
# https://github.com/posit-dev/positron/pull/1655#issuecomment-1780093395
data |>
- fn() |> "<>"
+ fn() |> "<>"
# ---
# Continuing a one-liner pipeline (trailing comment)
data |>
- fn() |> "<>" # foo
+ fn() |> "<>" # foo
# ---
# Continuing a one-liner pipeline (comment line)
# FIXME
data |>
- fn1() |>
- # foo"<>"
+ fn1() |>
+ # foo"<>"
# ---
# Continuing a one-liner pipeline (after a comment line)
# FIXME
data |>
- fn1() |>
- # foo
- "<>"
+ fn1() |>
+ # foo
+ "<>"
# ---
# Continuing a one-liner pipeline (longer pipeline)
data |>
- fn1() |>
- fn2() |>"<>"
+ fn1() |>
+ fn2() |>"<>"
# ---
# Continuing a multi-liner pipeline
# FIXME
data |>
- fn1(
- x,
- y
- ) |>"<>"
+ fn1(
+ x,
+ y
+ ) |>"<>"
# ---
# Continuing a multi-liner pipeline (trailing expression)
# FIXME
data |>
- fn1(
- x,
- y
- ) |> "<>" fn2()
+ fn1(
+ x,
+ y
+ ) |> "<>" fn2()
# ---
# Dedent after pipeline
data |>
- fn()"<>"
+ fn()"<>"
# ---
# Dedent after pipeline (trailing comment)
data |>
- fn()"<>" # foo
+ fn()"<>" # foo
# ---
# Dedent after pipeline (multiple lines)
# FIXME
data |>
- fn1() |>
- fn2(
- "arg"
- )"<>"
+ fn1() |>
+ fn2(
+ "arg"
+ )"<>"
# ---
# Stickiness of dedent after pipeline
# https://github.com/posit-dev/positron/issues/1727
data |>
- fn()
+ fn()
"<>"
# ---
# Stickiness of dedent after pipeline (trailing comment)
data |>
- fn()
+ fn()
"<>" # foo
# ---
# Indent after function in call
# FIXME
{
- fn(function() {}"<>")
+ fn(function() {}"<>")
}
# ---
# Indent after function in call (multiple lines)
# FIXME
{
- fn(function() {
- #
- }"<>")
+ fn(function() {
+ #
+ }"<>")
}
# ---
@@ -152,22 +152,22 @@ for (i in 1) fn()"<>"
# ---
# Breaking parentheses
foo("<>") +
- bar()
+ bar()
# ---
# Breaking parentheses in a pipeline
# https://github.com/posit-dev/positron/issues/2650
# https://github.com/posit-dev/positron-beta/discussions/46
foo() +
- bar("<>")
+ bar("<>")
# ---
# Breaking parentheses in a pipeline (comment in the way)
foo() +
- bar("<>") # foo
+ bar("<>") # foo
# ---
# Breaking parentheses in the middle of a pipeline
foo() +
- bar("<>") +
- baz()
+ bar("<>") +
+ baz()
diff --git a/extensions/positron-r/src/test/snapshots/indentation-snapshots.R b/extensions/positron-r/src/test/snapshots/indentation-snapshots.R
index 632ffdc4b2c..da6e18d5726 100644
--- a/extensions/positron-r/src/test/snapshots/indentation-snapshots.R
+++ b/extensions/positron-r/src/test/snapshots/indentation-snapshots.R
@@ -6,7 +6,7 @@
# ->
1 +
- "<>"
+ "<>"
# ---
# Starting a pipeline (pipe operator)
@@ -14,17 +14,17 @@ data |>"<>"
# ->
data |>
- "<>"
+ "<>"
# ---
# Starting a pipeline (one empty line)
# FIXME
data |>
- "<>"
+ "<>"
# ->
data |>
-
+
"<>"
# ---
@@ -32,191 +32,191 @@ data |>
# FIXME
data |>
- "<>"
+ "<>"
# ->
data |>
-
+
"<>"
# ---
# Continuing a pipeline
1 +
- 2 +"<>"
+ 2 +"<>"
# ->
1 +
- 2 +
- "<>"
+ 2 +
+ "<>"
# ---
# Continuing a one-liner pipeline
# https://github.com/posit-dev/positron/issues/1316
data |>
- fn() |>"<>"
+ fn() |>"<>"
# ->
data |>
- fn() |>
- "<>"
+ fn() |>
+ "<>"
# ---
# Continuing a one-liner pipeline (trailing whitespace)
# https://github.com/posit-dev/positron/pull/1655#issuecomment-1780093395
data |>
- fn() |> "<>"
+ fn() |> "<>"
# ->
data |>
- fn() |>
- "<>"
+ fn() |>
+ "<>"
# ---
# Continuing a one-liner pipeline (trailing comment)
data |>
- fn() |> "<>" # foo
+ fn() |> "<>" # foo
# ->
data |>
- fn() |>
- "<>" # foo
+ fn() |>
+ "<>" # foo
# ---
# Continuing a one-liner pipeline (comment line)
# FIXME
data |>
- fn1() |>
- # foo"<>"
+ fn1() |>
+ # foo"<>"
# ->
data |>
- fn1() |>
- # foo
+ fn1() |>
+ # foo
"<>"
# ---
# Continuing a one-liner pipeline (after a comment line)
# FIXME
data |>
- fn1() |>
- # foo
- "<>"
+ fn1() |>
+ # foo
+ "<>"
# ->
data |>
- fn1() |>
- # foo
-
+ fn1() |>
+ # foo
+
"<>"
# ---
# Continuing a one-liner pipeline (longer pipeline)
data |>
- fn1() |>
- fn2() |>"<>"
+ fn1() |>
+ fn2() |>"<>"
# ->
data |>
- fn1() |>
- fn2() |>
- "<>"
+ fn1() |>
+ fn2() |>
+ "<>"
# ---
# Continuing a multi-liner pipeline
# FIXME
data |>
- fn1(
- x,
- y
- ) |>"<>"
+ fn1(
+ x,
+ y
+ ) |>"<>"
# ->
data |>
- fn1(
- x,
- y
- ) |>
- "<>"
+ fn1(
+ x,
+ y
+ ) |>
+ "<>"
# ---
# Continuing a multi-liner pipeline (trailing expression)
# FIXME
data |>
- fn1(
- x,
- y
- ) |> "<>" fn2()
+ fn1(
+ x,
+ y
+ ) |> "<>" fn2()
# ->
data |>
- fn1(
- x,
- y
- ) |>
- "<>" fn2()
+ fn1(
+ x,
+ y
+ ) |>
+ "<>" fn2()
# ---
# Dedent after pipeline
data |>
- fn()"<>"
+ fn()"<>"
# ->
data |>
- fn()
+ fn()
"<>"
# ---
# Dedent after pipeline (trailing comment)
data |>
- fn()"<>" # foo
+ fn()"<>" # foo
# ->
data |>
- fn()
+ fn()
"<>" # foo
# ---
# Dedent after pipeline (multiple lines)
# FIXME
data |>
- fn1() |>
- fn2(
- "arg"
- )"<>"
+ fn1() |>
+ fn2(
+ "arg"
+ )"<>"
# ->
data |>
- fn1() |>
- fn2(
- "arg"
- )
- "<>"
+ fn1() |>
+ fn2(
+ "arg"
+ )
+ "<>"
# ---
# Stickiness of dedent after pipeline
# https://github.com/posit-dev/positron/issues/1727
data |>
- fn()
+ fn()
"<>"
# ->
data |>
- fn()
+ fn()
"<>"
# ---
# Stickiness of dedent after pipeline (trailing comment)
data |>
- fn()
+ fn()
"<>" # foo
# ->
data |>
- fn()
+ fn()
"<>"# foo
@@ -224,12 +224,12 @@ data |>
# Indent after function in call
# FIXME
{
- fn(function() {}"<>")
+ fn(function() {}"<>")
}
# ->
{
- fn(function() {}
+ fn(function() {}
"<>")
}
@@ -237,16 +237,16 @@ data |>
# Indent after function in call (multiple lines)
# FIXME
{
- fn(function() {
- #
- }"<>")
+ fn(function() {
+ #
+ }"<>")
}
# ->
{
- fn(function() {
- #
- }
+ fn(function() {
+ #
+ }
"<>")
}
@@ -266,51 +266,51 @@ for (i in 1) fn()"<>"
# ->
for (i in 1) fn()
- "<>"
+ "<>"
# ---
# Breaking parentheses
foo("<>") +
- bar()
+ bar()
# ->
foo(
- "<>"
+ "<>"
) +
- bar()
+ bar()
# ---
# Breaking parentheses in a pipeline
# https://github.com/posit-dev/positron/issues/2650
# https://github.com/posit-dev/positron-beta/discussions/46
foo() +
- bar("<>")
+ bar("<>")
# ->
foo() +
- bar(
- "<>"
- )
+ bar(
+ "<>"
+ )
# ---
# Breaking parentheses in a pipeline (comment in the way)
foo() +
- bar("<>") # foo
+ bar("<>") # foo
# ->
foo() +
- bar(
- "<>"
- ) # foo
+ bar(
+ "<>"
+ ) # foo
# ---
# Breaking parentheses in the middle of a pipeline
foo() +
- bar("<>") +
- baz()
+ bar("<>") +
+ baz()
# ->
foo() +
- bar(
- "<>"
- ) +
- baz()
+ bar(
+ "<>"
+ ) +
+ baz()
diff --git a/extensions/positron-r/src/testing/parser.ts b/extensions/positron-r/src/testing/parser.ts
index ff19c5a0e08..1d1ed16a8f7 100644
--- a/extensions/positron-r/src/testing/parser.ts
+++ b/extensions/positron-r/src/testing/parser.ts
@@ -93,43 +93,37 @@ async function findTests(uri: vscode.Uri) {
(call
function: [
(identifier) @_function.name
- (namespace_get
- function: (identifier) @_function.name
- )
] (#eq? @_function.name "test_that")
- arguments:
- (arguments
- value: (string) @label
- value: (_)
+ arguments: (arguments
+ (argument
+ value: (string) @label
)
+ )
) @call
(call
function: [
(identifier) @_superfunction.name
- (namespace_get
- function: (identifier) @_superfunction.name
- )
] (#eq? @_superfunction.name "describe")
- arguments:
- (arguments
+ arguments: (arguments
+ (argument
value: (string) @superlabel
- value: (_
- (call
+ )
+ (argument
+ value: (braced_expression
+ body: (call
function: [
(identifier) @_function.name
- (namespace_get
- function: (identifier) @_function.name
- )
] (#eq? @_function.name "it")
- arguments:
- (arguments
+ arguments: (arguments
+ (argument
value: (string) @label
- value: (_)
)
+ )
) @call
)
)
+ )
) @supercall
`
);
diff --git a/extensions/positron-r/yarn.lock b/extensions/positron-r/yarn.lock
index 9723ebc301f..9ca28e1a6e5 100644
--- a/extensions/positron-r/yarn.lock
+++ b/extensions/positron-r/yarn.lock
@@ -109,6 +109,13 @@
dependencies:
"@types/node" "*"
+"@types/fs-extra@^9.0.13":
+ version "9.0.13"
+ resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-9.0.13.tgz#7594fbae04fe7f1918ce8b3d213f74ff44ac1f45"
+ integrity sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==
+ dependencies:
+ "@types/node" "*"
+
"@types/glob@^7.2.0":
version "7.2.0"
resolved "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz"
@@ -1038,6 +1045,15 @@ fs-constants@^1.0.0:
resolved "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz"
integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==
+fs-extra@^10.0.1:
+ version "10.1.0"
+ resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf"
+ integrity sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==
+ dependencies:
+ graceful-fs "^4.2.0"
+ jsonfile "^6.0.1"
+ universalify "^2.0.0"
+
fs.realpath@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz"
@@ -1140,7 +1156,7 @@ globby@^11.0.4:
merge2 "^1.4.1"
slash "^3.0.0"
-graceful-fs@^4.1.10:
+graceful-fs@^4.1.10, graceful-fs@^4.1.6, graceful-fs@^4.2.0:
version "4.2.11"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3"
integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
@@ -1333,6 +1349,15 @@ json-stable-stringify-without-jsonify@^1.0.1:
resolved "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz"
integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=
+jsonfile@^6.0.1:
+ version "6.1.0"
+ resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae"
+ integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==
+ dependencies:
+ universalify "^2.0.0"
+ optionalDependencies:
+ graceful-fs "^4.1.6"
+
keytar@^7.7.0:
version "7.9.0"
resolved "https://registry.npmjs.org/keytar/-/keytar-7.9.0.tgz"
@@ -2177,6 +2202,11 @@ underscore@^1.12.1:
resolved "https://registry.npmjs.org/underscore/-/underscore-1.13.4.tgz"
integrity sha512-BQFnUDuAQ4Yf/cYY5LNrK9NCJFKriaRbD9uR1fTeXnBeoa97W0i41qkZfGO9pSo8I5KzjAcSY2XYtdf0oKd7KQ==
+universalify@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d"
+ integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==
+
unzipper@^0.10.11:
version "0.10.11"
resolved "https://registry.npmjs.org/unzipper/-/unzipper-0.10.11.tgz"
diff --git a/positron/comms/data_explorer-backend-openrpc.json b/positron/comms/data_explorer-backend-openrpc.json
index f626b52c55f..8449a63ba5b 100644
--- a/positron/comms/data_explorer-backend-openrpc.json
+++ b/positron/comms/data_explorer-backend-openrpc.json
@@ -240,14 +240,20 @@
"result": {
"schema": {
"type": "object",
- "name": "table_state",
- "description": "The current backend table state",
+ "name": "backend_state",
+ "description": "The current backend state for the data explorer",
"required": [
+ "display_name",
"table_shape",
"row_filters",
- "sort_keys"
+ "sort_keys",
+ "supported_features"
],
"properties": {
+ "display_name": {
+ "type": "string",
+ "description": "Variable name or other string to display for tab name in UI"
+ },
"table_shape": {
"type": "object",
"name": "table_shape",
@@ -280,38 +286,10 @@
"items": {
"$ref": "#/components/schemas/column_sort_key"
}
- }
- }
- }
- }
- },
- {
- "name": "get_supported_features",
- "summary": "Query the backend to determine supported features",
- "description": "Query the backend to determine supported features, to enable feature toggling",
- "params": [],
- "result": {
- "schema": {
- "type": "object",
- "name": "supported_features",
- "description": "For each field, returns flags indicating supported features",
- "required": [
- "search_schema",
- "set_row_filters",
- "get_column_profiles"
- ],
- "properties": {
- "search_schema": {
- "description": "Support for 'search_schema' RPC and its features",
- "$ref": "#/components/schemas/search_schema_features"
},
- "set_row_filters": {
- "description": "Support for 'set_row_filters' RPC and its features",
- "$ref": "#/components/schemas/set_row_filters_features"
- },
- "get_column_profiles": {
- "description": "Support for 'get_column_profiles' RPC and its features",
- "$ref": "#/components/schemas/get_column_profiles_features"
+ "supported_features": {
+ "description": "The features currently supported by the backend instance",
+ "$ref": "#/components/schemas/supported_features"
}
}
}
@@ -414,7 +392,8 @@
"required": [
"filter_id",
"filter_type",
- "column_index"
+ "column_index",
+ "condition"
],
"properties": {
"filter_id": {
@@ -429,6 +408,18 @@
"type": "integer",
"description": "Column index to apply filter to"
},
+ "condition": {
+ "type": "string",
+ "description": "The binary condition to use to combine with preceding row filters",
+ "enum": [
+ "and",
+ "or"
+ ]
+ },
+ "is_valid": {
+ "type": "boolean",
+ "description": "Whether the filter is valid and supported by the backend, if undefined then true"
+ },
"between_params": {
"description": "Parameters for the 'between' and 'not_between' filter types",
"$ref": "#/components/schemas/between_filter_params"
@@ -453,8 +444,10 @@
"enum": [
"between",
"compare",
+ "is_empty",
"is_null",
"not_between",
+ "not_empty",
"not_null",
"search",
"set_membership"
@@ -800,6 +793,29 @@
}
}
},
+ "supported_features": {
+ "type": "object",
+ "description": "For each field, returns flags indicating supported features",
+ "required": [
+ "search_schema",
+ "set_row_filters",
+ "get_column_profiles"
+ ],
+ "properties": {
+ "search_schema": {
+ "description": "Support for 'search_schema' RPC and its features",
+ "$ref": "#/components/schemas/search_schema_features"
+ },
+ "set_row_filters": {
+ "description": "Support for 'set_row_filters' RPC and its features",
+ "$ref": "#/components/schemas/set_row_filters_features"
+ },
+ "get_column_profiles": {
+ "description": "Support for 'get_column_profiles' RPC and its features",
+ "$ref": "#/components/schemas/get_column_profiles_features"
+ }
+ }
+ },
"search_schema_features": {
"type": "object",
"description": "Feature flags for 'search_schema' RPC",
diff --git a/positron/comms/generate-comms.ts b/positron/comms/generate-comms.ts
index c98530a95d0..28f2c5e698f 100644
--- a/positron/comms/generate-comms.ts
+++ b/positron/comms/generate-comms.ts
@@ -45,7 +45,7 @@ const tsOutputDir = `${__dirname}/../../src/vs/workbench/services/languageRuntim
const rustOutputDir = `${__dirname}/../../../amalthea/crates/amalthea/src/comm`;
/// The directory to write the generated Python files to
-const pythonOutputDir = `${__dirname}/../../extensions/positron-python/pythonFiles/positron/positron_ipykernel`;
+const pythonOutputDir = `${__dirname}/../../extensions/positron-python/python_files/positron/positron_ipykernel`;
const year = new Date().getFullYear();
diff --git a/positron/comms/plot-backend-openrpc.json b/positron/comms/plot-backend-openrpc.json
index ae607cc8cc9..5a80970a01e 100644
--- a/positron/comms/plot-backend-openrpc.json
+++ b/positron/comms/plot-backend-openrpc.json
@@ -30,6 +30,14 @@
"schema": {
"type": "number"
}
+ },
+ {
+ "name": "format",
+ "description": "The requested plot format",
+ "schema": {
+ "type": "string"
+ },
+ "required": false
}
],
"result": {
diff --git a/positron/comms/plot-frontend-openrpc.json b/positron/comms/plot-frontend-openrpc.json
index 4a24bb86447..a38bc2cacbb 100644
--- a/positron/comms/plot-frontend-openrpc.json
+++ b/positron/comms/plot-frontend-openrpc.json
@@ -9,6 +9,11 @@
"name": "update",
"summary": "Notification that a plot has been updated on the backend.",
"params": []
+ },
+ {
+ "name": "show",
+ "summary": "Show a plot.",
+ "params": []
}
]
}
diff --git a/src/vs/base/browser/ui/positronComponents/button/button.css b/src/vs/base/browser/ui/positronComponents/button/button.css
index 415d1e04d62..3fc256a2d0b 100644
--- a/src/vs/base/browser/ui/positronComponents/button/button.css
+++ b/src/vs/base/browser/ui/positronComponents/button/button.css
@@ -6,7 +6,10 @@
border: none;
cursor: pointer;
background-color: transparent;
+
+ /* Unset user agent stylesheet styles. */
font-family: unset !important;
+ text-align: unset !important;
}
.positron-button:focus {
diff --git a/src/vs/platform/clipboard/browser/clipboardService.ts b/src/vs/platform/clipboard/browser/clipboardService.ts
index f9a61f6ce3c..65e38be21ec 100644
--- a/src/vs/platform/clipboard/browser/clipboardService.ts
+++ b/src/vs/platform/clipboard/browser/clipboardService.ts
@@ -189,6 +189,17 @@ export class BrowserClipboardService extends Disposable implements IClipboardSer
return this.resources;
}
+ // --- Start Positron ---
+ async writeImage(data: string): Promise {
+ const blob = new Blob([data], { type: 'image/png' });
+ navigator.clipboard.write([
+ new ClipboardItem({
+ [blob.type]: blob
+ }
+ )]);
+ }
+ // --- End Positron ---
+
private async computeResourcesStateHash(): Promise {
if (this.resources.length === 0) {
return undefined; // no resources, no hash needed
diff --git a/src/vs/platform/clipboard/common/clipboardService.ts b/src/vs/platform/clipboard/common/clipboardService.ts
index c4aea9f7132..4fdd6c3f3eb 100644
--- a/src/vs/platform/clipboard/common/clipboardService.ts
+++ b/src/vs/platform/clipboard/common/clipboardService.ts
@@ -37,6 +37,10 @@ export interface IClipboardService {
*/
writeResources(resources: URI[]): Promise;
+ // --- Start Positron ---
+ writeImage(data: string): Promise;
+ // --- End Positron ---
+
/**
* Reads resources from the system clipboard.
*/
diff --git a/src/vs/platform/clipboard/test/common/testClipboardService.ts b/src/vs/platform/clipboard/test/common/testClipboardService.ts
index 0ab758a9990..c20a75bb9aa 100644
--- a/src/vs/platform/clipboard/test/common/testClipboardService.ts
+++ b/src/vs/platform/clipboard/test/common/testClipboardService.ts
@@ -36,6 +36,12 @@ export class TestClipboardService implements IClipboardService {
this.resources = resources;
}
+ // --- Start Positron ---
+ async writeImage(data: string): Promise {
+ // no-op
+ }
+ // --- End Positron ---
+
async readResources(): Promise {
return this.resources ?? [];
}
diff --git a/src/vs/platform/native/common/native.ts b/src/vs/platform/native/common/native.ts
index 94ae20b32f8..c61f0cca591 100644
--- a/src/vs/platform/native/common/native.ts
+++ b/src/vs/platform/native/common/native.ts
@@ -152,6 +152,10 @@ export interface ICommonNativeHostService {
readClipboardBuffer(format: string): Promise;
hasClipboard(format: string, type?: 'selection' | 'clipboard'): Promise;
+ // --- Start Positron ---
+ writeClipboardImage(dataUri: string): Promise;
+ // --- End Positron ---
+
// macOS Touchbar
newWindowTab(): Promise;
showPreviousWindowTab(): Promise;
diff --git a/src/vs/platform/native/electron-main/nativeHostMainService.ts b/src/vs/platform/native/electron-main/nativeHostMainService.ts
index a1d28301611..c8dfeeaaf30 100644
--- a/src/vs/platform/native/electron-main/nativeHostMainService.ts
+++ b/src/vs/platform/native/electron-main/nativeHostMainService.ts
@@ -44,6 +44,11 @@ import { IAuxiliaryWindowsMainService } from 'vs/platform/auxiliaryWindow/electr
import { IAuxiliaryWindow } from 'vs/platform/auxiliaryWindow/electron-main/auxiliaryWindow';
import { CancellationError } from 'vs/base/common/errors';
+// --- Start Positron ---
+// eslint-disable-next-line no-duplicate-imports
+import { nativeImage } from 'electron';
+// --- End Positron ---
+
export interface INativeHostMainService extends AddFirstParameterToFunctions /* only methods, not events */, number | undefined /* window ID */> { }
export const INativeHostMainService = createDecorator('nativeHostMainService');
@@ -647,6 +652,12 @@ export class NativeHostMainService extends Disposable implements INativeHostMain
return clipboard.has(format, type);
}
+ // --- Start Positron ---
+ async writeClipboardImage(windowId: number | undefined, dataUri: string): Promise {
+ return clipboard.writeImage(nativeImage.createFromDataURL(dataUri));
+ }
+ // --- End Positron ---
+
//#endregion
diff --git a/src/vs/workbench/browser/positronComponents/dropDownListBox/dropDownListBox.css b/src/vs/workbench/browser/positronComponents/dropDownListBox/dropDownListBox.css
index fbca2b23cd7..53c9eea95c3 100644
--- a/src/vs/workbench/browser/positronComponents/dropDownListBox/dropDownListBox.css
+++ b/src/vs/workbench/browser/positronComponents/dropDownListBox/dropDownListBox.css
@@ -18,9 +18,7 @@
.drop-down-list-box
.title {
- display: flex;
padding-left: 6px;
- align-items: center;
grid-column: title / chevron;
color: var(--vscode-positronContextMenu-foreground);
}
@@ -33,10 +31,6 @@
grid-column: chevron / end;
}
-.drop-down-list-box
-.chevron.disabled {
-}
-
.drop-down-list-box-items {
width: 100%;
margin: 4px;
diff --git a/src/vs/workbench/browser/positronComponents/dropDownListBox/dropDownListBox.tsx b/src/vs/workbench/browser/positronComponents/dropDownListBox/dropDownListBox.tsx
index 95702891a0b..1a6ec30cefd 100644
--- a/src/vs/workbench/browser/positronComponents/dropDownListBox/dropDownListBox.tsx
+++ b/src/vs/workbench/browser/positronComponents/dropDownListBox/dropDownListBox.tsx
@@ -20,29 +20,35 @@ import { PositronModalPopup } from 'vs/workbench/browser/positronComponents/posi
import { PositronModalReactRenderer } from 'vs/workbench/browser/positronModalReactRenderer/positronModalReactRenderer';
import { DropDownListBoxSeparator } from 'vs/workbench/browser/positronComponents/dropDownListBox/dropDownListBoxSeparator';
+/**
+ * DropDownListBoxEntry type.
+ */
+export type DropDownListBoxEntry, V extends NonNullable> = DropDownListBoxItem | DropDownListBoxSeparator;
+
/**
* DropDownListBoxProps interface.
*/
-interface DropDownListBoxProps {
+interface DropDownListBoxProps, V extends NonNullable> {
keybindingService: IKeybindingService;
layoutService: ILayoutService;
className?: string;
disabled?: boolean;
title: string;
- entries: (DropDownListBoxItem | DropDownListBoxSeparator)[];
- selectedIdentifier?: string;
- onSelectionChanged: (identifier: string) => void;
+ entries: DropDownListBoxEntry[];
+ createItem?: (dropDownListBoxItem: DropDownListBoxItem) => JSX.Element;
+ selectedIdentifier?: T;
+ onSelectionChanged: (dropDownListBoxItem: DropDownListBoxItem) => void;
}
/**
* Finds a drop down list box item by identifier.
- * @param identifier The identifier of the drop down list box item to find.
* @param entries The set of drop down list box entries.
+ * @param identifier The identifier of the drop down list box item to find.
* @returns The drop down list box item, if it was found; otherwise, undefined.
*/
-const findDropDownListBoxItem = (
- identifier: string | undefined,
- entries: (DropDownListBoxItem | DropDownListBoxSeparator)[]
+const findDropDownListBoxItem = , V>(
+ entries: DropDownListBoxEntry[],
+ identifier?: T | undefined
) => {
// Find the drop down list box item.
for (let i = 0; i < entries.length; i++) {
@@ -61,23 +67,23 @@ const findDropDownListBoxItem = (
* @param props The component properties.
* @returns The rendered component.
*/
-export const DropDownListBox = (props: DropDownListBoxProps) => {
+export const DropDownListBox = , V,>(props: DropDownListBoxProps) => {
// Reference hooks.
const ref = useRef(undefined!);
// State hooks.
const [selectedDropDownListBoxItem, setSelectedDropDownListBoxItem] =
- useState(
- findDropDownListBoxItem(props.selectedIdentifier, props.entries)
+ useState | undefined>(
+ findDropDownListBoxItem(props.entries, props.selectedIdentifier)
);
const [highlightedDropDownListBoxItem, setHighlightedDropDownListBoxItem] =
- useState(undefined);
+ useState | undefined>(undefined);
// Updates the selected drop down list box item.
useEffect(() => {
setSelectedDropDownListBoxItem(findDropDownListBoxItem(
+ props.entries,
props.selectedIdentifier,
- props.entries
));
}, [props.entries, props.selectedIdentifier]);
@@ -85,14 +91,22 @@ export const DropDownListBox = (props: DropDownListBoxProps) => {
* Gets the title to display.
* @returns The title to display.
*/
- const titleToDisplay = () => {
- if (highlightedDropDownListBoxItem) {
- return highlightedDropDownListBoxItem.options.title;
- } else if (selectedDropDownListBoxItem) {
- return selectedDropDownListBoxItem.options.title;
+ const Title = () => {
+ if (!props.createItem) {
+ if (highlightedDropDownListBoxItem) {
+ return {highlightedDropDownListBoxItem.options.title} ;
+ } else if (selectedDropDownListBoxItem) {
+ return {selectedDropDownListBoxItem.options.title} ;
+ }
} else {
- return props.title;
+ if (highlightedDropDownListBoxItem) {
+ return props.createItem(highlightedDropDownListBoxItem);
+ } else if (selectedDropDownListBoxItem) {
+ return props.createItem(selectedDropDownListBoxItem);
+ }
}
+
+ return {props.title} ;
};
// Render.
@@ -115,23 +129,26 @@ export const DropDownListBox = (props: DropDownListBoxProps) => {
// Show the drop down list box modal popup.
renderer.render(
-
renderer={renderer}
anchor={ref.current}
entries={props.entries}
+ createItem={props.createItem}
onItemHighlighted={dropDownListBoxItem =>
setHighlightedDropDownListBoxItem(dropDownListBoxItem)
}
onItemSelected={dropDownListBoxItem => {
setSelectedDropDownListBoxItem(dropDownListBoxItem);
- props.onSelectionChanged(dropDownListBoxItem.options.identifier);
+ props.onSelectionChanged(dropDownListBoxItem);
}}
/>
);
}}
>
- {titleToDisplay()}
-
+
+
+
+
@@ -141,12 +158,13 @@ export const DropDownListBox = (props: DropDownListBoxProps) => {
/**
* DropDownListBoxModalPopupProps interface.
*/
-interface DropDownListBoxModalPopupProps {
+interface DropDownListBoxModalPopupProps
{
renderer: PositronModalReactRenderer;
anchor: HTMLElement;
- entries: (DropDownListBoxItem | DropDownListBoxSeparator)[];
- onItemHighlighted: (dropdownListBoxItem: DropDownListBoxItem) => void;
- onItemSelected: (dropdownListBoxItem: DropDownListBoxItem) => void;
+ entries: DropDownListBoxEntry[];
+ createItem?: (dropDownListBoxItem: DropDownListBoxItem) => JSX.Element;
+ onItemHighlighted: (dropdownListBoxItem: DropDownListBoxItem) => void;
+ onItemSelected: (dropdownListBoxItem: DropDownListBoxItem) => void;
}
/**
@@ -154,7 +172,7 @@ interface DropDownListBoxModalPopupProps {
* @param props The component properties.
* @returns The rendered component.
*/
-const DropDownListBoxModalPopup = (props: DropDownListBoxModalPopupProps) => {
+const DropDownListBoxModalPopup = (props: DropDownListBoxModalPopupProps) => {
// Render.
return (
{
props.onItemSelected(entry);
}}
>
-
- {entry.options.title}
-
- {entry.options.icon &&
-
+ {props.createItem && props.createItem(entry)}
+ {!props.createItem && (
+ <>
+
+ {entry.options.title}
+
+ {entry.options.icon &&
+
+ }
+ >
+ )
}
);
diff --git a/src/vs/workbench/browser/positronComponents/dropDownListBox/dropDownListBoxItem.ts b/src/vs/workbench/browser/positronComponents/dropDownListBox/dropDownListBoxItem.ts
index 9834a8f8d55..dd9f318e808 100644
--- a/src/vs/workbench/browser/positronComponents/dropDownListBox/dropDownListBoxItem.ts
+++ b/src/vs/workbench/browser/positronComponents/dropDownListBox/dropDownListBoxItem.ts
@@ -5,20 +5,21 @@
/**
* DropDownListBoxItemOptions interface.
*/
-export interface DropDownListBoxItemOptions {
- readonly identifier: string;
- readonly title: string;
+export interface DropDownListBoxItemOptions, V extends NonNullable> {
+ readonly identifier: T;
+ readonly title?: string;
readonly icon?: string;
readonly disabled?: boolean;
+ value: V;
}
/**
* DropDownListBoxItem class.
*/
-export class DropDownListBoxItem {
+export class DropDownListBoxItem, V extends NonNullable> {
/**
* Constructor.
* @param options A DropDownListBoxItemOptions that contains the down list box item options.
*/
- constructor(readonly options: DropDownListBoxItemOptions) { }
+ constructor(readonly options: DropDownListBoxItemOptions) { }
}
diff --git a/src/vs/workbench/browser/positronComponents/positronModalDialog/components/labeledFolderInput.css b/src/vs/workbench/browser/positronComponents/positronModalDialog/components/labeledFolderInput.css
index e95dc74f3d9..6df70db0ca2 100644
--- a/src/vs/workbench/browser/positronComponents/positronModalDialog/components/labeledFolderInput.css
+++ b/src/vs/workbench/browser/positronComponents/positronModalDialog/components/labeledFolderInput.css
@@ -24,7 +24,7 @@
border-radius: 4px;
color: var(--vscode-positronModalDialog-buttonForeground);
background: var(--vscode-positronModalDialog-buttonBackground);
-
+ border: 1px solid var(--vscode-positronModalDialog-buttonBorder);
}
.positron-modal-dialog-box
diff --git a/src/vs/workbench/browser/positronComponents/positronModalDialog/components/okCancelBackNextActionBar.css b/src/vs/workbench/browser/positronComponents/positronModalDialog/components/okCancelBackNextActionBar.css
index d995663cf13..6de5fb86ed6 100644
--- a/src/vs/workbench/browser/positronComponents/positronModalDialog/components/okCancelBackNextActionBar.css
+++ b/src/vs/workbench/browser/positronComponents/positronModalDialog/components/okCancelBackNextActionBar.css
@@ -47,3 +47,12 @@
align-items: end;
justify-content: space-between;
}
+
+.positron-modal-dialog-box
+.ok-cancel-action-bar
+.action-bar-button:disabled {
+ color: var(--vscode-positronModalDialog-buttonDisabledForeground);
+ background-color: var(--vscode-positronModalDialog-buttonDisabledBackground);
+ border: 1px solid var(--vscode-positronModalDialog-buttonDisabledBorder);
+ cursor: default;
+}
diff --git a/src/vs/workbench/browser/positronComponents/positronModalPopup/positronModalPopup.css b/src/vs/workbench/browser/positronComponents/positronModalPopup/positronModalPopup.css
index 74230d09f50..b3745ac8cf3 100644
--- a/src/vs/workbench/browser/positronComponents/positronModalPopup/positronModalPopup.css
+++ b/src/vs/workbench/browser/positronComponents/positronModalPopup/positronModalPopup.css
@@ -3,8 +3,13 @@
*--------------------------------------------------------------------------------------------*/
@keyframes fade-in {
- 0% { opacity: 0; }
- 100% { opacity: 1; }
+ 0% {
+ opacity: 0;
+ }
+
+ 100% {
+ opacity: 1;
+ }
}
.positron-modal-popup-container {
@@ -14,7 +19,7 @@
height: 100%;
position: absolute;
outline: none !important;
- animation: fade-in 0.25s;
+ /* animation: fade-in 0.25s; */
/* Makes some devices run their hardware acceleration. */
transform: translate3d(0px, 0px, 0px);
}
diff --git a/src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/addEditRowFilterModalPopup/addEditRowFilterModalPopup.tsx b/src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/addEditRowFilterModalPopup/addEditRowFilterModalPopup.tsx
index 7f8fdb2cfb8..8ee1c0a31a0 100644
--- a/src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/addEditRowFilterModalPopup/addEditRowFilterModalPopup.tsx
+++ b/src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/addEditRowFilterModalPopup/addEditRowFilterModalPopup.tsx
@@ -12,16 +12,29 @@ import { useEffect, useRef, useState } from 'react'; // eslint-disable-line no-d
// Other dependencies.
import { localize } from 'vs/nls';
import { Button } from 'vs/base/browser/ui/positronComponents/button/button';
-import { DropDownListBox } from 'vs/workbench/browser/positronComponents/dropDownListBox/dropDownListBox';
import { DropDownListBoxItem } from 'vs/workbench/browser/positronComponents/dropDownListBox/dropDownListBoxItem';
import { PositronModalPopup } from 'vs/workbench/browser/positronComponents/positronModalPopup/positronModalPopup';
+import { ColumnSchema, ColumnDisplayType } from 'vs/workbench/services/languageRuntime/common/positronDataExplorerComm';
import { PositronModalReactRenderer } from 'vs/workbench/browser/positronModalReactRenderer/positronModalReactRenderer';
import { DropDownListBoxSeparator } from 'vs/workbench/browser/positronComponents/dropDownListBox/dropDownListBoxSeparator';
import { DataExplorerClientInstance } from 'vs/workbench/services/languageRuntime/common/languageRuntimeDataExplorerClient';
-import { ColumnSchema, ColumnDisplayType } from 'vs/workbench/services/languageRuntime/common/positronDataExplorerComm';
+import { DropDownListBox, DropDownListBoxEntry } from 'vs/workbench/browser/positronComponents/dropDownListBox/dropDownListBox';
import { RowFilterParameter } from 'vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/addEditRowFilterModalPopup/components/rowFilterParameter';
import { DropDownColumnSelector } from 'vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/addEditRowFilterModalPopup/components/dropDownColumnSelector';
-import { RangeRowFilter, RowFilter, RowFilterCondition, RowFilterIsBetween, RowFilterIsEmpty, RowFilterIsEqualTo, RowFilterIsGreaterThan, RowFilterIsLessThan, RowFilterIsNotBetween, RowFilterIsNotEmpty, SingleValueRowFilter } from 'vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/addEditRowFilterModalPopup/rowFilter';
+import {
+ RangeRowFilterDescriptor,
+ RowFilterDescriptor,
+ RowFilterCondition,
+ RowFilterDescriptorComparison,
+ RowFilterDescriptorIsBetween,
+ RowFilterDescriptorIsEmpty,
+ RowFilterDescriptorIsNotBetween,
+ RowFilterDescriptorIsNotEmpty,
+ SingleValueRowFilterDescriptor,
+ RowFilterDescriptorIsNotNull,
+ RowFilterDescriptorIsNull,
+ RowFilterDescriptorSearch,
+} from 'vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/addEditRowFilterModalPopup/rowFilterDescriptor';
/**
* Validates a row filter value.
@@ -74,6 +87,43 @@ const validateRowFilterValue = (columnSchema: ColumnSchema, value: string) => {
}
};
+const conditionNumParams = (cond: RowFilterCondition | undefined) => {
+ switch (cond) {
+ case undefined:
+ case RowFilterCondition.CONDITION_IS_EMPTY:
+ case RowFilterCondition.CONDITION_IS_NOT_EMPTY:
+ case RowFilterCondition.CONDITION_IS_NULL:
+ case RowFilterCondition.CONDITION_IS_NOT_NULL:
+ return 0;
+ case RowFilterCondition.CONDITION_IS_EQUAL_TO:
+ case RowFilterCondition.CONDITION_IS_NOT_EQUAL_TO:
+ case RowFilterCondition.CONDITION_IS_GREATER_OR_EQUAL:
+ case RowFilterCondition.CONDITION_IS_GREATER_THAN:
+ case RowFilterCondition.CONDITION_IS_LESS_OR_EQUAL:
+ case RowFilterCondition.CONDITION_IS_LESS_THAN:
+ case RowFilterCondition.CONDITION_SEARCH_CONTAINS:
+ case RowFilterCondition.CONDITION_SEARCH_STARTS_WITH:
+ case RowFilterCondition.CONDITION_SEARCH_ENDS_WITH:
+ case RowFilterCondition.CONDITION_SEARCH_REGEX_MATCHES:
+ return 1;
+ case RowFilterCondition.CONDITION_IS_BETWEEN:
+ case RowFilterCondition.CONDITION_IS_NOT_BETWEEN:
+ return 2;
+ }
+};
+
+/**
+ * Checks whether a RowFilterCondition is a comparison or not.
+ * @param cond A row filter condition.
+ * @returns Whether the condition is a comparison.
+ */
+const isSingleParam = (cond: RowFilterCondition | undefined) => {
+ if (cond === undefined) {
+ return false;
+ }
+ return conditionNumParams(cond) === 1;
+};
+
/**
* AddEditRowFilterModalPopupProps interface.
*/
@@ -81,8 +131,8 @@ interface AddEditRowFilterModalPopupProps {
dataExplorerClientInstance: DataExplorerClientInstance;
renderer: PositronModalReactRenderer;
anchor: HTMLElement;
- editRowFilter?: RowFilter;
- onApplyRowFilter: (rowFilter: RowFilter) => void;
+ editRowFilter?: RowFilterDescriptor;
+ onApplyRowFilter: (rowFilter: RowFilterDescriptor) => void;
}
/**
@@ -99,20 +149,20 @@ export const AddEditRowFilterModalPopup = (props: AddEditRowFilterModalPopupProp
const [selectedColumnSchema, setSelectedColumnSchema] = useState(
props.editRowFilter?.columnSchema
);
- const [selectedCondition, setSelectedCondition] = useState(
+ const [selectedCondition, setSelectedCondition] = useState(
props.editRowFilter?.rowFilterCondition
);
const [firstRowFilterValue, setFirstRowFilterValue] = useState(() => {
- if (props.editRowFilter instanceof SingleValueRowFilter) {
+ if (props.editRowFilter instanceof SingleValueRowFilterDescriptor) {
return props.editRowFilter.value;
- } else if (props.editRowFilter instanceof RangeRowFilter) {
+ } else if (props.editRowFilter instanceof RangeRowFilterDescriptor) {
return props.editRowFilter.lowerLimit;
} else {
return '';
}
});
const [secondRowFilterValue, setSecondRowFilterValue] = useState(() => {
- if (props.editRowFilter instanceof RangeRowFilter) {
+ if (props.editRowFilter instanceof RangeRowFilterDescriptor) {
return props.editRowFilter.upperLimit;
} else {
return '';
@@ -139,25 +189,81 @@ export const AddEditRowFilterModalPopup = (props: AddEditRowFilterModalPopupProp
}
// Build the condition entries.
- const conditionEntries: (DropDownListBoxItem | DropDownListBoxSeparator)[] = [];
+ const conditionEntries: DropDownListBoxEntry[] = [];
- // Every type allows is empty and is not empty conditions.
+ // Every type allows is null and is not null conditions.
conditionEntries.push(new DropDownListBoxItem({
- identifier: RowFilterCondition.CONDITION_IS_EMPTY,
+ identifier: RowFilterCondition.CONDITION_IS_NULL,
title: localize(
- 'positron.addEditRowFilter.conditionIsEmpty',
- "is empty"
- )
+ 'positron.addEditRowFilter.conditionIsNull',
+ "is null"
+ ),
+ value: RowFilterCondition.CONDITION_IS_NULL
}));
conditionEntries.push(new DropDownListBoxItem({
- identifier: RowFilterCondition.CONDITION_IS_NOT_EMPTY,
+ identifier: RowFilterCondition.CONDITION_IS_NOT_NULL,
title: localize(
- 'positron.addEditRowFilter.conditionIsNotEmpty',
- "is not empty"
- )
+ 'positron.addEditRowFilter.conditionIsNotNull',
+ "is not null"
+ ),
+ value: RowFilterCondition.CONDITION_IS_NOT_NULL
}));
conditionEntries.push(new DropDownListBoxSeparator());
+ if (selectedColumnSchema.type_display === ColumnDisplayType.String) {
+ conditionEntries.push(new DropDownListBoxItem({
+ identifier: RowFilterCondition.CONDITION_SEARCH_CONTAINS,
+ title: localize(
+ 'positron.addEditRowFilter.conditionSearchContains',
+ "contains"
+ ),
+ value: RowFilterCondition.CONDITION_SEARCH_CONTAINS
+ }));
+ conditionEntries.push(new DropDownListBoxItem({
+ identifier: RowFilterCondition.CONDITION_SEARCH_STARTS_WITH,
+ title: localize(
+ 'positron.addEditRowFilter.conditionSearchStartsWith',
+ "starts with"
+ ),
+ value: RowFilterCondition.CONDITION_SEARCH_STARTS_WITH
+ }));
+ conditionEntries.push(new DropDownListBoxItem({
+ identifier: RowFilterCondition.CONDITION_SEARCH_ENDS_WITH,
+ title: localize(
+ 'positron.addEditRowFilter.conditionSearchEndsWith',
+ "ends with"
+ ),
+ value: RowFilterCondition.CONDITION_SEARCH_ENDS_WITH
+ }));
+ conditionEntries.push(new DropDownListBoxItem({
+ identifier: RowFilterCondition.CONDITION_SEARCH_REGEX_MATCHES,
+ title: localize(
+ 'positron.addEditRowFilter.conditionSearchRegexMatches',
+ "regex matches"
+ ),
+ value: RowFilterCondition.CONDITION_SEARCH_REGEX_MATCHES
+ }));
+
+ // String types support is empty, is not empty filter types
+ conditionEntries.push(new DropDownListBoxItem({
+ identifier: RowFilterCondition.CONDITION_IS_EMPTY,
+ title: localize(
+ 'positron.addEditRowFilter.conditionIsEmpty',
+ "is empty"
+ ),
+ value: RowFilterCondition.CONDITION_IS_EMPTY
+ }));
+ conditionEntries.push(new DropDownListBoxItem({
+ identifier: RowFilterCondition.CONDITION_IS_NOT_EMPTY,
+ title: localize(
+ 'positron.addEditRowFilter.conditionIsNotEmpty',
+ "is not empty"
+ ),
+ value: RowFilterCondition.CONDITION_IS_NOT_EMPTY
+ }));
+ conditionEntries.push(new DropDownListBoxSeparator());
+ }
+
// Add is less than / is greater than conditions.
switch (selectedColumnSchema.type_display) {
case ColumnDisplayType.Number:
@@ -169,19 +275,37 @@ export const AddEditRowFilterModalPopup = (props: AddEditRowFilterModalPopupProp
title: localize(
'positron.addEditRowFilter.conditionIsLessThan',
"is less than"
- )
+ ),
+ value: RowFilterCondition.CONDITION_IS_LESS_THAN
+ }));
+ conditionEntries.push(new DropDownListBoxItem({
+ identifier: RowFilterCondition.CONDITION_IS_LESS_OR_EQUAL,
+ title: localize(
+ 'positron.addEditRowFilter.conditionIsLessThanOrEqual',
+ "is less than or equal to"
+ ),
+ value: RowFilterCondition.CONDITION_IS_LESS_OR_EQUAL
}));
conditionEntries.push(new DropDownListBoxItem({
identifier: RowFilterCondition.CONDITION_IS_GREATER_THAN,
title: localize(
'positron.addEditRowFilter.conditionIsGreaterThan',
"is greater than"
- )
+ ),
+ value: RowFilterCondition.CONDITION_IS_GREATER_THAN
+ }));
+ conditionEntries.push(new DropDownListBoxItem({
+ identifier: RowFilterCondition.CONDITION_IS_GREATER_OR_EQUAL,
+ title: localize(
+ 'positron.addEditRowFilter.conditionIsGreaterThanOrEqual',
+ "is greater than or equal to"
+ ),
+ value: RowFilterCondition.CONDITION_IS_GREATER_OR_EQUAL
}));
break;
}
- // Add is equal to condition.
+ // Add is equal to, is not equal to conditions.
switch (selectedColumnSchema.type_display) {
case ColumnDisplayType.Number:
case ColumnDisplayType.Boolean:
@@ -194,7 +318,16 @@ export const AddEditRowFilterModalPopup = (props: AddEditRowFilterModalPopupProp
title: localize(
'positron.addEditRowFilter.conditionIsEqualTo',
"is equal to"
- )
+ ),
+ value: RowFilterCondition.CONDITION_IS_EQUAL_TO
+ }));
+ conditionEntries.push(new DropDownListBoxItem({
+ identifier: RowFilterCondition.CONDITION_IS_NOT_EQUAL_TO,
+ title: localize(
+ 'positron.addEditRowFilter.conditionIsNotEqualTo',
+ "is not equal to"
+ ),
+ value: RowFilterCondition.CONDITION_IS_NOT_EQUAL_TO
}));
break;
}
@@ -211,14 +344,16 @@ export const AddEditRowFilterModalPopup = (props: AddEditRowFilterModalPopupProp
title: localize(
'positron.addEditRowFilter.conditionIsBetween',
"is between"
- )
+ ),
+ value: RowFilterCondition.CONDITION_IS_BETWEEN
}));
conditionEntries.push(new DropDownListBoxItem({
identifier: RowFilterCondition.CONDITION_IS_NOT_BETWEEN,
title: localize(
'positron.addEditRowFilter.conditionIsNotBetween',
"is not between"
- )
+ ),
+ value: RowFilterCondition.CONDITION_IS_NOT_BETWEEN
}));
break;
}
@@ -227,20 +362,19 @@ export const AddEditRowFilterModalPopup = (props: AddEditRowFilterModalPopupProp
return conditionEntries;
};
+ const numParams = conditionNumParams(selectedCondition);
+
// Set the first row filter parameter component.
const firstRowFilterParameterComponent = (() => {
let placeholderText: string | undefined = undefined;
- switch (selectedCondition) {
+
+ switch (numParams) {
// Do not render the first row filter parameter component.
- case undefined:
- case RowFilterCondition.CONDITION_IS_EMPTY:
- case RowFilterCondition.CONDITION_IS_NOT_EMPTY:
+ case 0:
return null;
// Render the first row filter parameter component in single-value mode.
- case RowFilterCondition.CONDITION_IS_LESS_THAN:
- case RowFilterCondition.CONDITION_IS_GREATER_THAN:
- case RowFilterCondition.CONDITION_IS_EQUAL_TO:
+ case 1:
placeholderText = localize(
'positron.addEditRowFilter.valuePlaceholder',
"value"
@@ -248,8 +382,8 @@ export const AddEditRowFilterModalPopup = (props: AddEditRowFilterModalPopupProp
break;
// Render the first row filter parameter component in two-value mode.
- case RowFilterCondition.CONDITION_IS_BETWEEN:
- case RowFilterCondition.CONDITION_IS_NOT_BETWEEN:
+ case 2:
+ // TODO: handle between vs. other type of conditions with two parameters
placeholderText = localize(
'positron.addEditRowFilter.lowerLimitPlaceholder',
"lower limit"
@@ -277,19 +411,15 @@ export const AddEditRowFilterModalPopup = (props: AddEditRowFilterModalPopupProp
// Set the second row filter parameter component.
const secondRowFilterParameterComponent = (() => {
let placeholderText: string | undefined = undefined;
- switch (selectedCondition) {
+ switch (numParams) {
// Do not render the second row filter parameter component.
- case undefined:
- case RowFilterCondition.CONDITION_IS_EMPTY:
- case RowFilterCondition.CONDITION_IS_NOT_EMPTY:
- case RowFilterCondition.CONDITION_IS_LESS_THAN:
- case RowFilterCondition.CONDITION_IS_GREATER_THAN:
- case RowFilterCondition.CONDITION_IS_EQUAL_TO:
+ case 0:
+ case 1:
return null;
// Render the second row filter parameter component in two-value mode.
- case RowFilterCondition.CONDITION_IS_BETWEEN:
- case RowFilterCondition.CONDITION_IS_NOT_BETWEEN:
+ case 2:
+ // TODO: handle between vs. other type of conditions with two parameters
placeholderText = localize(
'positron.addEditRowFilter.upperLimitPlaceholder',
"upper limit"
@@ -442,7 +572,7 @@ export const AddEditRowFilterModalPopup = (props: AddEditRowFilterModalPopupProp
* Applies a row filter.
* @param rowFilter The row filter to add.
*/
- const applyRowFilter = (rowFilter: RowFilter) => {
+ const applyRowFilter = (rowFilter: RowFilterDescriptor) => {
setErrorText(undefined);
props.renderer.dispose();
props.onApplyRowFilter(rowFilter);
@@ -452,40 +582,59 @@ export const AddEditRowFilterModalPopup = (props: AddEditRowFilterModalPopupProp
switch (selectedCondition) {
// Apply the is empty row filter.
case RowFilterCondition.CONDITION_IS_EMPTY: {
- applyRowFilter(new RowFilterIsEmpty(selectedColumnSchema));
+ applyRowFilter(new RowFilterDescriptorIsEmpty(selectedColumnSchema));
break;
}
// Apply the is not empty row filter.
case RowFilterCondition.CONDITION_IS_NOT_EMPTY: {
- applyRowFilter(new RowFilterIsNotEmpty(selectedColumnSchema));
+ applyRowFilter(new RowFilterDescriptorIsNotEmpty(selectedColumnSchema));
break;
}
- // Apply the is less than row filter.
- case RowFilterCondition.CONDITION_IS_LESS_THAN: {
- if (!validateFirstRowFilterValue()) {
- return;
- }
- applyRowFilter(new RowFilterIsLessThan(selectedColumnSchema, firstRowFilterValue));
+ // Apply the is null row filter.
+ case RowFilterCondition.CONDITION_IS_NULL: {
+ applyRowFilter(new RowFilterDescriptorIsNull(selectedColumnSchema));
+ break;
+ }
+
+ // Apply the is not null row filter.
+ case RowFilterCondition.CONDITION_IS_NOT_NULL: {
+ applyRowFilter(new RowFilterDescriptorIsNotNull(selectedColumnSchema));
break;
}
- // Apply the is greater than row filter.
- case RowFilterCondition.CONDITION_IS_GREATER_THAN: {
+ // Apply comparison row filter.
+ case RowFilterCondition.CONDITION_SEARCH_CONTAINS:
+ case RowFilterCondition.CONDITION_SEARCH_STARTS_WITH:
+ case RowFilterCondition.CONDITION_SEARCH_ENDS_WITH:
+ case RowFilterCondition.CONDITION_SEARCH_REGEX_MATCHES: {
if (!validateFirstRowFilterValue()) {
return;
}
- applyRowFilter(new RowFilterIsGreaterThan(selectedColumnSchema, firstRowFilterValue));
+ applyRowFilter(new RowFilterDescriptorSearch(
+ selectedColumnSchema,
+ firstRowFilterValue,
+ selectedCondition
+ ));
break;
}
- // Apply the is equal to row filter.
- case RowFilterCondition.CONDITION_IS_EQUAL_TO: {
+ // Apply comparison row filter.
+ case RowFilterCondition.CONDITION_IS_LESS_THAN:
+ case RowFilterCondition.CONDITION_IS_LESS_OR_EQUAL:
+ case RowFilterCondition.CONDITION_IS_GREATER_THAN:
+ case RowFilterCondition.CONDITION_IS_GREATER_OR_EQUAL:
+ case RowFilterCondition.CONDITION_IS_EQUAL_TO:
+ case RowFilterCondition.CONDITION_IS_NOT_EQUAL_TO: {
if (!validateFirstRowFilterValue()) {
return;
}
- applyRowFilter(new RowFilterIsEqualTo(selectedColumnSchema, firstRowFilterValue));
+ applyRowFilter(new RowFilterDescriptorComparison(
+ selectedColumnSchema,
+ firstRowFilterValue,
+ selectedCondition
+ ));
break;
}
@@ -497,7 +646,7 @@ export const AddEditRowFilterModalPopup = (props: AddEditRowFilterModalPopupProp
if (!validateSecondRowFilterValue()) {
return;
}
- applyRowFilter(new RowFilterIsBetween(
+ applyRowFilter(new RowFilterDescriptorIsBetween(
selectedColumnSchema,
firstRowFilterValue,
secondRowFilterValue
@@ -513,7 +662,7 @@ export const AddEditRowFilterModalPopup = (props: AddEditRowFilterModalPopupProp
if (!validateSecondRowFilterValue()) {
return;
}
- applyRowFilter(new RowFilterIsNotBetween(
+ applyRowFilter(new RowFilterDescriptorIsNotBetween(
selectedColumnSchema,
firstRowFilterValue,
secondRowFilterValue
@@ -575,14 +724,19 @@ export const AddEditRowFilterModalPopup = (props: AddEditRowFilterModalPopupProp
))()}
entries={conditionEntries()}
selectedIdentifier={selectedCondition}
- onSelectionChanged={identifier => {
+ onSelectionChanged={dropDownListBoxItem => {
+ const prevSelected = selectedCondition;
+ const nextSelected = dropDownListBoxItem.options.identifier;
// Set the selected condition.
- setSelectedCondition(identifier);
+ setSelectedCondition(nextSelected);
// Clear the filter values and error text.
- clearFilterValuesAndErrorText();
+ if (!(isSingleParam(prevSelected) && isSingleParam(nextSelected))) {
+ clearFilterValuesAndErrorText();
+ }
}}
/>
+
{firstRowFilterParameterComponent}
{secondRowFilterParameterComponent}
{errorText && (
diff --git a/src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/addEditRowFilterModalPopup/rowFilter.ts b/src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/addEditRowFilterModalPopup/rowFilter.ts
deleted file mode 100644
index af899b68472..00000000000
--- a/src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/addEditRowFilterModalPopup/rowFilter.ts
+++ /dev/null
@@ -1,238 +0,0 @@
-/*---------------------------------------------------------------------------------------------
- * Copyright (C) 2024 Posit Software, PBC. All rights reserved.
- *--------------------------------------------------------------------------------------------*/
-
-import { generateUuid } from 'vs/base/common/uuid';
-import { ColumnSchema } from 'vs/workbench/services/languageRuntime/common/positronDataExplorerComm';
-
-/**
- * RowFilterCondition enumeration.
- */
-export enum RowFilterCondition {
- // Conditions with no parameters.
- CONDITION_IS_EMPTY = 'is-empty',
- CONDITION_IS_NOT_EMPTY = 'is-not-empty',
-
- // Conditions with one parameter.
- CONDITION_IS_LESS_THAN = 'is-less-than',
- CONDITION_IS_GREATER_THAN = 'is-greater-than',
- CONDITION_IS_EQUAL_TO = 'is-equal-to',
-
- // Conditions with two parameters.
- CONDITION_IS_BETWEEN = 'is-between',
- CONDITION_IS_NOT_BETWEEN = 'is-not-between'
-}
-
-/**
- * BaseRowFilter class.
- */
-abstract class BaseRowFilter {
- /**
- * Gets the identifier.
- */
- readonly identifier;
-
- /**
- * Constructor.
- * @param columnSchema The column schema.
- */
- constructor(public readonly columnSchema: ColumnSchema) {
- this.identifier = generateUuid();
- }
-
- /**
- * Gets the row filter condition.
- */
- abstract get rowFilterCondition(): RowFilterCondition;
-}
-
-/**
- * RowFilterIsEmpty class.
- */
-export class RowFilterIsEmpty extends BaseRowFilter {
- /**
- * Constructor.
- * @param columnSchema The column schema.
- */
- constructor(columnSchema: ColumnSchema) {
- super(columnSchema);
- }
-
- /**
- * Gets the row filter condition.
- */
- get rowFilterCondition() {
- return RowFilterCondition.CONDITION_IS_EMPTY;
- }
-}
-
-/**
- * RowFilterIsNotEmpty class.
- */
-export class RowFilterIsNotEmpty extends BaseRowFilter {
- /**
- * Constructor.
- * @param columnSchema The column schema.
- */
- constructor(columnSchema: ColumnSchema) {
- super(columnSchema);
- }
-
- /**
- * Gets the row filter condition.
- */
- get rowFilterCondition() {
- return RowFilterCondition.CONDITION_IS_NOT_EMPTY;
- }
-}
-
-/**
- * SingleValueRowFilter class.
- */
-export abstract class SingleValueRowFilter extends BaseRowFilter {
- /**
- * Constructor.
- * @param columnSchema The column schema.
- * @param value The value.
- */
- constructor(columnSchema: ColumnSchema, public readonly value: string) {
- super(columnSchema);
- }
-}
-
-/**
- * RowFilterIsLessThan row filter.
- */
-export class RowFilterIsLessThan extends SingleValueRowFilter {
- /**
- * Constructor.
- * @param columnSchema The column schema.
- * @param value The value.
- */
- constructor(columnSchema: ColumnSchema, value: string) {
- super(columnSchema, value);
- }
-
- /**
- * Gets the row filter condition.
- */
- get rowFilterCondition() {
- return RowFilterCondition.CONDITION_IS_LESS_THAN;
- }
-}
-
-/**
- * RowFilterIsGreaterThan row filter.
- */
-export class RowFilterIsGreaterThan extends SingleValueRowFilter {
- /**
- * Constructor.
- * @param columnSchema The column schema.
- * @param value The value.
- */
- constructor(columnSchema: ColumnSchema, value: string) {
- super(columnSchema, value);
- }
-
- /**
- * Gets the row filter condition.
- */
- get rowFilterCondition() {
- return RowFilterCondition.CONDITION_IS_GREATER_THAN;
- }
-}
-
-/**
- * RowFilterIsEqualTo row filter.
- */
-export class RowFilterIsEqualTo extends SingleValueRowFilter {
- /**
- * Constructor.
- * @param columnSchema The column schema.
- * @param value The value.
- */
- constructor(columnSchema: ColumnSchema, value: string) {
- super(columnSchema, value);
- }
-
- /**
- * Gets the row filter condition.
- */
- get rowFilterCondition() {
- return RowFilterCondition.CONDITION_IS_EQUAL_TO;
- }
-}
-
-/**
- * RangeRowFilter class.
- */
-export abstract class RangeRowFilter extends BaseRowFilter {
- /**
- * Constructor.
- * @param columnSchema The column schema.
- * @param lowerLimit The lower limit.
- * @param upperLimit The lower limit.
- */
- constructor(
- columnSchema: ColumnSchema,
- public readonly lowerLimit: string,
- public readonly upperLimit: string
- ) {
- super(columnSchema);
- }
-}
-
-/**
- * RowFilterIsBetween row filter.
- */
-export class RowFilterIsBetween extends RangeRowFilter {
- /**
- * Constructor.
- * @param columnSchema The column schema.
- * @param lowerLimit The lower limit.
- * @param upperLimit The lower limit.
- */
- constructor(columnSchema: ColumnSchema, lowerLimit: string, upperLimit: string) {
- super(columnSchema, lowerLimit, upperLimit);
- }
-
- /**
- * Gets the row filter condition.
- */
- get rowFilterCondition() {
- return RowFilterCondition.CONDITION_IS_BETWEEN;
- }
-}
-
-/**
- * RowFilterIsNotBetween row filter.
- */
-export class RowFilterIsNotBetween extends RangeRowFilter {
- /**
- * Constructor.
- * @param columnSchema The column schema.
- * @param lowerLimit The lower limit.
- * @param upperLimit The lower limit.
- */
- constructor(columnSchema: ColumnSchema, lowerLimit: string, upperLimit: string) {
- super(columnSchema, lowerLimit, upperLimit);
- }
-
- /**
- * Gets the row filter condition.
- */
- get rowFilterCondition() {
- return RowFilterCondition.CONDITION_IS_NOT_BETWEEN;
- }
-}
-
-/**
- * RowFilter type.
- */
-export type RowFilter =
- RowFilterIsEmpty |
- RowFilterIsNotEmpty |
- RowFilterIsLessThan |
- RowFilterIsGreaterThan |
- RowFilterIsBetween |
- RowFilterIsNotBetween;
diff --git a/src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/addEditRowFilterModalPopup/rowFilterDescriptor.ts b/src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/addEditRowFilterModalPopup/rowFilterDescriptor.ts
new file mode 100644
index 00000000000..7a12c317f3e
--- /dev/null
+++ b/src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/addEditRowFilterModalPopup/rowFilterDescriptor.ts
@@ -0,0 +1,341 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (C) 2024 Posit Software, PBC. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+import { generateUuid } from 'vs/base/common/uuid';
+import { ColumnSchema, CompareFilterParamsOp, SearchFilterType } from 'vs/workbench/services/languageRuntime/common/positronDataExplorerComm';
+
+/**
+ * RowFilterCondition enumeration.
+ */
+export enum RowFilterCondition {
+ // Conditions with no parameters.
+ CONDITION_IS_EMPTY = 'is-empty',
+ CONDITION_IS_NOT_EMPTY = 'is-not-empty',
+ CONDITION_IS_NULL = 'is-null',
+ CONDITION_IS_NOT_NULL = 'is-not-null',
+
+ // Conditions with one parameter.
+ CONDITION_IS_LESS_THAN = 'is-less-than',
+ CONDITION_IS_LESS_OR_EQUAL = 'is-less-than-or-equal-to',
+ CONDITION_IS_GREATER_THAN = 'is-greater-than',
+ CONDITION_IS_GREATER_OR_EQUAL = 'is-greater-than-or-equal-to',
+ CONDITION_IS_EQUAL_TO = 'is-equal-to',
+ CONDITION_IS_NOT_EQUAL_TO = 'is-not-equal-to',
+ CONDITION_SEARCH_CONTAINS = 'search-contains',
+ CONDITION_SEARCH_STARTS_WITH = 'search-starts-with',
+ CONDITION_SEARCH_ENDS_WITH = 'search-ends-with',
+ CONDITION_SEARCH_REGEX_MATCHES = 'search-regex',
+
+ // Conditions with two parameters.
+ CONDITION_IS_BETWEEN = 'is-between',
+ CONDITION_IS_NOT_BETWEEN = 'is-not-between'
+}
+
+/**
+ * BaseRowFilterDescriptor class.
+ */
+abstract class BaseRowFilterDescriptor {
+ /**
+ * Gets the identifier.
+ */
+ readonly identifier;
+
+ /**
+ * Constructor.
+ * @param columnSchema The column schema.
+ */
+ constructor(public readonly columnSchema: ColumnSchema) {
+ this.identifier = generateUuid();
+ }
+
+ /**
+ * Gets the row filter condition.
+ */
+ abstract get rowFilterCondition(): RowFilterCondition;
+}
+
+/**
+ * RowFilterDescriptorIsEmpty class.
+ */
+export class RowFilterDescriptorIsEmpty extends BaseRowFilterDescriptor {
+ /**
+ * Constructor.
+ * @param columnSchema The column schema.
+ */
+ constructor(columnSchema: ColumnSchema) {
+ super(columnSchema);
+ }
+
+ /**
+ * Gets the row filter condition.
+ */
+ get rowFilterCondition() {
+ return RowFilterCondition.CONDITION_IS_EMPTY;
+ }
+}
+
+/**
+ * RowFilterDescriptorIsNotEmpty class.
+ */
+export class RowFilterDescriptorIsNotEmpty extends BaseRowFilterDescriptor {
+ /**
+ * Constructor.
+ * @param columnSchema The column schema.
+ */
+ constructor(columnSchema: ColumnSchema) {
+ super(columnSchema);
+ }
+
+ /**
+ * Gets the row filter condition.
+ */
+ get rowFilterCondition() {
+ return RowFilterCondition.CONDITION_IS_NOT_EMPTY;
+ }
+}
+
+/**
+ * RowFilterDescriptorIsNull class.
+ */
+export class RowFilterDescriptorIsNull extends BaseRowFilterDescriptor {
+ /**
+ * Constructor.
+ * @param columnSchema The column schema.
+ */
+ constructor(columnSchema: ColumnSchema) {
+ super(columnSchema);
+ }
+
+ /**
+ * Gets the row filter condition.
+ */
+ get rowFilterCondition() {
+ return RowFilterCondition.CONDITION_IS_NULL;
+ }
+}
+
+/**
+ * RowFilterDescriptorIsNotEmpty class.
+ */
+export class RowFilterDescriptorIsNotNull extends BaseRowFilterDescriptor {
+ /**
+ * Constructor.
+ * @param columnSchema The column schema.
+ */
+ constructor(columnSchema: ColumnSchema) {
+ super(columnSchema);
+ }
+
+ /**
+ * Gets the row filter condition.
+ */
+ get rowFilterCondition() {
+ return RowFilterCondition.CONDITION_IS_NOT_NULL;
+ }
+}
+
+/**
+ * SingleValueRowFilterDescriptor class.
+ */
+export abstract class SingleValueRowFilterDescriptor extends BaseRowFilterDescriptor {
+ /**
+ * Constructor.
+ * @param columnSchema The column schema.
+ * @param value The value.
+ */
+ constructor(columnSchema: ColumnSchema, public readonly value: string) {
+ super(columnSchema);
+ }
+}
+
+/**
+ * RowFilterDescriptorComparison class.
+ */
+export class RowFilterDescriptorComparison extends SingleValueRowFilterDescriptor {
+ /**
+ * Constructor.
+ * @param columnSchema The column schema.
+ * @param value The value.
+ * @param condition The filter condition.
+ */
+ condition: RowFilterCondition;
+
+ constructor(columnSchema: ColumnSchema, value: string, condition: RowFilterCondition) {
+ super(columnSchema, value);
+ this.condition = condition;
+ }
+
+ get operatorText() {
+ switch (this.condition) {
+ case RowFilterCondition.CONDITION_IS_EQUAL_TO:
+ return '=';
+ case RowFilterCondition.CONDITION_IS_GREATER_OR_EQUAL:
+ return '>=';
+ case RowFilterCondition.CONDITION_IS_GREATER_THAN:
+ return '>';
+ case RowFilterCondition.CONDITION_IS_LESS_OR_EQUAL:
+ return '<=';
+ case RowFilterCondition.CONDITION_IS_LESS_THAN:
+ return '<';
+ case RowFilterCondition.CONDITION_IS_NOT_EQUAL_TO:
+ return '!=';
+ default:
+ return '';
+ }
+ }
+
+ get compareFilterOp() {
+ switch (this.condition) {
+ case RowFilterCondition.CONDITION_IS_EQUAL_TO:
+ return CompareFilterParamsOp.Eq;
+ case RowFilterCondition.CONDITION_IS_GREATER_OR_EQUAL:
+ return CompareFilterParamsOp.GtEq;
+ case RowFilterCondition.CONDITION_IS_GREATER_THAN:
+ return CompareFilterParamsOp.Gt;
+ case RowFilterCondition.CONDITION_IS_LESS_OR_EQUAL:
+ return CompareFilterParamsOp.LtEq;
+ case RowFilterCondition.CONDITION_IS_LESS_THAN:
+ return CompareFilterParamsOp.Lt;
+ default:
+ // CONDITION_IS_NOT_EQUAL_TO
+ return CompareFilterParamsOp.NotEq;
+ }
+ }
+
+ /**
+ * Gets the row filter condition.
+ */
+ get rowFilterCondition() {
+ return this.condition;
+ }
+}
+
+/**
+ * RowFilterDescriptorSearch class.
+ */
+export class RowFilterDescriptorSearch extends SingleValueRowFilterDescriptor {
+ /**
+ * Constructor.
+ * @param columnSchema The column schema.
+ * @param value The value.
+ * @param condition The filter condition.
+ */
+ condition: RowFilterCondition;
+
+ constructor(columnSchema: ColumnSchema, value: string, condition: RowFilterCondition) {
+ super(columnSchema, value);
+ this.condition = condition;
+ }
+
+ get operatorText() {
+ switch (this.condition) {
+ case RowFilterCondition.CONDITION_SEARCH_CONTAINS:
+ return 'contains';
+ case RowFilterCondition.CONDITION_SEARCH_STARTS_WITH:
+ return 'starts with';
+ case RowFilterCondition.CONDITION_SEARCH_ENDS_WITH:
+ return 'ends with';
+ default:
+ // CONDITION_SEARCH_REGEX_MATCHES
+ return 'matches regex';
+ }
+ }
+
+ get searchOp() {
+ switch (this.condition) {
+ case RowFilterCondition.CONDITION_SEARCH_CONTAINS:
+ return SearchFilterType.Contains;
+ case RowFilterCondition.CONDITION_SEARCH_STARTS_WITH:
+ return SearchFilterType.StartsWith;
+ case RowFilterCondition.CONDITION_SEARCH_ENDS_WITH:
+ return SearchFilterType.EndsWith;
+ default:
+ // CONDITION_SEARCH_REGEX_MATCHES
+ return SearchFilterType.RegexMatch;
+ }
+ }
+
+ /**
+ * Gets the row filter condition.
+ */
+ get rowFilterCondition() {
+ return this.condition;
+ }
+}
+
+/**
+ * RangeRowFilterDescriptor class.
+ */
+export abstract class RangeRowFilterDescriptor extends BaseRowFilterDescriptor {
+ /**
+ * Constructor.
+ * @param columnSchema The column schema.
+ * @param lowerLimit The lower limit.
+ * @param upperLimit The lower limit.
+ */
+ constructor(
+ columnSchema: ColumnSchema,
+ public readonly lowerLimit: string,
+ public readonly upperLimit: string
+ ) {
+ super(columnSchema);
+ }
+}
+
+/**
+ * RowFilterDescriptorIsBetween class.
+ */
+export class RowFilterDescriptorIsBetween extends RangeRowFilterDescriptor {
+ /**
+ * Constructor.
+ * @param columnSchema The column schema.
+ * @param lowerLimit The lower limit.
+ * @param upperLimit The lower limit.
+ */
+ constructor(columnSchema: ColumnSchema, lowerLimit: string, upperLimit: string) {
+ super(columnSchema, lowerLimit, upperLimit);
+ }
+
+ /**
+ * Gets the row filter condition.
+ */
+ get rowFilterCondition() {
+ return RowFilterCondition.CONDITION_IS_BETWEEN;
+ }
+}
+
+/**
+ * RowFilterDescriptorIsNotBetween class.
+ */
+export class RowFilterDescriptorIsNotBetween extends RangeRowFilterDescriptor {
+ /**
+ * Constructor.
+ * @param columnSchema The column schema.
+ * @param lowerLimit The lower limit.
+ * @param upperLimit The lower limit.
+ */
+ constructor(columnSchema: ColumnSchema, lowerLimit: string, upperLimit: string) {
+ super(columnSchema, lowerLimit, upperLimit);
+ }
+
+ /**
+ * Gets the row filter condition.
+ */
+ get rowFilterCondition() {
+ return RowFilterCondition.CONDITION_IS_NOT_BETWEEN;
+ }
+}
+
+/**
+ * RowFilterDescriptor type.
+ */
+export type RowFilterDescriptor =
+ RowFilterDescriptorComparison |
+ RowFilterDescriptorIsEmpty |
+ RowFilterDescriptorIsNotEmpty |
+ RowFilterDescriptorIsNull |
+ RowFilterDescriptorIsNotNull |
+ RowFilterDescriptorIsBetween |
+ RowFilterDescriptorIsNotBetween |
+ RowFilterDescriptorSearch;
diff --git a/src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/rowFilterBar/components/rowFilterWidget.tsx b/src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/rowFilterBar/components/rowFilterWidget.tsx
index 277f09f7a69..d7e2e683189 100644
--- a/src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/rowFilterBar/components/rowFilterWidget.tsx
+++ b/src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/rowFilterBar/components/rowFilterWidget.tsx
@@ -12,13 +12,23 @@ import { forwardRef } from 'react'; // eslint-disable-line no-duplicate-imports
// Other dependencies.
import { localize } from 'vs/nls';
import { Button } from 'vs/base/browser/ui/positronComponents/button/button';
-import { RowFilter, RowFilterIsBetween, RowFilterIsEmpty, RowFilterIsEqualTo, RowFilterIsGreaterThan, RowFilterIsLessThan, RowFilterIsNotBetween, RowFilterIsNotEmpty } from 'vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/addEditRowFilterModalPopup/rowFilter';
+import {
+ RowFilterDescriptor,
+ RowFilterDescriptorComparison,
+ RowFilterDescriptorIsBetween,
+ RowFilterDescriptorIsEmpty,
+ RowFilterDescriptorIsNotBetween,
+ RowFilterDescriptorIsNotEmpty,
+ RowFilterDescriptorIsNotNull,
+ RowFilterDescriptorIsNull,
+ RowFilterDescriptorSearch
+} from 'vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/addEditRowFilterModalPopup/rowFilterDescriptor';
/**
* RowFilterWidgetProps interface.
*/
interface RowFilterWidgetProps {
- rowFilter: RowFilter;
+ rowFilter: RowFilterDescriptor;
booleanOperator?: 'and';
onEdit: () => void;
onClear: () => void;
@@ -32,39 +42,48 @@ interface RowFilterWidgetProps {
export const RowFilterWidget = forwardRef((props, ref) => {
// Compute the title.
const title = (() => {
- if (props.rowFilter instanceof RowFilterIsEmpty) {
+ if (props.rowFilter instanceof RowFilterDescriptorIsEmpty) {
return <>
{props.rowFilter.columnSchema.column_name}
{localize('positron.dataExplorer.rowFilterWidget.isEmpty', "is empty")}
>;
- } else if (props.rowFilter instanceof RowFilterIsNotEmpty) {
+ } else if (props.rowFilter instanceof RowFilterDescriptorIsNotEmpty) {
return <>
{props.rowFilter.columnSchema.column_name}
{localize('positron.dataExplorer.rowFilterWidget.isNotEmpty', "is not empty")}
>;
- } else if (props.rowFilter instanceof RowFilterIsLessThan) {
+ } else if (props.rowFilter instanceof RowFilterDescriptorIsNull) {
return <>
{props.rowFilter.columnSchema.column_name}
- <
- {props.rowFilter.value}
+
+ {localize('positron.dataExplorer.rowFilterWidget.isNull', "is null")}
+
>;
- } else if (props.rowFilter instanceof RowFilterIsGreaterThan) {
+ } else if (props.rowFilter instanceof RowFilterDescriptorIsNotNull) {
return <>
{props.rowFilter.columnSchema.column_name}
- >
+
+ {localize('positron.dataExplorer.rowFilterWidget.isNotNull', "is not null")}
+
+ >;
+
+ } else if (props.rowFilter instanceof RowFilterDescriptorComparison) {
+ return <>
+ {props.rowFilter.columnSchema.column_name}
+ {props.rowFilter.operatorText}
{props.rowFilter.value}
>;
- } else if (props.rowFilter instanceof RowFilterIsEqualTo) {
+ } else if (props.rowFilter instanceof RowFilterDescriptorSearch) {
return <>
{props.rowFilter.columnSchema.column_name}
- =
+ {props.rowFilter.operatorText}
{props.rowFilter.value}
>;
- } else if (props.rowFilter instanceof RowFilterIsBetween) {
+ } else if (props.rowFilter instanceof RowFilterDescriptorIsBetween) {
return <>
{props.rowFilter.columnSchema.column_name}
>=
@@ -76,7 +95,7 @@ export const RowFilterWidget = forwardRef<=
{props.rowFilter.upperLimit}
>;
- } else if (props.rowFilter instanceof RowFilterIsNotBetween) {
+ } else if (props.rowFilter instanceof RowFilterDescriptorIsNotBetween) {
return <>
{props.rowFilter.columnSchema.column_name}
<
diff --git a/src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/rowFilterBar/rowFilterBar.tsx b/src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/rowFilterBar/rowFilterBar.tsx
index c24ee5d628e..92c98e22475 100644
--- a/src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/rowFilterBar/rowFilterBar.tsx
+++ b/src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/rowFilterBar/rowFilterBar.tsx
@@ -18,9 +18,103 @@ import { ContextMenuItem } from 'vs/workbench/browser/positronComponents/context
import { ContextMenuSeparator } from 'vs/workbench/browser/positronComponents/contextMenu/contextMenuSeparator';
import { usePositronDataExplorerContext } from 'vs/workbench/browser/positronDataExplorer/positronDataExplorerContext';
import { PositronModalReactRenderer } from 'vs/workbench/browser/positronModalReactRenderer/positronModalReactRenderer';
-import { RowFilter } from 'vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/addEditRowFilterModalPopup/rowFilter';
+import { RowFilter, RowFilterCondition, RowFilterType } from 'vs/workbench/services/languageRuntime/common/positronDataExplorerComm';
import { RowFilterWidget } from 'vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/rowFilterBar/components/rowFilterWidget';
import { AddEditRowFilterModalPopup } from 'vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/addEditRowFilterModalPopup/addEditRowFilterModalPopup';
+import {
+ RowFilterDescriptor,
+ RowFilterDescriptorComparison,
+ RowFilterDescriptorIsEmpty,
+ RowFilterDescriptorIsNotEmpty,
+ RowFilterDescriptorIsNull,
+ RowFilterDescriptorIsNotNull,
+ RowFilterDescriptorIsBetween,
+ RowFilterDescriptorIsNotBetween,
+ RowFilterDescriptorSearch
+} from 'vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/addEditRowFilterModalPopup/rowFilterDescriptor';
+
+/**
+ * Creates row filters from row filter descriptors.
+ * @param rowFilterDescriptors The row filter descriptors.
+ * @returns The row filters.
+ */
+const createRowFilters = (rowFilterDescriptors: RowFilterDescriptor[]) => {
+ // Create the set of row filters.
+ return rowFilterDescriptors.reduce((
+ rowFilters,
+ rowFilterDescriptor
+ ) => {
+ //
+ const sharedParams = {
+ filter_id: rowFilterDescriptor.identifier,
+ column_index: rowFilterDescriptor.columnSchema.column_index,
+ condition: RowFilterCondition.And
+ };
+
+ if (rowFilterDescriptor instanceof RowFilterDescriptorIsEmpty) {
+ rowFilters.push({
+ filter_type: RowFilterType.IsEmpty,
+ ...sharedParams
+ });
+ } else if (rowFilterDescriptor instanceof RowFilterDescriptorIsNotEmpty) {
+ rowFilters.push({
+ filter_type: RowFilterType.NotEmpty,
+ ...sharedParams
+ });
+ } else if (rowFilterDescriptor instanceof RowFilterDescriptorIsNull) {
+ rowFilters.push({
+ filter_type: RowFilterType.IsNull,
+ ...sharedParams
+ });
+ } else if (rowFilterDescriptor instanceof RowFilterDescriptorIsNotNull) {
+ rowFilters.push({
+ filter_type: RowFilterType.NotNull,
+ ...sharedParams
+ });
+ } else if (rowFilterDescriptor instanceof RowFilterDescriptorComparison) {
+ rowFilters.push({
+ filter_type: RowFilterType.Compare,
+ compare_params: {
+ op: rowFilterDescriptor.compareFilterOp,
+ value: rowFilterDescriptor.value
+ },
+ ...sharedParams
+ });
+ } else if (rowFilterDescriptor instanceof RowFilterDescriptorSearch) {
+ rowFilters.push({
+ filter_type: RowFilterType.Search,
+ search_params: {
+ search_type: rowFilterDescriptor.searchOp,
+ term: rowFilterDescriptor.value,
+ case_sensitive: false
+ },
+ ...sharedParams
+ });
+ } else if (rowFilterDescriptor instanceof RowFilterDescriptorIsBetween) {
+ rowFilters.push({
+ filter_type: RowFilterType.Between,
+ between_params: {
+ left_value: rowFilterDescriptor.lowerLimit,
+ right_value: rowFilterDescriptor.upperLimit
+ },
+ ...sharedParams
+ });
+ } else if (rowFilterDescriptor instanceof RowFilterDescriptorIsNotBetween) {
+ rowFilters.push({
+ filter_type: RowFilterType.NotBetween,
+ between_params: {
+ left_value: rowFilterDescriptor.lowerLimit,
+ right_value: rowFilterDescriptor.upperLimit
+ },
+ ...sharedParams
+ });
+ }
+
+ // Return the row filters.
+ return rowFilters;
+ }, []);
+};
+
/**
* RowFilterBar component.
@@ -32,19 +126,22 @@ export const RowFilterBar = () => {
// Reference hooks.
const ref = useRef(undefined!);
- const filterButtonRef = useRef(undefined!);
+ const rowFilterButtonRef = useRef(undefined!);
const rowFilterWidgetRefs = useRef<(HTMLButtonElement)[]>([]);
const addFilterButtonRef = useRef(undefined!);
// State hooks.
- const [rowFilters, setRowFilters] = useState([]);
- const [filtersHidden, setFiltersHidden] = useState(false);
+ const [rowFilterDescriptors, setRowFilterDescriptors] = useState([]);
+ const [rowFiltersHidden, setRowFiltersHidden] = useState(false);
/**
* Shows the add / edit row filter modal popup.
- * @param rowFilterToEdit The row filter to edit, or undefined, to add a row filter.
+ * @param editRowFilterDescriptor The row filter to edit, or undefined, to add a row filter.
*/
- const showAddEditRowFilterModalPopup = (anchor: HTMLElement, rowFilterToEdit?: RowFilter) => {
+ const showAddEditRowFilterModalPopup = (
+ anchor: HTMLElement,
+ editRowFilterDescriptor?: RowFilterDescriptor
+ ) => {
// Create the renderer.
const renderer = new PositronModalReactRenderer({
keybindingService: context.keybindingService,
@@ -54,27 +151,36 @@ export const RowFilterBar = () => {
/**
* onApplyRowFilter event handler.
- * @param rowFilterToApply The row filter to apply.
+ * @param applyRowFilterDescriptor The row filter descriptor to apply.
*/
- const applyRowFilterHandler = (rowFilterToApply: RowFilter) => {
- // If this is a new row filter, append it to the array of row filters. Otherwise,
- // replace the row filter that was edited.
- if (!rowFilterToEdit) {
- setRowFilters(rowFilters => [...rowFilters, rowFilterToApply]);
+ const applyRowFilterHandler = async (applyRowFilterDescriptor: RowFilterDescriptor) => {
+ // Create the new row filter descriptors. If this is a new row filter, append it;
+ // otherwise, replace the row filter that was edited.
+ let newRowFilterDescriptors: RowFilterDescriptor[];
+ if (!editRowFilterDescriptor) {
+ // Update the row filters.
+ newRowFilterDescriptors = [...rowFilterDescriptors, applyRowFilterDescriptor];
} else {
- // Find the index of the row filter to edit.
- const index = rowFilters.findIndex(rowFilter =>
- rowFilterToEdit.identifier === rowFilter.identifier
+ // Find the index of the row filter.
+ const index = rowFilterDescriptors.findIndex(rowFilter =>
+ editRowFilterDescriptor.identifier === rowFilter.identifier
);
- setRowFilters(rowFilters =>
- [
- ...rowFilters.slice(0, index),
- rowFilterToApply,
- ...rowFilters.slice(index + 1)
- ]
- );
+ // Update the row filters.
+ newRowFilterDescriptors = [
+ ...rowFilterDescriptors.slice(0, index),
+ applyRowFilterDescriptor,
+ ...rowFilterDescriptors.slice(index + 1)
+ ];
}
+
+ // Set the new row filter descriptors.
+ setRowFilterDescriptors(newRowFilterDescriptors);
+
+ // Set the new row filters.
+ await context.instance.tableDataDataGridInstance.setRowFilters(createRowFilters(
+ newRowFilterDescriptors
+ ));
};
// Show the add /edit row filter modal popup.
@@ -83,7 +189,7 @@ export const RowFilterBar = () => {
dataExplorerClientInstance={context.instance.dataExplorerClientInstance}
renderer={renderer}
anchor={anchor}
- editRowFilter={rowFilterToEdit}
+ editRowFilter={editRowFilterDescriptor}
onApplyRowFilter={applyRowFilterHandler}
/>
);
@@ -98,36 +204,42 @@ export const RowFilterBar = () => {
entries.push(new ContextMenuItem({
label: localize('positron.dataExplorer.addFilter', "Add filter"),
icon: 'positron-add-filter',
- onSelected: () => showAddEditRowFilterModalPopup(filterButtonRef.current)
+ onSelected: () => showAddEditRowFilterModalPopup(rowFilterButtonRef.current)
}));
entries.push(new ContextMenuSeparator());
- if (!filtersHidden) {
+ if (!rowFiltersHidden) {
entries.push(new ContextMenuItem({
label: localize('positron.dataExplorer.hideFilters', "Hide filters"),
icon: 'positron-hide-filters',
- disabled: rowFilters.length === 0,
- onSelected: () => setFiltersHidden(true)
+ disabled: rowFilterDescriptors.length === 0,
+ onSelected: () => setRowFiltersHidden(true)
}));
} else {
entries.push(new ContextMenuItem({
label: localize('positron.dataExplorer.showFilters', "Show filters"),
icon: 'positron-show-filters',
- onSelected: () => setFiltersHidden(false)
+ onSelected: () => setRowFiltersHidden(false)
}));
}
entries.push(new ContextMenuSeparator());
entries.push(new ContextMenuItem({
label: localize('positron.dataExplorer.clearFilters', "Clear filters"),
icon: 'positron-clear-row-filters',
- disabled: rowFilters.length === 0,
- onSelected: () => setRowFilters([])
+ disabled: rowFilterDescriptors.length === 0,
+ onSelected: async () => {
+ // Clear the row filter descriptors.
+ setRowFilterDescriptors([]);
+
+ // Clear the row filters.
+ await context.instance.tableDataDataGridInstance.setRowFilters([]);
+ }
}));
// Show the context menu.
await showContextMenu(
context.keybindingService,
context.layoutService,
- filterButtonRef.current,
+ rowFilterButtonRef.current,
'left',
200,
entries
@@ -138,9 +250,18 @@ export const RowFilterBar = () => {
* Clears the row filter at the specified row filter index.
* @param rowFilterIndex The row filter index.
*/
- const clearRowFilter = (identifier: string) => {
- setRowFilters(rowFilters => rowFilters.filter(rowFilter =>
+ const clearRowFilter = async (identifier: string): Promise => {
+ // Remove the row filter.
+ const newRowFilterDescriptors = rowFilterDescriptors.filter(rowFilter =>
identifier !== rowFilter.identifier
+ );
+
+ // Set the new row filter descriptors.
+ setRowFilterDescriptors(newRowFilterDescriptors);
+
+ // Set the new row filters.
+ await context.instance.tableDataDataGridInstance.setRowFilters(createRowFilters(
+ newRowFilterDescriptors
));
};
@@ -148,16 +269,16 @@ export const RowFilterBar = () => {
return (