From e5f7d40fd350efba64055bdd7efe8f846a202f1f Mon Sep 17 00:00:00 2001 From: Rich Chiodo Date: Mon, 28 Sep 2020 10:44:03 -0700 Subject: [PATCH] Merge from main into WIP/NoDS (#14134) * Add an optional onUpdated event to the iterator returned by ILocator.iterEnvs(). (#13950) In order to ensure that ILocator.iterEnvs() can finish as fast as possible, we add a side-channel event for each iteration. This event fires any time an already-yielded env info object is updated (e.g. reduced/merged or resolved/completed). The update only relates to operations triggered by that particular iteration. ILocator.onChanged remains separate and only relates to when the locators finds a new env, notices one was removed, or that one was otherwise fundamentally changed. There are two similar approaches we could take for this update event. Either we added the event as a property of IPythonEnvsIterator or we change the return value of ILocator.iterEnvs() to be a 2-tuple ([PythonEnvsIterator, Event]). We took the property approach since most of the time callers won't need to worry about the update events. * fully bump raw kernel (#13941) Co-authored-by: Ian Huff * Virtualenvwrapper locator (#13895) * virtualenvwrapper locator * skip os-specific tests * Test getDefaultVirtualenvwrapperDir * Update src/client/pythonEnvironments/discovery/locators/services/virtualenvwrapperLocator.ts Co-authored-by: Karthik Nadig * add pathExists check to WORKON_HOME dir * Change how the locator works + move util func out * Fix functional tests * utils unit tests * Update locator tests * Stub getUserHomeDir instead of getDefaultetc Co-authored-by: Karthik Nadig * Clean up eslint errors (#13951) * Clean up eslint errors * Fix linting and address comments. * Add a legacy DI adapter for the Python envs component. (#13858) This allows us to start using the new discovery code in the extension. * Extension api for DataScience (#13923) * Extension API * Api for DS * Fix installing ipykernel into interpreters for raw kernel (#13959) * Fix kernel and server name missing in certain situations (#13974) * Fix kernel name and server name * Fixup server name for remote situations * Add some functional tests * Add news entry * Extension API to return path to debugpy for DS (#13973) * Use the component adapter in the extension. (#13869) This allows us to start using the new discovery code in the extension. The key thing is to be careful not to regress in the available functionality. So for now we have disabled use of the component. When we feel comfortable with it we can enable it by setting the default for `ComponentAdapter.enabled` to `true`. This PR involves small fixes to a large number of files due to API changes. The adapter is actually used (injected) in the following files: src/client/interpreter/interpreterService.ts src/client/interpreter/helpers.ts src/client/pythonEnvironments/discovery/locators/index.ts src/client/pythonEnvironments/discovery/locators/services/condaService.ts src/client/pythonEnvironments/discovery/locators/services/windowsStoreInterpreter.ts * Disable split views of custom editors (#13985) * Fix backup storage by looking at the options correctly (#13983) * Fix backup storage by looking at the options correctly * Fix backup by being more explicit * Only linux tests are failing. Hopefully fix them * update test plan for ipykernel (python repo) (#13993) * Find interpreter based on hash in kernelspec of nb metadata (#13856) * Pyenv locator (#13996) * Pyenv locator * Skip tests per platform * Wrong pyenv path order in ternary * Add description * Autoformat venv locator * Revert "Autoformat venv locator" This reverts commit 5c8c4ab66aea87cc72632e466cdf7412a3f07717. * Add links * Windows-specific fixes * Typo * merge * Revert "merge" This reverts commit ee0cad9b52d4ffdd509a0fd094d89304cbc94dd9. * Remove cwd while running using isolate script (#14014) * Remove cwd while running using isolate script * Run inplace. * Fix formatting * Upgrade isort to 5.5.3 (#14035) * Modify environment info worker to support new type and to work with resolver (#13997) * Modify environment info worker to support new environment type * Do not replace existing fields and return a new object * Modify cache to carry deferred instead * Change worker to return interpreter information instead * Handle error and rename * Fix bug with interpreterInfo.py * Code reviews * Rename old PythonEnvInfo type to InterpreterInfoJson * Remove cross process lock (#14036) * Add API to get language server from external extensions (#14021) * Ideas for lang server API with python extension * Working with new API * Minimize API surface * Fix up tests for intellisense * Fix unit tests * Put back custom editor service * Remove unnecessary changes for service registry * Code review feedback * Move connection type to types file * Fix failing virtualenvwrapper Windows unit tests (#14012) * Fix tests * Typo Co-authored-by: Karthik Nadig Co-authored-by: Karthik Nadig * make sure that we run our show code when a custom editor is shown (#14022) * make sure that we run our show code when a custom editor is shown * Trigger notebook load in the constructor, not show Co-authored-by: Ian Huff * Rely on AppInsights API for exceptions (#13878) * Fix path * Actually fix settings * Add news * Add test * Format * Suppress 'jediEnabled' removal * Drop survey first launch threshold * Telemetry for exceptions * Remove custom exception telemetry * Remove unused * Fix HTML escaping to match what Jupyter does (#14038) * Basic idea * add some functional tests * Add news entry * Fix functional tests * Have dependabot ignore pytest and py (#14039) Otherwise it doesn't understand the Python 2.7 restrictions for those dependencies. * Added environments reducer (#13953) * Add environments reducer * Added tests * Use path.join to construct paths * Code reviews * Correct dummy implementations and adjust tests * Modify resolveEnv() * Rename to a general parentLocator * Merge release back to master (#14059) * Port ipykernel install fix to release (#13975) * Fix installing ipykernel into interpreters for raw kernel (#13959) * update news Co-authored-by: Ian Huff * Merge in changes to release (#13976) * Up release version for new release (#13928) * Up release version * Update changelog * Update changelog * Workaround test issue (#13930) * Try different version of VS code in release * Change to make it use the actual variable * Use a real version * More tests failing with gpu error (#13935) * Try different version of VS code in release * Change to make it use the actual variable * Use a real version * Two more version changes * Fix kernel and server name missing in certain situations (#13974) * Fix kernel name and server name * Fixup server name for remote situations * Add some functional tests * Add news entry * Delete news file * Port two fixes to the release branch (#13995) * Disable split views of custom editors (#13985) * Fix backup storage by looking at the options correctly (#13983) * Fix backup storage by looking at the options correctly * Fix backup by being more explicit * Only linux tests are failing. Hopefully fix them * Fixup changelog Co-authored-by: Don Jayamanne * add jedi-language-server to 3rd party notices (#13977) * add jedi-language-server to 3rd party notices * move license from distribution to repository file * disable test_discover_complex_default and (#14024) test_discover_complex_doctest * Upgrade isort to 5.5.3 (#14035) (#14037) * prepare release (#14042) * fixed annoying warnings (#14049) * update version Co-authored-by: Ian Huff Co-authored-by: Ian Huff Co-authored-by: Rich Chiodo Co-authored-by: Don Jayamanne Co-authored-by: Kartik Raj * Fix object sort order in tools tests (#14050) * Fix object sort order in tools tests * rebase with main * Fix formatting * Update ubuntu images for GPU issue fix (#14064) * Update ubuntu images for GPU issue fix * Try with ubuntu-20.04 * Added environments resolver (#14019) * Modify environment info worker to support new environment type * Change worker to return interpreter information instead * Modify resolveEnv() * Code reviews * Code reviews * Move stuff * Add Windows unit tests to the PR pipeline (#14106) * Do not opt users out of the insiders program if they have a stable version installed (#14091) * Remove code that opts users out of the insiders program * News entry * Code reviews * Do not quote isolated in exec module (#14108) * Do not quote isolated in exec module * Revert "Do not quote isolated in exec module" This reverts commit b9fa04c06be0876861017eff5ee032ca71acda3d. * Revert "IPyKernel install issue with windows paths (#13667)" This reverts commit 23725abd4249811f24475e215c2d78ed5e508e79. * Update cell output and metadata using Edit API (#13737) * Fix unit test broken by recent revert (#14122) Co-authored-by: Eric Snow Co-authored-by: Ian Huff Co-authored-by: Ian Huff Co-authored-by: Kim-Adeline Miguel <51720070+kimadeline@users.noreply.github.com> Co-authored-by: Karthik Nadig Co-authored-by: Don Jayamanne Co-authored-by: Graham Wheeler Co-authored-by: Kartik Raj Co-authored-by: Joyce Er Co-authored-by: Mikhail Arkhipov Co-authored-by: Brett Cannon Co-authored-by: David Kutugata --- .github/dependabot.yml | 3 + CHANGELOG.md | 13 +- ThirdPartyNotices-Distribution.txt | 132 +++++----- ThirdPartyNotices-Repository.txt | 30 ++- build/ci/templates/globals.yml | 1 + build/ci/templates/jobs/build_compile.yml | 8 +- build/ci/templates/jobs/coverage.yml | 2 +- build/ci/vscode-python-ci-manual.yaml | 6 +- build/ci/vscode-python-ci.yaml | 4 +- build/ci/vscode-python-nightly-ci.yaml | 8 +- build/ci/vscode-python-nightly-flake-ci.yaml | 2 +- build/ci/vscode-python-pr-validation.yaml | 8 +- experiments.json | 2 +- news/1 Enhancements/14090.md | 1 + news/2 Fixes/5678.md | 1 + news/3 Code Health/14013.md | 1 + .../testing_tools/adapter/test_functional.py | 15 +- .../insidersBuild/insidersExtensionService.ts | 23 +- src/client/common/process/internal/python.ts | 2 +- .../common/process/internal/scripts/index.ts | 2 +- src/client/common/utils/decorators.ts | 4 +- .../pythonEnvironments/base/info/index.ts | 29 +++ .../locators/composite/environmentsReducer.ts | 165 ++++++++++++ .../composite/environmentsResolver.ts | 136 ++++++++++ .../insidersExtensionService.unit.test.ts | 137 ++-------- .../installer/moduleInstaller.unit.test.ts | 2 +- src/test/common/moduleInstaller.test.ts | 2 +- .../common/process/pythonProcess.unit.test.ts | 2 +- .../synchronousTerminalService.unit.test.ts | 2 +- src/test/debuggerTest.ts | 3 +- src/test/mocks/vsc/extHostedTypes.ts | 9 +- src/test/multiRootTest.ts | 4 +- src/test/pythonEnvironments/base/common.ts | 15 +- .../environmentsReducer.unit.test.ts | 218 ++++++++++++++++ .../environmentsResolver.unit.test.ts | 244 ++++++++++++++++++ src/test/standardTest.ts | 4 +- types/vscode-proposed/index.d.ts | 147 +++++++---- types/vscode.proposed.d.ts | 149 +++++++---- typings/vscode-proposed/index.d.ts | 149 +++++++---- 39 files changed, 1279 insertions(+), 406 deletions(-) create mode 100644 news/1 Enhancements/14090.md create mode 100644 news/2 Fixes/5678.md create mode 100644 news/3 Code Health/14013.md create mode 100644 src/client/pythonEnvironments/base/locators/composite/environmentsReducer.ts create mode 100644 src/client/pythonEnvironments/base/locators/composite/environmentsResolver.ts create mode 100644 src/test/pythonEnvironments/base/locators/composite/environmentsReducer.unit.test.ts create mode 100644 src/test/pythonEnvironments/base/locators/composite/environmentsResolver.unit.test.ts diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 70e7c6ac3680..9e212f96f329 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -12,6 +12,9 @@ updates: directory: / schedule: interval: daily + ignore: + - dependency-name: pytest # Due to Python 2.7 and #13776. + - dependency-name: py # Due to Python 2.7. - package-ecosystem: 'pip' directory: /news diff --git a/CHANGELOG.md b/CHANGELOG.md index f89e1e35e763..dc0ec9e5af07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## 2020.9.0 (14 September 2020) +## 2020.9.0 (23 September 2020) ### Enhancements @@ -19,6 +19,8 @@ ([#13831](https://github.com/Microsoft/vscode-python/issues/13831)) 1. Enable custom editor support in stable VS code at 20%. ([#13890](https://github.com/Microsoft/vscode-python/issues/13890)) +1. Upgraded to isort `5.5.3`. + ([#14027](https://github.com/Microsoft/vscode-python/issues/14027)) ### Fixes @@ -57,6 +59,10 @@ ([#13612](https://github.com/Microsoft/vscode-python/issues/13612)) 1. Fix the behavior of the 'python.showStartPage' setting. ([#13706](https://github.com/Microsoft/vscode-python/issues/13706)) +1. Correctly install ipykernel when launching from an interpreter. + ([#13956](https://github.com/Microsoft/vscode-python/issues/13956)) +1. Backup on custom editors is being ignored. + ([#13981](https://github.com/Microsoft/vscode-python/issues/13981)) ### Code Health @@ -82,6 +88,11 @@ ([#13729](https://github.com/Microsoft/vscode-python/issues/13729)) 1. Fix nighly failure with beakerx. ([#13734](https://github.com/Microsoft/vscode-python/issues/13734)) +## 2020.8.6 (15 September 2020) + +### Fixes + +1. Workaround problem caused by https://github.com/microsoft/vscode/issues/106547 ### Thanks diff --git a/ThirdPartyNotices-Distribution.txt b/ThirdPartyNotices-Distribution.txt index 2e62e34b8564..ddb4327c18dd 100644 --- a/ThirdPartyNotices-Distribution.txt +++ b/ThirdPartyNotices-Distribution.txt @@ -3265,12 +3265,13 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND --------------------------------------------------------- -estraverse 1.5.1 - BSD-2-Clause -https://github.com/Constellation/estraverse +estraverse 4.3.0 - BSD-2-Clause +https://github.com/estools/estraverse +Copyright (c) 2014 Yusuke Suzuki Copyright (c) 2012 Ariya Hidayat Copyright (c) 2012-2013 Yusuke Suzuki -Copyright (c) 2012-2013 Yusuke Suzuki (http://github.com/Constellation) +Copyright (c) 2012-2016 Yusuke Suzuki (http://github.com/Constellation) Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: @@ -3297,13 +3298,12 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. --------------------------------------------------------- -estraverse 4.3.0 - BSD-2-Clause -https://github.com/estools/estraverse +estraverse 1.5.1 - BSD-2-Clause +https://github.com/Constellation/estraverse -Copyright (c) 2014 Yusuke Suzuki Copyright (c) 2012 Ariya Hidayat Copyright (c) 2012-2013 Yusuke Suzuki -Copyright (c) 2012-2016 Yusuke Suzuki (http://github.com/Constellation) +Copyright (c) 2012-2013 Yusuke Suzuki (http://github.com/Constellation) Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: @@ -4436,7 +4436,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. --------------------------------------------------------- -hoist-non-react-statics 3.3.0 - BSD-3-Clause +hoist-non-react-statics 3.3.1 - BSD-3-Clause https://github.com/mridgway/hoist-non-react-statics#readme Copyright 2015, Yahoo! Inc. @@ -4477,7 +4477,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. --------------------------------------------------------- -hoist-non-react-statics 3.3.1 - BSD-3-Clause +hoist-non-react-statics 3.3.0 - BSD-3-Clause https://github.com/mridgway/hoist-non-react-statics#readme Copyright 2015, Yahoo! Inc. @@ -4597,7 +4597,7 @@ The complete list of contributors can be found at: https://github.com/hapijs/qs/ --------------------------------------------------------- -source-map 0.5.7 - BSD-3-Clause +source-map 0.6.1 - BSD-3-Clause https://github.com/mozilla/source-map Copyright 2011 The Closure Compiler @@ -4640,7 +4640,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. --------------------------------------------------------- -source-map 0.6.1 - BSD-3-Clause +source-map 0.5.7 - BSD-3-Clause https://github.com/mozilla/source-map Copyright 2011 The Closure Compiler @@ -5502,8 +5502,8 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. --------------------------------------------------------- -@babel/runtime 7.8.3 - MIT -https://babeljs.io/docs/en/next/babel-runtime +@babel/runtime 7.5.4 - MIT + Copyright (c) 2014-present Sebastian McKenzie and other contributors @@ -5535,8 +5535,8 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- -@babel/runtime 7.5.4 - MIT - +@babel/runtime 7.8.3 - MIT +https://babeljs.io/docs/en/next/babel-runtime Copyright (c) 2014-present Sebastian McKenzie and other contributors @@ -7420,7 +7420,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- -date-format 3.0.0 - MIT +date-format 2.1.0 - MIT https://github.com/nomiddlename/date-format#readme Copyright (c) 2013 Gareth Jones @@ -7451,7 +7451,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- -date-format 2.1.0 - MIT +date-format 3.0.0 - MIT https://github.com/nomiddlename/date-format#readme Copyright (c) 2013 Gareth Jones @@ -7482,7 +7482,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- -debug 4.1.1 - MIT +debug 3.1.0 - MIT https://github.com/visionmedia/debug#readme Copyright (c) 2014 TJ Holowaychuk @@ -7575,7 +7575,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- -debug 3.1.0 - MIT +debug 4.1.1 - MIT https://github.com/visionmedia/debug#readme Copyright (c) 2014 TJ Holowaychuk @@ -8928,7 +8928,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI --------------------------------------------------------- -got 8.3.2 - MIT +got 9.6.0 - MIT https://github.com/sindresorhus/got#readme Copyright (c) Sindre Sorhus (sindresorhus.com) @@ -8948,7 +8948,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI --------------------------------------------------------- -got 9.6.0 - MIT +got 8.3.2 - MIT https://github.com/sindresorhus/got#readme Copyright (c) Sindre Sorhus (sindresorhus.com) @@ -9431,7 +9431,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- -isarray 1.0.0 - MIT +isarray 0.0.1 - MIT https://github.com/juliangruber/isarray Copyright (c) 2013 Julian Gruber @@ -9450,7 +9450,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI --------------------------------------------------------- -isarray 0.0.1 - MIT +isarray 1.0.0 - MIT https://github.com/juliangruber/isarray Copyright (c) 2013 Julian Gruber @@ -9638,7 +9638,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI --------------------------------------------------------- -isort 5.5.2 - MIT +isort 5.5.3 - MIT Copyright 2017 Jack Evans @@ -11173,10 +11173,9 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- -normalize-url 2.0.1 - MIT +normalize-url 4.5.0 - MIT https://github.com/sindresorhus/normalize-url#readme -(c) Sindre Sorhus (https://sindresorhus.com) Copyright (c) Sindre Sorhus (sindresorhus.com) MIT License @@ -11194,9 +11193,10 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI --------------------------------------------------------- -normalize-url 4.5.0 - MIT +normalize-url 2.0.1 - MIT https://github.com/sindresorhus/normalize-url#readme +(c) Sindre Sorhus (https://sindresorhus.com) Copyright (c) Sindre Sorhus (sindresorhus.com) MIT License @@ -11558,7 +11558,7 @@ SOFTWARE. --------------------------------------------------------- -p-cancelable 0.4.1 - MIT +p-cancelable 1.1.0 - MIT https://github.com/sindresorhus/p-cancelable#readme (c) Sindre Sorhus (https://sindresorhus.com) @@ -11579,7 +11579,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI --------------------------------------------------------- -p-cancelable 1.1.0 - MIT +p-cancelable 0.4.1 - MIT https://github.com/sindresorhus/p-cancelable#readme (c) Sindre Sorhus (https://sindresorhus.com) @@ -11931,7 +11931,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI --------------------------------------------------------- -process-nextick-args 2.0.1 - MIT +process-nextick-args 1.0.7 - MIT https://github.com/calvinmetcalf/process-nextick-args Copyright (c) 2015 Calvin Metcalf @@ -11961,7 +11961,7 @@ SOFTWARE.** --------------------------------------------------------- -process-nextick-args 1.0.7 - MIT +process-nextick-args 2.0.1 - MIT https://github.com/calvinmetcalf/process-nextick-args Copyright (c) 2015 Calvin Metcalf @@ -12128,7 +12128,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI --------------------------------------------------------- -pump 3.0.0 - MIT +pump 2.0.1 - MIT https://github.com/mafintosh/pump#readme Copyright (c) 2014 Mathias Buus @@ -12159,7 +12159,7 @@ THE SOFTWARE. --------------------------------------------------------- -pump 2.0.1 - MIT +pump 3.0.0 - MIT https://github.com/mafintosh/pump#readme Copyright (c) 2014 Mathias Buus @@ -12429,7 +12429,7 @@ IN THE SOFTWARE. --------------------------------------------------------- -readable-stream 2.3.7 - MIT +readable-stream 2.3.6 - MIT https://github.com/nodejs/readable-stream#readme Copyright Joyent, Inc. and other Node contributors. @@ -12487,7 +12487,7 @@ IN THE SOFTWARE. --------------------------------------------------------- -readable-stream 2.3.6 - MIT +readable-stream 2.3.7 - MIT https://github.com/nodejs/readable-stream#readme Copyright Joyent, Inc. and other Node contributors. @@ -12703,8 +12703,8 @@ SOFTWARE. --------------------------------------------------------- -resolve 1.1.7 - MIT -https://github.com/substack/node-resolve#readme +resolve 1.11.1 - MIT +https://github.com/browserify/resolve#readme This software is released under the MIT license: @@ -12731,8 +12731,8 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- -resolve 1.11.1 - MIT -https://github.com/browserify/resolve#readme +resolve 1.1.7 - MIT +https://github.com/substack/node-resolve#readme This software is released under the MIT license: @@ -13134,7 +13134,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- -static-module 2.2.5 - MIT +static-module 3.0.3 - MIT https://github.com/substack/static-module @@ -13162,7 +13162,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- -static-module 3.0.3 - MIT +static-module 2.2.5 - MIT https://github.com/substack/static-module @@ -14067,7 +14067,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI --------------------------------------------------------- -unicode-trie 2.0.0 - MIT +unicode-trie 1.0.0 - MIT https://github.com/devongovett/unicode-trie Copyright 2018 @@ -14086,7 +14086,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI --------------------------------------------------------- -unicode-trie 1.0.0 - MIT +unicode-trie 2.0.0 - MIT https://github.com/devongovett/unicode-trie Copyright 2018 @@ -14320,6 +14320,27 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +--------------------------------------------------------- + +--------------------------------------------------------- + +uuid 8.2.0 - MIT +https://github.com/uuidjs/uuid#readme + +Copyright 2011, Sebastian Tschan https://blueimp.net +Copyright (c) Paul Johnston 1999 - 2009 Other contributors Greg Holt, Andrew Kepert, Ydnar, Lostinet + +The MIT License (MIT) + +Copyright (c) 2010-2020 Robert Kieffer and other contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + --------------------------------------------------------- --------------------------------------------------------- @@ -14354,27 +14375,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---------------------------------------------------------- - ---------------------------------------------------------- - -uuid 8.2.0 - MIT -https://github.com/uuidjs/uuid#readme - -Copyright 2011, Sebastian Tschan https://blueimp.net -Copyright (c) Paul Johnston 1999 - 2009 Other contributors Greg Holt, Andrew Kepert, Ydnar, Lostinet - -The MIT License (MIT) - -Copyright (c) 2010-2020 Robert Kieffer and other contributors - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - --------------------------------------------------------- --------------------------------------------------------- @@ -14825,7 +14825,7 @@ SOFTWARE. --------------------------------------------------------- -xml2js 0.4.19 - MIT +xml2js 0.2.8 - MIT https://github.com/Leonidas-from-XIV/node-xml2js Copyright 2010, 2011, 2012, 2013. @@ -14855,7 +14855,7 @@ IN THE SOFTWARE. --------------------------------------------------------- -xml2js 0.2.8 - MIT +xml2js 0.4.19 - MIT https://github.com/Leonidas-from-XIV/node-xml2js Copyright 2010, 2011, 2012, 2013. diff --git a/ThirdPartyNotices-Repository.txt b/ThirdPartyNotices-Repository.txt index 1162db48bc9c..98c64072ca51 100644 --- a/ThirdPartyNotices-Repository.txt +++ b/ThirdPartyNotices-Repository.txt @@ -1167,4 +1167,32 @@ trademarks does not indicate endorsement of the trademark holder by Font Awesome, nor vice versa. **Please do not use brand logos for any purpose except to represent the company, product, or service to which they refer.** ========================================= -END OF font-awesome NOTICES, INFORMATION, AND LICENSE \ No newline at end of file +END OF font-awesome NOTICES, INFORMATION, AND LICENSE + +%% jedi-language-server NOTICES, INFORMATION, AND LICENSE BEGIN HERE +========================================= +jedi-language-server + +MIT License + +Copyright (c) 2019 Sam Roeca + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +========================================= +END OF jedi-language-server NOTICES, INFORMATION, AND LICENSE \ No newline at end of file diff --git a/build/ci/templates/globals.yml b/build/ci/templates/globals.yml index 03457023e99e..98bd51685dd0 100644 --- a/build/ci/templates/globals.yml +++ b/build/ci/templates/globals.yml @@ -11,3 +11,4 @@ variables: npm_config_cache: $(Pipeline.Workspace)/.npm vmImageMacOS: 'macOS-10.15' TS_NODE_FILES: true # Temporarily enabled to allow using types from vscode.proposed.d.ts from ts-node (for tests). + VSC_PYTHON_CI_TEST_VSC_CHANNEL: '1.48.0' # Enforce this until https://github.com/microsoft/vscode-test/issues/73 is fixed diff --git a/build/ci/templates/jobs/build_compile.yml b/build/ci/templates/jobs/build_compile.yml index 13b6915f7e3d..8a258787070e 100644 --- a/build/ci/templates/jobs/build_compile.yml +++ b/build/ci/templates/jobs/build_compile.yml @@ -4,25 +4,25 @@ jobs: - job: Compile pool: - vmImage: 'ubuntu-16.04' + vmImage: 'ubuntu-20.04' steps: - template: ../steps/compile.yml - job: Build pool: - vmImage: 'ubuntu-16.04' + vmImage: 'ubuntu-20.04' steps: - template: ../steps/build.yml - job: Dependencies pool: - vmImage: 'ubuntu-16.04' + vmImage: 'ubuntu-20.04' steps: - template: ../steps/dependencies.yml - job: Hygiene pool: - vmImage: 'ubuntu-16.04' + vmImage: 'ubuntu-20.04' steps: - template: ../steps/initialization.yml parameters: diff --git a/build/ci/templates/jobs/coverage.yml b/build/ci/templates/jobs/coverage.yml index 50fb4af733e0..26914271f5f0 100644 --- a/build/ci/templates/jobs/coverage.yml +++ b/build/ci/templates/jobs/coverage.yml @@ -1,7 +1,7 @@ jobs: - job: Coverage pool: - vmImage: 'ubuntu-16.04' + vmImage: 'ubuntu-20.04' variables: TestsToRun: 'testUnitTests' steps: diff --git a/build/ci/vscode-python-ci-manual.yaml b/build/ci/vscode-python-ci-manual.yaml index bad74182082c..b16482e851be 100644 --- a/build/ci/vscode-python-ci-manual.yaml +++ b/build/ci/vscode-python-ci-manual.yaml @@ -64,7 +64,7 @@ stages: NeedsIPythonReqs: true #maxParallel: 3 pool: - vmImage: 'ubuntu-16.04' + vmImage: 'ubuntu-20.04' steps: - template: templates/test_phases.yml @@ -91,7 +91,7 @@ stages: NeedsPythonTestReqs: true #maxParallel: 3 pool: - vmImage: 'ubuntu-16.04' + vmImage: 'ubuntu-20.04' steps: - template: templates/test_phases.yml @@ -128,7 +128,7 @@ stages: NeedsPythonTestReqs: true #maxParallel: 3 pool: - vmImage: 'ubuntu-16.04' + vmImage: 'ubuntu-20.04' steps: - template: templates/test_phases.yml diff --git a/build/ci/vscode-python-ci.yaml b/build/ci/vscode-python-ci.yaml index 31d40da97d76..a53aea5170db 100644 --- a/build/ci/vscode-python-ci.yaml +++ b/build/ci/vscode-python-ci.yaml @@ -72,7 +72,7 @@ stages: NeedsIPythonReqs: true maxParallel: 2 pool: - vmImage: 'ubuntu-16.04' + vmImage: 'ubuntu-20.04' steps: - template: templates/test_phases.yml @@ -100,7 +100,7 @@ stages: NeedsPythonTestReqs: true maxParallel: 2 pool: - vmImage: 'ubuntu-16.04' + vmImage: 'ubuntu-20.04' steps: - template: templates/test_phases.yml diff --git a/build/ci/vscode-python-nightly-ci.yaml b/build/ci/vscode-python-nightly-ci.yaml index 228909a49162..aa3bc2d007cb 100644 --- a/build/ci/vscode-python-nightly-ci.yaml +++ b/build/ci/vscode-python-nightly-ci.yaml @@ -68,7 +68,7 @@ stages: NeedsIPythonReqs: true maxParallel: 1 pool: - vmImage: 'ubuntu-16.04' + vmImage: 'ubuntu-20.04' steps: - template: templates/test_phases.yml @@ -108,7 +108,7 @@ stages: # Note: We only run the smoke tests with the latest Python release. maxParallel: 1 pool: - vmImage: 'ubuntu-16.04' + vmImage: 'ubuntu-20.04' steps: - template: templates/test_phases.yml @@ -148,7 +148,7 @@ stages: # Note: We only run the smoke tests with the latest Python release. maxParallel: 1 pool: - vmImage: 'ubuntu-16.04' + vmImage: 'ubuntu-20.04' steps: - template: templates/test_phases.yml @@ -184,7 +184,7 @@ stages: # Note: We only run the smoke tests with the latest Python release. maxParallel: 1 pool: - vmImage: 'ubuntu-16.04' + vmImage: 'ubuntu-20.04' steps: - template: templates/test_phases.yml diff --git a/build/ci/vscode-python-nightly-flake-ci.yaml b/build/ci/vscode-python-nightly-flake-ci.yaml index db168f479927..6c7a7b3e73b0 100644 --- a/build/ci/vscode-python-nightly-flake-ci.yaml +++ b/build/ci/vscode-python-nightly-flake-ci.yaml @@ -46,7 +46,7 @@ stages: NeedsPythonFunctionalReqs: true VSCODE_PYTHON_ROLLING: true pool: - vmImage: 'ubuntu-16.04' + vmImage: 'ubuntu-20.04' steps: - template: templates/test_phases.yml diff --git a/build/ci/vscode-python-pr-validation.yaml b/build/ci/vscode-python-pr-validation.yaml index 45879e1c1cf1..eef39c8e4029 100644 --- a/build/ci/vscode-python-pr-validation.yaml +++ b/build/ci/vscode-python-pr-validation.yaml @@ -60,7 +60,7 @@ stages: NeedsPythonTestReqs: true NeedsIPythonReqs: true pool: - vmImage: 'ubuntu-16.04' + vmImage: 'ubuntu-20.04' steps: - template: templates/test_phases.yml @@ -79,7 +79,7 @@ stages: NeedsPythonTestReqs: true NeedsPythonFunctionalReqs: true pool: - vmImage: 'ubuntu-16.04' + vmImage: 'ubuntu-20.04' steps: - template: templates/test_phases.yml @@ -108,6 +108,10 @@ stages: timeoutInMinutes: 90 strategy: matrix: + 'Unit': + TestsToRun: 'testUnitTests, pythonUnitTests, pythonInternalTools, pythonIPythonTests' + NeedsPythonTestReqs: true + NeedsIPythonReqs: true # This gives us our best functional coverage for the OS. 'Functional': TestsToRun: 'testfunctional' diff --git a/experiments.json b/experiments.json index b3bab2e359c2..9d6b4f82517d 100644 --- a/experiments.json +++ b/experiments.json @@ -124,5 +124,5 @@ "salt": "DeprecatePythonPath", "min": 80, "max": 100 - }, + } ] diff --git a/news/1 Enhancements/14090.md b/news/1 Enhancements/14090.md new file mode 100644 index 000000000000..341e003b141c --- /dev/null +++ b/news/1 Enhancements/14090.md @@ -0,0 +1 @@ +Do not opt users out of the insiders program if they have a stable version installed. diff --git a/news/2 Fixes/5678.md b/news/2 Fixes/5678.md new file mode 100644 index 000000000000..1a7d1c5fd977 --- /dev/null +++ b/news/2 Fixes/5678.md @@ -0,0 +1 @@ +Fix escaping of output to encode HTML chars correctly. \ No newline at end of file diff --git a/news/3 Code Health/14013.md b/news/3 Code Health/14013.md new file mode 100644 index 000000000000..0a4af31929a2 --- /dev/null +++ b/news/3 Code Health/14013.md @@ -0,0 +1 @@ +Add Windows unit tests to the PR validation pipeline . diff --git a/pythonFiles/tests/testing_tools/adapter/test_functional.py b/pythonFiles/tests/testing_tools/adapter/test_functional.py index bd6c6b200314..153ad5508d9b 100644 --- a/pythonFiles/tests/testing_tools/adapter/test_functional.py +++ b/pythonFiles/tests/testing_tools/adapter/test_functional.py @@ -10,8 +10,6 @@ import sys import unittest -import pytest - from ...__main__ import TESTING_TOOLS_ROOT from testing_tools.adapter.util import fix_path, PATH_SEP @@ -83,6 +81,15 @@ def fix_source(tests, testid, srcfile, lineno): test["source"] = fix_path("{}:{}".format(srcfile, lineno)) +def sorted_object(obj): + if isinstance(obj, dict): + return sorted((key, sorted_object(obj[key])) for key in obj.keys()) + if isinstance(obj, list): + return sorted((sorted_object(x) for x in obj)) + else: + return obj + + # Note that these tests are skipped if util.PATH_SEP is not os.path.sep. # This is because the functional tests should reflect the actual # operating environment. @@ -159,7 +166,7 @@ def test_discover_complex_default(self): result[0]["tests"] = fix_test_order(result[0]["tests"]) self.maxDiff = None - self.assertEqual(result, expected) + self.assertEqual(sorted_object(result), sorted_object(expected)) def test_discover_complex_doctest(self): projroot, _ = resolve_testroot("complex") @@ -243,7 +250,7 @@ def test_discover_complex_doctest(self): result[0]["tests"] = fix_test_order(result[0]["tests"]) self.maxDiff = None - self.assertEqual(result, expected) + self.assertEqual(sorted_object(result), sorted_object(expected)) def test_discover_not_found(self): projroot, testroot = resolve_testroot("notests") diff --git a/src/client/common/insidersBuild/insidersExtensionService.ts b/src/client/common/insidersBuild/insidersExtensionService.ts index f7896cd3e4f7..63f0e8f8eda3 100644 --- a/src/client/common/insidersBuild/insidersExtensionService.ts +++ b/src/client/common/insidersBuild/insidersExtensionService.ts @@ -61,7 +61,7 @@ export class InsidersExtensionService implements IExtensionSingleActivationServi const channel = this.extensionChannelService.getChannel(); const isDefault = this.extensionChannelService.isChannelUsingDefaultConfiguration; - const alreadyHandled = await this.handleEdgeCases(channel, isDefault); + const alreadyHandled = await this.handleEdgeCases(isDefault); if (!alreadyHandled) { this.handleChannel(channel).ignoreErrors(); } @@ -84,14 +84,12 @@ export class InsidersExtensionService implements IExtensionSingleActivationServi * Choose what to do in miscellaneous situations * @returns `true` if install channel is handled in these miscellaneous cases, `false` if install channel needs further handling */ - public async handleEdgeCases(installChannel: ExtensionChannels, isDefault: boolean): Promise { + public async handleEdgeCases(isDefault: boolean): Promise { // When running UI Tests we might want to disable these prompts. if (process.env.UITEST_DISABLE_INSIDERS) { return true; } else if (await this.promptToInstallInsidersIfApplicable(isDefault)) { return true; - } else if (await this.setInsidersChannelToOffIfApplicable(installChannel)) { - return true; } else { return false; } @@ -115,21 +113,4 @@ export class InsidersExtensionService implements IExtensionSingleActivationServi await this.insidersPrompt.promptToInstallInsiders(); return true; } - - /** - * When install channel is not in sync with what is installed, resolve discrepency by setting channel to "off" - * @returns `true` if channel is set to off, `false` otherwise - */ - private async setInsidersChannelToOffIfApplicable(installChannel: ExtensionChannels): Promise { - if (installChannel === 'off') { - return false; - } - if (this.appEnvironment.extensionChannel !== 'stable') { - return false; - } - - // Install channel is set to "weekly" or "daily" but stable version of extension is installed. Switch channel to "off" to use the installed version - await this.extensionChannelService.updateChannel('off'); - return true; - } } diff --git a/src/client/common/process/internal/python.ts b/src/client/common/process/internal/python.ts index d553e54293c1..86123367f852 100644 --- a/src/client/common/process/internal/python.ts +++ b/src/client/common/process/internal/python.ts @@ -28,7 +28,7 @@ export function execCode(code: string, isolated = true): string[] { export function execModule(name: string, moduleArgs: string[], isolated = true): string[] { const args = ['-m', name, ...moduleArgs]; if (isolated) { - args[0] = ISOLATED.fileToCommandArgument(); + args[0] = ISOLATED; // replace } // "code" isn't specific enough to know how to parse it, // so we only return the args. diff --git a/src/client/common/process/internal/scripts/index.ts b/src/client/common/process/internal/scripts/index.ts index 2219cce5f3c7..098fbdae4361 100644 --- a/src/client/common/process/internal/scripts/index.ts +++ b/src/client/common/process/internal/scripts/index.ts @@ -305,7 +305,7 @@ export function shell_exec(command: string, lockfile: string, shellArgs: string[ // We don't bother with a "parse" function since the output // could be anything. return [ - ISOLATED.fileToCommandArgument(), + ISOLATED, script, command.fileToCommandArgument(), // The shell args must come after the command diff --git a/src/client/common/utils/decorators.ts b/src/client/common/utils/decorators.ts index 512f31e65e19..2c9362d329f7 100644 --- a/src/client/common/utils/decorators.ts +++ b/src/client/common/utils/decorators.ts @@ -178,11 +178,11 @@ export function cache(expiryDurationMs: number) { * @param {string} [scopeName] Scope for the error message to be logged along with the error. * @returns void */ -export function swallowExceptions(scopeName: string) { +export function swallowExceptions(scopeName?: string) { // tslint:disable-next-line:no-any no-function-expression return function (_target: any, propertyName: string, descriptor: TypedPropertyDescriptor) { const originalMethod = descriptor.value!; - const errorMessage = `Python Extension (Error in ${scopeName}, method:${propertyName}):`; + const errorMessage = `Python Extension (Error in ${scopeName || propertyName}, method:${propertyName}):`; // tslint:disable-next-line:no-any no-function-expression descriptor.value = function (...args: any[]) { try { diff --git a/src/client/pythonEnvironments/base/info/index.ts b/src/client/pythonEnvironments/base/info/index.ts index 11398217a512..c24cf8dc2aa3 100644 --- a/src/client/pythonEnvironments/base/info/index.ts +++ b/src/client/pythonEnvironments/base/info/index.ts @@ -4,6 +4,7 @@ import { Uri } from 'vscode'; import { Architecture } from '../../../common/utils/platform'; import { BasicVersionInfo, VersionInfo } from '../../../common/utils/version'; +import { arePathsSame } from '../../common/externalDependencies'; /** * IDs for the various supported Python environments. @@ -143,3 +144,31 @@ export type PythonEnvInfo = _PythonEnvInfo & { defaultDisplayName?: string; searchLocation?: Uri; }; + +/** + * Determine if the given infos correspond to the same env. + * + * @param environment1 - one of the two envs to compare + * @param environment2 - one of the two envs to compare + */ +export function areSameEnvironment( + environment1: PythonEnvInfo | string, + environment2: PythonEnvInfo | string, +): boolean { + let path1: string; + let path2: string; + if (typeof environment1 === 'string') { + path1 = environment1; + } else { + path1 = environment1.executable.filename; + } + if (typeof environment2 === 'string') { + path2 = environment2; + } else { + path2 = environment2.executable.filename; + } + if (arePathsSame(path1, path2)) { + return true; + } + return false; +} diff --git a/src/client/pythonEnvironments/base/locators/composite/environmentsReducer.ts b/src/client/pythonEnvironments/base/locators/composite/environmentsReducer.ts new file mode 100644 index 000000000000..ba00eac2a546 --- /dev/null +++ b/src/client/pythonEnvironments/base/locators/composite/environmentsReducer.ts @@ -0,0 +1,165 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { cloneDeep, isEqual } from 'lodash'; +import { Event, EventEmitter } from 'vscode'; +import { traceVerbose } from '../../../../common/logger'; +import { createDeferred } from '../../../../common/utils/async'; +import { areSameEnvironment, PythonEnvInfo, PythonEnvKind } from '../../info'; +import { + ILocator, IPythonEnvsIterator, PythonEnvUpdatedEvent, PythonLocatorQuery, +} from '../../locator'; +import { PythonEnvsChangedEvent } from '../../watcher'; + +/** + * Combines duplicate environments received from the incoming locator into one and passes on unique environments + */ +export class PythonEnvsReducer implements ILocator { + public get onChanged(): Event { + return this.parentLocator.onChanged; + } + + constructor(private readonly parentLocator: ILocator) {} + + public async resolveEnv(env: string | PythonEnvInfo): Promise { + let environment: PythonEnvInfo | undefined; + const waitForUpdatesDeferred = createDeferred(); + const iterator = this.iterEnvs(); + iterator.onUpdated!((event) => { + if (event === null) { + waitForUpdatesDeferred.resolve(); + } else if (environment && areSameEnvironment(environment, event.new)) { + environment = event.new; + } + }); + let result = await iterator.next(); + while (!result.done) { + if (areSameEnvironment(result.value, env)) { + environment = result.value; + } + // eslint-disable-next-line no-await-in-loop + result = await iterator.next(); + } + if (!environment) { + return undefined; + } + await waitForUpdatesDeferred.promise; + return this.parentLocator.resolveEnv(environment); + } + + public iterEnvs(query?: PythonLocatorQuery): IPythonEnvsIterator { + const didUpdate = new EventEmitter(); + const incomingIterator = this.parentLocator.iterEnvs(query); + const iterator: IPythonEnvsIterator = iterEnvsIterator(incomingIterator, didUpdate); + iterator.onUpdated = didUpdate.event; + return iterator; + } +} + +async function* iterEnvsIterator( + iterator: IPythonEnvsIterator, + didUpdate: EventEmitter, +): AsyncIterator { + const state = { + done: false, + pending: 0, + }; + const seen: PythonEnvInfo[] = []; + + if (iterator.onUpdated !== undefined) { + iterator.onUpdated((event) => { + if (event === null) { + state.done = true; + checkIfFinishedAndNotify(state, didUpdate); + } else { + const oldIndex = seen.findIndex((s) => areSameEnvironment(s, event.old)); + if (oldIndex !== -1) { + state.pending += 1; + resolveDifferencesInBackground(oldIndex, event.new, state, didUpdate, seen).ignoreErrors(); + } else { + // This implies a problem in a downstream locator + traceVerbose(`Expected already iterated env, got ${event.old}`); + } + } + }); + } + + let result = await iterator.next(); + while (!result.done) { + const currEnv = result.value; + const oldIndex = seen.findIndex((s) => areSameEnvironment(s, currEnv)); + if (oldIndex !== -1) { + state.pending += 1; + resolveDifferencesInBackground(oldIndex, currEnv, state, didUpdate, seen).ignoreErrors(); + } else { + // We haven't yielded a matching env so yield this one as-is. + yield currEnv; + seen.push(currEnv); + } + // eslint-disable-next-line no-await-in-loop + result = await iterator.next(); + } + if (iterator.onUpdated === undefined) { + state.done = true; + checkIfFinishedAndNotify(state, didUpdate); + } +} + +async function resolveDifferencesInBackground( + oldIndex: number, + newEnv: PythonEnvInfo, + state: { done: boolean; pending: number }, + didUpdate: EventEmitter, + seen: PythonEnvInfo[], +) { + const oldEnv = seen[oldIndex]; + const merged = mergeEnvironments(oldEnv, newEnv); + if (!isEqual(oldEnv, merged)) { + didUpdate.fire({ old: oldEnv, new: merged }); + seen[oldIndex] = merged; + } + state.pending -= 1; + checkIfFinishedAndNotify(state, didUpdate); +} + +/** + * When all info from incoming iterator has been received and all background calls finishes, notify that we're done + * @param state Carries the current state of progress + * @param didUpdate Used to notify when finished + */ +function checkIfFinishedAndNotify( + state: { done: boolean; pending: number }, + didUpdate: EventEmitter, +) { + if (state.done && state.pending === 0) { + didUpdate.fire(null); + didUpdate.dispose(); + } +} + +export function mergeEnvironments(environment: PythonEnvInfo, other: PythonEnvInfo): PythonEnvInfo { + const result = cloneDeep(environment); + // Preserve type information. + // Possible we identified environment as unknown, but a later provider has identified env type. + if (environment.kind === PythonEnvKind.Unknown && other.kind && other.kind !== PythonEnvKind.Unknown) { + result.kind = other.kind; + } + const props: (keyof PythonEnvInfo)[] = [ + 'version', + 'kind', + 'executable', + 'name', + 'arch', + 'distro', + 'defaultDisplayName', + 'searchLocation', + ]; + props.forEach((prop) => { + if (!result[prop] && other[prop]) { + // tslint:disable: no-any + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (result as any)[prop] = other[prop]; + } + }); + return result; +} diff --git a/src/client/pythonEnvironments/base/locators/composite/environmentsResolver.ts b/src/client/pythonEnvironments/base/locators/composite/environmentsResolver.ts new file mode 100644 index 000000000000..347785f73660 --- /dev/null +++ b/src/client/pythonEnvironments/base/locators/composite/environmentsResolver.ts @@ -0,0 +1,136 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { cloneDeep } from 'lodash'; +import { Event, EventEmitter } from 'vscode'; +import { traceVerbose } from '../../../../common/logger'; +import { IEnvironmentInfoService } from '../../../info/environmentInfoService'; +import { areSameEnvironment, PythonEnvInfo } from '../../info'; +import { InterpreterInformation } from '../../info/interpreter'; +import { + ILocator, IPythonEnvsIterator, PythonEnvUpdatedEvent, PythonLocatorQuery, +} from '../../locator'; +import { PythonEnvsChangedEvent } from '../../watcher'; + +/** + * Calls environment info service which runs `interpreterInfo.py` script on environments received + * from the parent locator. Uses information received to populate environments further and pass it on. + */ +export class PythonEnvsResolver implements ILocator { + public get onChanged(): Event { + return this.parentLocator.onChanged; + } + + constructor( + private readonly parentLocator: ILocator, + private readonly environmentInfoService: IEnvironmentInfoService, + ) {} + + public async resolveEnv(env: string | PythonEnvInfo): Promise { + const environment = await this.parentLocator.resolveEnv(env); + if (!environment) { + return undefined; + } + const interpreterInfo = await this.environmentInfoService.getEnvironmentInfo(environment.executable.filename); + if (!interpreterInfo) { + return undefined; + } + return getResolvedEnv(interpreterInfo, environment); + } + + public iterEnvs(query?: PythonLocatorQuery): IPythonEnvsIterator { + const didUpdate = new EventEmitter(); + const incomingIterator = this.parentLocator.iterEnvs(query); + const iterator: IPythonEnvsIterator = this.iterEnvsIterator(incomingIterator, didUpdate); + iterator.onUpdated = didUpdate.event; + return iterator; + } + + private async* iterEnvsIterator( + iterator: IPythonEnvsIterator, + didUpdate: EventEmitter, + ): AsyncIterator { + const state = { + done: false, + pending: 0, + }; + const seen: PythonEnvInfo[] = []; + + if (iterator.onUpdated !== undefined) { + iterator.onUpdated((event) => { + if (event === null) { + state.done = true; + checkIfFinishedAndNotify(state, didUpdate); + } else { + const oldIndex = seen.findIndex((s) => areSameEnvironment(s, event.old)); + if (oldIndex !== -1) { + seen[oldIndex] = event.new; + state.pending += 1; + this.resolveInBackground(oldIndex, state, didUpdate, seen).ignoreErrors(); + } else { + // This implies a problem in a downstream locator + traceVerbose(`Expected already iterated env in resolver, got ${event.old}`); + } + } + }); + } + + let result = await iterator.next(); + while (!result.done) { + const currEnv = result.value; + seen.push(currEnv); + yield currEnv; + state.pending += 1; + this.resolveInBackground(seen.indexOf(currEnv), state, didUpdate, seen).ignoreErrors(); + // eslint-disable-next-line no-await-in-loop + result = await iterator.next(); + } + if (iterator.onUpdated === undefined) { + state.done = true; + checkIfFinishedAndNotify(state, didUpdate); + } + } + + private async resolveInBackground( + envIndex: number, + state: { done: boolean; pending: number }, + didUpdate: EventEmitter, + seen: PythonEnvInfo[], + ) { + const interpreterInfo = await this.environmentInfoService.getEnvironmentInfo( + seen[envIndex].executable.filename, + ); + if (interpreterInfo) { + const resolvedEnv = getResolvedEnv(interpreterInfo, seen[envIndex]); + didUpdate.fire({ old: seen[envIndex], new: resolvedEnv }); + seen[envIndex] = resolvedEnv; + } + state.pending -= 1; + checkIfFinishedAndNotify(state, didUpdate); + } +} + +/** + * When all info from incoming iterator has been received and all background calls finishes, notify that we're done + * @param state Carries the current state of progress + * @param didUpdate Used to notify when finished + */ +function checkIfFinishedAndNotify( + state: { done: boolean; pending: number }, + didUpdate: EventEmitter, +) { + if (state.done && state.pending === 0) { + didUpdate.fire(null); + didUpdate.dispose(); + } +} + +function getResolvedEnv(interpreterInfo: InterpreterInformation, environment: PythonEnvInfo) { + // Deep copy into a new object + const resolvedEnv = cloneDeep(environment); + resolvedEnv.version = interpreterInfo.version; + resolvedEnv.executable.filename = interpreterInfo.executable.filename; + resolvedEnv.executable.sysPrefix = interpreterInfo.executable.sysPrefix; + resolvedEnv.arch = interpreterInfo.arch; + return resolvedEnv; +} diff --git a/src/test/common/insidersBuild/insidersExtensionService.unit.test.ts b/src/test/common/insidersBuild/insidersExtensionService.unit.test.ts index a0d96984e168..96e4dfb396ca 100644 --- a/src/test/common/insidersBuild/insidersExtensionService.unit.test.ts +++ b/src/test/common/insidersBuild/insidersExtensionService.unit.test.ts @@ -150,7 +150,7 @@ suite('Insiders Extension Service - Activation', () => { verify(extensionChannelService.isChannelUsingDefaultConfiguration).once(); assert.ok(registerCommandsAndHandlers.calledOnce); assert.ok(handleEdgeCases.calledOnce); - assert.ok(handleEdgeCases.calledWith('daily', false)); + assert.ok(handleEdgeCases.calledWith(false)); assert.ok(handleChannel.notCalled); }); @@ -176,7 +176,7 @@ suite('Insiders Extension Service - Activation', () => { verify(extensionChannelService.isChannelUsingDefaultConfiguration).once(); assert.ok(registerCommandsAndHandlers.calledOnce); assert.ok(handleEdgeCases.calledOnce); - assert.ok(handleEdgeCases.calledWith('daily', false)); + assert.ok(handleEdgeCases.calledWith(false)); assert.ok(handleChannel.calledOnce); }); @@ -210,7 +210,7 @@ suite('Insiders Extension Service - Activation', () => { assert.ok(registerCommandsAndHandlers.calledOnce); assert.ok(handleEdgeCases.calledOnce); assert.ok(handleChannel.calledOnce); - assert.ok(handleEdgeCases.calledWith('daily', false)); + assert.ok(handleEdgeCases.calledWith(false)); }); }); @@ -252,6 +252,10 @@ suite('Insiders Extension Service - Function handleEdgeCases()', () => { .verifiable(TypeMoq.Times.atLeast(0)); } + setup(() => { + setupCommon(); + }); + function verifyAll() { // the most important ones: insidersPrompt.verifyAll(); @@ -265,23 +269,16 @@ suite('Insiders Extension Service - Function handleEdgeCases()', () => { type TestInfo = { vscodeChannel?: Channel; - extensionChannel?: Channel; - installChannel: ExtensionChannels; + installChannel?: ExtensionChannels; isChannelUsingDefaultConfiguration?: boolean; hasUserBeenNotified?: boolean; }; - function setState(info: TestInfo, checkPromptEnroll: boolean, checkDisable: boolean) { + function setState(info: TestInfo, checkPromptEnroll: boolean) { if (info.vscodeChannel) { appEnvironment.setup((e) => e.channel).returns(() => info.vscodeChannel!); } - if (info.extensionChannel) { - appEnvironment.setup((e) => e.extensionChannel).returns(() => info.extensionChannel!); - } - if (checkDisable) { - extensionChannelService.setup((ec) => ec.updateChannel('off')).returns(() => Promise.resolve()); - } if (info.hasUserBeenNotified !== undefined) { when(hasUserBeenNotifiedState.value).thenReturn(info.hasUserBeenNotified!); } @@ -290,125 +287,42 @@ suite('Insiders Extension Service - Function handleEdgeCases()', () => { } } - suite('Case II - Verify Insiders Install Prompt is displayed when conditions are met', async () => { - const testsForHandleEdgeCaseII: TestInfo[] = [ + test(`Insiders Install Prompt is displayed when vscode channel = 'insiders', user has not been notified to install insiders, isChannelUsingDefaultConfiguration = true`, async () => { + setState( { - installChannel: 'daily', // prompt to enroll vscodeChannel: 'insiders', hasUserBeenNotified: false, isChannelUsingDefaultConfiguration: true }, - { - installChannel: 'off', - // prompt to enroll - vscodeChannel: 'insiders', - hasUserBeenNotified: false, - isChannelUsingDefaultConfiguration: true - } - ]; - - setup(() => { - setupCommon(); - }); - - testsForHandleEdgeCaseII.forEach((testParams) => { - const testName = `Insiders Install Prompt is displayed when vscode channel = '${ - testParams.vscodeChannel - }', extension channel = '${testParams.extensionChannel}', install channel = '${ - testParams.installChannel - }', ${ - !testParams.hasUserBeenNotified - ? 'user has not been notified to install insiders' - : 'user has already been notified to install insiders' - }, isChannelUsingDefaultConfiguration = ${testParams.isChannelUsingDefaultConfiguration}`; - test(testName, async () => { - setState(testParams, true, false); - - await insidersExtensionService.handleEdgeCases( - testParams.installChannel, - testParams.isChannelUsingDefaultConfiguration! - ); - - verifyAll(); - verify(hasUserBeenNotifiedState.value).once(); - }); - }); - }); - - suite('Case III - Verify Insiders channel is set to off when conditions are met', async () => { - const testsForHandleEdgeCaseIII: TestInfo[] = [ - { - installChannel: 'daily', - // skip enroll - vscodeChannel: 'stable', - // disable - // with installChannel from above - extensionChannel: 'stable' - }, - { - installChannel: 'weekly', - // skip enroll - vscodeChannel: 'stable', - // disable - // with installChannel from above - extensionChannel: 'stable' - } - ]; - - setup(() => { - setupCommon(); - }); + true + ); - testsForHandleEdgeCaseIII.forEach((testParams) => { - const testName = `Insiders channel is set to off when vscode channel = '${ - testParams.vscodeChannel - }', extension channel = '${testParams.extensionChannel}', install channel = '${ - testParams.installChannel - }', ${ - !testParams.hasUserBeenNotified - ? 'user has not been notified to install insiders' - : 'user has already been notified to install insiders' - }, isChannelUsingDefaultConfiguration = ${testParams.isChannelUsingDefaultConfiguration}`; - test(testName, async () => { - setState(testParams, false, true); + await insidersExtensionService.handleEdgeCases(true); - await insidersExtensionService.handleEdgeCases( - testParams.installChannel, - false // isDefault - ); - - verifyAll(); - verify(hasUserBeenNotifiedState.value).never(); - }); - }); + verifyAll(); + verify(hasUserBeenNotifiedState.value).once(); }); - suite('Case IV - Verify no operation is performed if none of the case conditions are met', async () => { - const testsForHandleEdgeCaseIV: TestInfo[] = [ + suite('Verify no operation is performed if none of the case conditions are met', async () => { + const testsForHandleEdgeCases: TestInfo[] = [ { installChannel: 'daily', // skip enroll vscodeChannel: 'insiders', - hasUserBeenNotified: true, - // skip disable - extensionChannel: 'insiders' + hasUserBeenNotified: true }, { installChannel: 'daily', // skip enroll vscodeChannel: 'insiders', hasUserBeenNotified: false, - isChannelUsingDefaultConfiguration: false, - // skip disable - extensionChannel: 'insiders' + isChannelUsingDefaultConfiguration: false }, { installChannel: 'daily', // skip enroll - vscodeChannel: 'stable', - // skip disable - extensionChannel: 'insiders' + vscodeChannel: 'stable' }, { installChannel: 'off', @@ -436,21 +350,18 @@ suite('Insiders Extension Service - Function handleEdgeCases()', () => { setupCommon(); }); - testsForHandleEdgeCaseIV.forEach((testParams) => { + testsForHandleEdgeCases.forEach((testParams) => { const testName = `No operation is performed when vscode channel = '${ testParams.vscodeChannel - }', extension channel = '${testParams.extensionChannel}', install channel = '${ - testParams.installChannel - }', ${ + }', install channel = '${testParams.installChannel}', ${ !testParams.hasUserBeenNotified ? 'user has not been notified to install insiders' : 'user has already been notified to install insiders' }, isChannelUsingDefaultConfiguration = ${testParams.isChannelUsingDefaultConfiguration}`; test(testName, async () => { - setState(testParams, false, false); + setState(testParams, false); await insidersExtensionService.handleEdgeCases( - testParams.installChannel, testParams.isChannelUsingDefaultConfiguration || testParams.installChannel === 'off' ); diff --git a/src/test/common/installer/moduleInstaller.unit.test.ts b/src/test/common/installer/moduleInstaller.unit.test.ts index 93594c050e46..73ad8ded6dfa 100644 --- a/src/test/common/installer/moduleInstaller.unit.test.ts +++ b/src/test/common/installer/moduleInstaller.unit.test.ts @@ -47,7 +47,7 @@ import { IServiceContainer } from '../../../client/ioc/types'; import { EnvironmentType, PythonEnvironment } from '../../../client/pythonEnvironments/info'; import { EXTENSION_ROOT_DIR_FOR_TESTS } from '../../constants'; -const isolated = path.join(EXTENSION_ROOT_DIR_FOR_TESTS, 'pythonFiles', 'pyvsc-run-isolated.py').replace(/\\/g, '/'); +const isolated = path.join(EXTENSION_ROOT_DIR_FOR_TESTS, 'pythonFiles', 'pyvsc-run-isolated.py'); /* Complex test to ensure we cover all combinations: We could have written separate tests for each installer, but we'd be replicate code. diff --git a/src/test/common/moduleInstaller.test.ts b/src/test/common/moduleInstaller.test.ts index a4752d58f27a..69cdb28a8910 100644 --- a/src/test/common/moduleInstaller.test.ts +++ b/src/test/common/moduleInstaller.test.ts @@ -137,7 +137,7 @@ import { closeActiveWindows, initializeTest } from './../initialize'; chai_use(chaiAsPromised); -const isolated = path.join(EXTENSION_ROOT_DIR_FOR_TESTS, 'pythonFiles', 'pyvsc-run-isolated.py').replace(/\\/g, '/'); +const isolated = path.join(EXTENSION_ROOT_DIR_FOR_TESTS, 'pythonFiles', 'pyvsc-run-isolated.py'); const info: PythonEnvironment = { architecture: Architecture.Unknown, diff --git a/src/test/common/process/pythonProcess.unit.test.ts b/src/test/common/process/pythonProcess.unit.test.ts index cc0a847e7e7e..646ad8473c05 100644 --- a/src/test/common/process/pythonProcess.unit.test.ts +++ b/src/test/common/process/pythonProcess.unit.test.ts @@ -12,7 +12,7 @@ import { IProcessService, StdErrError } from '../../../client/common/process/typ import { EXTENSION_ROOT_DIR_FOR_TESTS } from '../../constants'; import { noop } from '../../core'; -const isolated = path.join(EXTENSION_ROOT_DIR_FOR_TESTS, 'pythonFiles', 'pyvsc-run-isolated.py').replace(/\\/g, '/'); +const isolated = path.join(EXTENSION_ROOT_DIR_FOR_TESTS, 'pythonFiles', 'pyvsc-run-isolated.py'); use(chaiAsPromised); diff --git a/src/test/common/terminals/synchronousTerminalService.unit.test.ts b/src/test/common/terminals/synchronousTerminalService.unit.test.ts index 75b0e27bb63a..082eeb1953ca 100644 --- a/src/test/common/terminals/synchronousTerminalService.unit.test.ts +++ b/src/test/common/terminals/synchronousTerminalService.unit.test.ts @@ -67,7 +67,7 @@ suite('Terminal Service (synchronous)', () => { }); }); suite('sendCommand', () => { - const isolated = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'pyvsc-run-isolated.py').replace(/\\/g, '/'); + const isolated = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'pyvsc-run-isolated.py'); const shellExecFile = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'shell_exec.py'); test('run sendCommand in terminalService if there is no cancellation token', async () => { diff --git a/src/test/debuggerTest.ts b/src/test/debuggerTest.ts index c7fc3e1058d1..3720b0e662ed 100644 --- a/src/test/debuggerTest.ts +++ b/src/test/debuggerTest.ts @@ -10,6 +10,7 @@ import { EXTENSION_ROOT_DIR_FOR_TESTS } from './constants'; const workspacePath = path.join(__dirname, '..', '..', 'src', 'testMultiRootWkspc', 'multi.code-workspace'); process.env.IS_CI_SERVER_TEST_DEBUGGER = '1'; process.env.VSC_PYTHON_CI_TEST = '1'; +const channel = process.env.VSC_PYTHON_CI_TEST_VSC_CHANNEL || 'stable'; function start() { console.log('*'.repeat(100)); @@ -18,7 +19,7 @@ function start() { extensionDevelopmentPath: EXTENSION_ROOT_DIR_FOR_TESTS, extensionTestsPath: path.join(EXTENSION_ROOT_DIR_FOR_TESTS, 'out', 'test', 'index'), launchArgs: [workspacePath], - version: 'stable', + version: channel, extensionTestsEnv: { ...process.env, UITEST_DISABLE_INSIDERS: '1' } }).catch((ex) => { console.error('End Debugger tests (with errors)', ex); diff --git a/src/test/mocks/vsc/extHostedTypes.ts b/src/test/mocks/vsc/extHostedTypes.ts index 35d5f7b82132..fc8f77279b65 100644 --- a/src/test/mocks/vsc/extHostedTypes.ts +++ b/src/test/mocks/vsc/extHostedTypes.ts @@ -547,7 +547,10 @@ export namespace vscMockExtHostedTypes { } export class WorkspaceEdit implements vscode.WorkspaceEdit { - replaceCells( + replaceNotebookMetadata(_uri: vscode.Uri, _value: vscode.NotebookDocumentMetadata): void { + // + } + replaceNotebookCells( _uri: vscode.Uri, _start: number, _end: number, @@ -557,7 +560,7 @@ export namespace vscMockExtHostedTypes { // Noop. } - replaceCellOutput( + replaceNotebookCellOutput( _uri: vscode.Uri, _index: number, _outputs: vscode.CellOutput[], @@ -566,7 +569,7 @@ export namespace vscMockExtHostedTypes { // Noop. } - replaceCellMetadata( + replaceNotebookCellMetadata( _uri: vscode.Uri, _index: number, _cellMetadata: vscode.NotebookCellMetadata, diff --git a/src/test/multiRootTest.ts b/src/test/multiRootTest.ts index 5859708a8e99..04631bd1b2ca 100644 --- a/src/test/multiRootTest.ts +++ b/src/test/multiRootTest.ts @@ -11,6 +11,8 @@ process.env.VSC_PYTHON_CI_TEST = '1'; initializeLogger(); +const channel = process.env.VSC_PYTHON_CI_TEST_VSC_CHANNEL || 'stable'; + function start() { console.log('*'.repeat(100)); console.log('Start Multiroot tests'); @@ -18,7 +20,7 @@ function start() { extensionDevelopmentPath: EXTENSION_ROOT_DIR_FOR_TESTS, extensionTestsPath: path.join(EXTENSION_ROOT_DIR_FOR_TESTS, 'out', 'test', 'index'), launchArgs: [workspacePath], - version: 'stable', + version: channel, extensionTestsEnv: { ...process.env, UITEST_DISABLE_INSIDERS: '1' } }).catch((ex) => { console.error('End Multiroot tests (with errors)', ex); diff --git a/src/test/pythonEnvironments/base/common.ts b/src/test/pythonEnvironments/base/common.ts index 6f56011f1f03..7b0516d92a65 100644 --- a/src/test/pythonEnvironments/base/common.ts +++ b/src/test/pythonEnvironments/base/common.ts @@ -1,14 +1,17 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { createDeferred, flattenIterator, iterable, mapToIterator } from '../../../client/common/utils/async'; +import { Event } from 'vscode'; +import { + createDeferred, flattenIterator, iterable, mapToIterator, +} from '../../../client/common/utils/async'; import { Architecture } from '../../../client/common/utils/platform'; import { PythonEnvInfo, PythonEnvKind, } from '../../../client/pythonEnvironments/base/info'; import { parseVersion } from '../../../client/pythonEnvironments/base/info/pythonVersion'; -import { IPythonEnvsIterator, Locator, PythonLocatorQuery } from '../../../client/pythonEnvironments/base/locator'; +import { IPythonEnvsIterator, Locator, PythonEnvUpdatedEvent, PythonLocatorQuery } from '../../../client/pythonEnvironments/base/locator'; import { PythonEnvsChangedEvent } from '../../../client/pythonEnvironments/base/watcher'; export function createEnv( @@ -66,6 +69,7 @@ export class SimpleLocator extends Locator { resolve?: null | ((env: PythonEnvInfo) => Promise); before?: Promise; after?: Promise; + onUpdated?: Event; beforeEach?(e: PythonEnvInfo): Promise; afterEach?(e: PythonEnvInfo): Promise; onQuery?(query: PythonLocatorQuery | undefined, envs: PythonEnvInfo[]): Promise; @@ -83,7 +87,7 @@ export class SimpleLocator extends Locator { const deferred = this.deferred; const callbacks = this.callbacks; let envs = this.envs; - async function* iterator() { + const iterator: IPythonEnvsIterator = async function*() { if (callbacks?.onQuery !== undefined) { envs = await callbacks.onQuery(query, envs); } @@ -114,8 +118,9 @@ export class SimpleLocator extends Locator { await callbacks.after; } deferred.resolve(); - } - return iterator(); + }(); + iterator.onUpdated = this.callbacks?.onUpdated; + return iterator; } public async resolveEnv(env: string | PythonEnvInfo): Promise { const envInfo: PythonEnvInfo = typeof env === 'string' ? createEnv('', '', undefined, env) : env; diff --git a/src/test/pythonEnvironments/base/locators/composite/environmentsReducer.unit.test.ts b/src/test/pythonEnvironments/base/locators/composite/environmentsReducer.unit.test.ts new file mode 100644 index 000000000000..9bb67f672768 --- /dev/null +++ b/src/test/pythonEnvironments/base/locators/composite/environmentsReducer.unit.test.ts @@ -0,0 +1,218 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { assert, expect } from 'chai'; +import { isEqual } from 'lodash'; +import * as path from 'path'; +import { EventEmitter } from 'vscode'; +import { PythonEnvInfo, PythonEnvKind } from '../../../../../client/pythonEnvironments/base/info'; +import { PythonEnvUpdatedEvent } from '../../../../../client/pythonEnvironments/base/locator'; +import { + mergeEnvironments, + PythonEnvsReducer, +} from '../../../../../client/pythonEnvironments/base/locators/composite/environmentsReducer'; +import { PythonEnvsChangedEvent } from '../../../../../client/pythonEnvironments/base/watcher'; +import { sleep } from '../../../../core'; +import { createEnv, getEnvs, SimpleLocator } from '../../common'; + +suite('Environments Reducer', () => { + suite('iterEnvs()', () => { + test('Iterator only yields unique environments', async () => { + const env1 = createEnv('env1', '3.5', PythonEnvKind.Venv, path.join('path', 'to', 'exec1')); + const env2 = createEnv('env2', '3.8', PythonEnvKind.Conda, path.join('path', 'to', 'exec2')); + const env3 = createEnv('env3', '2.7', PythonEnvKind.System, path.join('path', 'to', 'exec3')); + const env4 = createEnv('env4', '3.8.1', PythonEnvKind.Unknown, path.join('path', 'to', 'exec2')); // Same as env2 + const env5 = createEnv('env5', '3.5.12b1', PythonEnvKind.Venv, path.join('path', 'to', 'exec1')); // Same as env1 + const environmentsToBeIterated = [env1, env2, env3, env4, env5]; // Contains 3 unique environments + const parentLocator = new SimpleLocator(environmentsToBeIterated); + const reducer = new PythonEnvsReducer(parentLocator); + + const iterator = reducer.iterEnvs(); + const envs = await getEnvs(iterator); + + const expected = [env1, env2, env3]; + assert.deepEqual(envs, expected); + }); + + test('Single updates for multiple environments are sent correctly followed by the null event', async () => { + // Arrange + const env1 = createEnv('env1', '3.5', PythonEnvKind.Unknown, path.join('path', 'to', 'exec1')); + const env2 = createEnv('env2', '3.8', PythonEnvKind.Unknown, path.join('path', 'to', 'exec2')); + const env3 = createEnv('env3', '2.7', PythonEnvKind.System, path.join('path', 'to', 'exec3')); + const env4 = createEnv('env4', '3.8.1', PythonEnvKind.Conda, path.join('path', 'to', 'exec2')); // Same as env2; + const env5 = createEnv('env5', '3.5.12b1', PythonEnvKind.Venv, path.join('path', 'to', 'exec1')); // Same as env1; + const environmentsToBeIterated = [env1, env2, env3, env4, env5]; // Contains 3 unique environments + const parentLocator = new SimpleLocator(environmentsToBeIterated); + const onUpdatedEvents: (PythonEnvUpdatedEvent | null)[] = []; + const reducer = new PythonEnvsReducer(parentLocator); + + const iterator = reducer.iterEnvs(); // Act + + // Assert + let { onUpdated } = iterator; + expect(onUpdated).to.not.equal(undefined, ''); + + // Arrange + onUpdated = onUpdated!; + onUpdated((e) => { + onUpdatedEvents.push(e); + }); + + // Act + await getEnvs(iterator); + await sleep(1); // Resolve pending calls in the background + + // Assert + const expectedUpdates = [ + { old: env2, new: mergeEnvironments(env2, env4) }, + { old: env1, new: mergeEnvironments(env1, env5) }, + null, + ]; + assert.deepEqual(expectedUpdates, onUpdatedEvents); + }); + + test('Multiple updates for the same environment are sent correctly followed by the null event', async () => { + // Arrange + const env1 = createEnv('env1', '3.8', PythonEnvKind.Unknown, path.join('path', 'to', 'exec')); + const env2 = createEnv('env2', '3.8.1', PythonEnvKind.System, path.join('path', 'to', 'exec')); + const env3 = createEnv('env3', '3.8.1', PythonEnvKind.Conda, path.join('path', 'to', 'exec')); + const environmentsToBeIterated = [env1, env2, env3]; // All refer to the same environment + const parentLocator = new SimpleLocator(environmentsToBeIterated); + const onUpdatedEvents: (PythonEnvUpdatedEvent | null)[] = []; + const reducer = new PythonEnvsReducer(parentLocator); + + const iterator = reducer.iterEnvs(); // Act + + // Assert + let { onUpdated } = iterator; + expect(onUpdated).to.not.equal(undefined, ''); + + // Arrange + onUpdated = onUpdated!; + onUpdated((e) => { + onUpdatedEvents.push(e); + }); + + // Act + await getEnvs(iterator); + await sleep(1); // Resolve pending calls in the background + + // Assert + const env12 = mergeEnvironments(env1, env2); + const env123 = mergeEnvironments(env12, env3); + const expectedUpdates: (PythonEnvUpdatedEvent | null)[] = []; + if (isEqual(env12, env123)) { + expectedUpdates.push({ old: env1, new: env12 }, null); + } else { + expectedUpdates.push({ old: env1, new: env12 }, { old: env12, new: env123 }, null); + } + assert.deepEqual(onUpdatedEvents, expectedUpdates); + }); + + test('Updates to environments from the incoming iterator are passed on correctly followed by the null event', async () => { + // Arrange + const env1 = createEnv('env1', '3.8', PythonEnvKind.Unknown, path.join('path', 'to', 'exec')); + const env2 = createEnv('env2', '3.8.1', PythonEnvKind.System, path.join('path', 'to', 'exec')); + const environmentsToBeIterated = [env1]; + const didUpdate = new EventEmitter(); + const parentLocator = new SimpleLocator(environmentsToBeIterated, { onUpdated: didUpdate.event }); + const onUpdatedEvents: (PythonEnvUpdatedEvent | null)[] = []; + const reducer = new PythonEnvsReducer(parentLocator); + + const iterator = reducer.iterEnvs(); // Act + + // Assert + let { onUpdated } = iterator; + expect(onUpdated).to.not.equal(undefined, ''); + + // Arrange + onUpdated = onUpdated!; + onUpdated((e) => { + onUpdatedEvents.push(e); + }); + + // Act + await getEnvs(iterator); + didUpdate.fire({ old: env1, new: env2 }); + didUpdate.fire(null); // It is essential for the incoming iterator to fire "null" event signifying it's done + await sleep(1); + + // Assert + const expectedUpdates = [{ old: env1, new: mergeEnvironments(env1, env2) }, null]; + assert.deepEqual(expectedUpdates, onUpdatedEvents); + didUpdate.dispose(); + }); + }); + + test('onChanged fires iff onChanged from locator manager fires', () => { + const parentLocator = new SimpleLocator([]); + const event1: PythonEnvsChangedEvent = {}; + const event2: PythonEnvsChangedEvent = { kind: PythonEnvKind.Unknown }; + const expected = [event1, event2]; + const reducer = new PythonEnvsReducer(parentLocator); + + const events: PythonEnvsChangedEvent[] = []; + reducer.onChanged((e) => events.push(e)); + + parentLocator.fire(event1); + parentLocator.fire(event2); + + assert.deepEqual(events, expected); + }); + + suite('resolveEnv()', () => { + test('Iterates environments from the reducer to get resolved environment, then calls into locator manager to resolve environment further and return it', async () => { + const env1 = createEnv('env1', '3.8', PythonEnvKind.Unknown, path.join('path', 'to', 'exec')); + const env2 = createEnv('env2', '2.7', PythonEnvKind.System, path.join('path', 'to', 'exec3')); + const env3 = createEnv('env3', '3.8.1', PythonEnvKind.Conda, path.join('path', 'to', 'exec')); + const env4 = createEnv('env4', '3.8.1', PythonEnvKind.Conda, path.join('path', 'to', 'exec2')); + const env5 = createEnv('env5', '3.5.12b1', PythonEnvKind.Venv, path.join('path', 'to', 'exec1')); + const env6 = createEnv('env6', '3.8.1', PythonEnvKind.System, path.join('path', 'to', 'exec')); + const environmentsToBeIterated = [env1, env2, env3, env4, env5, env6]; // env1 env3 env6 are same + + const env13 = mergeEnvironments(env1, env3); + const env136 = mergeEnvironments(env13, env6); + const expectedResolvedEnv = createEnv('resolvedEnv', '3.8.1', PythonEnvKind.Conda, 'resolved/path/to/exec'); + const parentLocator = new SimpleLocator(environmentsToBeIterated, { + resolve: async (e: PythonEnvInfo) => { + if (isEqual(e, env136)) { + return expectedResolvedEnv; + } + return undefined; + }, + }); + const reducer = new PythonEnvsReducer(parentLocator); + + // Trying to resolve the environment corresponding to env1 env3 env6 + const expected = await reducer.resolveEnv(path.join('path', 'to', 'exec')); + + assert.deepEqual(expected, expectedResolvedEnv); + }); + + test("If the reducer isn't able to resolve environment, return undefined", async () => { + const env1 = createEnv('env1', '3.8', PythonEnvKind.Unknown, path.join('path', 'to', 'exec')); + const env2 = createEnv('env2', '2.7', PythonEnvKind.System, path.join('path', 'to', 'exec3')); + const env3 = createEnv('env3', '3.8.1', PythonEnvKind.Conda, path.join('path', 'to', 'exec')); + const env4 = createEnv('env4', '3.8.1', PythonEnvKind.Conda, path.join('path', 'to', 'exec2')); + const env5 = createEnv('env5', '3.5.12b1', PythonEnvKind.Venv, path.join('path', 'to', 'exec1')); + const env6 = createEnv('env6', '3.8.1', PythonEnvKind.System, path.join('path', 'to', 'exec')); + const environmentsToBeIterated = [env1, env2, env3, env4, env5, env6]; // env1 env3 env6 are same + + const env13 = mergeEnvironments(env1, env3); + const env136 = mergeEnvironments(env13, env6); + const parentLocator = new SimpleLocator(environmentsToBeIterated, { + resolve: async (e: PythonEnvInfo) => { + if (isEqual(e, env136)) { + return createEnv('resolvedEnv', '3.8.1', PythonEnvKind.Conda, 'resolved/path/to/exec'); + } + return undefined; + }, + }); + const reducer = new PythonEnvsReducer(parentLocator); + + const expected = await reducer.resolveEnv(path.join('path', 'to', 'execNeverSeenBefore')); + + assert.deepEqual(expected, undefined); + }); + }); +}); diff --git a/src/test/pythonEnvironments/base/locators/composite/environmentsResolver.unit.test.ts b/src/test/pythonEnvironments/base/locators/composite/environmentsResolver.unit.test.ts new file mode 100644 index 000000000000..b0a7d5bbf17b --- /dev/null +++ b/src/test/pythonEnvironments/base/locators/composite/environmentsResolver.unit.test.ts @@ -0,0 +1,244 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { assert, expect } from 'chai'; +import { cloneDeep } from 'lodash'; +import * as path from 'path'; +import { ImportMock } from 'ts-mock-imports'; +import { EventEmitter } from 'vscode'; +import { ExecutionResult } from '../../../../../client/common/process/types'; +import { Architecture } from '../../../../../client/common/utils/platform'; +import { PythonEnvInfo, PythonEnvKind } from '../../../../../client/pythonEnvironments/base/info'; +import { parseVersion } from '../../../../../client/pythonEnvironments/base/info/pythonVersion'; +import { PythonEnvUpdatedEvent } from '../../../../../client/pythonEnvironments/base/locator'; +import { PythonEnvsResolver } from '../../../../../client/pythonEnvironments/base/locators/composite/environmentsResolver'; +import { PythonEnvsChangedEvent } from '../../../../../client/pythonEnvironments/base/watcher'; +import * as ExternalDep from '../../../../../client/pythonEnvironments/common/externalDependencies'; +import { EnvironmentInfoService } from '../../../../../client/pythonEnvironments/info/environmentInfoService'; +import { sleep } from '../../../../core'; +import { createEnv, getEnvs, SimpleLocator } from '../../common'; + +suite('Environments Resolver', () => { + /** + * Returns the expected environment to be returned by Environment info service + */ + function createExpectedEnvInfo(env: PythonEnvInfo): PythonEnvInfo { + const updatedEnv = cloneDeep(env); + updatedEnv.version = { + ...parseVersion('3.8.3-final'), + sysVersion: '3.8.3 (tags/v3.8.3:6f8c832, May 13 2020, 22:37:02) [MSC v.1924 64 bit (AMD64)]', + }; + updatedEnv.executable.filename = env.executable.filename; + updatedEnv.executable.sysPrefix = 'path'; + updatedEnv.arch = Architecture.x64; + return updatedEnv; + } + suite('iterEnvs()', () => { + let stubShellExec: sinon.SinonStub; + setup(() => { + stubShellExec = ImportMock.mockFunction( + ExternalDep, + 'shellExecute', + new Promise>((resolve) => { + resolve({ + stdout: + '{"versionInfo": [3, 8, 3, "final", 0], "sysPrefix": "path", "sysVersion": "3.8.3 (tags/v3.8.3:6f8c832, May 13 2020, 22:37:02) [MSC v.1924 64 bit (AMD64)]", "is64Bit": true}', + }); + }), + ); + }); + + teardown(() => { + stubShellExec.restore(); + }); + + test('Iterator yields environments as-is', async () => { + const env1 = createEnv('env1', '3.5.12b1', PythonEnvKind.Venv, path.join('path', 'to', 'exec1')); + const env2 = createEnv('env2', '3.8.1', PythonEnvKind.Conda, path.join('path', 'to', 'exec2')); + const env3 = createEnv('env3', '2.7', PythonEnvKind.System, path.join('path', 'to', 'exec3')); + const env4 = createEnv('env4', '3.9.0rc2', PythonEnvKind.Unknown, path.join('path', 'to', 'exec2')); + const environmentsToBeIterated = [env1, env2, env3, env4]; + const parentLocator = new SimpleLocator(environmentsToBeIterated); + const resolver = new PythonEnvsResolver(parentLocator, new EnvironmentInfoService()); + + const iterator = resolver.iterEnvs(); + const envs = await getEnvs(iterator); + + assert.deepEqual(envs, environmentsToBeIterated); + }); + + test('Updates for environments are sent correctly followed by the null event', async () => { + // Arrange + const env1 = createEnv('env1', '3.5.12b1', PythonEnvKind.Unknown, path.join('path', 'to', 'exec1')); + const env2 = createEnv('env2', '3.8.1', PythonEnvKind.Unknown, path.join('path', 'to', 'exec2')); + const environmentsToBeIterated = [env1, env2]; + const parentLocator = new SimpleLocator(environmentsToBeIterated); + const onUpdatedEvents: (PythonEnvUpdatedEvent | null)[] = []; + const resolver = new PythonEnvsResolver(parentLocator, new EnvironmentInfoService()); + + const iterator = resolver.iterEnvs(); // Act + + // Assert + let { onUpdated } = iterator; + expect(onUpdated).to.not.equal(undefined, ''); + + // Arrange + onUpdated = onUpdated!; + onUpdated((e) => { + onUpdatedEvents.push(e); + }); + + // Act + await getEnvs(iterator); + await sleep(1); // Resolve pending calls in the background + + // Assert + const expectedUpdates = [ + { old: env1, new: createExpectedEnvInfo(env1) }, + { old: env2, new: createExpectedEnvInfo(env2) }, + null, + ]; + assert.deepEqual(expectedUpdates, onUpdatedEvents); + }); + + test('Updates to environments from the incoming iterator are sent correctly followed by the null event', async () => { + // Arrange + const env = createEnv('env1', '3.8', PythonEnvKind.Unknown, path.join('path', 'to', 'exec')); + const updatedEnv = createEnv('env1', '3.8.1', PythonEnvKind.System, path.join('path', 'to', 'exec')); + const environmentsToBeIterated = [env]; + const didUpdate = new EventEmitter(); + const parentLocator = new SimpleLocator(environmentsToBeIterated, { onUpdated: didUpdate.event }); + const onUpdatedEvents: (PythonEnvUpdatedEvent | null)[] = []; + const resolver = new PythonEnvsResolver(parentLocator, new EnvironmentInfoService()); + + const iterator = resolver.iterEnvs(); // Act + + // Assert + let { onUpdated } = iterator; + expect(onUpdated).to.not.equal(undefined, ''); + + // Arrange + onUpdated = onUpdated!; + onUpdated((e) => { + onUpdatedEvents.push(e); + }); + + // Act + await getEnvs(iterator); + await sleep(1); + didUpdate.fire({ old: env, new: updatedEnv }); + didUpdate.fire(null); // It is essential for the incoming iterator to fire "null" event signifying it's done + await sleep(1); + + // Assert + // The updates can be anything, even the number of updates, but they should lead to the same final state + const { length } = onUpdatedEvents; + assert.deepEqual( + onUpdatedEvents[length - 2]?.new, + createExpectedEnvInfo(updatedEnv), + 'The final update to environment is incorrect', + ); + assert.equal(onUpdatedEvents[length - 1], null, 'Last update should be null'); + didUpdate.dispose(); + }); + }); + + test('onChanged fires iff onChanged from resolver fires', () => { + const parentLocator = new SimpleLocator([]); + const event1: PythonEnvsChangedEvent = {}; + const event2: PythonEnvsChangedEvent = { kind: PythonEnvKind.Unknown }; + const expected = [event1, event2]; + const resolver = new PythonEnvsResolver(parentLocator, new EnvironmentInfoService()); + + const events: PythonEnvsChangedEvent[] = []; + resolver.onChanged((e) => events.push(e)); + + parentLocator.fire(event1); + parentLocator.fire(event2); + + assert.deepEqual(events, expected); + }); + + suite('resolveEnv()', () => { + let stubShellExec: sinon.SinonStub; + setup(() => { + stubShellExec = ImportMock.mockFunction( + ExternalDep, + 'shellExecute', + new Promise>((resolve) => { + resolve({ + stdout: + '{"versionInfo": [3, 8, 3, "final", 0], "sysPrefix": "path", "sysVersion": "3.8.3 (tags/v3.8.3:6f8c832, May 13 2020, 22:37:02) [MSC v.1924 64 bit (AMD64)]", "is64Bit": true}', + }); + }), + ); + }); + + teardown(() => { + stubShellExec.restore(); + }); + + test('Calls into parent locator to get resolved environment, then calls environnment service to resolve environment further and return it', async () => { + const env = createEnv('env1', '3.8', PythonEnvKind.Unknown, path.join('path', 'to', 'exec')); + const resolvedEnvReturnedByReducer = createEnv( + 'env1', + '3.8.1', + PythonEnvKind.Conda, + 'resolved/path/to/exec', + ); + const parentLocator = new SimpleLocator([], { + resolve: async (e: PythonEnvInfo) => { + if (e === env) { + return resolvedEnvReturnedByReducer; + } + throw new Error('Incorrect environment sent to the resolver'); + }, + }); + const resolver = new PythonEnvsResolver(parentLocator, new EnvironmentInfoService()); + + const expected = await resolver.resolveEnv(env); + + assert.deepEqual(expected, createExpectedEnvInfo(resolvedEnvReturnedByReducer)); + }); + + test('If the parent locator resolves environment, but fetching interpreter info returns undefined, return undefined', async () => { + stubShellExec.returns( + new Promise>((_resolve, reject) => { + reject(); + }), + ); + const env = createEnv('env1', '3.8', PythonEnvKind.Unknown, path.join('path', 'to', 'exec')); + const resolvedEnvReturnedByReducer = createEnv( + 'env1', + '3.8.1', + PythonEnvKind.Conda, + 'resolved/path/to/exec', + ); + const parentLocator = new SimpleLocator([], { + resolve: async (e: PythonEnvInfo) => { + if (e === env) { + return resolvedEnvReturnedByReducer; + } + throw new Error('Incorrect environment sent to the resolver'); + }, + }); + const resolver = new PythonEnvsResolver(parentLocator, new EnvironmentInfoService()); + + const expected = await resolver.resolveEnv(env); + + assert.deepEqual(expected, undefined); + }); + + test("If the parent locator isn't able to resolve environment, return undefined", async () => { + const env = createEnv('env', '3.8', PythonEnvKind.Unknown, path.join('path', 'to', 'exec')); + const parentLocator = new SimpleLocator([], { + resolve: async () => undefined, + }); + const resolver = new PythonEnvsResolver(parentLocator, new EnvironmentInfoService()); + + const expected = await resolver.resolveEnv(env); + + assert.deepEqual(expected, undefined); + }); + }); +}); diff --git a/src/test/standardTest.ts b/src/test/standardTest.ts index 4a6dd9f2e287..76739ae4b667 100644 --- a/src/test/standardTest.ts +++ b/src/test/standardTest.ts @@ -16,9 +16,7 @@ const extensionDevelopmentPath = process.env.CODE_EXTENSIONS_PATH ? process.env.CODE_EXTENSIONS_PATH : EXTENSION_ROOT_DIR_FOR_TESTS; -const channel = (process.env.VSC_PYTHON_CI_TEST_VSC_CHANNEL || '').toLowerCase().includes('insiders') - ? 'insiders' - : 'stable'; +const channel = process.env.VSC_PYTHON_CI_TEST_VSC_CHANNEL || 'stable'; function start() { console.log('*'.repeat(100)); diff --git a/types/vscode-proposed/index.d.ts b/types/vscode-proposed/index.d.ts index 2abb5824d26f..8c4fc94024b8 100644 --- a/types/vscode-proposed/index.d.ts +++ b/types/vscode-proposed/index.d.ts @@ -17,6 +17,8 @@ import { } from 'vscode'; // Copy nb section from https://github.com/microsoft/vscode/blob/master/src/vs/vscode.proposed.d.ts. +//#region @rebornix: Notebook + export enum CellKind { Markdown = 1, Code = 2 @@ -98,60 +100,60 @@ export interface NotebookCellMetadata { /** * Controls whether a cell's editor is editable/readonly. */ - editable?: boolean; + readonly editable?: boolean; /** * Controls if the cell is executable. * This metadata is ignored for markdown cell. */ - runnable?: boolean; + readonly runnable?: boolean; /** * Controls if the cell has a margin to support the breakpoint UI. * This metadata is ignored for markdown cell. */ - breakpointMargin?: boolean; + readonly breakpointMargin?: boolean; /** * Whether the [execution order](#NotebookCellMetadata.executionOrder) indicator will be displayed. * Defaults to true. */ - hasExecutionOrder?: boolean; + readonly hasExecutionOrder?: boolean; /** * The order in which this cell was executed. */ - executionOrder?: number; + readonly executionOrder?: number; /** * A status message to be shown in the cell's status bar */ - statusMessage?: string; + readonly statusMessage?: string; /** * The cell's current run state */ - runState?: NotebookCellRunState; + readonly runState?: NotebookCellRunState; /** * If the cell is running, the time at which the cell started running */ - runStartTime?: number; + readonly runStartTime?: number; /** * The total duration of the cell's last run */ - lastRunDuration?: number; + readonly lastRunDuration?: number; /** * Whether a code cell's editor is collapsed */ - inputCollapsed?: boolean; + readonly inputCollapsed?: boolean; /** * Whether a code cell's outputs are collapsed */ - outputCollapsed?: boolean; + readonly outputCollapsed?: boolean; /** * Additional attributes of a cell metadata. @@ -160,13 +162,14 @@ export interface NotebookCellMetadata { } export interface NotebookCell { + readonly index: number; readonly notebook: NotebookDocument; readonly uri: Uri; readonly cellKind: CellKind; readonly document: TextDocument; readonly language: string; - outputs: CellOutput[]; - metadata: NotebookCellMetadata; + readonly outputs: CellOutput[]; + readonly metadata: NotebookCellMetadata; } export interface NotebookDocumentMetadata { @@ -174,43 +177,57 @@ export interface NotebookDocumentMetadata { * Controls if users can add or delete cells * Defaults to true */ - editable?: boolean; + readonly editable?: boolean; /** * Controls whether the full notebook can be run at once. * Defaults to true */ - runnable?: boolean; + readonly runnable?: boolean; /** * Default value for [cell editable metadata](#NotebookCellMetadata.editable). * Defaults to true. */ - cellEditable?: boolean; + readonly cellEditable?: boolean; /** * Default value for [cell runnable metadata](#NotebookCellMetadata.runnable). * Defaults to true. */ - cellRunnable?: boolean; + readonly cellRunnable?: boolean; /** * Default value for [cell hasExecutionOrder metadata](#NotebookCellMetadata.hasExecutionOrder). * Defaults to true. */ - cellHasExecutionOrder?: boolean; + readonly cellHasExecutionOrder?: boolean; - displayOrder?: GlobPattern[]; + readonly displayOrder?: GlobPattern[]; /** * Additional attributes of the document metadata. */ - custom?: { [key: string]: any }; + readonly custom?: { [key: string]: any }; /** * The document's current run state */ - runState?: NotebookRunState; + readonly runState?: NotebookRunState; +} + +export interface NotebookDocumentContentOptions { + /** + * Controls if outputs change will trigger notebook document content change and if it will be used in the diff editor + * Default to false. If the content provider doesn't persisit the outputs in the file document, this should be set to true. + */ + readonly transientOutputs: boolean; + + /** + * Controls if a meetadata property change will trigger notebook document content change and if it will be used in the diff editor + * Default to false. If the content provider doesn't persisit a metadata property in the file document, it should be set to true. + */ + readonly transientMetadata: { [K in keyof NotebookCellMetadata]?: boolean }; } export interface NotebookDocument { @@ -221,8 +238,9 @@ export interface NotebookDocument { readonly isDirty: boolean; readonly isUntitled: boolean; readonly cells: ReadonlyArray; - languages: string[]; - metadata: NotebookDocumentMetadata; + readonly contentOptions: Readonly; + readonly languages: string[]; + readonly metadata: Readonly; } export interface NotebookConcatTextDocument { @@ -245,15 +263,21 @@ export interface NotebookConcatTextDocument { } export interface WorkspaceEdit { - replaceCells( + replaceNotebookMetadata(uri: Uri, value: NotebookDocumentMetadata): void; + replaceNotebookCells( uri: Uri, start: number, end: number, cells: NotebookCellData[], metadata?: WorkspaceEditEntryMetadata ): void; - replaceCellOutput(uri: Uri, index: number, outputs: CellOutput[], metadata?: WorkspaceEditEntryMetadata): void; - replaceCellMetadata( + replaceNotebookCellOutput( + uri: Uri, + index: number, + outputs: CellOutput[], + metadata?: WorkspaceEditEntryMetadata + ): void; + replaceNotebookCellMetadata( uri: Uri, index: number, cellMetadata: NotebookCellMetadata, @@ -261,26 +285,18 @@ export interface WorkspaceEdit { ): void; } -export interface NotebookEditorCellEdit { +export interface NotebookEditorEdit { + replaceMetadata(value: NotebookDocumentMetadata): void; replaceCells(start: number, end: number, cells: NotebookCellData[]): void; - replaceOutput(index: number, outputs: CellOutput[]): void; - replaceMetadata(index: number, metadata: NotebookCellMetadata): void; - - /** @deprecated */ - insert( - index: number, - content: string | string[], - language: string, - type: CellKind, - outputs: CellOutput[], - metadata: NotebookCellMetadata | undefined - ): void; - /** @deprecated */ - delete(index: number): void; + replaceCellOutput(index: number, outputs: CellOutput[]): void; + replaceCellMetadata(index: number, metadata: NotebookCellMetadata): void; } export interface NotebookCellRange { readonly start: number; + /** + * exclusive + */ readonly end: number; } @@ -359,7 +375,19 @@ export interface NotebookEditor { */ asWebviewUri(localResource: Uri): Uri; - edit(callback: (editBuilder: NotebookEditorCellEdit) => void): Thenable; + /** + * Perform an edit on the notebook associated with this notebook editor. + * + * The given callback-function is invoked with an [edit-builder](#NotebookEditorEdit) which must + * be used to make edits. Note that the edit-builder is only valid while the + * callback executes. + * + * @param callback A function which can create edits using an [edit-builder](#NotebookEditorEdit). + * @return A promise that resolves with a value indicating if the edits could be applied. + */ + edit(callback: (editBuilder: NotebookEditorEdit) => void): Thenable; + + setDecorations(decorationType: NotebookEditorDecorationType, range: NotebookCellRange): void; revealRange(range: NotebookCellRange, revealType?: NotebookEditorRevealType): void; } @@ -374,6 +402,10 @@ export interface NotebookRenderRequest { outputId: string; } +export interface NotebookDocumentMetadataChangeEvent { + readonly document: NotebookDocument; +} + export interface NotebookCellsChangeData { readonly start: number; readonly deletedCount: number; @@ -541,6 +573,10 @@ export interface NotebookCommunication { } export interface NotebookContentProvider { + readonly options?: NotebookDocumentContentOptions; + readonly onDidChangeNotebookContentOptions?: Event; + readonly onDidChangeNotebook: Event; + /** * Content providers should always use [file system providers](#FileSystemProvider) to * resolve the raw content for `uri` as the resouce is not necessarily a file on disk. @@ -549,7 +585,6 @@ export interface NotebookContentProvider { resolveNotebook(document: NotebookDocument, webview: NotebookCommunication): Promise; saveNotebook(document: NotebookDocument, cancellation: CancellationToken): Promise; saveNotebookAs(targetResource: Uri, document: NotebookDocument, cancellation: CancellationToken): Promise; - readonly onDidChangeNotebook: Event; backupNotebook( document: NotebookDocument, context: NotebookDocumentBackupContext, @@ -570,9 +605,11 @@ export interface NotebookKernel { cancelAllCellsExecution(document: NotebookDocument): void; } +export type NotebookFilenamePattern = GlobPattern | { include: GlobPattern; exclude: GlobPattern }; + export interface NotebookDocumentFilter { viewType?: string | string[]; - filenamePattern?: GlobPattern | { include: GlobPattern; exclude: GlobPattern }; + filenamePattern?: NotebookFilenamePattern; } export interface NotebookKernelProvider { @@ -614,21 +651,24 @@ export interface NotebookCellStatusBarItem { dispose(): void; } +export interface NotebookEditorDecorationType { + readonly key: string; + dispose(): void; +} + export namespace notebook { export function registerNotebookContentProvider( notebookType: string, provider: NotebookContentProvider, - options?: { - /** - * Controls if outputs change will trigger notebook document content change and if it will be used in the diff editor - * Default to false. If the content provider doesn't persisit the outputs in the file document, this should be set to true. - */ - transientOutputs: boolean; + options?: NotebookDocumentContentOptions & { /** - * Controls if a meetadata property change will trigger notebook document content change and if it will be used in the diff editor - * Default to false. If the content provider doesn't persisit a metadata property in the file document, it should be set to true. + * Not ready for production or development use yet. */ - transientMetadata: { [K in keyof NotebookCellMetadata]?: boolean }; + viewOptions?: { + displayName: string; + filenamePattern: NotebookFilenamePattern[]; + exclusive?: boolean; + }; } ): Disposable; @@ -653,6 +693,7 @@ export namespace notebook { export const onDidChangeActiveNotebookEditor: Event; export const onDidChangeNotebookEditorSelection: Event; export const onDidChangeNotebookEditorVisibleRanges: Event; + export const onDidChangeNotebookDocumentMetadata: Event; export const onDidChangeNotebookCells: Event; export const onDidChangeCellOutputs: Event; export const onDidChangeCellLanguage: Event; diff --git a/types/vscode.proposed.d.ts b/types/vscode.proposed.d.ts index 9f80ab43e6df..f693f822bcc1 100644 --- a/types/vscode.proposed.d.ts +++ b/types/vscode.proposed.d.ts @@ -3,6 +3,8 @@ // Copy nb section from https://github.com/microsoft/vscode/blob/master/src/vs/vscode.proposed.d.ts. declare module 'vscode' { + //#region @rebornix: Notebook + export enum CellKind { Markdown = 1, Code = 2 @@ -84,75 +86,76 @@ declare module 'vscode' { /** * Controls whether a cell's editor is editable/readonly. */ - editable?: boolean; + readonly editable?: boolean; /** * Controls if the cell is executable. * This metadata is ignored for markdown cell. */ - runnable?: boolean; + readonly runnable?: boolean; /** * Controls if the cell has a margin to support the breakpoint UI. * This metadata is ignored for markdown cell. */ - breakpointMargin?: boolean; + readonly breakpointMargin?: boolean; /** * Whether the [execution order](#NotebookCellMetadata.executionOrder) indicator will be displayed. * Defaults to true. */ - hasExecutionOrder?: boolean; + readonly hasExecutionOrder?: boolean; /** * The order in which this cell was executed. */ - executionOrder?: number; + readonly executionOrder?: number; /** * A status message to be shown in the cell's status bar */ - statusMessage?: string; + readonly statusMessage?: string; /** * The cell's current run state */ - runState?: NotebookCellRunState; + readonly runState?: NotebookCellRunState; /** * If the cell is running, the time at which the cell started running */ - runStartTime?: number; + readonly runStartTime?: number; /** * The total duration of the cell's last run */ - lastRunDuration?: number; + readonly lastRunDuration?: number; /** * Whether a code cell's editor is collapsed */ - inputCollapsed?: boolean; + readonly inputCollapsed?: boolean; /** * Whether a code cell's outputs are collapsed */ - outputCollapsed?: boolean; + readonly outputCollapsed?: boolean; /** * Additional attributes of a cell metadata. */ - custom?: { [key: string]: any }; + readonly custom?: { [key: string]: any }; } export interface NotebookCell { + readonly index: number; readonly notebook: NotebookDocument; readonly uri: Uri; readonly cellKind: CellKind; readonly document: TextDocument; readonly language: string; - outputs: CellOutput[]; - metadata: NotebookCellMetadata; + readonly outputs: CellOutput[]; + readonly metadata: NotebookCellMetadata; } export interface NotebookDocumentMetadata { @@ -160,43 +163,57 @@ declare module 'vscode' { * Controls if users can add or delete cells * Defaults to true */ - editable?: boolean; + readonly editable?: boolean; /** * Controls whether the full notebook can be run at once. * Defaults to true */ - runnable?: boolean; + readonly runnable?: boolean; /** * Default value for [cell editable metadata](#NotebookCellMetadata.editable). * Defaults to true. */ - cellEditable?: boolean; + readonly cellEditable?: boolean; /** * Default value for [cell runnable metadata](#NotebookCellMetadata.runnable). * Defaults to true. */ - cellRunnable?: boolean; + readonly cellRunnable?: boolean; /** * Default value for [cell hasExecutionOrder metadata](#NotebookCellMetadata.hasExecutionOrder). * Defaults to true. */ - cellHasExecutionOrder?: boolean; + readonly cellHasExecutionOrder?: boolean; - displayOrder?: GlobPattern[]; + readonly displayOrder?: GlobPattern[]; /** * Additional attributes of the document metadata. */ - custom?: { [key: string]: any }; + readonly custom?: { [key: string]: any }; /** * The document's current run state */ - runState?: NotebookRunState; + readonly runState?: NotebookRunState; + } + + export interface NotebookDocumentContentOptions { + /** + * Controls if outputs change will trigger notebook document content change and if it will be used in the diff editor + * Default to false. If the content provider doesn't persisit the outputs in the file document, this should be set to true. + */ + readonly transientOutputs: boolean; + + /** + * Controls if a meetadata property change will trigger notebook document content change and if it will be used in the diff editor + * Default to false. If the content provider doesn't persisit a metadata property in the file document, it should be set to true. + */ + readonly transientMetadata: { [K in keyof NotebookCellMetadata]?: boolean }; } export interface NotebookDocument { @@ -207,8 +224,9 @@ declare module 'vscode' { readonly isDirty: boolean; readonly isUntitled: boolean; readonly cells: ReadonlyArray; - languages: string[]; - metadata: NotebookDocumentMetadata; + readonly contentOptions: Readonly; + readonly languages: string[]; + readonly metadata: Readonly; } export interface NotebookConcatTextDocument { @@ -231,15 +249,21 @@ declare module 'vscode' { } export interface WorkspaceEdit { - replaceCells( + replaceNotebookMetadata(uri: Uri, value: NotebookDocumentMetadata): void; + replaceNotebookCells( uri: Uri, start: number, end: number, cells: NotebookCellData[], metadata?: WorkspaceEditEntryMetadata ): void; - replaceCellOutput(uri: Uri, index: number, outputs: CellOutput[], metadata?: WorkspaceEditEntryMetadata): void; - replaceCellMetadata( + replaceNotebookCellOutput( + uri: Uri, + index: number, + outputs: CellOutput[], + metadata?: WorkspaceEditEntryMetadata + ): void; + replaceNotebookCellMetadata( uri: Uri, index: number, cellMetadata: NotebookCellMetadata, @@ -247,26 +271,18 @@ declare module 'vscode' { ): void; } - export interface NotebookEditorCellEdit { + export interface NotebookEditorEdit { + replaceMetadata(value: NotebookDocumentMetadata): void; replaceCells(start: number, end: number, cells: NotebookCellData[]): void; - replaceOutput(index: number, outputs: CellOutput[]): void; - replaceMetadata(index: number, metadata: NotebookCellMetadata): void; - - /** @deprecated */ - insert( - index: number, - content: string | string[], - language: string, - type: CellKind, - outputs: CellOutput[], - metadata: NotebookCellMetadata | undefined - ): void; - /** @deprecated */ - delete(index: number): void; + replaceCellOutput(index: number, outputs: CellOutput[]): void; + replaceCellMetadata(index: number, metadata: NotebookCellMetadata): void; } export interface NotebookCellRange { readonly start: number; + /** + * exclusive + */ readonly end: number; } @@ -345,7 +361,19 @@ declare module 'vscode' { */ asWebviewUri(localResource: Uri): Uri; - edit(callback: (editBuilder: NotebookEditorCellEdit) => void): Thenable; + /** + * Perform an edit on the notebook associated with this notebook editor. + * + * The given callback-function is invoked with an [edit-builder](#NotebookEditorEdit) which must + * be used to make edits. Note that the edit-builder is only valid while the + * callback executes. + * + * @param callback A function which can create edits using an [edit-builder](#NotebookEditorEdit). + * @return A promise that resolves with a value indicating if the edits could be applied. + */ + edit(callback: (editBuilder: NotebookEditorEdit) => void): Thenable; + + setDecorations(decorationType: NotebookEditorDecorationType, range: NotebookCellRange): void; revealRange(range: NotebookCellRange, revealType?: NotebookEditorRevealType): void; } @@ -360,6 +388,10 @@ declare module 'vscode' { outputId: string; } + export interface NotebookDocumentMetadataChangeEvent { + readonly document: NotebookDocument; + } + export interface NotebookCellsChangeData { readonly start: number; readonly deletedCount: number; @@ -527,6 +559,10 @@ declare module 'vscode' { } export interface NotebookContentProvider { + readonly options?: NotebookDocumentContentOptions; + readonly onDidChangeNotebookContentOptions?: Event; + readonly onDidChangeNotebook: Event; + /** * Content providers should always use [file system providers](#FileSystemProvider) to * resolve the raw content for `uri` as the resouce is not necessarily a file on disk. @@ -535,7 +571,6 @@ declare module 'vscode' { resolveNotebook(document: NotebookDocument, webview: NotebookCommunication): Promise; saveNotebook(document: NotebookDocument, cancellation: CancellationToken): Promise; saveNotebookAs(targetResource: Uri, document: NotebookDocument, cancellation: CancellationToken): Promise; - readonly onDidChangeNotebook: Event; backupNotebook( document: NotebookDocument, context: NotebookDocumentBackupContext, @@ -556,9 +591,11 @@ declare module 'vscode' { cancelAllCellsExecution(document: NotebookDocument): void; } + export type NotebookFilenamePattern = GlobPattern | { include: GlobPattern; exclude: GlobPattern }; + export interface NotebookDocumentFilter { viewType?: string | string[]; - filenamePattern?: GlobPattern | { include: GlobPattern; exclude: GlobPattern }; + filenamePattern?: NotebookFilenamePattern; } export interface NotebookKernelProvider { @@ -600,21 +637,24 @@ declare module 'vscode' { dispose(): void; } + export interface NotebookEditorDecorationType { + readonly key: string; + dispose(): void; + } + export namespace notebook { export function registerNotebookContentProvider( notebookType: string, provider: NotebookContentProvider, - options?: { - /** - * Controls if outputs change will trigger notebook document content change and if it will be used in the diff editor - * Default to false. If the content provider doesn't persisit the outputs in the file document, this should be set to true. - */ - transientOutputs: boolean; + options?: NotebookDocumentContentOptions & { /** - * Controls if a meetadata property change will trigger notebook document content change and if it will be used in the diff editor - * Default to false. If the content provider doesn't persisit a metadata property in the file document, it should be set to true. + * Not ready for production or development use yet. */ - transientMetadata: { [K in keyof NotebookCellMetadata]?: boolean }; + viewOptions?: { + displayName: string; + filenamePattern: NotebookFilenamePattern[]; + exclusive?: boolean; + }; } ): Disposable; @@ -639,6 +679,7 @@ declare module 'vscode' { export const onDidChangeActiveNotebookEditor: Event; export const onDidChangeNotebookEditorSelection: Event; export const onDidChangeNotebookEditorVisibleRanges: Event; + export const onDidChangeNotebookDocumentMetadata: Event; export const onDidChangeNotebookCells: Event; export const onDidChangeCellOutputs: Event; export const onDidChangeCellLanguage: Event; diff --git a/typings/vscode-proposed/index.d.ts b/typings/vscode-proposed/index.d.ts index 2abb5824d26f..d78ec147d08b 100644 --- a/typings/vscode-proposed/index.d.ts +++ b/typings/vscode-proposed/index.d.ts @@ -17,6 +17,8 @@ import { } from 'vscode'; // Copy nb section from https://github.com/microsoft/vscode/blob/master/src/vs/vscode.proposed.d.ts. +//#region @rebornix: Notebook + export enum CellKind { Markdown = 1, Code = 2 @@ -98,75 +100,76 @@ export interface NotebookCellMetadata { /** * Controls whether a cell's editor is editable/readonly. */ - editable?: boolean; + readonly editable?: boolean; /** * Controls if the cell is executable. * This metadata is ignored for markdown cell. */ - runnable?: boolean; + readonly runnable?: boolean; /** * Controls if the cell has a margin to support the breakpoint UI. * This metadata is ignored for markdown cell. */ - breakpointMargin?: boolean; + readonly breakpointMargin?: boolean; /** * Whether the [execution order](#NotebookCellMetadata.executionOrder) indicator will be displayed. * Defaults to true. */ - hasExecutionOrder?: boolean; + readonly hasExecutionOrder?: boolean; /** * The order in which this cell was executed. */ - executionOrder?: number; + readonly executionOrder?: number; /** * A status message to be shown in the cell's status bar */ - statusMessage?: string; + readonly statusMessage?: string; /** * The cell's current run state */ - runState?: NotebookCellRunState; + readonly runState?: NotebookCellRunState; /** * If the cell is running, the time at which the cell started running */ - runStartTime?: number; + readonly runStartTime?: number; /** * The total duration of the cell's last run */ - lastRunDuration?: number; + readonly lastRunDuration?: number; /** * Whether a code cell's editor is collapsed */ - inputCollapsed?: boolean; + readonly inputCollapsed?: boolean; /** * Whether a code cell's outputs are collapsed */ - outputCollapsed?: boolean; + readonly outputCollapsed?: boolean; /** * Additional attributes of a cell metadata. */ - custom?: { [key: string]: any }; + readonly custom?: { [key: string]: any }; } export interface NotebookCell { + readonly index: number; readonly notebook: NotebookDocument; readonly uri: Uri; readonly cellKind: CellKind; readonly document: TextDocument; readonly language: string; - outputs: CellOutput[]; - metadata: NotebookCellMetadata; + readonly outputs: CellOutput[]; + readonly metadata: NotebookCellMetadata; } export interface NotebookDocumentMetadata { @@ -174,43 +177,57 @@ export interface NotebookDocumentMetadata { * Controls if users can add or delete cells * Defaults to true */ - editable?: boolean; + readonly editable?: boolean; /** * Controls whether the full notebook can be run at once. * Defaults to true */ - runnable?: boolean; + readonly runnable?: boolean; /** * Default value for [cell editable metadata](#NotebookCellMetadata.editable). * Defaults to true. */ - cellEditable?: boolean; + readonly cellEditable?: boolean; /** * Default value for [cell runnable metadata](#NotebookCellMetadata.runnable). * Defaults to true. */ - cellRunnable?: boolean; + readonly cellRunnable?: boolean; /** * Default value for [cell hasExecutionOrder metadata](#NotebookCellMetadata.hasExecutionOrder). * Defaults to true. */ - cellHasExecutionOrder?: boolean; + readonly cellHasExecutionOrder?: boolean; - displayOrder?: GlobPattern[]; + readonly displayOrder?: GlobPattern[]; /** * Additional attributes of the document metadata. */ - custom?: { [key: string]: any }; + readonly custom?: { [key: string]: any }; /** * The document's current run state */ - runState?: NotebookRunState; + readonly runState?: NotebookRunState; +} + +export interface NotebookDocumentContentOptions { + /** + * Controls if outputs change will trigger notebook document content change and if it will be used in the diff editor + * Default to false. If the content provider doesn't persisit the outputs in the file document, this should be set to true. + */ + readonly transientOutputs: boolean; + + /** + * Controls if a meetadata property change will trigger notebook document content change and if it will be used in the diff editor + * Default to false. If the content provider doesn't persisit a metadata property in the file document, it should be set to true. + */ + readonly transientMetadata: { [K in keyof NotebookCellMetadata]?: boolean }; } export interface NotebookDocument { @@ -221,8 +238,9 @@ export interface NotebookDocument { readonly isDirty: boolean; readonly isUntitled: boolean; readonly cells: ReadonlyArray; - languages: string[]; - metadata: NotebookDocumentMetadata; + readonly contentOptions: Readonly; + readonly languages: string[]; + readonly metadata: Readonly; } export interface NotebookConcatTextDocument { @@ -245,15 +263,21 @@ export interface NotebookConcatTextDocument { } export interface WorkspaceEdit { - replaceCells( + replaceNotebookMetadata(uri: Uri, value: NotebookDocumentMetadata): void; + replaceNotebookCells( uri: Uri, start: number, end: number, cells: NotebookCellData[], metadata?: WorkspaceEditEntryMetadata ): void; - replaceCellOutput(uri: Uri, index: number, outputs: CellOutput[], metadata?: WorkspaceEditEntryMetadata): void; - replaceCellMetadata( + replaceNotebookCellOutput( + uri: Uri, + index: number, + outputs: CellOutput[], + metadata?: WorkspaceEditEntryMetadata + ): void; + replaceNotebookCellMetadata( uri: Uri, index: number, cellMetadata: NotebookCellMetadata, @@ -261,26 +285,18 @@ export interface WorkspaceEdit { ): void; } -export interface NotebookEditorCellEdit { +export interface NotebookEditorEdit { + replaceMetadata(value: NotebookDocumentMetadata): void; replaceCells(start: number, end: number, cells: NotebookCellData[]): void; - replaceOutput(index: number, outputs: CellOutput[]): void; - replaceMetadata(index: number, metadata: NotebookCellMetadata): void; - - /** @deprecated */ - insert( - index: number, - content: string | string[], - language: string, - type: CellKind, - outputs: CellOutput[], - metadata: NotebookCellMetadata | undefined - ): void; - /** @deprecated */ - delete(index: number): void; + replaceCellOutput(index: number, outputs: CellOutput[]): void; + replaceCellMetadata(index: number, metadata: NotebookCellMetadata): void; } export interface NotebookCellRange { readonly start: number; + /** + * exclusive + */ readonly end: number; } @@ -359,7 +375,19 @@ export interface NotebookEditor { */ asWebviewUri(localResource: Uri): Uri; - edit(callback: (editBuilder: NotebookEditorCellEdit) => void): Thenable; + /** + * Perform an edit on the notebook associated with this notebook editor. + * + * The given callback-function is invoked with an [edit-builder](#NotebookEditorEdit) which must + * be used to make edits. Note that the edit-builder is only valid while the + * callback executes. + * + * @param callback A function which can create edits using an [edit-builder](#NotebookEditorEdit). + * @return A promise that resolves with a value indicating if the edits could be applied. + */ + edit(callback: (editBuilder: NotebookEditorEdit) => void): Thenable; + + setDecorations(decorationType: NotebookEditorDecorationType, range: NotebookCellRange): void; revealRange(range: NotebookCellRange, revealType?: NotebookEditorRevealType): void; } @@ -374,6 +402,10 @@ export interface NotebookRenderRequest { outputId: string; } +export interface NotebookDocumentMetadataChangeEvent { + readonly document: NotebookDocument; +} + export interface NotebookCellsChangeData { readonly start: number; readonly deletedCount: number; @@ -541,6 +573,10 @@ export interface NotebookCommunication { } export interface NotebookContentProvider { + readonly options?: NotebookDocumentContentOptions; + readonly onDidChangeNotebookContentOptions?: Event; + readonly onDidChangeNotebook: Event; + /** * Content providers should always use [file system providers](#FileSystemProvider) to * resolve the raw content for `uri` as the resouce is not necessarily a file on disk. @@ -549,7 +585,6 @@ export interface NotebookContentProvider { resolveNotebook(document: NotebookDocument, webview: NotebookCommunication): Promise; saveNotebook(document: NotebookDocument, cancellation: CancellationToken): Promise; saveNotebookAs(targetResource: Uri, document: NotebookDocument, cancellation: CancellationToken): Promise; - readonly onDidChangeNotebook: Event; backupNotebook( document: NotebookDocument, context: NotebookDocumentBackupContext, @@ -570,9 +605,11 @@ export interface NotebookKernel { cancelAllCellsExecution(document: NotebookDocument): void; } +export type NotebookFilenamePattern = GlobPattern | { include: GlobPattern; exclude: GlobPattern }; + export interface NotebookDocumentFilter { viewType?: string | string[]; - filenamePattern?: GlobPattern | { include: GlobPattern; exclude: GlobPattern }; + filenamePattern?: NotebookFilenamePattern; } export interface NotebookKernelProvider { @@ -614,21 +651,24 @@ export interface NotebookCellStatusBarItem { dispose(): void; } +export interface NotebookEditorDecorationType { + readonly key: string; + dispose(): void; +} + export namespace notebook { export function registerNotebookContentProvider( notebookType: string, provider: NotebookContentProvider, - options?: { - /** - * Controls if outputs change will trigger notebook document content change and if it will be used in the diff editor - * Default to false. If the content provider doesn't persisit the outputs in the file document, this should be set to true. - */ - transientOutputs: boolean; + options?: NotebookDocumentContentOptions & { /** - * Controls if a meetadata property change will trigger notebook document content change and if it will be used in the diff editor - * Default to false. If the content provider doesn't persisit a metadata property in the file document, it should be set to true. + * Not ready for production or development use yet. */ - transientMetadata: { [K in keyof NotebookCellMetadata]?: boolean }; + viewOptions?: { + displayName: string; + filenamePattern: NotebookFilenamePattern[]; + exclusive?: boolean; + }; } ): Disposable; @@ -653,6 +693,7 @@ export namespace notebook { export const onDidChangeActiveNotebookEditor: Event; export const onDidChangeNotebookEditorSelection: Event; export const onDidChangeNotebookEditorVisibleRanges: Event; + export const onDidChangeNotebookDocumentMetadata: Event; export const onDidChangeNotebookCells: Event; export const onDidChangeCellOutputs: Event; export const onDidChangeCellLanguage: Event;